#1,#3,#4+#5 keyboards: Binocular Compose, cursor watchdog 3s, SetText + VmNoopIme + sendText

This commit is contained in:
2026-09-27 11:18:52 +03:00
parent 3ac76daca1
commit 65f3f5d228
11 changed files with 238 additions and 94 deletions
@@ -0,0 +1,35 @@
package pw.binom.rayneovm.glasses
import android.inputmethodservice.InputMethodService
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
/**
* Пустой IME — глушилка системного IME гостя на нашем VirtualDisplay.
*
* Когда это дефолтный IME очков — гость (Zona, ...) не показывает свою клавиатуру поверх
* нашего приложения: InputMethodService.showInputRequestedAndUpdated → false (нет UI).
*
* Активируется через Shizuku/binder: `settings put secure default_input_method
* pw.binom.rayneovm.glasses/.VmNoopImeService`. Возврат к штатному IME не предусмотрен.
*/
class VmNoopImeService : InputMethodService() {
override fun onCreateInputView(): View? {
Log.i(TAG, "onCreateInputView: возвращаю null UI")
return null
}
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
Log.i(TAG, "onStartInputView: info=$info restarting=$restarting")
// UI не показываем (onCreateInputView == null). Фокус отдаём гостю, как обычно —
// его кастомные EditText-ы получают inputType как обычно.
}
override fun onFinishInputView(finishedInput: Boolean) {}
companion object {
private const val TAG = "VmNoopIme"
}
}
@@ -32,6 +32,7 @@ import pw.binom.rayneovm.shared.LaunchAppCommand
import pw.binom.rayneovm.shared.Ping
import pw.binom.rayneovm.shared.Pong
import pw.binom.rayneovm.shared.SetAudioMode
import pw.binom.rayneovm.shared.SetText
import pw.binom.rayneovm.shared.Shutdown
import pw.binom.rayneovm.shared.TouchEvent
import pw.binom.rayneovm.shared.VmMessage
@@ -220,6 +221,11 @@ class VmService : LifecycleService() {
Log.i(TAG, "ScreenControl: sleep=${msg.sleep}")
controller.setScreen(msg.sleep)
}
is SetText -> {
Log.i(TAG, "SetText: displayId=${msg.displayId} len=${msg.text.length}")
val ok = controller.injectText(msg.displayId, msg.text)
Log.i(TAG, "SetText -> $ok")
}
is Shutdown -> {
Log.i(TAG, "Shutdown")
controller.stopGuest()
@@ -295,6 +295,30 @@ class VmGlassesController {
}
}
/**
* Инжектит [text] в активное текстовое поле гостя на [displayId] (через InputConnection.commitText).
*
* Если фокуса в текстовом поле нет — возвращает false (тогда на телефоне остаётся
* keyCodeForChar-фолбэк). Биндер берётся у «стража» (он поднят пока живёт VmService), иначе —
* у активного гостевого дисплея.
*/
fun injectText(displayId: Int, text: String): Boolean {
val binder = guardBinder ?: activeSource?.shizukuBinder
if (binder == null) {
Log.w(TAG, "injectText: нет Shizuku-биндера")
return false
}
if (text.isEmpty()) return true
return try {
val ok = ShizukuVdBinder.injectText(binder, displayId, text)
if (!ok) Log.w(TAG, "injectText: binder вернул false")
ok
} catch (t: Throwable) {
Log.w(TAG, "injectText: binder transact упал", t)
false
}
}
/** Клавиатурное/медиа-событие гостю на его VirtualDisplay (Back, Play, Pause, ...). */
fun sendKey(keyCode: Int, action: Int) {
val src = activeSource ?: return