@limrun/ui 0.17.0-rc.1 → 0.17.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +18 -3
  2. package/dist/{GLTFLoader-DW6wpvyR.mjs → GLTFLoader-Cami_vza.mjs} +1 -1
  3. package/dist/{GLTFLoader-BKnsBFsj.js → GLTFLoader-DeCLxW_q.js} +1 -1
  4. package/dist/components/duo-fold-motion.d.ts +1 -1
  5. package/dist/components/duo-loading.d.ts +8 -0
  6. package/dist/components/duo-view.d.ts +6 -1
  7. package/dist/{duo-frame-BzyYljix.mjs → duo-frame-Cmw4p2uN.mjs} +414 -414
  8. package/dist/{duo-frame-DjfA4gG-.js → duo-frame-iMtrvx_a.js} +18 -18
  9. package/dist/duo-view-BAh-w2Kz.js +1 -0
  10. package/dist/duo-view-D9Ae00rj.mjs +512 -0
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.css +1 -1
  13. package/dist/index.js +2 -2
  14. package/dist/lite.cjs +1 -1
  15. package/dist/lite.js +1 -1
  16. package/dist/remote-control-B2jZpf3c.js +1 -0
  17. package/dist/remote-control-DiddxS8l.mjs +2883 -0
  18. package/package.json +5 -1
  19. package/dist/demo.d.ts +0 -1
  20. package/dist/duo-view-COZg8ieG.js +0 -1
  21. package/dist/duo-view-o1yxIzxu.mjs +0 -654
  22. package/dist/remote-control-CQjSEVDF.mjs +0 -2673
  23. package/dist/remote-control-DVRXfCR2.js +0 -1
  24. package/index.html +0 -199
  25. package/src/assets/Apple_logo_white.svg +0 -4
  26. package/src/assets/android_boot.gif +0 -0
  27. package/src/assets/android_boot.webp +0 -0
  28. package/src/assets/iphone16pro_black.webp +0 -0
  29. package/src/assets/iphone16pro_black_bg.webp +0 -0
  30. package/src/assets/iphone16pro_black_landscape.webp +0 -0
  31. package/src/assets/iphone16pro_black_landscape_bg.webp +0 -0
  32. package/src/assets/pixel9_black.webp +0 -0
  33. package/src/assets/pixel9_black_landscape.webp +0 -0
  34. package/src/assets/pixel_tablet_landscape.webp +0 -0
  35. package/src/assets/pixel_tablet_portrait.webp +0 -0
  36. package/src/components/device-install/index.ts +0 -1
  37. package/src/components/duo-button-hinge.test.tsx +0 -138
  38. package/src/components/duo-button-hinge.ts +0 -75
  39. package/src/components/duo-controls.tsx +0 -142
  40. package/src/components/duo-flat-frame.tsx +0 -145
  41. package/src/components/duo-flat-geometry.test.ts +0 -63
  42. package/src/components/duo-flat-geometry.ts +0 -97
  43. package/src/components/duo-fold-motion.test.tsx +0 -245
  44. package/src/components/duo-fold-motion.tsx +0 -192
  45. package/src/components/duo-fold-renderer.test.ts +0 -29
  46. package/src/components/duo-fold-renderer.ts +0 -397
  47. package/src/components/duo-fold-timing.test.ts +0 -44
  48. package/src/components/duo-fold-timing.ts +0 -9
  49. package/src/components/duo-frame.tsx +0 -579
  50. package/src/components/duo-hardware.test.ts +0 -61
  51. package/src/components/duo-hardware.ts +0 -61
  52. package/src/components/duo-model.test.ts +0 -243
  53. package/src/components/duo-model.ts +0 -607
  54. package/src/components/duo-screen-handoff.ts +0 -52
  55. package/src/components/duo-view.test.tsx +0 -104
  56. package/src/components/duo-view.tsx +0 -315
  57. package/src/components/inspect-overlay.css +0 -223
  58. package/src/components/inspect-overlay.tsx +0 -437
  59. package/src/components/remote-control-framed.tsx +0 -14
  60. package/src/components/remote-control.css +0 -441
  61. package/src/components/remote-control.test.ts +0 -34
  62. package/src/components/remote-control.tsx +0 -3989
  63. package/src/core/ax-fetcher.test.ts +0 -418
  64. package/src/core/ax-fetcher.ts +0 -387
  65. package/src/core/ax-tree.test.ts +0 -491
  66. package/src/core/ax-tree.ts +0 -416
  67. package/src/core/camera.test.ts +0 -155
  68. package/src/core/camera.ts +0 -133
  69. package/src/core/constants.ts +0 -344
  70. package/src/core/duo.test.ts +0 -142
  71. package/src/core/duo.ts +0 -156
  72. package/src/core/webrtc-messages.ts +0 -148
  73. package/src/demo.tsx +0 -283
  74. package/src/frame-assets.ts +0 -22
  75. package/src/hooks/index.ts +0 -1
  76. package/src/index.ts +0 -29
  77. package/src/lite.test.ts +0 -30
  78. package/src/lite.ts +0 -30
  79. package/src/vite-env.d.ts +0 -1
  80. package/tsconfig.json +0 -26
  81. package/tsconfig.node.json +0 -26
  82. package/vite.config.ts +0 -35
  83. package/vitest.config.ts +0 -24
@@ -1,344 +0,0 @@
1
- export const CONTROL_MSG_TYPE = {
2
- INJECT_KEYCODE: 0,
3
- INJECT_TEXT: 1,
4
- INJECT_TOUCH_EVENT: 2,
5
- // iOS-specific: send two touch points in one message (Option/Alt+drag pinch)
6
- INJECT_TWO_FINGER_TOUCH_EVENT: 18,
7
- INJECT_SCROLL_EVENT: 3,
8
- BACK_OR_SCREEN_ON: 4,
9
- EXPAND_NOTIFICATION_PANEL: 5,
10
- EXPAND_SETTINGS_PANEL: 6,
11
- COLLAPSE_PANELS: 7,
12
- GET_CLIPBOARD: 8,
13
- SET_CLIPBOARD: 9,
14
- SET_DISPLAY_POWER: 10,
15
- ROTATE_DEVICE: 11,
16
- } as const;
17
-
18
- export const AMOTION_EVENT = {
19
- ACTION_DOWN: 0,
20
- ACTION_UP: 1,
21
- ACTION_MOVE: 2,
22
- ACTION_CANCEL: 3,
23
- ACTION_POINTER_DOWN: 5,
24
- ACTION_POINTER_UP: 6,
25
- BUTTON_PRIMARY: 1,
26
- BUTTON_SECONDARY: 2,
27
- BUTTON_TERTIARY: 4,
28
- } as const;
29
-
30
- export const ANDROID_KEYS = {
31
- // Actions
32
- ACTION_DOWN: 0,
33
- ACTION_UP: 1,
34
-
35
- // Meta state flags (from android.view.KeyEvent)
36
- META_NONE: 0,
37
- META_SHIFT_ON: 1,
38
- META_ALT_ON: 2,
39
- META_CTRL_ON: 4096, // META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON
40
- META_META_ON: 65536, // META_META_LEFT_ON | META_META_RIGHT_ON
41
-
42
- // Keycodes (from android.view.KeyEvent)
43
- KEYCODE_UNKNOWN: 0,
44
- KEYCODE_SOFT_LEFT: 1,
45
- KEYCODE_SOFT_RIGHT: 2,
46
- KEYCODE_HOME: 3,
47
- KEYCODE_BACK: 4,
48
- KEYCODE_CALL: 5,
49
- KEYCODE_ENDCALL: 6,
50
- KEYCODE_0: 7,
51
- KEYCODE_1: 8,
52
- KEYCODE_2: 9,
53
- KEYCODE_3: 10,
54
- KEYCODE_4: 11,
55
- KEYCODE_5: 12,
56
- KEYCODE_6: 13,
57
- KEYCODE_7: 14,
58
- KEYCODE_8: 15,
59
- KEYCODE_9: 16,
60
- KEYCODE_STAR: 17,
61
- KEYCODE_POUND: 18,
62
- DPAD_UP: 19, // KEYCODE_DPAD_UP
63
- DPAD_DOWN: 20, // KEYCODE_DPAD_DOWN
64
- DPAD_LEFT: 21, // KEYCODE_DPAD_LEFT
65
- DPAD_RIGHT: 22, // KEYCODE_DPAD_RIGHT
66
- KEYCODE_DPAD_CENTER: 23,
67
- KEYCODE_VOLUME_UP: 24,
68
- KEYCODE_VOLUME_DOWN: 25,
69
- KEYCODE_POWER: 26,
70
- KEYCODE_CAMERA: 27,
71
- KEYCODE_CLEAR: 28,
72
- KEYCODE_A: 29,
73
- KEYCODE_B: 30,
74
- KEYCODE_C: 31,
75
- KEYCODE_D: 32,
76
- KEYCODE_E: 33,
77
- KEYCODE_F: 34,
78
- KEYCODE_G: 35,
79
- KEYCODE_H: 36,
80
- KEYCODE_I: 37,
81
- KEYCODE_J: 38,
82
- KEYCODE_K: 39,
83
- KEYCODE_L: 40,
84
- KEYCODE_M: 41,
85
- KEYCODE_N: 42,
86
- KEYCODE_O: 43,
87
- KEYCODE_P: 44,
88
- KEYCODE_Q: 45,
89
- KEYCODE_R: 46,
90
- KEYCODE_S: 47,
91
- KEYCODE_T: 48,
92
- KEYCODE_U: 49,
93
- KEYCODE_V: 50,
94
- KEYCODE_W: 51,
95
- KEYCODE_X: 52,
96
- KEYCODE_Y: 53,
97
- KEYCODE_Z: 54,
98
- KEYCODE_COMMA: 55,
99
- KEYCODE_PERIOD: 56,
100
- KEYCODE_ALT_LEFT: 57,
101
- KEYCODE_ALT_RIGHT: 58,
102
- KEYCODE_SHIFT_LEFT: 59,
103
- KEYCODE_SHIFT_RIGHT: 60,
104
- KEYCODE_TAB: 61,
105
- KEYCODE_SPACE: 62,
106
- KEYCODE_SYM: 63,
107
- KEYCODE_EXPLORER: 64,
108
- KEYCODE_ENVELOPE: 65,
109
- ENTER: 66, // KEYCODE_ENTER
110
- DEL: 67, // KEYCODE_DEL (Backspace)
111
- KEYCODE_GRAVE: 68, // `
112
- KEYCODE_MINUS: 69, // -
113
- KEYCODE_EQUALS: 70, // =
114
- KEYCODE_LEFT_BRACKET: 71, // [
115
- KEYCODE_RIGHT_BRACKET: 72, // ]
116
- KEYCODE_BACKSLASH: 73, // \
117
- KEYCODE_SEMICOLON: 74, // ;
118
- KEYCODE_APOSTROPHE: 75, // '
119
- KEYCODE_SLASH: 76, // /
120
- KEYCODE_AT: 77, // @
121
- KEYCODE_NUM: 78,
122
- KEYCODE_HEADSETHOOK: 79,
123
- KEYCODE_FOCUS: 80,
124
- KEYCODE_PLUS: 81,
125
- MENU: 82, // KEYCODE_MENU
126
- KEYCODE_NOTIFICATION: 83,
127
- KEYCODE_SEARCH: 84,
128
- KEYCODE_MEDIA_PLAY_PAUSE: 85,
129
- KEYCODE_MEDIA_STOP: 86,
130
- KEYCODE_MEDIA_NEXT: 87,
131
- KEYCODE_MEDIA_PREVIOUS: 88,
132
- KEYCODE_MEDIA_REWIND: 89,
133
- KEYCODE_MEDIA_FAST_FORWARD: 90,
134
- KEYCODE_MUTE: 91,
135
- KEYCODE_PAGE_UP: 92,
136
- KEYCODE_PAGE_DOWN: 93,
137
- KEYCODE_PICTSYMBOLS: 94,
138
- KEYCODE_SWITCH_CHARSET: 95,
139
- KEYCODE_BUTTON_A: 96,
140
- KEYCODE_BUTTON_B: 97,
141
- KEYCODE_BUTTON_C: 98,
142
- KEYCODE_BUTTON_X: 99,
143
- KEYCODE_BUTTON_Y: 100,
144
- KEYCODE_BUTTON_Z: 101,
145
- KEYCODE_BUTTON_L1: 102,
146
- KEYCODE_BUTTON_R1: 103,
147
- KEYCODE_BUTTON_L2: 104,
148
- KEYCODE_BUTTON_R2: 105,
149
- KEYCODE_BUTTON_THUMBL: 106,
150
- KEYCODE_BUTTON_THUMBR: 107,
151
- KEYCODE_BUTTON_START: 108,
152
- KEYCODE_BUTTON_SELECT: 109,
153
- KEYCODE_BUTTON_MODE: 110,
154
- KEYCODE_ESCAPE: 111,
155
- FORWARD_DEL: 112, // KEYCODE_FORWARD_DEL (Delete key)
156
- KEYCODE_CTRL_LEFT: 113,
157
- KEYCODE_CTRL_RIGHT: 114,
158
- KEYCODE_CAPS_LOCK: 115,
159
- KEYCODE_SCROLL_LOCK: 116,
160
- KEYCODE_META_LEFT: 117,
161
- KEYCODE_META_RIGHT: 118,
162
- KEYCODE_FUNCTION: 119,
163
- KEYCODE_SYSRQ: 120,
164
- KEYCODE_BREAK: 121,
165
- KEYCODE_MOVE_HOME: 122,
166
- KEYCODE_MOVE_END: 123,
167
- KEYCODE_INSERT: 124,
168
- KEYCODE_FORWARD: 125,
169
- KEYCODE_MEDIA_PLAY: 126,
170
- KEYCODE_MEDIA_PAUSE: 127,
171
- KEYCODE_MEDIA_CLOSE: 128,
172
- KEYCODE_MEDIA_EJECT: 129,
173
- KEYCODE_MEDIA_RECORD: 130,
174
- KEYCODE_F1: 131,
175
- KEYCODE_F2: 132,
176
- KEYCODE_F3: 133,
177
- KEYCODE_F4: 134,
178
- KEYCODE_F5: 135,
179
- KEYCODE_F6: 136,
180
- KEYCODE_F7: 137,
181
- KEYCODE_F8: 138,
182
- KEYCODE_F9: 139,
183
- KEYCODE_F10: 140,
184
- KEYCODE_F11: 141,
185
- KEYCODE_F12: 142,
186
- KEYCODE_NUM_LOCK: 143,
187
- KEYCODE_NUMPAD_0: 144,
188
- KEYCODE_NUMPAD_1: 145,
189
- KEYCODE_NUMPAD_2: 146,
190
- KEYCODE_NUMPAD_3: 147,
191
- KEYCODE_NUMPAD_4: 148,
192
- KEYCODE_NUMPAD_5: 149,
193
- KEYCODE_NUMPAD_6: 150,
194
- KEYCODE_NUMPAD_7: 151,
195
- KEYCODE_NUMPAD_8: 152,
196
- KEYCODE_NUMPAD_9: 153,
197
- KEYCODE_NUMPAD_DIVIDE: 154,
198
- KEYCODE_NUMPAD_MULTIPLY: 155,
199
- KEYCODE_NUMPAD_SUBTRACT: 156,
200
- KEYCODE_NUMPAD_ADD: 157,
201
- KEYCODE_NUMPAD_DOT: 158,
202
- KEYCODE_NUMPAD_COMMA: 159,
203
- KEYCODE_NUMPAD_ENTER: 160,
204
- KEYCODE_NUMPAD_EQUALS: 161,
205
- KEYCODE_NUMPAD_LEFT_PAREN: 162,
206
- KEYCODE_NUMPAD_RIGHT_PAREN: 163,
207
- KEYCODE_VOLUME_MUTE: 164,
208
- KEYCODE_INFO: 165,
209
- KEYCODE_CHANNEL_UP: 166,
210
- KEYCODE_CHANNEL_DOWN: 167,
211
- KEYCODE_ZOOM_IN: 168,
212
- KEYCODE_ZOOM_OUT: 169,
213
- KEYCODE_TV: 170,
214
- KEYCODE_WINDOW: 171,
215
- KEYCODE_GUIDE: 172,
216
- KEYCODE_DVR: 173,
217
- KEYCODE_BOOKMARK: 174,
218
- KEYCODE_CAPTIONS: 175,
219
- KEYCODE_SETTINGS: 176,
220
- KEYCODE_TV_POWER: 177,
221
- KEYCODE_TV_INPUT: 178,
222
- KEYCODE_STB_POWER: 179,
223
- KEYCODE_STB_INPUT: 180,
224
- KEYCODE_AVR_POWER: 181,
225
- KEYCODE_AVR_INPUT: 182,
226
- KEYCODE_PROG_RED: 183,
227
- KEYCODE_PROG_GREEN: 184,
228
- KEYCODE_PROG_YELLOW: 185,
229
- KEYCODE_PROG_BLUE: 186,
230
- KEYCODE_APP_SWITCH: 187,
231
- KEYCODE_LANGUAGE_SWITCH: 204,
232
- KEYCODE_ASSIST: 219,
233
- KEYCODE_BRIGHTNESS_DOWN: 220,
234
- KEYCODE_BRIGHTNESS_UP: 221,
235
- KEYCODE_SLEEP: 223,
236
- KEYCODE_WAKEUP: 224,
237
- KEYCODE_SOFT_SLEEP: 276,
238
- } as const;
239
-
240
- // Map based on event.code for layout independence
241
- export const codeMap: { [code: string]: number } = {
242
- KeyA: ANDROID_KEYS.KEYCODE_A,
243
- KeyB: ANDROID_KEYS.KEYCODE_B,
244
- KeyC: ANDROID_KEYS.KEYCODE_C,
245
- KeyD: ANDROID_KEYS.KEYCODE_D,
246
- KeyE: ANDROID_KEYS.KEYCODE_E,
247
- KeyF: ANDROID_KEYS.KEYCODE_F,
248
- KeyG: ANDROID_KEYS.KEYCODE_G,
249
- KeyH: ANDROID_KEYS.KEYCODE_H,
250
- KeyI: ANDROID_KEYS.KEYCODE_I,
251
- KeyJ: ANDROID_KEYS.KEYCODE_J,
252
- KeyK: ANDROID_KEYS.KEYCODE_K,
253
- KeyL: ANDROID_KEYS.KEYCODE_L,
254
- KeyM: ANDROID_KEYS.KEYCODE_M,
255
- KeyN: ANDROID_KEYS.KEYCODE_N,
256
- KeyO: ANDROID_KEYS.KEYCODE_O,
257
- KeyP: ANDROID_KEYS.KEYCODE_P,
258
- KeyQ: ANDROID_KEYS.KEYCODE_Q,
259
- KeyR: ANDROID_KEYS.KEYCODE_R,
260
- KeyS: ANDROID_KEYS.KEYCODE_S,
261
- KeyT: ANDROID_KEYS.KEYCODE_T,
262
- KeyU: ANDROID_KEYS.KEYCODE_U,
263
- KeyV: ANDROID_KEYS.KEYCODE_V,
264
- KeyW: ANDROID_KEYS.KEYCODE_W,
265
- KeyX: ANDROID_KEYS.KEYCODE_X,
266
- KeyY: ANDROID_KEYS.KEYCODE_Y,
267
- KeyZ: ANDROID_KEYS.KEYCODE_Z,
268
- Digit0: ANDROID_KEYS.KEYCODE_0,
269
- Digit1: ANDROID_KEYS.KEYCODE_1,
270
- Digit2: ANDROID_KEYS.KEYCODE_2,
271
- Digit3: ANDROID_KEYS.KEYCODE_3,
272
- Digit4: ANDROID_KEYS.KEYCODE_4,
273
- Digit5: ANDROID_KEYS.KEYCODE_5,
274
- Digit6: ANDROID_KEYS.KEYCODE_6,
275
- Digit7: ANDROID_KEYS.KEYCODE_7,
276
- Digit8: ANDROID_KEYS.KEYCODE_8,
277
- Digit9: ANDROID_KEYS.KEYCODE_9,
278
- Backquote: ANDROID_KEYS.KEYCODE_GRAVE,
279
- Minus: ANDROID_KEYS.KEYCODE_MINUS,
280
- Equal: ANDROID_KEYS.KEYCODE_EQUALS,
281
- BracketLeft: ANDROID_KEYS.KEYCODE_LEFT_BRACKET,
282
- BracketRight: ANDROID_KEYS.KEYCODE_RIGHT_BRACKET,
283
- Backslash: ANDROID_KEYS.KEYCODE_BACKSLASH,
284
- Semicolon: ANDROID_KEYS.KEYCODE_SEMICOLON,
285
- Quote: ANDROID_KEYS.KEYCODE_APOSTROPHE,
286
- Comma: ANDROID_KEYS.KEYCODE_COMMA,
287
- Period: ANDROID_KEYS.KEYCODE_PERIOD,
288
- Slash: ANDROID_KEYS.KEYCODE_SLASH,
289
- Space: ANDROID_KEYS.KEYCODE_SPACE,
290
- Tab: ANDROID_KEYS.KEYCODE_TAB,
291
- Escape: ANDROID_KEYS.KEYCODE_ESCAPE,
292
- ArrowUp: ANDROID_KEYS.DPAD_UP,
293
- ArrowDown: ANDROID_KEYS.DPAD_DOWN,
294
- ArrowLeft: ANDROID_KEYS.DPAD_LEFT,
295
- ArrowRight: ANDROID_KEYS.DPAD_RIGHT,
296
- Enter: ANDROID_KEYS.ENTER,
297
- Backspace: ANDROID_KEYS.DEL,
298
- Delete: ANDROID_KEYS.FORWARD_DEL,
299
- Home: ANDROID_KEYS.KEYCODE_MOVE_HOME,
300
- End: ANDROID_KEYS.KEYCODE_MOVE_END,
301
- PageUp: ANDROID_KEYS.KEYCODE_PAGE_UP,
302
- PageDown: ANDROID_KEYS.KEYCODE_PAGE_DOWN,
303
- Insert: ANDROID_KEYS.KEYCODE_INSERT,
304
- F1: ANDROID_KEYS.KEYCODE_F1,
305
- F2: ANDROID_KEYS.KEYCODE_F2,
306
- F3: ANDROID_KEYS.KEYCODE_F3,
307
- F4: ANDROID_KEYS.KEYCODE_F4,
308
- F5: ANDROID_KEYS.KEYCODE_F5,
309
- F6: ANDROID_KEYS.KEYCODE_F6,
310
- F7: ANDROID_KEYS.KEYCODE_F7,
311
- F8: ANDROID_KEYS.KEYCODE_F8,
312
- F9: ANDROID_KEYS.KEYCODE_F9,
313
- F10: ANDROID_KEYS.KEYCODE_F10,
314
- F11: ANDROID_KEYS.KEYCODE_F11,
315
- F12: ANDROID_KEYS.KEYCODE_F12,
316
- ShiftLeft: ANDROID_KEYS.KEYCODE_SHIFT_LEFT,
317
- ShiftRight: ANDROID_KEYS.KEYCODE_SHIFT_RIGHT,
318
- ControlLeft: ANDROID_KEYS.KEYCODE_CTRL_LEFT,
319
- ControlRight: ANDROID_KEYS.KEYCODE_CTRL_RIGHT,
320
- AltLeft: ANDROID_KEYS.KEYCODE_ALT_LEFT,
321
- AltRight: ANDROID_KEYS.KEYCODE_ALT_RIGHT,
322
- MetaLeft: ANDROID_KEYS.KEYCODE_META_LEFT,
323
- MetaRight: ANDROID_KEYS.KEYCODE_META_RIGHT, // Windows/Command key
324
- ContextMenu: ANDROID_KEYS.MENU, // Often the Menu key
325
- // Numpad mappings
326
- Numpad0: ANDROID_KEYS.KEYCODE_NUMPAD_0,
327
- Numpad1: ANDROID_KEYS.KEYCODE_NUMPAD_1,
328
- Numpad2: ANDROID_KEYS.KEYCODE_NUMPAD_2,
329
- Numpad3: ANDROID_KEYS.KEYCODE_NUMPAD_3,
330
- Numpad4: ANDROID_KEYS.KEYCODE_NUMPAD_4,
331
- Numpad5: ANDROID_KEYS.KEYCODE_NUMPAD_5,
332
- Numpad6: ANDROID_KEYS.KEYCODE_NUMPAD_6,
333
- Numpad7: ANDROID_KEYS.KEYCODE_NUMPAD_7,
334
- Numpad8: ANDROID_KEYS.KEYCODE_NUMPAD_8,
335
- Numpad9: ANDROID_KEYS.KEYCODE_NUMPAD_9,
336
- NumpadDivide: ANDROID_KEYS.KEYCODE_NUMPAD_DIVIDE,
337
- NumpadMultiply: ANDROID_KEYS.KEYCODE_NUMPAD_MULTIPLY,
338
- NumpadSubtract: ANDROID_KEYS.KEYCODE_NUMPAD_SUBTRACT,
339
- NumpadAdd: ANDROID_KEYS.KEYCODE_NUMPAD_ADD,
340
- NumpadDecimal: ANDROID_KEYS.KEYCODE_NUMPAD_DOT,
341
- NumpadComma: ANDROID_KEYS.KEYCODE_NUMPAD_COMMA, // Some numpads have comma
342
- NumpadEnter: ANDROID_KEYS.KEYCODE_NUMPAD_ENTER,
343
- NumpadEqual: ANDROID_KEYS.KEYCODE_NUMPAD_EQUALS,
344
- };
@@ -1,142 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import {
3
- createDisplayTouchMessage,
4
- flatDisplayTouch,
5
- duoPointerMode,
6
- DuoButtonSender,
7
- HingeSender,
8
- panelTouch,
9
- validHingeAngle,
10
- } from './duo';
11
-
12
- describe('native Duo input', () => {
13
- it.each([
14
- [0, 0.2, 0.3],
15
- [1, 0.3, 0.8],
16
- [2, 0.8, 0.7],
17
- [3, 0.7, 0.2],
18
- ])('maps 2D screen coordinates back to the native panel at rotation %s', (turns, x, y) => {
19
- const point = flatDisplayTouch(0.2, 0.3, turns!);
20
- expect(point.x).toBeCloseTo(x!);
21
- expect(point.y).toBeCloseTo(y!);
22
- });
23
- it('keeps a captured drag inside the native panel after it leaves the screen', () => {
24
- expect(flatDisplayTouch(-1, 2, 1)).toEqual({ x: 1, y: 1 });
25
- });
26
- it('rejects non-finite and out-of-range hinge values', () => {
27
- for (const angle of [NaN, Infinity, -1, 181]) expect(validHingeAngle(angle)).toBe(false);
28
- for (const angle of [0, 45.5, 90, 180]) expect(validHingeAngle(angle)).toBe(true);
29
- });
30
- it('encodes panel identity and top-left coordinates without video rotation', () => {
31
- const point = panelTouch(0.3, 0.6);
32
- const data = new DataView(createDisplayTouchMessage(0, 3, point.x, point.y));
33
- expect(data.byteLength).toBe(12);
34
- expect(data.getUint8(0)).toBe(19);
35
- expect(data.getUint8(2)).toBe(3);
36
- expect(data.getFloat32(4, true)).toBeCloseTo(0.3);
37
- expect(data.getFloat32(8, true)).toBeCloseTo(0.4);
38
- expect(() => createDisplayTouchMessage(0, 3, NaN, 0)).toThrow();
39
- });
40
- it('sends the final slider angle while coalescing intermediate positions', async () => {
41
- let finish!: () => void;
42
- const send = vi
43
- .fn()
44
- .mockImplementationOnce(
45
- () =>
46
- new Promise<void>((resolve) => {
47
- finish = resolve;
48
- }),
49
- )
50
- .mockResolvedValue(undefined);
51
- const sender = new HingeSender(send, vi.fn());
52
- sender.set(10);
53
- sender.set(20);
54
- sender.set(90);
55
- sender.set(180);
56
- expect(send.mock.calls).toEqual([[10]]);
57
- finish();
58
- await vi.waitFor(() => expect(send.mock.calls).toEqual([[10], [180]]));
59
- });
60
- it('drops pending angles after disconnect and reports failures', async () => {
61
- const failed = vi.fn();
62
- const sender = new HingeSender(async () => {
63
- throw new Error('offline');
64
- }, failed);
65
- sender.set(120);
66
- await vi.waitFor(() => expect(failed).toHaveBeenCalledOnce());
67
- sender.stop();
68
- sender.set(0);
69
- expect(failed).toHaveBeenCalledOnce();
70
- });
71
- });
72
-
73
- describe('Duo position lock', () => {
74
- it.each([false, true])('keeps touch active while locked (Alt=%s)', (alt) => {
75
- expect(duoPointerMode(true, true, alt)).toBe('touch');
76
- expect(duoPointerMode(true, false, alt)).toBe('ignore');
77
- });
78
- it('allows deliberate orbit only after unlocking', () => {
79
- expect(duoPointerMode(false, false, false)).toBe('orbit');
80
- expect(duoPointerMode(false, true, false)).toBe('touch');
81
- expect(duoPointerMode(false, true, true)).toBe('orbit');
82
- });
83
- });
84
-
85
- describe('Duo hardware input', () => {
86
- it('orders a quick release after the acknowledged down event', async () => {
87
- let ack!: () => void;
88
- const send = vi
89
- .fn()
90
- .mockImplementationOnce(
91
- () =>
92
- new Promise<void>((resolve) => {
93
- ack = resolve;
94
- }),
95
- )
96
- .mockResolvedValue(undefined);
97
- const sender = new DuoButtonSender(send, vi.fn());
98
- sender.press('side');
99
- sender.release();
100
- sender.release();
101
- await vi.waitFor(() => expect(send.mock.calls).toEqual([['side', true]]));
102
- ack();
103
- await vi.waitFor(() =>
104
- expect(send.mock.calls).toEqual([
105
- ['side', true],
106
- ['side', false],
107
- ]),
108
- );
109
- });
110
- it('still releases when a down event fails or times out', async () => {
111
- const send = vi.fn().mockRejectedValueOnce(new Error('timeout')).mockResolvedValue(undefined);
112
- const failed = vi.fn();
113
- const sender = new DuoButtonSender(send, failed);
114
- sender.press('volumeDown');
115
- sender.press('volumeUp');
116
- sender.release();
117
- await vi.waitFor(() =>
118
- expect(send.mock.calls).toEqual([
119
- ['volumeDown', true],
120
- ['volumeDown', false],
121
- ]),
122
- );
123
- expect(failed).toHaveBeenCalledOnce();
124
- });
125
- it('releases a button if the browser loses the pointer release', async () => {
126
- vi.useFakeTimers();
127
- try {
128
- const send = vi.fn().mockResolvedValue(undefined);
129
- const sender = new DuoButtonSender(send, vi.fn());
130
- sender.press('volumeUp');
131
- await vi.advanceTimersByTimeAsync(10000);
132
- expect(send.mock.calls).toEqual([
133
- ['volumeUp', true],
134
- ['volumeUp', false],
135
- ]);
136
- sender.release();
137
- expect(send).toHaveBeenCalledTimes(2);
138
- } finally {
139
- vi.useRealTimers();
140
- }
141
- });
142
- });
package/src/core/duo.ts DELETED
@@ -1,156 +0,0 @@
1
- /** Native iPhone Duo display metadata. Pixel geometry comes from CoreSimulator. */
2
- export interface DuoDisplay {
3
- id: 'outer' | 'inner';
4
- screenId: number;
5
- trackId: string;
6
- width: number;
7
- height: number;
8
- scale: number;
9
- orientation: number;
10
- }
11
-
12
- export type DuoOrientation = 'portrait' | 'pud' | 'landscape-left' | 'landscape-right';
13
-
14
- export interface DuoState {
15
- orientation: DuoOrientation;
16
- angleDegrees: number;
17
- minAngleDegrees: number;
18
- maxAngleDegrees: number;
19
- displays: DuoDisplay[];
20
- }
21
-
22
- export const DUO_DIMENSIONS = { width: 164.6, height: 117.8, depth: 5.2 } as const;
23
-
24
- /** Invert the CSS display rotation before sending native panel coordinates. */
25
- export function flatDisplayTouch(x: number, y: number, turns: number): { x: number; y: number } {
26
- x = Math.max(0, Math.min(1, x));
27
- y = Math.max(0, Math.min(1, y));
28
- if (turns === 1) return { x: y, y: 1 - x };
29
- if (turns === 2) return { x: 1 - x, y: 1 - y };
30
- if (turns === 3) return { x: 1 - y, y: x };
31
- return { x, y };
32
- }
33
-
34
- export function validHingeAngle(angle: number): boolean {
35
- return Number.isFinite(angle) && angle >= 0 && angle <= 180;
36
- }
37
-
38
- /** Mesh UVs use the native portrait panel; HID has a top-left origin. */
39
- export function panelTouch(u: number, v: number): { x: number; y: number } {
40
- return { x: Math.max(0, Math.min(1, u)), y: Math.max(0, Math.min(1, 1 - v)) };
41
- }
42
-
43
- /** Locking the view never suppresses screen input, including accidental Alt presses. */
44
- export function duoPointerMode(
45
- locked: boolean,
46
- screenHit: boolean,
47
- altKey: boolean,
48
- ): 'touch' | 'orbit' | 'ignore' {
49
- if (screenHit && (locked || !altKey)) return 'touch';
50
- return locked ? 'ignore' : 'orbit';
51
- }
52
-
53
- export function createDisplayTouchMessage(
54
- action: number,
55
- screenId: number,
56
- x: number,
57
- y: number,
58
- ): ArrayBuffer {
59
- if (
60
- !Number.isInteger(action) ||
61
- action < 0 ||
62
- action > 3 ||
63
- !Number.isInteger(screenId) ||
64
- screenId < 1 ||
65
- screenId > 255 ||
66
- !Number.isFinite(x) ||
67
- !Number.isFinite(y) ||
68
- x < 0 ||
69
- x > 1 ||
70
- y < 0 ||
71
- y > 1
72
- ) {
73
- throw new RangeError('Invalid display touch coordinates');
74
- }
75
- const message = new ArrayBuffer(12);
76
- const view = new DataView(message);
77
- view.setUint8(0, 19);
78
- view.setUint8(1, action);
79
- view.setUint8(2, screenId);
80
- view.setFloat32(4, x, true);
81
- view.setFloat32(8, y, true);
82
- return message;
83
- }
84
-
85
- /** One request in flight; intermediate slider positions are replaced by the newest position. */
86
- export class HingeSender {
87
- private latest: number | undefined;
88
- private running = false;
89
- private stopped = false;
90
- constructor(
91
- private send: (angle: number) => Promise<void>,
92
- private failed: (error: unknown) => void,
93
- ) {}
94
-
95
- set(angle: number): void {
96
- if (this.stopped || !validHingeAngle(angle)) return;
97
- this.latest = angle;
98
- void this.flush();
99
- }
100
-
101
- stop(): void {
102
- this.stopped = true;
103
- this.latest = undefined;
104
- }
105
-
106
- private async flush(): Promise<void> {
107
- if (this.running || this.stopped) return;
108
- this.running = true;
109
- try {
110
- while (this.latest !== undefined && !this.stopped) {
111
- const angle = this.latest;
112
- this.latest = undefined;
113
- await this.send(angle);
114
- }
115
- } catch (error) {
116
- this.latest = undefined;
117
- if (!this.stopped) this.failed(error);
118
- } finally {
119
- this.running = false;
120
- }
121
- }
122
- }
123
-
124
- export type DuoButton = 'side' | 'volumeUp' | 'volumeDown';
125
- export const DUO_BUTTONS: Record<DuoButton, string> = {
126
- side: 'Sleep/Wake',
127
- volumeUp: 'Volume Up',
128
- volumeDown: 'Volume Down',
129
- };
130
-
131
- /** Keeps button edges ordered, including releases after cancellation or a failed press. */
132
- export class DuoButtonSender {
133
- private held?: DuoButton;
134
- private tail = Promise.resolve();
135
- private timeout?: ReturnType<typeof setTimeout>;
136
- constructor(
137
- private send: (button: DuoButton, down: boolean) => Promise<void>,
138
- private failed: (error: unknown) => void,
139
- ) {}
140
- press(button: DuoButton): void {
141
- if (this.held) return;
142
- this.held = button;
143
- this.enqueue(button, true);
144
- this.timeout = setTimeout(() => this.release(), 10000);
145
- }
146
- release(): void {
147
- if (!this.held) return;
148
- clearTimeout(this.timeout);
149
- const button = this.held;
150
- this.held = undefined;
151
- this.enqueue(button, false);
152
- }
153
- private enqueue(button: DuoButton, down: boolean): void {
154
- this.tail = this.tail.then(() => this.send(button, down)).catch(this.failed);
155
- }
156
- }