phone: third «Пульт» tab with on-glasses cursor; atomic click injection
- shared: VmDisplay (guest 640x480 coordinate space), CursorMove and CursorClick messages (CursorClick replaces the DOWN/UP pair — see below). - glasses: cursor state + Canvas overlay drawn in both eyes, clickCursor injects one atomic tap at the cursor, sendKey for Back/Play/Pause. - phone: RemoteScreen — touchpad (relative drag moves the cursor, tap clicks), «Назад»/«Плей»/«Пауза» buttons, «Клавиатура» stub (TODO). - The old CursorButton(DOWN)/CursorButton(UP) pair was removed: the two messages were injected concurrently and could arrive out of order, breaking the guest's touch stream so clicks worked only once. - tests: CursorMove/CursorClick roundtrips.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package pw.binom.rayneovm.shared
|
||||
|
||||
/**
|
||||
* Параметры виртуального дисплея, на котором запускается гостевое приложение.
|
||||
*
|
||||
* Должны совпадать на телефоне и очках: телефон в этих координатах шлёт позицию курсора
|
||||
* ([CursorMove]) и тач-события ([TouchAction]), очки в этих же координатах создают
|
||||
* VirtualDisplay и рисуют курсор в обоих глазах.
|
||||
*/
|
||||
object VmDisplay {
|
||||
const val WIDTH = 640
|
||||
const val HEIGHT = 480
|
||||
const val DENSITY = 160
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import kotlinx.serialization.Serializable
|
||||
* (настраивается в [VmProtocol]).
|
||||
*
|
||||
* Потоки:
|
||||
* - Phone → Glasses: [TouchEvent], [KeyEvent], [LaunchAppCommand], [SetAudioMode], [Shutdown]
|
||||
* - Phone → Glasses: [TouchEvent], [KeyEvent], [CursorMove], [CursorClick], [LaunchAppCommand], [SetAudioMode], [Shutdown]
|
||||
* - Glasses → Phone: [GlassesStatus], [AppCrashedEvent], [AppLaunchedAck]
|
||||
* - Двунаправленные: [Ping], [Pong]
|
||||
*/
|
||||
@@ -55,6 +55,30 @@ data class KeyEvent(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Позиция виртуального курсора мыши в координатах виртуального дисплея гостя
|
||||
* (0..[VmDisplay.WIDTH]-1, 0..[VmDisplay.HEIGHT]-1).
|
||||
*
|
||||
* Очки рисуют курсор в обоих глазах и запоминают позицию как цель для [CursorClick].
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("cursor_move")
|
||||
data class CursorMove(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
) : VmMessage
|
||||
|
||||
/**
|
||||
* Клик виртуальной мыши в текущей позиции курсора ([CursorMove]).
|
||||
*
|
||||
* Отдельных DOWN/UP нет намеренно: очки инжектят пару DOWN+UP одной транзакцией
|
||||
* (см. `ShizukuVdBinder.tap`). Двумя независимыми сообщениями порядок не гарантирован:
|
||||
* тач-поток у гостя разбалансируется («залипший» палец) и клики перестают работать.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("cursor_click")
|
||||
data object CursorClick : VmMessage
|
||||
|
||||
@Serializable
|
||||
@SerialName("launch_app")
|
||||
data class LaunchAppCommand(
|
||||
|
||||
@@ -46,6 +46,28 @@ class VmProtocolTest {
|
||||
assertEquals(msg, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CursorMove roundtrip`() {
|
||||
val msg = CursorMove(x = 123.5f, y = 456.25f)
|
||||
val encoded = VmProtocol.encode(msg)
|
||||
val decoded = VmProtocol.decode<CursorMove>(encoded)
|
||||
assertEquals(msg, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CursorClick roundtrip`() {
|
||||
val decoded = VmProtocol.decode<CursorClick>(VmProtocol.encode(CursorClick))
|
||||
assertEquals(CursorClick, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodeAny for CursorMove uses type discriminator`() {
|
||||
val msg = VmProtocol.decodeAny("""{"type":"cursor_move","x":10.5,"y":20.25}""")
|
||||
assertTrue(msg is CursorMove)
|
||||
assertEquals(10.5f, msg.x)
|
||||
assertEquals(20.25f, msg.y)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GlassesStatus roundtrip`() {
|
||||
val msg = GlassesStatus(
|
||||
|
||||
Reference in New Issue
Block a user