@glassly/engine 0.3.5-beta.710 → 0.3.5-beta.760

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 (119) hide show
  1. package/build/engine.d.ts +4 -1
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +5 -1
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/ai.d.ts +3 -3
  6. package/build/facades/ai.d.ts.map +1 -1
  7. package/build/facades/ai.js +3 -3
  8. package/build/facades/ai.js.map +1 -1
  9. package/build/facades/glasses.d.ts +5 -1
  10. package/build/facades/glasses.d.ts.map +1 -1
  11. package/build/facades/glasses.js +5 -0
  12. package/build/facades/glasses.js.map +1 -1
  13. package/build/generated/releaseMetadata.js +3 -3
  14. package/build/generated/releaseMetadata.js.map +1 -1
  15. package/build/services/GestureDotsService.d.ts.map +1 -1
  16. package/build/services/GestureDotsService.js +2 -1
  17. package/build/services/GestureDotsService.js.map +1 -1
  18. package/build/services/GlassesTouchInput.d.ts +6 -4
  19. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  20. package/build/services/GlassesTouchInput.js +76 -37
  21. package/build/services/GlassesTouchInput.js.map +1 -1
  22. package/build/services/LocalDisplayManager.d.ts +12 -4
  23. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  24. package/build/services/LocalDisplayManager.js +21 -7
  25. package/build/services/LocalDisplayManager.js.map +1 -1
  26. package/build/services/LocalLlmModelManager.d.ts +3 -1
  27. package/build/services/LocalLlmModelManager.d.ts.map +1 -1
  28. package/build/services/LocalLlmModelManager.js +13 -5
  29. package/build/services/LocalLlmModelManager.js.map +1 -1
  30. package/build/services/LocalLlmPrompt.d.ts +10 -9
  31. package/build/services/LocalLlmPrompt.d.ts.map +1 -1
  32. package/build/services/LocalLlmPrompt.js +43 -32
  33. package/build/services/LocalLlmPrompt.js.map +1 -1
  34. package/build/services/LocalLlmProvider.d.ts.map +1 -1
  35. package/build/services/LocalLlmProvider.js +39 -20
  36. package/build/services/LocalLlmProvider.js.map +1 -1
  37. package/build/services/LocalMiniappRuntime.d.ts +22 -4
  38. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  39. package/build/services/LocalMiniappRuntime.js +108 -134
  40. package/build/services/LocalMiniappRuntime.js.map +1 -1
  41. package/build/services/MiniappExitGesture.d.ts +6 -3
  42. package/build/services/MiniappExitGesture.d.ts.map +1 -1
  43. package/build/services/MiniappExitGesture.js +4 -4
  44. package/build/services/MiniappExitGesture.js.map +1 -1
  45. package/build/services/MiniappWidgetMode.d.ts +6 -4
  46. package/build/services/MiniappWidgetMode.d.ts.map +1 -1
  47. package/build/services/MiniappWidgetMode.js +7 -6
  48. package/build/services/MiniappWidgetMode.js.map +1 -1
  49. package/build/services/SceneRenderer.d.ts.map +1 -1
  50. package/build/services/SceneRenderer.js +3 -1
  51. package/build/services/SceneRenderer.js.map +1 -1
  52. package/build/services/actionInvoke.d.ts +37 -0
  53. package/build/services/actionInvoke.d.ts.map +1 -0
  54. package/build/services/actionInvoke.js +89 -0
  55. package/build/services/actionInvoke.js.map +1 -0
  56. package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
  57. package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
  58. package/build/services/appleWatch/AppleWatchLink.js +259 -0
  59. package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
  60. package/build/services/controllers.d.ts.map +1 -1
  61. package/build/services/controllers.js +16 -0
  62. package/build/services/controllers.js.map +1 -1
  63. package/build/services/settingsBridge.d.ts +4 -4
  64. package/build/services/settingsBridge.d.ts.map +1 -1
  65. package/build/services/settingsBridge.js +9 -4
  66. package/build/services/settingsBridge.js.map +1 -1
  67. package/build/stores/apps.d.ts.map +1 -1
  68. package/build/stores/apps.js +13 -11
  69. package/build/stores/apps.js.map +1 -1
  70. package/build/stores/bluetoothSettingKeys.d.ts +2 -0
  71. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  72. package/build/stores/bluetoothSettingKeys.js +17 -0
  73. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  74. package/build/stores/settings.d.ts.map +1 -1
  75. package/build/stores/settings.js +32 -2
  76. package/build/stores/settings.js.map +1 -1
  77. package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
  78. package/build/types/capabilities/rayneo-io.js +2 -0
  79. package/build/types/capabilities/rayneo-io.js.map +1 -1
  80. package/build/types/enums.d.ts +2 -1
  81. package/build/types/enums.d.ts.map +1 -1
  82. package/build/types/enums.js +1 -0
  83. package/build/types/enums.js.map +1 -1
  84. package/build/utils/display/scene/index.d.ts +1 -1
  85. package/build/utils/display/scene/index.d.ts.map +1 -1
  86. package/build/utils/display/scene/index.js.map +1 -1
  87. package/build/utils/display/scene/process.d.ts.map +1 -1
  88. package/build/utils/display/scene/process.js +20 -1
  89. package/build/utils/display/scene/process.js.map +1 -1
  90. package/build/utils/display/scene/types.d.ts +1 -1
  91. package/build/utils/display/scene/types.d.ts.map +1 -1
  92. package/build/utils/display/scene/types.js.map +1 -1
  93. package/package.json +7 -7
  94. package/src/engine.ts +5 -1
  95. package/src/facades/ai.ts +3 -3
  96. package/src/facades/glasses.ts +7 -0
  97. package/src/generated/releaseMetadata.ts +3 -3
  98. package/src/services/GestureDotsService.ts +2 -1
  99. package/src/services/GlassesTouchInput.ts +81 -34
  100. package/src/services/LocalDisplayManager.ts +22 -8
  101. package/src/services/LocalLlmModelManager.ts +16 -6
  102. package/src/services/LocalLlmPrompt.ts +56 -37
  103. package/src/services/LocalLlmProvider.ts +49 -22
  104. package/src/services/LocalMiniappRuntime.ts +140 -134
  105. package/src/services/MiniappExitGesture.ts +8 -5
  106. package/src/services/MiniappWidgetMode.ts +9 -7
  107. package/src/services/SceneRenderer.ts +3 -1
  108. package/src/services/actionInvoke.ts +105 -0
  109. package/src/services/appleWatch/AppleWatchLink.ts +297 -0
  110. package/src/services/controllers.ts +17 -0
  111. package/src/services/settingsBridge.ts +9 -4
  112. package/src/stores/apps.ts +12 -10
  113. package/src/stores/bluetoothSettingKeys.ts +20 -0
  114. package/src/stores/settings.ts +34 -2
  115. package/src/types/capabilities/rayneo-io.ts +2 -0
  116. package/src/types/enums.ts +1 -0
  117. package/src/utils/display/scene/index.ts +1 -0
  118. package/src/utils/display/scene/process.ts +17 -1
  119. package/src/utils/display/scene/types.ts +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"GlassesTouchInput.d.ts","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAA;AA4CpE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CA4D1D;AAED,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CACzB,WAAW,EACP,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,SAAS,GACT,eAAe,GACf,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACd,IAAI,CAQN"}
1
+ {"version":3,"file":"GlassesTouchInput.d.ts","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAA;AAkFpE;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAQ1D;AA6DD,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CACzB,WAAW,EACP,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,SAAS,GACT,eAAe,GACf,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACd,IAAI,CAQN"}
@@ -4,6 +4,7 @@ import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
4
4
  import { useAppStatusStore } from "../stores/apps";
5
5
  import { useGlassesStore } from "../stores/glasses";
6
6
  import { BgTimer } from "../utils/timers";
7
+ import { hasInput2d } from "./controllers";
7
8
  import dashboardOpenTrace from "./debug/DashboardOpenTrace";
8
9
  import gestureDotsService from "./GestureDotsService";
9
10
  import { isGlassesConnected } from "./GlassesReadiness";
@@ -15,43 +16,86 @@ import { isOverlayApp } from "./pages/overlayApps";
15
16
  import { pagesEligible } from "./pages/pageEligibility";
16
17
  const miniappExitGesture = new MiniappExitGesture({
17
18
  getState: () => useAppStatusStore.getState(),
18
- getVisiblePackageName: () => localDisplayManager.getVisibleMiniappPackageName(),
19
+ getTargetPackageName: () => localDisplayManager.getMiniappToLeave(),
19
20
  beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),
20
21
  finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),
21
22
  cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),
22
23
  setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),
23
24
  clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),
24
25
  });
25
- // Raw ring reports include every scroll step, whether the CFW forwards them
26
- // (deviceModel G2) or the ring sends them to the phone itself (deviceModel R1);
27
- // both tag source 2. Rate-limit scroll actions separately from press/release
28
- // recognition, per ring, using native receipt timestamps so a JS backlog does
29
- // not collapse otherwise separate gestures.
30
- const RING_SCROLL_INTERVAL_MS = 100;
31
- const lastRingScrollAt = new Map();
32
- function shouldForwardScroll(event) {
33
- if (event.source !== 2 || (event.gestureName !== "swipe_up" && event.gestureName !== "swipe_down"))
34
- return true;
35
- const ring = event.controllerId ?? `source:${event.source}`;
36
- const last = lastRingScrollAt.get(ring);
26
+ // The ring reports a slide once per step of travel, whether the CFW forwards
27
+ // it (deviceModel G2) or the ring sends it to the phone itself (deviceModel
28
+ // R1); both tag source 2. One press is one swipe: the stroke's first step goes
29
+ // out as swipe_up / swipe_down, and every step, the first included, as
30
+ // scroll_up / scroll_down with its distance and speed for smooth scrolling. A
31
+ // stroke ends at the ring's next touch_down or tap_end, or after
32
+ // RING_STROKE_IDLE_MS without a step when a report was lost. Reversing within
33
+ // a stroke fires no swipe (a finger rolling back at lift-off). Native receipt
34
+ // timestamps keep strokes apart when JS drains a backlog.
35
+ const RING_STROKE_IDLE_MS = 1000;
36
+ /** Per ring, when the open stroke's last step arrived. */
37
+ const ringStrokes = new Map();
38
+ function ringKey(event) {
39
+ return event.controllerId ?? `source:${event.source}`;
40
+ }
41
+ function isRingSlideStep(event) {
42
+ return event.source === 2 && (event.gestureName === "swipe_up" || event.gestureName === "swipe_down");
43
+ }
44
+ function opensRingStroke(event) {
45
+ const last = ringStrokes.get(ringKey(event));
46
+ ringStrokes.set(ringKey(event), event.timestamp);
37
47
  const elapsed = last === undefined ? null : event.timestamp - last;
38
- if (elapsed !== null && elapsed >= 0 && elapsed < RING_SCROLL_INTERVAL_MS)
39
- return false;
40
- lastRingScrollAt.set(ring, event.timestamp);
41
- return true;
48
+ return elapsed === null || elapsed < 0 || elapsed >= RING_STROKE_IDLE_MS;
49
+ }
50
+ function endRingStroke(event) {
51
+ if (event.source === 2 && (event.gestureName === "touch_down" || event.gestureName === "tap_end")) {
52
+ ringStrokes.delete(ringKey(event));
53
+ }
54
+ }
55
+ /** The discrete swipe a stroke opens with: the step's travel belongs to scroll_*. */
56
+ function strokeSwipe(step) {
57
+ const swipe = { ...step };
58
+ delete swipe.distance;
59
+ delete swipe.speed;
60
+ return swipe;
61
+ }
62
+ /**
63
+ * Miniapps' copy of the touch: `input2d: true` when it came from a controller
64
+ * that swipes in two dimensions (the Neural Band), the field absent otherwise,
65
+ * so a 1D swipe from the temples or the ring stays tellable while a band is
66
+ * connected under the same gesture names.
67
+ */
68
+ function toMiniapps(event) {
69
+ const id = event.controllerId;
70
+ if (id === undefined)
71
+ return event;
72
+ const model = useGlassesStore.getState().controllers?.[id]?.model;
73
+ return model !== undefined && hasInput2d(model) ? { ...event, input2d: true } : event;
42
74
  }
43
75
  /**
44
76
  * Route both real glasses input and phone mirror controls through the same
45
77
  * policy. A claimed gesture goes to the app holding the claim, alone; an
46
78
  * unclaimed one goes to every subscriber and then the engine's default. The
47
- * one rule ahead of the claims: an unclaimed double tap on a miniapp whose
48
- * frame is visibly on the glasses leaves that app, whoever else claimed the
49
- * gesture — the dashboard's claim is for the empty screen. Head position
79
+ * one rule ahead of the claims: an unclaimed double tap leaves the miniapp
80
+ * the wearer is in — the one visibly on the glasses, else the running
81
+ * standard app on an empty screen — whoever else claimed the gesture; the
82
+ * dashboard's claim is for the screen with no app in it. Head position
50
83
  * (head_up / head_down) is a gesture like any other: the dashboard claims it
51
84
  * while it runs, and an unclaimed head_up on glasses that defer the dashboard
52
- * to the phone opens the native one, as it would with no app at all.
85
+ * to the phone opens the native one, as it would with no app at all. A ring
86
+ * slide step becomes one swipe per stroke plus a scroll step (see above).
53
87
  */
54
88
  export function dispatchTouchEvent(event) {
89
+ if (isRingSlideStep(event)) {
90
+ if (opensRingStroke(event))
91
+ routeTouchEvent(strokeSwipe(event));
92
+ routeTouchEvent({ ...event, gestureName: event.gestureName === "swipe_up" ? "scroll_up" : "scroll_down" });
93
+ return;
94
+ }
95
+ endRingStroke(event);
96
+ routeTouchEvent(event);
97
+ }
98
+ function routeTouchEvent(event) {
55
99
  const gesture = event.gestureName;
56
100
  const claimant = localMiniappRuntime.gestureClaimant(gesture);
57
101
  // The Neural Band's middle finger (index 1) shares the tap names but is left
@@ -59,15 +103,15 @@ export function dispatchTouchEvent(event) {
59
103
  const osDoubleTap = gesture === "double_tap" && event.index !== 1;
60
104
  const osHeadUp = gesture === "head_up";
61
105
  if (osDoubleTap) {
62
- // A visible main miniapp opts out of OS exit by registering double tap (the
106
+ // The app being left opts out of OS exit by registering double tap (the
63
107
  // terminal's back-to-menu). The latest claimant still receives it. Otherwise
64
108
  // the tap means "leave the app". The controller shows the bounded OS
65
- // notice first, then uses the normal
66
- // store stop path (and closes its phone overlay).
67
- const visible = localDisplayManager.getVisibleMiniappPackageName();
68
- if (visible !== null &&
69
- !localMiniappRuntime.hasGestureClaim(visible, gesture) &&
70
- miniappExitGesture.tryExitVisibleMiniapp()) {
109
+ // notice first, then uses the normal store stop path (and closes its
110
+ // phone overlay).
111
+ const leaving = localDisplayManager.getMiniappToLeave();
112
+ if (leaving !== null &&
113
+ !localMiniappRuntime.hasGestureClaim(leaving, gesture) &&
114
+ miniappExitGesture.tryExitMiniapp()) {
71
115
  gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId);
72
116
  return;
73
117
  }
@@ -79,18 +123,13 @@ export function dispatchTouchEvent(event) {
79
123
  }
80
124
  // The press may have started before this app claimed its release edge.
81
125
  gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId);
82
- if (!shouldForwardScroll(event))
83
- return;
84
- localMiniappRuntime.forwardEvent("touch_event", event, undefined, claimant);
126
+ localMiniappRuntime.forwardEvent("touch_event", toMiniapps(event), undefined, claimant);
85
127
  return;
86
128
  }
87
129
  // Gesture-dots debug indicator (any device, gated by its setting). It
88
- // only paints while the screen is otherwise empty. Process even suppressed
89
- // scroll repeats here so their completion still clears a held dot.
130
+ // only paints while the screen is otherwise empty.
90
131
  gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId);
91
- if (!shouldForwardScroll(event))
92
- return;
93
- localMiniappRuntime.forwardEvent("touch_event", event);
132
+ localMiniappRuntime.forwardEvent("touch_event", toMiniapps(event));
94
133
  // Nobody claimed a double tap or a head-up: the glasses' native dashboard.
95
134
  // On eligible Glassly CFW glasses the dashboard miniapp claims both while it
96
135
  // runs (ADR-0006); stock firmware keeps its native dashboard (ADR-0002) and
@@ -109,7 +148,7 @@ export function dispatchTouchEvent(event) {
109
148
  }
110
149
  export function resetTouchInput() {
111
150
  miniappExitGesture.reset();
112
- lastRingScrollAt.clear();
151
+ ringStrokes.clear();
113
152
  }
114
153
  /** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */
115
154
  export function sendGesture(gestureName) {
@@ -1 +1 @@
1
- {"version":3,"file":"GlassesTouchInput.js","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"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","sourcesContent":["import {useDisplayStore} from \"../stores/display\"\nimport {DeviceModels, type TouchEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {BgTimer} from \"../utils/timers\"\nimport dashboardOpenTrace from \"./debug/DashboardOpenTrace\"\nimport gestureDotsService from \"./GestureDotsService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport {MiniappExitGesture} from \"./MiniappExitGesture\"\nimport nativeNavigationService from \"./NativeNavigationService\"\nimport {isOverlayApp} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\n\nconst miniappExitGesture = new MiniappExitGesture({\n getState: () => useAppStatusStore.getState(),\n getVisiblePackageName: () => localDisplayManager.getVisibleMiniappPackageName(),\n beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),\n finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),\n cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),\n setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),\n clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),\n})\n\n// Raw ring reports include every scroll step, whether the CFW forwards them\n// (deviceModel G2) or the ring sends them to the phone itself (deviceModel R1);\n// both tag source 2. Rate-limit scroll actions separately from press/release\n// recognition, per ring, using native receipt timestamps so a JS backlog does\n// not collapse otherwise separate gestures.\nconst RING_SCROLL_INTERVAL_MS = 100\nconst lastRingScrollAt = new Map<string, number>()\n\nfunction shouldForwardScroll(event: TouchEvent): boolean {\n if (event.source !== 2 || (event.gestureName !== \"swipe_up\" && event.gestureName !== \"swipe_down\")) return true\n const ring = event.controllerId ?? `source:${event.source}`\n const last = lastRingScrollAt.get(ring)\n const elapsed = last === undefined ? null : event.timestamp - last\n if (elapsed !== null && elapsed >= 0 && elapsed < RING_SCROLL_INTERVAL_MS) return false\n lastRingScrollAt.set(ring, event.timestamp)\n return true\n}\n\n/**\n * Route both real glasses input and phone mirror controls through the same\n * policy. A claimed gesture goes to the app holding the claim, alone; an\n * unclaimed one goes to every subscriber and then the engine's default. The\n * one rule ahead of the claims: an unclaimed double tap on a miniapp whose\n * frame is visibly on the glasses leaves that app, whoever else claimed the\n * gesture — the dashboard's claim is for the empty screen. Head position\n * (head_up / head_down) is a gesture like any other: the dashboard claims it\n * while it runs, and an unclaimed head_up on glasses that defer the dashboard\n * to the phone opens the native one, as it would with no app at all.\n */\nexport function dispatchTouchEvent(event: TouchEvent): void {\n const gesture = event.gestureName\n const claimant = localMiniappRuntime.gestureClaimant(gesture)\n // The Neural Band's middle finger (index 1) shares the tap names but is left\n // to miniapps: the engine's own double-tap behaviors are the index finger's.\n const osDoubleTap = gesture === \"double_tap\" && event.index !== 1\n const osHeadUp = gesture === \"head_up\"\n\n if (osDoubleTap) {\n // A visible main miniapp opts out of OS exit by registering double tap (the\n // terminal's back-to-menu). The latest claimant still receives it. Otherwise\n // the tap means \"leave the app\". The controller shows the bounded OS\n // notice first, then uses the normal\n // store stop path (and closes its phone overlay).\n const visible = localDisplayManager.getVisibleMiniappPackageName()\n if (\n visible !== null &&\n !localMiniappRuntime.hasGestureClaim(visible, gesture) &&\n miniappExitGesture.tryExitVisibleMiniapp()\n ) {\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n return\n }\n }\n\n if (claimant !== null) {\n // An overlay app claims the double tap and head-up on an empty screen to open the dashboard.\n if ((gesture === \"double_tap\" || osHeadUp) && isOverlayApp(claimant)) {\n dashboardOpenTrace.trigger(osHeadUp ? \"head-up\" : \"double-tap\", event.timestamp)\n }\n // The press may have started before this app claimed its release edge.\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n if (!shouldForwardScroll(event)) return\n localMiniappRuntime.forwardEvent(\"touch_event\", event, undefined, claimant)\n return\n }\n\n // Gesture-dots debug indicator (any device, gated by its setting). It\n // only paints while the screen is otherwise empty. Process even suppressed\n // scroll repeats here so their completion still clears a held dot.\n gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId)\n\n if (!shouldForwardScroll(event)) return\n localMiniappRuntime.forwardEvent(\"touch_event\", event)\n\n // Nobody claimed a double tap or a head-up: the glasses' native dashboard.\n // On eligible Glassly CFW glasses the dashboard miniapp claims both while it\n // runs (ADR-0006); stock firmware keeps its native dashboard (ADR-0002) and\n // opens it on a head-up by itself, so only the double tap is relayed there.\n // The modal nav UI owns the screen; showDashboard would fight it. Without\n // glasses there is no dashboard to open, and the mirror keeps its frame.\n // pagesEligible: connected Glassly CFW glasses, whose firmware leaves the\n // dashboard to the phone; other firmware opens its own on a lift.\n const opensNativeDashboard = osDoubleTap || (osHeadUp && pagesEligible())\n if (opensNativeDashboard && !nativeNavigationService.isActive && isGlassesConnected(useGlassesStore.getState().connection)) {\n useDisplayStore.getState().setDisplayEvent({layout: {layoutType: \"unavailable\", reason: \"firmware-dashboard\"}})\n void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {\n console.error(\"DeviceEventRouter: showDashboard failed:\", err)\n })\n }\n}\n\nexport function resetTouchInput(): void {\n miniappExitGesture.reset()\n lastRingScrollAt.clear()\n}\n\n/** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */\nexport function sendGesture(\n gestureName:\n | \"single_tap\"\n | \"tap_start\"\n | \"double_tap\"\n | \"tap_end\"\n | \"tap_then_hold\"\n | \"touch_down\"\n | \"swipe_up\"\n | \"swipe_down\"\n | \"swipe_left\"\n | \"swipe_right\"\n | \"long_press\"\n | \"dial_up\"\n | \"dial_down\"\n | \"head_up\"\n | \"head_down\",\n): void {\n const model = useGlassesStore.getState().deviceModel\n dispatchTouchEvent({\n type: \"touch_event\",\n gestureName,\n deviceModel: Object.values(DeviceModels).find((candidate) => candidate === model) ?? DeviceModels.Simulated,\n timestamp: Date.now(),\n })\n}\n"]}
1
+ {"version":3,"file":"GlassesTouchInput.js","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"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","sourcesContent":["import {useDisplayStore} from \"../stores/display\"\nimport {DeviceModels, type TouchEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {BgTimer} from \"../utils/timers\"\nimport {hasInput2d} from \"./controllers\"\nimport dashboardOpenTrace from \"./debug/DashboardOpenTrace\"\nimport gestureDotsService from \"./GestureDotsService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport {MiniappExitGesture} from \"./MiniappExitGesture\"\nimport nativeNavigationService from \"./NativeNavigationService\"\nimport {isOverlayApp} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\n\nconst miniappExitGesture = new MiniappExitGesture({\n getState: () => useAppStatusStore.getState(),\n getTargetPackageName: () => localDisplayManager.getMiniappToLeave(),\n beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),\n finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),\n cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),\n setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),\n clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),\n})\n\n// The ring reports a slide once per step of travel, whether the CFW forwards\n// it (deviceModel G2) or the ring sends it to the phone itself (deviceModel\n// R1); both tag source 2. One press is one swipe: the stroke's first step goes\n// out as swipe_up / swipe_down, and every step, the first included, as\n// scroll_up / scroll_down with its distance and speed for smooth scrolling. A\n// stroke ends at the ring's next touch_down or tap_end, or after\n// RING_STROKE_IDLE_MS without a step when a report was lost. Reversing within\n// a stroke fires no swipe (a finger rolling back at lift-off). Native receipt\n// timestamps keep strokes apart when JS drains a backlog.\nconst RING_STROKE_IDLE_MS = 1000\n/** Per ring, when the open stroke's last step arrived. */\nconst ringStrokes = new Map<string, number>()\n\nfunction ringKey(event: TouchEvent): string {\n return event.controllerId ?? `source:${event.source}`\n}\n\nfunction isRingSlideStep(event: TouchEvent): boolean {\n return event.source === 2 && (event.gestureName === \"swipe_up\" || event.gestureName === \"swipe_down\")\n}\n\nfunction opensRingStroke(event: TouchEvent): boolean {\n const last = ringStrokes.get(ringKey(event))\n ringStrokes.set(ringKey(event), event.timestamp)\n const elapsed = last === undefined ? null : event.timestamp - last\n return elapsed === null || elapsed < 0 || elapsed >= RING_STROKE_IDLE_MS\n}\n\nfunction endRingStroke(event: TouchEvent): void {\n if (event.source === 2 && (event.gestureName === \"touch_down\" || event.gestureName === \"tap_end\")) {\n ringStrokes.delete(ringKey(event))\n }\n}\n\n/** The discrete swipe a stroke opens with: the step's travel belongs to scroll_*. */\nfunction strokeSwipe(step: TouchEvent): TouchEvent {\n const swipe = {...step}\n delete swipe.distance\n delete swipe.speed\n return swipe\n}\n\n/**\n * Miniapps' copy of the touch: `input2d: true` when it came from a controller\n * that swipes in two dimensions (the Neural Band), the field absent otherwise,\n * so a 1D swipe from the temples or the ring stays tellable while a band is\n * connected under the same gesture names.\n */\nfunction toMiniapps(event: TouchEvent): TouchEvent & {input2d?: true} {\n const id = event.controllerId\n if (id === undefined) return event\n const model = useGlassesStore.getState().controllers?.[id]?.model\n return model !== undefined && hasInput2d(model) ? {...event, input2d: true} : event\n}\n\n/**\n * Route both real glasses input and phone mirror controls through the same\n * policy. A claimed gesture goes to the app holding the claim, alone; an\n * unclaimed one goes to every subscriber and then the engine's default. The\n * one rule ahead of the claims: an unclaimed double tap leaves the miniapp\n * the wearer is in — the one visibly on the glasses, else the running\n * standard app on an empty screen — whoever else claimed the gesture; the\n * dashboard's claim is for the screen with no app in it. Head position\n * (head_up / head_down) is a gesture like any other: the dashboard claims it\n * while it runs, and an unclaimed head_up on glasses that defer the dashboard\n * to the phone opens the native one, as it would with no app at all. A ring\n * slide step becomes one swipe per stroke plus a scroll step (see above).\n */\nexport function dispatchTouchEvent(event: TouchEvent): void {\n if (isRingSlideStep(event)) {\n if (opensRingStroke(event)) routeTouchEvent(strokeSwipe(event))\n routeTouchEvent({...event, gestureName: event.gestureName === \"swipe_up\" ? \"scroll_up\" : \"scroll_down\"})\n return\n }\n endRingStroke(event)\n routeTouchEvent(event)\n}\n\nfunction routeTouchEvent(event: TouchEvent): void {\n const gesture = event.gestureName\n const claimant = localMiniappRuntime.gestureClaimant(gesture)\n // The Neural Band's middle finger (index 1) shares the tap names but is left\n // to miniapps: the engine's own double-tap behaviors are the index finger's.\n const osDoubleTap = gesture === \"double_tap\" && event.index !== 1\n const osHeadUp = gesture === \"head_up\"\n\n if (osDoubleTap) {\n // The app being left opts out of OS exit by registering double tap (the\n // terminal's back-to-menu). The latest claimant still receives it. Otherwise\n // the tap means \"leave the app\". The controller shows the bounded OS\n // notice first, then uses the normal store stop path (and closes its\n // phone overlay).\n const leaving = localDisplayManager.getMiniappToLeave()\n if (\n leaving !== null &&\n !localMiniappRuntime.hasGestureClaim(leaving, gesture) &&\n miniappExitGesture.tryExitMiniapp()\n ) {\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n return\n }\n }\n\n if (claimant !== null) {\n // An overlay app claims the double tap and head-up on an empty screen to open the dashboard.\n if ((gesture === \"double_tap\" || osHeadUp) && isOverlayApp(claimant)) {\n dashboardOpenTrace.trigger(osHeadUp ? \"head-up\" : \"double-tap\", event.timestamp)\n }\n // The press may have started before this app claimed its release edge.\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n localMiniappRuntime.forwardEvent(\"touch_event\", toMiniapps(event), undefined, claimant)\n return\n }\n\n // Gesture-dots debug indicator (any device, gated by its setting). It\n // only paints while the screen is otherwise empty.\n gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId)\n\n localMiniappRuntime.forwardEvent(\"touch_event\", toMiniapps(event))\n\n // Nobody claimed a double tap or a head-up: the glasses' native dashboard.\n // On eligible Glassly CFW glasses the dashboard miniapp claims both while it\n // runs (ADR-0006); stock firmware keeps its native dashboard (ADR-0002) and\n // opens it on a head-up by itself, so only the double tap is relayed there.\n // The modal nav UI owns the screen; showDashboard would fight it. Without\n // glasses there is no dashboard to open, and the mirror keeps its frame.\n // pagesEligible: connected Glassly CFW glasses, whose firmware leaves the\n // dashboard to the phone; other firmware opens its own on a lift.\n const opensNativeDashboard = osDoubleTap || (osHeadUp && pagesEligible())\n if (opensNativeDashboard && !nativeNavigationService.isActive && isGlassesConnected(useGlassesStore.getState().connection)) {\n useDisplayStore.getState().setDisplayEvent({layout: {layoutType: \"unavailable\", reason: \"firmware-dashboard\"}})\n void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {\n console.error(\"DeviceEventRouter: showDashboard failed:\", err)\n })\n }\n}\n\nexport function resetTouchInput(): void {\n miniappExitGesture.reset()\n ringStrokes.clear()\n}\n\n/** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */\nexport function sendGesture(\n gestureName:\n | \"single_tap\"\n | \"tap_start\"\n | \"double_tap\"\n | \"tap_end\"\n | \"tap_then_hold\"\n | \"touch_down\"\n | \"swipe_up\"\n | \"swipe_down\"\n | \"swipe_left\"\n | \"swipe_right\"\n | \"long_press\"\n | \"dial_up\"\n | \"dial_down\"\n | \"head_up\"\n | \"head_down\",\n): void {\n const model = useGlassesStore.getState().deviceModel\n dispatchTouchEvent({\n type: \"touch_event\",\n gestureName,\n deviceModel: Object.values(DeviceModels).find((candidate) => candidate === model) ?? DeviceModels.Simulated,\n timestamp: Date.now(),\n })\n}\n"]}
@@ -118,10 +118,18 @@ declare class LocalDisplayManager {
118
118
  */
119
119
  getVisibleMiniappPackageName(): string | null;
120
120
  /**
121
- * Cover the currently visible miniapp with a persistent system exit notice.
122
- * While it is up, miniapp display traffic is held off so a streaming app
123
- * cannot immediately overwrite the notice. The caller completes the normal
124
- * stop flow and then calls finishMiniappExit(); onUnmount does the same as a
121
+ * The miniapp an unclaimed double tap leaves: the one visibly drawing on
122
+ * the main view, else the core (running standard) app on an empty or
123
+ * system-drawn screen — an app that has drawn nothing yet, or whose frame
124
+ * expired, is still the one the wearer is in. Nothing while a page covers
125
+ * the main view: the page's claims own the tap.
126
+ */
127
+ getMiniappToLeave(): string | null;
128
+ /**
129
+ * Cover the leaving miniapp with a persistent system exit notice. While it
130
+ * is up, miniapp display traffic is held off so a streaming app cannot
131
+ * immediately overwrite the notice. The caller completes the normal stop
132
+ * flow and then calls finishMiniappExit(); onUnmount does the same as a
125
133
  * safety net.
126
134
  */
127
135
  beginMiniappExit(packageName: string, text: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"LocalDisplayManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalDisplayManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LocalDisplayManager.d.ts","sourceRoot":"","sources":["../../src/services/LocalDisplayManager.ts"],"names":[],"mappings":"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"}
@@ -231,18 +231,32 @@ class LocalDisplayManager {
231
231
  return current;
232
232
  }
233
233
  /**
234
- * Cover the currently visible miniapp with a persistent system exit notice.
235
- * While it is up, miniapp display traffic is held off so a streaming app
236
- * cannot immediately overwrite the notice. The caller completes the normal
237
- * stop flow and then calls finishMiniappExit(); onUnmount does the same as a
234
+ * The miniapp an unclaimed double tap leaves: the one visibly drawing on
235
+ * the main view, else the core (running standard) app on an empty or
236
+ * system-drawn screen — an app that has drawn nothing yet, or whose frame
237
+ * expired, is still the one the wearer is in. Nothing while a page covers
238
+ * the main view: the page's claims own the tap.
239
+ */
240
+ getMiniappToLeave() {
241
+ if (this.overlayOwner !== null)
242
+ return null;
243
+ return this.getVisibleMiniappPackageName() ?? this.coreApp;
244
+ }
245
+ /**
246
+ * Cover the leaving miniapp with a persistent system exit notice. While it
247
+ * is up, miniapp display traffic is held off so a streaming app cannot
248
+ * immediately overwrite the notice. The caller completes the normal stop
249
+ * flow and then calls finishMiniappExit(); onUnmount does the same as a
238
250
  * safety net.
239
251
  */
240
252
  beginMiniappExit(packageName, text) {
241
253
  if (this.exitingApp !== null)
242
254
  return this.exitingApp.packageName === packageName;
243
- const coveredDisplay = this.currentDisplay;
244
- if (!coveredDisplay || this.getVisibleMiniappPackageName() !== packageName)
255
+ if (this.getMiniappToLeave() !== packageName)
245
256
  return false;
257
+ // Only the app's own visible frame comes back on cancel; a system frame
258
+ // or an empty screen does not.
259
+ const coveredDisplay = this.getVisibleMiniappPackageName() === packageName ? this.currentDisplay : null;
246
260
  console.log(`${LOG_TAG}: beginMiniappExit(${packageName})`);
247
261
  this.exitingApp = { packageName, coveredDisplay };
248
262
  this.clearExpiryTimer();
@@ -273,7 +287,7 @@ class LocalDisplayManager {
273
287
  this.exitingApp = null;
274
288
  if (this.currentDisplay?.packageName !== SYSTEM_NOTICE_PKG)
275
289
  return;
276
- if (covered.expiresAt === null || covered.expiresAt > this.now()) {
290
+ if (covered && (covered.expiresAt === null || covered.expiresAt > this.now())) {
277
291
  this.restoreDisplay(covered);
278
292
  }
279
293
  else {