#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
@@ -95,6 +95,20 @@
android:resource="@xml/vm_accessibility_service" />
</service>
<!-- 4.1: системный IME-заглушка. Нулевой UI; назначается дефолтным через Shizuku
(settings put secure default_input_method). Обратно НЕ возвращаем. -->
<service
android:name=".VmNoopImeService"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data
android:name="android.view.im"
android:resource="@xml/vm_noop_ime_method" />
</service>
<!-- VmShizukuService НЕ объявляется здесь как <service> — это Shizuku UserService,
который ServiceStarter создаёт через reflection в отдельном isolated-процессе.
Если объявить — Shizuku-сервер не сможет привести класс к IBinder
@@ -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
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
IME-метод для [VmNoopImeService]. Никакой UI, никакого ввода — сервис существует лишь
чтобы система не поднимала системный IME, когда гость (Zona) получает фокус в поле ввода.
Включается вторично через настройки системного уровня (см. ShizukuProvisioner).
-->
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:isDefault="false"
android:supportsSwitchingToNextInputMethod="false" />