@glassly/engine 0.1.0-dev.1 → 0.1.0-dev.101

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 (343) hide show
  1. package/README.md +57 -19
  2. package/build/bluetooth-sdk/debug.d.ts +2 -0
  3. package/build/bluetooth-sdk/debug.d.ts.map +1 -0
  4. package/build/bluetooth-sdk/debug.js +2 -0
  5. package/build/bluetooth-sdk/debug.js.map +1 -0
  6. package/build/bluetooth-sdk/index.d.ts +3 -0
  7. package/build/bluetooth-sdk/index.d.ts.map +1 -0
  8. package/build/bluetooth-sdk/index.js +3 -0
  9. package/build/bluetooth-sdk/index.js.map +1 -0
  10. package/build/bluetooth-sdk/ota-transport.d.ts +2 -0
  11. package/build/bluetooth-sdk/ota-transport.d.ts.map +1 -0
  12. package/build/bluetooth-sdk/ota-transport.js +2 -0
  13. package/build/bluetooth-sdk/ota-transport.js.map +1 -0
  14. package/build/bluetooth-sdk/photo-receiver.d.ts +3 -0
  15. package/build/bluetooth-sdk/photo-receiver.d.ts.map +1 -0
  16. package/build/bluetooth-sdk/photo-receiver.js +3 -0
  17. package/build/bluetooth-sdk/photo-receiver.js.map +1 -0
  18. package/build/bluetooth-sdk/react.d.ts +2 -0
  19. package/build/bluetooth-sdk/react.d.ts.map +1 -0
  20. package/build/bluetooth-sdk/react.js +2 -0
  21. package/build/bluetooth-sdk/react.js.map +1 -0
  22. package/build/bluetooth-sdk/types.d.ts +2 -0
  23. package/build/bluetooth-sdk/types.d.ts.map +1 -0
  24. package/build/bluetooth-sdk/types.js +2 -0
  25. package/build/bluetooth-sdk/types.js.map +1 -0
  26. package/build/engine.d.ts +30 -18
  27. package/build/engine.d.ts.map +1 -1
  28. package/build/engine.js +12 -3
  29. package/build/engine.js.map +1 -1
  30. package/build/facades/dev.d.ts +1 -1
  31. package/build/facades/display.d.ts +11 -4
  32. package/build/facades/display.d.ts.map +1 -1
  33. package/build/facades/display.js +12 -2
  34. package/build/facades/display.js.map +1 -1
  35. package/build/facades/displayMirror.d.ts +2 -6
  36. package/build/facades/displayMirror.d.ts.map +1 -1
  37. package/build/facades/displayMirror.js +7 -14
  38. package/build/facades/displayMirror.js.map +1 -1
  39. package/build/facades/glasses.d.ts +6 -13
  40. package/build/facades/glasses.d.ts.map +1 -1
  41. package/build/facades/glasses.js +7 -3
  42. package/build/facades/glasses.js.map +1 -1
  43. package/build/facades/ota.d.ts +21 -21
  44. package/build/facades/ota.d.ts.map +1 -1
  45. package/build/facades/ota.js +26 -10
  46. package/build/facades/ota.js.map +1 -1
  47. package/build/facades/pairing.d.ts +11 -5
  48. package/build/facades/pairing.d.ts.map +1 -1
  49. package/build/facades/pairing.js +93 -17
  50. package/build/facades/pairing.js.map +1 -1
  51. package/build/facades/phoneNotifications.d.ts +12 -2
  52. package/build/facades/phoneNotifications.d.ts.map +1 -1
  53. package/build/facades/phoneNotifications.js +30 -10
  54. package/build/facades/phoneNotifications.js.map +1 -1
  55. package/build/facades/speakers.d.ts +1 -1
  56. package/build/facades/speakers.d.ts.map +1 -1
  57. package/build/facades/speakers.js.map +1 -1
  58. package/build/facades/speech.d.ts +7 -6
  59. package/build/facades/speech.d.ts.map +1 -1
  60. package/build/facades/speech.js +1 -1
  61. package/build/facades/speech.js.map +1 -1
  62. package/build/generated/releaseMetadata.d.ts +12 -0
  63. package/build/generated/releaseMetadata.d.ts.map +1 -0
  64. package/build/generated/releaseMetadata.js +10 -0
  65. package/build/generated/releaseMetadata.js.map +1 -0
  66. package/build/index.d.ts +9 -6
  67. package/build/index.d.ts.map +1 -1
  68. package/build/index.js +7 -5
  69. package/build/index.js.map +1 -1
  70. package/build/react/MentraLiveOtaFlow.d.ts +44 -0
  71. package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
  72. package/build/react/MentraLiveOtaFlow.js +322 -0
  73. package/build/react/MentraLiveOtaFlow.js.map +1 -0
  74. package/build/react/index.d.ts +3 -0
  75. package/build/react/index.d.ts.map +1 -0
  76. package/build/react/index.js +3 -0
  77. package/build/react/index.js.map +1 -0
  78. package/build/react/useEngineSnapshot.d.ts +2 -0
  79. package/build/react/useEngineSnapshot.d.ts.map +1 -0
  80. package/build/react/useEngineSnapshot.js +14 -0
  81. package/build/react/useEngineSnapshot.js.map +1 -0
  82. package/build/react/useMentraLiveOta.d.ts +84 -0
  83. package/build/react/useMentraLiveOta.d.ts.map +1 -0
  84. package/build/react/useMentraLiveOta.js +627 -0
  85. package/build/react/useMentraLiveOta.js.map +1 -0
  86. package/build/services/AppRegistry.d.ts +8 -0
  87. package/build/services/AppRegistry.d.ts.map +1 -1
  88. package/build/services/AppRegistry.js +15 -0
  89. package/build/services/AppRegistry.js.map +1 -1
  90. package/build/services/BlobStore.d.ts +2 -0
  91. package/build/services/BlobStore.d.ts.map +1 -1
  92. package/build/services/BlobStore.js +63 -10
  93. package/build/services/BlobStore.js.map +1 -1
  94. package/build/services/CustomDashboardModel.d.ts +48 -0
  95. package/build/services/CustomDashboardModel.d.ts.map +1 -0
  96. package/build/services/CustomDashboardModel.js +73 -0
  97. package/build/services/CustomDashboardModel.js.map +1 -0
  98. package/build/services/CustomDashboardService.d.ts +55 -0
  99. package/build/services/CustomDashboardService.d.ts.map +1 -0
  100. package/build/services/CustomDashboardService.js +520 -0
  101. package/build/services/CustomDashboardService.js.map +1 -0
  102. package/build/services/DashboardCoordinator.d.ts +26 -0
  103. package/build/services/DashboardCoordinator.d.ts.map +1 -0
  104. package/build/services/DashboardCoordinator.js +125 -0
  105. package/build/services/DashboardCoordinator.js.map +1 -0
  106. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  107. package/build/services/DeviceEventRouter.js +45 -6
  108. package/build/services/DeviceEventRouter.js.map +1 -1
  109. package/build/services/DisplayProcessor.d.ts +3 -3
  110. package/build/services/DisplayProcessor.js +3 -3
  111. package/build/services/DisplayProcessor.js.map +1 -1
  112. package/build/services/GestureDotsService.d.ts +40 -0
  113. package/build/services/GestureDotsService.d.ts.map +1 -0
  114. package/build/services/GestureDotsService.js +167 -0
  115. package/build/services/GestureDotsService.js.map +1 -0
  116. package/build/services/GlassesStatusProjection.d.ts +15 -1
  117. package/build/services/GlassesStatusProjection.d.ts.map +1 -1
  118. package/build/services/GlassesStatusProjection.js +18 -11
  119. package/build/services/GlassesStatusProjection.js.map +1 -1
  120. package/build/services/HotspotOtaTransport.d.ts.map +1 -1
  121. package/build/services/HotspotOtaTransport.js +8 -7
  122. package/build/services/HotspotOtaTransport.js.map +1 -1
  123. package/build/services/LocalDisplayManager.d.ts +55 -3
  124. package/build/services/LocalDisplayManager.d.ts.map +1 -1
  125. package/build/services/LocalDisplayManager.js +196 -16
  126. package/build/services/LocalDisplayManager.js.map +1 -1
  127. package/build/services/LocalMiniappRuntime.d.ts +13 -0
  128. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  129. package/build/services/LocalMiniappRuntime.js +32 -0
  130. package/build/services/LocalMiniappRuntime.js.map +1 -1
  131. package/build/services/MiniappExitGesture.d.ts +33 -0
  132. package/build/services/MiniappExitGesture.d.ts.map +1 -0
  133. package/build/services/MiniappExitGesture.js +54 -0
  134. package/build/services/MiniappExitGesture.js.map +1 -0
  135. package/build/services/OtaAutoChain.d.ts +41 -0
  136. package/build/services/OtaAutoChain.d.ts.map +1 -0
  137. package/build/services/OtaAutoChain.js +91 -0
  138. package/build/services/OtaAutoChain.js.map +1 -0
  139. package/build/services/OtaErrorMapping.d.ts +18 -0
  140. package/build/services/OtaErrorMapping.d.ts.map +1 -0
  141. package/build/services/OtaErrorMapping.js +63 -0
  142. package/build/services/OtaErrorMapping.js.map +1 -0
  143. package/build/services/OtaInstallCoordinator.d.ts +26 -3
  144. package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
  145. package/build/services/OtaInstallCoordinator.js +38 -13
  146. package/build/services/OtaInstallCoordinator.js.map +1 -1
  147. package/build/services/OtaService.d.ts.map +1 -1
  148. package/build/services/OtaService.js +1 -1
  149. package/build/services/OtaService.js.map +1 -1
  150. package/build/services/OtaUpdateCheckService.d.ts +4 -1
  151. package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
  152. package/build/services/OtaUpdateCheckService.js +28 -16
  153. package/build/services/OtaUpdateCheckService.js.map +1 -1
  154. package/build/services/PhoneNotificationsSync.d.ts +13 -0
  155. package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
  156. package/build/services/PhoneNotificationsSync.js +101 -9
  157. package/build/services/PhoneNotificationsSync.js.map +1 -1
  158. package/build/services/PhonePhotoCoordinator.js +1 -1
  159. package/build/services/PhonePhotoCoordinator.js.map +1 -1
  160. package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
  161. package/build/services/PhoneStreamCoordinator.js +9 -6
  162. package/build/services/PhoneStreamCoordinator.js.map +1 -1
  163. package/build/services/SceneRenderer.d.ts.map +1 -1
  164. package/build/services/SceneRenderer.js +5 -0
  165. package/build/services/SceneRenderer.js.map +1 -1
  166. package/build/services/StreamLifecycleController.d.ts +4 -0
  167. package/build/services/StreamLifecycleController.d.ts.map +1 -1
  168. package/build/services/StreamLifecycleController.js +4 -0
  169. package/build/services/StreamLifecycleController.js.map +1 -1
  170. package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
  171. package/build/services/asg/localNetworkTransport.js +13 -12
  172. package/build/services/asg/localNetworkTransport.js.map +1 -1
  173. package/build/services/effectiveCapabilities.d.ts +1 -1
  174. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  175. package/build/services/effectiveCapabilities.js +26 -7
  176. package/build/services/effectiveCapabilities.js.map +1 -1
  177. package/build/services/gestureClaims.d.ts +18 -0
  178. package/build/services/gestureClaims.d.ts.map +1 -0
  179. package/build/services/gestureClaims.js +24 -0
  180. package/build/services/gestureClaims.js.map +1 -0
  181. package/build/services/gestureDotGlyphs.d.ts +8 -0
  182. package/build/services/gestureDotGlyphs.d.ts.map +1 -0
  183. package/build/services/gestureDotGlyphs.js +13 -0
  184. package/build/services/gestureDotGlyphs.js.map +1 -0
  185. package/build/services/glassesClockSync.d.ts.map +1 -1
  186. package/build/services/glassesClockSync.js +5 -1
  187. package/build/services/glassesClockSync.js.map +1 -1
  188. package/build/services/iosNotificationAppCatalog.d.ts +13 -0
  189. package/build/services/iosNotificationAppCatalog.d.ts.map +1 -0
  190. package/build/services/iosNotificationAppCatalog.js +28 -0
  191. package/build/services/iosNotificationAppCatalog.js.map +1 -0
  192. package/build/services/otaDisplayState.d.ts +1 -1
  193. package/build/services/otaDisplayState.d.ts.map +1 -1
  194. package/build/services/otaDisplayState.js.map +1 -1
  195. package/build/services/otaInstallPolicy.d.ts +5 -1
  196. package/build/services/otaInstallPolicy.d.ts.map +1 -1
  197. package/build/services/otaInstallPolicy.js +4 -0
  198. package/build/services/otaInstallPolicy.js.map +1 -1
  199. package/build/services/otaLegacyMapping.d.ts +1 -1
  200. package/build/services/otaLegacyMapping.d.ts.map +1 -1
  201. package/build/services/otaLegacyMapping.js.map +1 -1
  202. package/build/services/otaManifestPolicy.d.ts +14 -0
  203. package/build/services/otaManifestPolicy.d.ts.map +1 -0
  204. package/build/services/otaManifestPolicy.js +20 -0
  205. package/build/services/otaManifestPolicy.js.map +1 -0
  206. package/build/services/otaManifestUrl.d.ts +2 -1
  207. package/build/services/otaManifestUrl.d.ts.map +1 -1
  208. package/build/services/otaManifestUrl.js +24 -32
  209. package/build/services/otaManifestUrl.js.map +1 -1
  210. package/build/services/otaReleaseVersion.d.ts +10 -0
  211. package/build/services/otaReleaseVersion.d.ts.map +1 -0
  212. package/build/services/otaReleaseVersion.js +14 -0
  213. package/build/services/otaReleaseVersion.js.map +1 -0
  214. package/build/stores/appVisibility.d.ts +3 -0
  215. package/build/stores/appVisibility.d.ts.map +1 -0
  216. package/build/stores/appVisibility.js +3 -0
  217. package/build/stores/appVisibility.js.map +1 -0
  218. package/build/stores/apps.d.ts.map +1 -1
  219. package/build/stores/apps.js +7 -1
  220. package/build/stores/apps.js.map +1 -1
  221. package/build/stores/core.d.ts +3 -3
  222. package/build/stores/core.d.ts.map +1 -1
  223. package/build/stores/core.js.map +1 -1
  224. package/build/stores/display.d.ts +4 -4
  225. package/build/stores/display.d.ts.map +1 -1
  226. package/build/stores/display.js +12 -36
  227. package/build/stores/display.js.map +1 -1
  228. package/build/stores/glasses.d.ts +10 -4
  229. package/build/stores/glasses.d.ts.map +1 -1
  230. package/build/stores/glasses.js +2 -0
  231. package/build/stores/glasses.js.map +1 -1
  232. package/build/stores/settings.d.ts.map +1 -1
  233. package/build/stores/settings.js +140 -5
  234. package/build/stores/settings.js.map +1 -1
  235. package/build/types/applet.d.ts +3 -1
  236. package/build/types/applet.d.ts.map +1 -1
  237. package/build/types/applet.js +0 -3
  238. package/build/types/applet.js.map +1 -1
  239. package/build/types/capabilities/even-realities-g2.d.ts.map +1 -1
  240. package/build/types/capabilities/even-realities-g2.js +2 -2
  241. package/build/types/capabilities/even-realities-g2.js.map +1 -1
  242. package/build/types/enums.d.ts +1 -5
  243. package/build/types/enums.d.ts.map +1 -1
  244. package/build/types/enums.js +1 -8
  245. package/build/types/enums.js.map +1 -1
  246. package/build/types/hardware.d.ts +1 -1
  247. package/build/types/hardware.d.ts.map +1 -1
  248. package/build/types/hardware.js +1 -0
  249. package/build/types/hardware.js.map +1 -1
  250. package/build/types/index.d.ts +3 -7
  251. package/build/types/index.d.ts.map +1 -1
  252. package/build/types/index.js +3 -7
  253. package/build/types/index.js.map +1 -1
  254. package/build/utils/display/composer/ColumnComposer.d.ts +1 -1
  255. package/build/utils/display/composer/ColumnComposer.js +1 -1
  256. package/build/utils/display/composer/ColumnComposer.js.map +1 -1
  257. package/build/utils/display/scene/process.d.ts.map +1 -1
  258. package/build/utils/display/scene/process.js +4 -3
  259. package/build/utils/display/scene/process.js.map +1 -1
  260. package/build/utils/display/scene/types.d.ts +6 -2
  261. package/build/utils/display/scene/types.d.ts.map +1 -1
  262. package/build/utils/display/scene/types.js.map +1 -1
  263. package/package.json +69 -35
  264. package/src/bluetooth-sdk/debug.ts +1 -0
  265. package/src/bluetooth-sdk/index.ts +2 -0
  266. package/src/bluetooth-sdk/ota-transport.ts +1 -0
  267. package/src/bluetooth-sdk/photo-receiver.ts +2 -0
  268. package/src/bluetooth-sdk/react.ts +1 -0
  269. package/src/bluetooth-sdk/types.ts +1 -0
  270. package/src/engine.ts +12 -3
  271. package/src/facades/display.ts +12 -2
  272. package/src/facades/displayMirror.ts +7 -15
  273. package/src/facades/glasses.ts +8 -4
  274. package/src/facades/ota.ts +36 -11
  275. package/src/facades/pairing.ts +103 -23
  276. package/src/facades/phoneNotifications.ts +32 -10
  277. package/src/facades/speakers.ts +1 -1
  278. package/src/facades/speech.ts +21 -7
  279. package/src/generated/releaseMetadata.ts +20 -0
  280. package/src/index.ts +31 -5
  281. package/src/react/MentraLiveOtaFlow.tsx +754 -0
  282. package/src/react/index.ts +21 -0
  283. package/src/react/useEngineSnapshot.ts +16 -0
  284. package/src/react/useMentraLiveOta.ts +793 -0
  285. package/src/services/AppRegistry.ts +16 -0
  286. package/src/services/BlobStore.ts +57 -10
  287. package/src/services/CustomDashboardModel.ts +83 -0
  288. package/src/services/CustomDashboardService.ts +544 -0
  289. package/src/services/DashboardCoordinator.ts +121 -0
  290. package/src/services/DeviceEventRouter.ts +48 -8
  291. package/src/services/DisplayProcessor.ts +3 -3
  292. package/src/services/GestureDotsService.ts +164 -0
  293. package/src/services/GlassesStatusProjection.ts +20 -11
  294. package/src/services/HotspotOtaTransport.ts +8 -7
  295. package/src/services/LocalDisplayManager.ts +198 -18
  296. package/src/services/LocalMiniappRuntime.ts +37 -0
  297. package/src/services/MiniappExitGesture.ts +76 -0
  298. package/src/services/OtaAutoChain.ts +130 -0
  299. package/src/services/OtaErrorMapping.ts +80 -0
  300. package/src/services/OtaInstallCoordinator.ts +40 -13
  301. package/src/services/OtaService.ts +1 -2
  302. package/src/services/OtaUpdateCheckService.ts +33 -19
  303. package/src/services/PhoneNotificationsSync.ts +125 -9
  304. package/src/services/PhonePhotoCoordinator.ts +1 -1
  305. package/src/services/PhoneStreamCoordinator.ts +10 -6
  306. package/src/services/SceneRenderer.ts +4 -0
  307. package/src/services/StreamLifecycleController.ts +4 -0
  308. package/src/services/asg/localNetworkTransport.ts +20 -19
  309. package/src/services/effectiveCapabilities.ts +27 -7
  310. package/src/services/gestureClaims.ts +28 -0
  311. package/src/services/gestureDotGlyphs.ts +15 -0
  312. package/src/services/glassesClockSync.ts +5 -1
  313. package/src/services/iosNotificationAppCatalog.ts +39 -0
  314. package/src/services/otaDisplayState.ts +1 -1
  315. package/src/services/otaInstallPolicy.ts +5 -1
  316. package/src/services/otaLegacyMapping.ts +1 -1
  317. package/src/services/otaManifestPolicy.ts +43 -0
  318. package/src/services/otaManifestUrl.ts +27 -36
  319. package/src/services/otaReleaseVersion.ts +22 -0
  320. package/src/stores/appVisibility.ts +3 -0
  321. package/src/stores/apps.ts +7 -1
  322. package/src/stores/core.ts +4 -4
  323. package/src/stores/display.ts +16 -48
  324. package/src/stores/glasses.ts +15 -6
  325. package/src/stores/settings.ts +140 -5
  326. package/src/types/applet.ts +3 -1
  327. package/src/types/capabilities/even-realities-g2.ts +3 -2
  328. package/src/types/enums.ts +1 -9
  329. package/src/types/hardware.ts +2 -1
  330. package/src/types/index.ts +3 -7
  331. package/src/utils/display/composer/ColumnComposer.ts +1 -1
  332. package/src/utils/display/scene/process.ts +4 -3
  333. package/src/utils/display/scene/types.ts +0 -0
  334. package/build/devtools.d.ts +0 -11
  335. package/build/devtools.d.ts.map +0 -1
  336. package/build/devtools.js +0 -13
  337. package/build/devtools.js.map +0 -1
  338. package/build/internal.d.ts +0 -67
  339. package/build/internal.d.ts.map +0 -1
  340. package/build/internal.js +0 -90
  341. package/build/internal.js.map +0 -1
  342. package/src/devtools.ts +0 -13
  343. package/src/internal.ts +0 -155
@@ -1 +1 @@
1
- {"version":3,"file":"DisplayProcessor.js","sourceRoot":"","sources":["../../src/services/DisplayProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DisplayProcessor\n *\n * Processes display events with pixel-accurate text wrapping using the same\n * display-utils library as the cloud. This ensures the mobile preview matches\n * exactly what is shown on the glasses.\n *\n * Key responsibilities:\n * 1. Intercept display events before sending to native SGC\n * 2. Wrap text using the correct device profile (G1, etc.)\n * 3. Ensure GlassesDisplayMirror shows exactly what glasses will show\n *\n * @see cloud/issues/026-mobile-display-processor for design docs\n */\n\nimport {\n createDisplayToolkit,\n G1_PROFILE,\n G2_PROFILE,\n Z100_PROFILE,\n NEX_PROFILE,\n TextMeasurer,\n TextWrapper,\n ColumnComposer,\n type DisplayProfile,\n type WrapOptions,\n type BreakMode,\n} from \"../utils/display\"\n\nimport {ISLAND_SETTINGS_KEYS} from \"../runtime/config\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Supported device models for display processing\n */\nexport type DeviceModel = \"g1\" | \"g2\" | \"z100\" | \"nex\" | \"mach1\" | \"mentra-live\" | \"simulated\" | \"unknown\"\n\n/**\n * Display event types that we process\n */\nexport type DisplayLayoutType =\n | \"text_wall\"\n | \"text_line\"\n | \"text_rows\"\n | \"reference_card\"\n | \"double_text_wall\"\n | \"bitmap_view\"\n\n/**\n * Raw display event from the cloud/WebSocket\n */\nexport interface DisplayEvent {\n view: \"main\" | \"dashboard\"\n layoutType?: DisplayLayoutType\n layout?: {\n layoutType: DisplayLayoutType\n text?: string | string[] // string for text_wall/text_line, string[] for text_rows\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n data?: string // For bitmap_view\n x?: number // bitmap_view container position/size (forwarded to native, used by G2)\n y?: number\n width?: number\n height?: number\n [key: string]: unknown\n }\n text?: string | string[]\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n [key: string]: unknown\n}\n\n/**\n * Processed display event with guaranteed wrapped text\n */\nexport interface ProcessedDisplayEvent extends DisplayEvent {\n /** Marks this event as processed */\n _processed: true\n /** The device profile used for processing */\n _profile: string\n /** Pre-split lines for text_wall/text_line (for easy rendering) */\n _lines?: string[]\n /** Original layout type before processing (e.g., double_text_wall -> text_wall) */\n _originalLayoutType?: DisplayLayoutType\n /** Column composition metadata for double_text_wall */\n _composedColumns?: {\n leftLines: string[]\n rightLines: string[]\n config: {\n leftColumnWidthPx: number\n rightColumnStartPx: number\n rightColumnWidthPx: number\n maxLines: number\n leftMarginSpaces?: number\n }\n }\n}\n\n/**\n * Options for the DisplayProcessor\n */\nexport interface DisplayProcessorOptions {\n /** Default break mode for text wrapping */\n breakMode?: BreakMode\n /** Whether to log processing details */\n debug?: boolean\n}\n\n// =============================================================================\n// Placeholder Replacement\n// =============================================================================\n\n/**\n * Placeholder values that can be replaced in display text.\n * These match the placeholders used in CoreManager.kt/CoreManager.swift\n */\ninterface PlaceholderValues {\n /** Current time in 12-hour format (e.g., \"2:30 PM\") */\n TIME12: string\n /** Current time in 24-hour format (e.g., \"14:30\") */\n TIME24: string\n /** Current date (e.g., \"1/22\") */\n DATE: string\n /** Glasses battery level (e.g., \"85%\" or \"\" if unknown) */\n GBATT: string\n /** Connection status */\n CONNECTION_STATUS: string\n}\n\n/**\n * Get current placeholder values from device state\n */\nfunction getPlaceholderValues(): PlaceholderValues {\n const now = new Date()\n\n // Format time in 12-hour format\n const hours12 = now.getHours() % 12 || 12\n const minutes = now.getMinutes().toString().padStart(2, \"0\")\n const ampm = now.getHours() >= 12 ? \"PM\" : \"AM\"\n const TIME12 = `${hours12}:${minutes} ${ampm}`\n\n // Format time in 24-hour format\n const hours24 = now.getHours().toString().padStart(2, \"0\")\n const TIME24 = `${hours24}:${minutes}`\n\n // Format date\n const month = now.getMonth() + 1\n const day = now.getDate()\n const DATE = `${month}/${day}`\n\n // Battery + connection read from the engine glasses store directly (was a host\n // glassesStatus hook).\n const batteryLevel = useGlassesStore.getState().batteryLevel ?? -1\n const GBATT = batteryLevel === -1 ? \"\" : `${batteryLevel}%`\n\n // Connection status\n const connected = isGlassesConnected(useGlassesStore.getState().connection)\n const CONNECTION_STATUS = connected ? \"Connected\" : \"\"\n\n return {TIME12, TIME24, DATE, GBATT, CONNECTION_STATUS}\n}\n\n/**\n * Replace placeholders in text with actual values.\n * Placeholders are in format $NAME$ (e.g., $GBATT$, $TIME12$)\n *\n * @param text - Text containing placeholders\n * @returns Text with placeholders replaced\n */\nfunction replacePlaceholders(text: string): string {\n if (!text || !text.includes(\"$\")) {\n return text\n }\n\n const values = getPlaceholderValues()\n\n return text\n .replace(/\\$TIME12\\$/g, values.TIME12)\n .replace(/\\$TIME24\\$/g, values.TIME24)\n .replace(/\\$DATE\\$/g, values.DATE)\n .replace(/\\$GBATT\\$/g, values.GBATT)\n .replace(/\\$CONNECTION_STATUS\\$/g, values.CONNECTION_STATUS)\n .replace(/\\$no_datetime\\$/g, `${values.DATE}, ${values.TIME12}`)\n}\n\nfunction isBreakMode(value: unknown): value is BreakMode {\n return value === \"character\" || value === \"character-no-hyphen\" || value === \"word\" || value === \"strict-word\"\n}\n\n// =============================================================================\n// Device Profile Mapping\n// =============================================================================\n\n/**\n * Map device model names to display profiles\n */\nconst DEVICE_PROFILES: Record<DeviceModel, DisplayProfile> = {\n \"g1\": G1_PROFILE,\n \"g2\": G2_PROFILE,\n \"z100\": Z100_PROFILE,\n \"nex\": NEX_PROFILE,\n \"mach1\": Z100_PROFILE, // Mach1 uses same hardware as Vuzix Z100\n \"mentra-live\": G1_PROFILE, // Mentra Live has no display, uses G1 as fallback\n \"simulated\": G1_PROFILE, // Simulated uses G1 profile\n \"unknown\": G1_PROFILE, // Default to G1\n}\n\n/**\n * Normalize various model name strings to our DeviceModel type\n */\nfunction normalizeModelName(modelName: string | null | undefined): DeviceModel {\n if (!modelName) return \"unknown\"\n\n const lower = modelName.toLowerCase()\n\n if (lower.includes(\"g2\") || lower.includes(\"even realities g2\")) {\n return \"g2\"\n }\n if (lower.includes(\"g1\") || lower.includes(\"even realities\")) {\n return \"g1\"\n }\n if (lower.includes(\"z100\") || lower.includes(\"vuzix\")) {\n return \"z100\"\n }\n if (lower.includes(\"nex\") || lower.includes(\"glassly display\")) {\n return \"nex\"\n }\n if (lower.includes(\"mach1\") || lower.includes(\"mach 1\")) {\n return \"mach1\"\n }\n if (lower.includes(\"glassly live\") || lower.includes(\"mentra-live\")) {\n return \"mentra-live\"\n }\n if (lower.includes(\"simulated\") || lower.includes(\"simulator\")) {\n return \"simulated\"\n }\n\n return \"unknown\"\n}\n\n// =============================================================================\n// DisplayProcessor Class\n// =============================================================================\n\n/**\n * Processes display events with pixel-accurate text wrapping.\n *\n * Usage:\n * ```typescript\n * const processor = DisplayProcessor.getInstance()\n *\n * // When glasses connect\n * processor.setDeviceModel(\"Even Realities G1\")\n *\n * // Process display events\n * const processed = processor.processDisplayEvent(rawEvent)\n * ```\n */\nexport class DisplayProcessor {\n private static instance: DisplayProcessor | null = null\n\n private measurer: TextMeasurer\n private wrapper: TextWrapper\n private composer: ColumnComposer\n private profile: DisplayProfile\n private deviceModel: DeviceModel = \"unknown\"\n private options: DisplayProcessorOptions\n\n private runtimeAttached = false\n private unsubscribeGlassesStatus: (() => void) | null = null\n\n private constructor(options: DisplayProcessorOptions = {}) {\n this.options = {\n breakMode: \"character-no-hyphen\",\n debug: false,\n ...options,\n }\n\n // Initialize with default G1 profile\n const engine = createDisplayToolkit(G1_PROFILE, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(engine.profile, this.options.breakMode)\n this.profile = engine.profile\n\n // Engine stores may not be hydrated yet at module-load time; attachToRuntime()\n // is called again by the host after app services start.\n this.attachToRuntime()\n }\n\n /**\n * Read the current default wearable and subscribe to engine glasses-status changes.\n * Idempotent — safe to call again after app services start.\n */\n public attachToRuntime(): void {\n if (this.runtimeAttached) {\n return\n }\n\n // Seed the device profile from the saved default wearable, read off the engine\n // settings store directly (was a host settings hook).\n const defaultWearable = useSettingsStore.getState().getSetting(ISLAND_SETTINGS_KEYS.defaultWearable) as\n | string\n | undefined\n if (defaultWearable) {\n this.setDeviceModel(defaultWearable)\n console.log(`DISPLAY_PROCESSOR: Initialized DisplayProcessor with default wearable: ${defaultWearable}`)\n }\n\n // Track device-model changes off the engine glasses store directly (was a host\n // subscribeGlassesStatus hook).\n this.unsubscribeGlassesStatus = useGlassesStore.subscribe(\n (s) => s.deviceModel,\n (deviceModel) => {\n if (deviceModel) this.setDeviceModel(String(deviceModel))\n },\n )\n\n this.runtimeAttached = true\n }\n\n /** Detach runtime subscriptions installed by attachToRuntime(). */\n public detachFromRuntime(): void {\n this.unsubscribeGlassesStatus?.()\n this.unsubscribeGlassesStatus = null\n this.runtimeAttached = false\n }\n\n /**\n * Get the singleton instance\n */\n public static getInstance(options?: DisplayProcessorOptions): DisplayProcessor {\n if (!DisplayProcessor.instance) {\n DisplayProcessor.instance = new DisplayProcessor(options)\n }\n return DisplayProcessor.instance\n }\n\n /**\n * Reset the singleton (useful for testing)\n */\n public static resetInstance(): void {\n DisplayProcessor.instance?.detachFromRuntime()\n DisplayProcessor.instance = null\n }\n\n // ===========================================================================\n // Device Profile Management\n // ===========================================================================\n\n /**\n * Set the device model and update the display profile accordingly.\n * Call this when glasses connect.\n *\n * @param modelName - The model name from the connected glasses (e.g., \"Even Realities G1\")\n */\n public setDeviceModel(modelName: string | null | undefined): void {\n const normalizedModel = normalizeModelName(modelName)\n\n if (normalizedModel === this.deviceModel) {\n return // No change needed\n }\n\n this.deviceModel = normalizedModel\n const newProfile = DEVICE_PROFILES[normalizedModel]\n\n if (newProfile !== this.profile) {\n this.updateProfile(newProfile)\n }\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Device model set to: ${normalizedModel} (from: ${modelName})`)\n }\n }\n\n /**\n * Update the display profile and recreate engine\n */\n private updateProfile(newProfile: DisplayProfile): void {\n const engine = createDisplayToolkit(newProfile, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(newProfile, this.options.breakMode)\n this.profile = engine.profile\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Profile updated to: ${newProfile.id}`)\n }\n }\n\n /**\n * Get the current device model\n */\n public getDeviceModel(): DeviceModel {\n return this.deviceModel\n }\n\n /**\n * Get the current display profile\n */\n public getProfile(): DisplayProfile {\n return this.profile\n }\n\n // ===========================================================================\n // Display Event Processing\n // ===========================================================================\n\n /**\n * Process a display event, wrapping text as needed.\n *\n * @param event - Raw display event from WebSocket\n * @returns Processed event with wrapped text\n */\n public processDisplayEvent(event: DisplayEvent): ProcessedDisplayEvent {\n // Already processed? Return as-is\n if ((event as ProcessedDisplayEvent)._processed) {\n return event as ProcessedDisplayEvent\n }\n\n // Get layout type from either root or nested layout object\n const layoutType = event.layoutType || event.layout?.layoutType\n const layout = event.layout || event\n\n if (!layoutType) {\n // No layout type - pass through\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n switch (layoutType) {\n case \"text_wall\":\n case \"text_line\":\n return this.processTextWall(event, layout)\n\n case \"text_rows\":\n return this.processTextRows(event, layout)\n\n case \"reference_card\":\n return this.processReferenceCard(event, layout)\n\n case \"double_text_wall\":\n return this.processDoubleTextWall(event, layout)\n\n case \"bitmap_view\":\n // Bitmap views don't need text processing\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n\n default:\n // Unknown layout type - pass through\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Unknown layout type: ${layoutType}`)\n }\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n }\n\n /**\n * Process text_wall or text_line layout\n */\n private processTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n // text_wall/text_line always have string text, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping (so we measure actual content)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap the text\n const lines = this.wrapText(textWithPlaceholders, breakMode ? {breakMode} : undefined)\n const wrappedText = lines.join(\"\\n\")\n\n // Update both root and nested layout if present\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedText,\n }\n : undefined\n\n return {\n ...event,\n // Store original text with placeholders for debugging\n _originalText: text,\n text: wrappedText,\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n _lines: lines,\n }\n }\n\n /**\n * Process text_rows layout\n */\n private processTextRows(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const textField = layout.text\n const rows: string[] = Array.isArray(textField) ? textField : []\n\n // Replace placeholders and wrap each row\n const breakMode = this.getEventBreakMode(event, layout)\n const wrappedRows = rows.map((row: string) => {\n const rowWithPlaceholders = replacePlaceholders(row)\n const lines = this.wrapText(rowWithPlaceholders, breakMode ? {breakMode} : undefined)\n return lines.join(\"\\n\")\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedRows as string | string[],\n }\n : undefined\n\n return {\n ...event,\n text: wrappedRows as string | string[],\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process reference_card layout\n */\n private processReferenceCard(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const title = layout.title || \"\"\n // reference_card text is always string, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping\n const titleWithPlaceholders = replacePlaceholders(title)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap title and text separately\n // Title typically gets 1 line, text gets remaining lines\n const wrappedTitle = this.wrapText(titleWithPlaceholders, {maxLines: 1, ...(breakMode ? {breakMode} : {})})\n const wrappedText = this.wrapText(textWithPlaceholders, {\n maxLines: this.profile.maxLines - 1,\n ...(breakMode ? {breakMode} : {}),\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n }\n : undefined\n\n return {\n ...event,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process double_text_wall layout\n *\n * Uses ColumnComposer to create a fully composed string with both columns\n * merged line-by-line with pixel-precise space alignment. This is the single\n * source of truth for double_text_wall composition, replacing duplicate\n * implementations in native iOS (G1Text.swift) and Android (G1Text.kt).\n *\n * The composed text is sent to native as a regular text_wall - native just\n * chunks and sends it without any re-wrapping or column composition.\n */\n private processDoubleTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const leftText = layout.topText || \"\"\n const rightText = layout.bottomText || \"\"\n\n // Replace placeholders BEFORE composition (so we measure actual content)\n const leftTextWithPlaceholders = replacePlaceholders(leftText)\n const rightTextWithPlaceholders = replacePlaceholders(rightText)\n const breakMode = this.getEventBreakMode(event, layout)\n const composer =\n breakMode && breakMode !== this.options.breakMode ? new ColumnComposer(this.profile, breakMode) : this.composer\n\n // Use ColumnComposer for pixel-precise column composition\n const result = composer.composeDoubleTextWall(leftTextWithPlaceholders, rightTextWithPlaceholders)\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] double_text_wall composed:`)\n console.log(` Left lines: ${result.leftLines.length}`)\n console.log(` Right lines: ${result.rightLines.length}`)\n console.log(` Config: ${JSON.stringify(result.config)}`)\n }\n\n // The composed text is a single string with both columns merged\n // Native will receive this as a text_wall and just chunk & send it\n const processedLayout = event.layout\n ? {\n ...event.layout,\n // Keep original topText/bottomText for reference but add composed text\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // The composed text is what native should actually display\n _composedText: result.composedText,\n }\n : undefined\n\n return {\n ...event,\n // Override the layout type to text_wall since we've pre-composed the columns\n // This tells native to use sendTextWall instead of sendDoubleTextWall\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n // Keep original values for debugging/preview (with placeholders replaced)\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // Store original values with placeholders for debugging\n _originalTopText: leftText,\n _originalBottomText: rightText,\n layout: processedLayout\n ? {\n ...processedLayout,\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n }\n : undefined,\n _processed: true,\n _profile: this.profile.id,\n _originalLayoutType: \"double_text_wall\",\n _composedColumns: {\n leftLines: result.leftLines,\n rightLines: result.rightLines,\n config: result.config,\n },\n }\n }\n\n // ===========================================================================\n // Text Wrapping Utilities\n // ===========================================================================\n\n /**\n * Wrap text using the current profile\n *\n * @param text - Text to wrap\n * @param options - Optional override options\n * @returns Array of wrapped lines\n */\n public wrapText(text: string, options?: WrapOptions): string[] {\n if (!text) return [\"\"]\n\n const result = this.wrapper.wrap(text, options)\n return result.lines\n }\n\n private getEventBreakMode(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): BreakMode | undefined {\n const breakMode = layout.breakMode ?? event.breakMode\n return isBreakMode(breakMode) ? breakMode : undefined\n }\n\n /**\n * Measure the pixel width of text\n *\n * @param text - Text to measure\n * @returns Width in pixels\n */\n public measureText(text: string): number {\n return this.measurer.measureText(text)\n }\n\n /**\n * Check if text fits on a single line\n *\n * @param text - Text to check\n * @returns true if text fits without wrapping\n */\n public fitsOnSingleLine(text: string): boolean {\n return this.measurer.measureText(text) <= this.profile.displayWidthPx\n }\n\n /**\n * Get the display width in pixels\n */\n public getDisplayWidth(): number {\n return this.profile.displayWidthPx\n }\n\n /**\n * Get the maximum number of lines\n */\n public getMaxLines(): number {\n return this.profile.maxLines\n }\n\n /**\n * Get detailed text measurement\n */\n public measureTextDetailed(text: string) {\n return this.measurer.measureTextDetailed(text)\n }\n\n // ===========================================================================\n // Break Mode Control\n // ===========================================================================\n\n /**\n * Set the break mode for text wrapping\n *\n * @param breakMode - 'character' | 'word' | 'strict-word'\n */\n public setBreakMode(breakMode: BreakMode): void {\n if (this.options.breakMode === breakMode) return\n\n this.options.breakMode = breakMode\n this.updateProfile(this.profile) // Recreate wrapper with new break mode\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Break mode set to: ${breakMode}`)\n }\n }\n\n /**\n * Get the current break mode\n */\n public getBreakMode(): BreakMode {\n return this.options.breakMode || \"character\"\n }\n}\n\nexport const displayProcessor = DisplayProcessor.getInstance()\nexport default displayProcessor\n"]}
1
+ {"version":3,"file":"DisplayProcessor.js","sourceRoot":"","sources":["../../src/services/DisplayProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DisplayProcessor\n *\n * Processes display events with pixel-accurate text wrapping using the same\n * Engine display utilities. This ensures the mobile preview matches exactly\n * what is shown on the glasses.\n *\n * Key responsibilities:\n * 1. Intercept display events before sending to native SGC\n * 2. Wrap text using the correct device profile (G1, etc.)\n * 3. Ensure GlassesDisplayMirror shows exactly what glasses will show\n *\n * @see notes/miniapp-display-api-redesign.md for the current display design\n */\n\nimport {\n createDisplayToolkit,\n G1_PROFILE,\n G2_PROFILE,\n Z100_PROFILE,\n NEX_PROFILE,\n TextMeasurer,\n TextWrapper,\n ColumnComposer,\n type DisplayProfile,\n type WrapOptions,\n type BreakMode,\n} from \"../utils/display\"\n\nimport {ISLAND_SETTINGS_KEYS} from \"../runtime/config\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Supported device models for display processing\n */\nexport type DeviceModel = \"g1\" | \"g2\" | \"z100\" | \"nex\" | \"mach1\" | \"mentra-live\" | \"simulated\" | \"unknown\"\n\n/**\n * Display event types that we process\n */\nexport type DisplayLayoutType =\n | \"text_wall\"\n | \"text_line\"\n | \"text_rows\"\n | \"reference_card\"\n | \"double_text_wall\"\n | \"bitmap_view\"\n\n/**\n * Raw display event from the cloud/WebSocket\n */\nexport interface DisplayEvent {\n view: \"main\" | \"dashboard\"\n layoutType?: DisplayLayoutType\n layout?: {\n layoutType: DisplayLayoutType\n text?: string | string[] // string for text_wall/text_line, string[] for text_rows\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n data?: string // For bitmap_view\n x?: number // bitmap_view container position/size (forwarded to native, used by G2)\n y?: number\n width?: number\n height?: number\n [key: string]: unknown\n }\n text?: string | string[]\n title?: string\n topText?: string\n bottomText?: string\n breakMode?: BreakMode\n [key: string]: unknown\n}\n\n/**\n * Processed display event with guaranteed wrapped text\n */\nexport interface ProcessedDisplayEvent extends DisplayEvent {\n /** Marks this event as processed */\n _processed: true\n /** The device profile used for processing */\n _profile: string\n /** Pre-split lines for text_wall/text_line (for easy rendering) */\n _lines?: string[]\n /** Original layout type before processing (e.g., double_text_wall -> text_wall) */\n _originalLayoutType?: DisplayLayoutType\n /** Column composition metadata for double_text_wall */\n _composedColumns?: {\n leftLines: string[]\n rightLines: string[]\n config: {\n leftColumnWidthPx: number\n rightColumnStartPx: number\n rightColumnWidthPx: number\n maxLines: number\n leftMarginSpaces?: number\n }\n }\n}\n\n/**\n * Options for the DisplayProcessor\n */\nexport interface DisplayProcessorOptions {\n /** Default break mode for text wrapping */\n breakMode?: BreakMode\n /** Whether to log processing details */\n debug?: boolean\n}\n\n// =============================================================================\n// Placeholder Replacement\n// =============================================================================\n\n/**\n * Placeholder values that can be replaced in display text.\n * These match the placeholders used in CoreManager.kt/CoreManager.swift\n */\ninterface PlaceholderValues {\n /** Current time in 12-hour format (e.g., \"2:30 PM\") */\n TIME12: string\n /** Current time in 24-hour format (e.g., \"14:30\") */\n TIME24: string\n /** Current date (e.g., \"1/22\") */\n DATE: string\n /** Glasses battery level (e.g., \"85%\" or \"\" if unknown) */\n GBATT: string\n /** Connection status */\n CONNECTION_STATUS: string\n}\n\n/**\n * Get current placeholder values from device state\n */\nfunction getPlaceholderValues(): PlaceholderValues {\n const now = new Date()\n\n // Format time in 12-hour format\n const hours12 = now.getHours() % 12 || 12\n const minutes = now.getMinutes().toString().padStart(2, \"0\")\n const ampm = now.getHours() >= 12 ? \"PM\" : \"AM\"\n const TIME12 = `${hours12}:${minutes} ${ampm}`\n\n // Format time in 24-hour format\n const hours24 = now.getHours().toString().padStart(2, \"0\")\n const TIME24 = `${hours24}:${minutes}`\n\n // Format date\n const month = now.getMonth() + 1\n const day = now.getDate()\n const DATE = `${month}/${day}`\n\n // Battery + connection read from the engine glasses store directly (was a host\n // glassesStatus hook).\n const batteryLevel = useGlassesStore.getState().batteryLevel ?? -1\n const GBATT = batteryLevel === -1 ? \"\" : `${batteryLevel}%`\n\n // Connection status\n const connected = isGlassesConnected(useGlassesStore.getState().connection)\n const CONNECTION_STATUS = connected ? \"Connected\" : \"\"\n\n return {TIME12, TIME24, DATE, GBATT, CONNECTION_STATUS}\n}\n\n/**\n * Replace placeholders in text with actual values.\n * Placeholders are in format $NAME$ (e.g., $GBATT$, $TIME12$)\n *\n * @param text - Text containing placeholders\n * @returns Text with placeholders replaced\n */\nfunction replacePlaceholders(text: string): string {\n if (!text || !text.includes(\"$\")) {\n return text\n }\n\n const values = getPlaceholderValues()\n\n return text\n .replace(/\\$TIME12\\$/g, values.TIME12)\n .replace(/\\$TIME24\\$/g, values.TIME24)\n .replace(/\\$DATE\\$/g, values.DATE)\n .replace(/\\$GBATT\\$/g, values.GBATT)\n .replace(/\\$CONNECTION_STATUS\\$/g, values.CONNECTION_STATUS)\n .replace(/\\$no_datetime\\$/g, `${values.DATE}, ${values.TIME12}`)\n}\n\nfunction isBreakMode(value: unknown): value is BreakMode {\n return value === \"character\" || value === \"character-no-hyphen\" || value === \"word\" || value === \"strict-word\"\n}\n\n// =============================================================================\n// Device Profile Mapping\n// =============================================================================\n\n/**\n * Map device model names to display profiles\n */\nconst DEVICE_PROFILES: Record<DeviceModel, DisplayProfile> = {\n \"g1\": G1_PROFILE,\n \"g2\": G2_PROFILE,\n \"z100\": Z100_PROFILE,\n \"nex\": NEX_PROFILE,\n \"mach1\": Z100_PROFILE, // Mach1 uses same hardware as Vuzix Z100\n \"mentra-live\": G1_PROFILE, // Mentra Live has no display, uses G1 as fallback\n \"simulated\": G1_PROFILE, // Simulated uses G1 profile\n \"unknown\": G1_PROFILE, // Default to G1\n}\n\n/**\n * Normalize various model name strings to our DeviceModel type\n */\nfunction normalizeModelName(modelName: string | null | undefined): DeviceModel {\n if (!modelName) return \"unknown\"\n\n const lower = modelName.toLowerCase()\n\n if (lower.includes(\"g2\") || lower.includes(\"even realities g2\")) {\n return \"g2\"\n }\n if (lower.includes(\"g1\") || lower.includes(\"even realities\")) {\n return \"g1\"\n }\n if (lower.includes(\"z100\") || lower.includes(\"vuzix\")) {\n return \"z100\"\n }\n if (lower.includes(\"nex\") || lower.includes(\"glassly display\")) {\n return \"nex\"\n }\n if (lower.includes(\"mach1\") || lower.includes(\"mach 1\")) {\n return \"mach1\"\n }\n if (lower.includes(\"glassly live\") || lower.includes(\"mentra-live\")) {\n return \"mentra-live\"\n }\n if (lower.includes(\"simulated\") || lower.includes(\"simulator\")) {\n return \"simulated\"\n }\n\n return \"unknown\"\n}\n\n// =============================================================================\n// DisplayProcessor Class\n// =============================================================================\n\n/**\n * Processes display events with pixel-accurate text wrapping.\n *\n * Usage:\n * ```typescript\n * const processor = DisplayProcessor.getInstance()\n *\n * // When glasses connect\n * processor.setDeviceModel(\"Even Realities G1\")\n *\n * // Process display events\n * const processed = processor.processDisplayEvent(rawEvent)\n * ```\n */\nexport class DisplayProcessor {\n private static instance: DisplayProcessor | null = null\n\n private measurer: TextMeasurer\n private wrapper: TextWrapper\n private composer: ColumnComposer\n private profile: DisplayProfile\n private deviceModel: DeviceModel = \"unknown\"\n private options: DisplayProcessorOptions\n\n private runtimeAttached = false\n private unsubscribeGlassesStatus: (() => void) | null = null\n\n private constructor(options: DisplayProcessorOptions = {}) {\n this.options = {\n breakMode: \"character-no-hyphen\",\n debug: false,\n ...options,\n }\n\n // Initialize with default G1 profile\n const engine = createDisplayToolkit(G1_PROFILE, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(engine.profile, this.options.breakMode)\n this.profile = engine.profile\n\n // Engine stores may not be hydrated yet at module-load time; attachToRuntime()\n // is called again by the host after app services start.\n this.attachToRuntime()\n }\n\n /**\n * Read the current default wearable and subscribe to engine glasses-status changes.\n * Idempotent — safe to call again after app services start.\n */\n public attachToRuntime(): void {\n if (this.runtimeAttached) {\n return\n }\n\n // Seed the device profile from the saved default wearable, read off the engine\n // settings store directly (was a host settings hook).\n const defaultWearable = useSettingsStore.getState().getSetting(ISLAND_SETTINGS_KEYS.defaultWearable) as\n | string\n | undefined\n if (defaultWearable) {\n this.setDeviceModel(defaultWearable)\n console.log(`DISPLAY_PROCESSOR: Initialized DisplayProcessor with default wearable: ${defaultWearable}`)\n }\n\n // Track device-model changes off the engine glasses store directly (was a host\n // subscribeGlassesStatus hook).\n this.unsubscribeGlassesStatus = useGlassesStore.subscribe(\n (s) => s.deviceModel,\n (deviceModel) => {\n if (deviceModel) this.setDeviceModel(String(deviceModel))\n },\n )\n\n this.runtimeAttached = true\n }\n\n /** Detach runtime subscriptions installed by attachToRuntime(). */\n public detachFromRuntime(): void {\n this.unsubscribeGlassesStatus?.()\n this.unsubscribeGlassesStatus = null\n this.runtimeAttached = false\n }\n\n /**\n * Get the singleton instance\n */\n public static getInstance(options?: DisplayProcessorOptions): DisplayProcessor {\n if (!DisplayProcessor.instance) {\n DisplayProcessor.instance = new DisplayProcessor(options)\n }\n return DisplayProcessor.instance\n }\n\n /**\n * Reset the singleton (useful for testing)\n */\n public static resetInstance(): void {\n DisplayProcessor.instance?.detachFromRuntime()\n DisplayProcessor.instance = null\n }\n\n // ===========================================================================\n // Device Profile Management\n // ===========================================================================\n\n /**\n * Set the device model and update the display profile accordingly.\n * Call this when glasses connect.\n *\n * @param modelName - The model name from the connected glasses (e.g., \"Even Realities G1\")\n */\n public setDeviceModel(modelName: string | null | undefined): void {\n const normalizedModel = normalizeModelName(modelName)\n\n if (normalizedModel === this.deviceModel) {\n return // No change needed\n }\n\n this.deviceModel = normalizedModel\n const newProfile = DEVICE_PROFILES[normalizedModel]\n\n if (newProfile !== this.profile) {\n this.updateProfile(newProfile)\n }\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Device model set to: ${normalizedModel} (from: ${modelName})`)\n }\n }\n\n /**\n * Update the display profile and recreate engine\n */\n private updateProfile(newProfile: DisplayProfile): void {\n const engine = createDisplayToolkit(newProfile, {\n breakMode: this.options.breakMode,\n })\n\n this.measurer = engine.measurer\n this.wrapper = engine.wrapper\n this.composer = new ColumnComposer(newProfile, this.options.breakMode)\n this.profile = engine.profile\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Profile updated to: ${newProfile.id}`)\n }\n }\n\n /**\n * Get the current device model\n */\n public getDeviceModel(): DeviceModel {\n return this.deviceModel\n }\n\n /**\n * Get the current display profile\n */\n public getProfile(): DisplayProfile {\n return this.profile\n }\n\n // ===========================================================================\n // Display Event Processing\n // ===========================================================================\n\n /**\n * Process a display event, wrapping text as needed.\n *\n * @param event - Raw display event from WebSocket\n * @returns Processed event with wrapped text\n */\n public processDisplayEvent(event: DisplayEvent): ProcessedDisplayEvent {\n // Already processed? Return as-is\n if ((event as ProcessedDisplayEvent)._processed) {\n return event as ProcessedDisplayEvent\n }\n\n // Get layout type from either root or nested layout object\n const layoutType = event.layoutType || event.layout?.layoutType\n const layout = event.layout || event\n\n if (!layoutType) {\n // No layout type - pass through\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n switch (layoutType) {\n case \"text_wall\":\n case \"text_line\":\n return this.processTextWall(event, layout)\n\n case \"text_rows\":\n return this.processTextRows(event, layout)\n\n case \"reference_card\":\n return this.processReferenceCard(event, layout)\n\n case \"double_text_wall\":\n return this.processDoubleTextWall(event, layout)\n\n case \"bitmap_view\":\n // Bitmap views don't need text processing\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n\n default:\n // Unknown layout type - pass through\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Unknown layout type: ${layoutType}`)\n }\n return {\n ...event,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n }\n\n /**\n * Process text_wall or text_line layout\n */\n private processTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n // text_wall/text_line always have string text, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping (so we measure actual content)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap the text\n const lines = this.wrapText(textWithPlaceholders, breakMode ? {breakMode} : undefined)\n const wrappedText = lines.join(\"\\n\")\n\n // Update both root and nested layout if present\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedText,\n }\n : undefined\n\n return {\n ...event,\n // Store original text with placeholders for debugging\n _originalText: text,\n text: wrappedText,\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n _lines: lines,\n }\n }\n\n /**\n * Process text_rows layout\n */\n private processTextRows(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const textField = layout.text\n const rows: string[] = Array.isArray(textField) ? textField : []\n\n // Replace placeholders and wrap each row\n const breakMode = this.getEventBreakMode(event, layout)\n const wrappedRows = rows.map((row: string) => {\n const rowWithPlaceholders = replacePlaceholders(row)\n const lines = this.wrapText(rowWithPlaceholders, breakMode ? {breakMode} : undefined)\n return lines.join(\"\\n\")\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n text: wrappedRows as string | string[],\n }\n : undefined\n\n return {\n ...event,\n text: wrappedRows as string | string[],\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process reference_card layout\n */\n private processReferenceCard(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const title = layout.title || \"\"\n // reference_card text is always string, not string[]\n const rawText = layout.text\n const text = typeof rawText === \"string\" ? rawText : \"\"\n\n // Replace placeholders BEFORE wrapping\n const titleWithPlaceholders = replacePlaceholders(title)\n const textWithPlaceholders = replacePlaceholders(text)\n const breakMode = this.getEventBreakMode(event, layout)\n\n // Wrap title and text separately\n // Title typically gets 1 line, text gets remaining lines\n const wrappedTitle = this.wrapText(titleWithPlaceholders, {maxLines: 1, ...(breakMode ? {breakMode} : {})})\n const wrappedText = this.wrapText(textWithPlaceholders, {\n maxLines: this.profile.maxLines - 1,\n ...(breakMode ? {breakMode} : {}),\n })\n\n const processedLayout = event.layout\n ? {\n ...event.layout,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n }\n : undefined\n\n return {\n ...event,\n title: wrappedTitle.join(\"\\n\"),\n text: wrappedText.join(\"\\n\"),\n layout: processedLayout,\n _processed: true,\n _profile: this.profile.id,\n }\n }\n\n /**\n * Process double_text_wall layout\n *\n * Uses ColumnComposer to create a fully composed string with both columns\n * merged line-by-line with pixel-precise space alignment. This is the single\n * source of truth for double_text_wall composition, replacing duplicate\n * implementations in native iOS (G1Text.swift) and Android (G1Text.kt).\n *\n * The composed text is sent to native as a regular text_wall - native just\n * chunks and sends it without any re-wrapping or column composition.\n */\n private processDoubleTextWall(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): ProcessedDisplayEvent {\n const leftText = layout.topText || \"\"\n const rightText = layout.bottomText || \"\"\n\n // Replace placeholders BEFORE composition (so we measure actual content)\n const leftTextWithPlaceholders = replacePlaceholders(leftText)\n const rightTextWithPlaceholders = replacePlaceholders(rightText)\n const breakMode = this.getEventBreakMode(event, layout)\n const composer =\n breakMode && breakMode !== this.options.breakMode ? new ColumnComposer(this.profile, breakMode) : this.composer\n\n // Use ColumnComposer for pixel-precise column composition\n const result = composer.composeDoubleTextWall(leftTextWithPlaceholders, rightTextWithPlaceholders)\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] double_text_wall composed:`)\n console.log(` Left lines: ${result.leftLines.length}`)\n console.log(` Right lines: ${result.rightLines.length}`)\n console.log(` Config: ${JSON.stringify(result.config)}`)\n }\n\n // The composed text is a single string with both columns merged\n // Native will receive this as a text_wall and just chunk & send it\n const processedLayout = event.layout\n ? {\n ...event.layout,\n // Keep original topText/bottomText for reference but add composed text\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // The composed text is what native should actually display\n _composedText: result.composedText,\n }\n : undefined\n\n return {\n ...event,\n // Override the layout type to text_wall since we've pre-composed the columns\n // This tells native to use sendTextWall instead of sendDoubleTextWall\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n // Keep original values for debugging/preview (with placeholders replaced)\n topText: leftTextWithPlaceholders,\n bottomText: rightTextWithPlaceholders,\n // Store original values with placeholders for debugging\n _originalTopText: leftText,\n _originalBottomText: rightText,\n layout: processedLayout\n ? {\n ...processedLayout,\n layoutType: \"text_wall\" as DisplayLayoutType,\n text: result.composedText,\n }\n : undefined,\n _processed: true,\n _profile: this.profile.id,\n _originalLayoutType: \"double_text_wall\",\n _composedColumns: {\n leftLines: result.leftLines,\n rightLines: result.rightLines,\n config: result.config,\n },\n }\n }\n\n // ===========================================================================\n // Text Wrapping Utilities\n // ===========================================================================\n\n /**\n * Wrap text using the current profile\n *\n * @param text - Text to wrap\n * @param options - Optional override options\n * @returns Array of wrapped lines\n */\n public wrapText(text: string, options?: WrapOptions): string[] {\n if (!text) return [\"\"]\n\n const result = this.wrapper.wrap(text, options)\n return result.lines\n }\n\n private getEventBreakMode(\n event: DisplayEvent,\n layout: DisplayEvent | NonNullable<DisplayEvent[\"layout\"]>,\n ): BreakMode | undefined {\n const breakMode = layout.breakMode ?? event.breakMode\n return isBreakMode(breakMode) ? breakMode : undefined\n }\n\n /**\n * Measure the pixel width of text\n *\n * @param text - Text to measure\n * @returns Width in pixels\n */\n public measureText(text: string): number {\n return this.measurer.measureText(text)\n }\n\n /**\n * Check if text fits on a single line\n *\n * @param text - Text to check\n * @returns true if text fits without wrapping\n */\n public fitsOnSingleLine(text: string): boolean {\n return this.measurer.measureText(text) <= this.profile.displayWidthPx\n }\n\n /**\n * Get the display width in pixels\n */\n public getDisplayWidth(): number {\n return this.profile.displayWidthPx\n }\n\n /**\n * Get the maximum number of lines\n */\n public getMaxLines(): number {\n return this.profile.maxLines\n }\n\n /**\n * Get detailed text measurement\n */\n public measureTextDetailed(text: string) {\n return this.measurer.measureTextDetailed(text)\n }\n\n // ===========================================================================\n // Break Mode Control\n // ===========================================================================\n\n /**\n * Set the break mode for text wrapping\n *\n * @param breakMode - 'character' | 'word' | 'strict-word'\n */\n public setBreakMode(breakMode: BreakMode): void {\n if (this.options.breakMode === breakMode) return\n\n this.options.breakMode = breakMode\n this.updateProfile(this.profile) // Recreate wrapper with new break mode\n\n if (this.options.debug) {\n console.log(`[DisplayProcessor] Break mode set to: ${breakMode}`)\n }\n }\n\n /**\n * Get the current break mode\n */\n public getBreakMode(): BreakMode {\n return this.options.breakMode || \"character\"\n }\n}\n\nexport const displayProcessor = DisplayProcessor.getInstance()\nexport default displayProcessor\n"]}
@@ -0,0 +1,40 @@
1
+ declare class GestureDotsService {
2
+ private started;
3
+ private flash;
4
+ private flashTimer;
5
+ /** Finger currently on the touch bar (tap_start..tap_end; the CFW forwards holds as these edges). */
6
+ private held;
7
+ /**
8
+ * The CFW's tap_then_hold fired during this press (discrete recognition
9
+ * event; the tap_end that follows on release is what clears it).
10
+ */
11
+ private holdRecognized;
12
+ /** Whether the indicator scene currently has anything on it (needs a clear when the flash ends). */
13
+ private visible;
14
+ private unsubs;
15
+ start(): void;
16
+ stop(): void;
17
+ private enabled;
18
+ /**
19
+ * Flash a gesture on the indicator. Other gesture names are ignored. The
20
+ * device-event router calls this after the CFW dashboard had its chance to
21
+ * consume the gesture, so dashboard navigation does not flash.
22
+ */
23
+ noteGesture(gestureName: string): void;
24
+ /**
25
+ * Paint or clear the indicator. Nothing is drawn while anything else is on
26
+ * the glasses (miniapp frame or system screen) or when no gesture was just
27
+ * received. The top-right anchor resolves against the device's real canvas,
28
+ * so no size math here.
29
+ *
30
+ * Between flashes the element STAYS in the scene with the blank glyph:
31
+ * removing it would tear down the firmware image container and rebuild the
32
+ * page on every flash (a rebuild storm on stock firmware). Only disabling
33
+ * the setting removes the element outright.
34
+ */
35
+ private render;
36
+ }
37
+ declare const gestureDotsService: GestureDotsService;
38
+ export default gestureDotsService;
39
+ export { GestureDotsService };
40
+ //# sourceMappingURL=GestureDotsService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GestureDotsService.d.ts","sourceRoot":"","sources":["../../src/services/GestureDotsService.ts"],"names":[],"mappings":"AA2BA,cAAM,kBAAkB;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,UAAU,CAAsB;IACxC,qGAAqG;IACrG,OAAO,CAAC,IAAI,CAAQ;IACpB;;;OAGG;IACH,OAAO,CAAC,cAAc,CAAQ;IAC9B,oGAAoG;IACpG,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,MAAM,CAAwB;IAEtC,KAAK,IAAI,IAAI;IAcb,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,OAAO;IAIf;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAyCtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM;CA6Bf;AAED,QAAA,MAAM,kBAAkB,oBAA2B,CAAA;AACnD,eAAe,kBAAkB,CAAA;AACjC,OAAO,EAAC,kBAAkB,EAAC,CAAA"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Gesture dots — a debug indicator that flashes a small glyph top-right on the
3
+ * glasses when a touch gesture arrives, while nothing else is on the display:
4
+ * a filled circle for a single tap, an arrow for a swipe (cleared after a
5
+ * short pulse), a bullseye while a CFW tap-then-hold is held. Extracted from
6
+ * the CFW dashboard service; it renders through
7
+ * the ordinary scene pipeline, so it works on any positioning device (stock
8
+ * G2 included), and is gated by the gesture_dots debug setting.
9
+ *
10
+ * The glyphs are pre-baked images (not text): the stock firmware's builtin
11
+ * font has no ●/↑/↓, and a single image element with a stable id and box maps
12
+ * to ONE re-used firmware image container — flash and idle swap its bytes
13
+ * (data-only update, no page rebuild) instead of creating/removing containers.
14
+ */
15
+ import { SETTINGS, useSettingsStore } from "../stores/settings";
16
+ import { BgTimer } from "../utils/timers";
17
+ import { GESTURE_DOT_GLYPHS, GESTURE_DOT_GLYPH_SIZE } from "./gestureDotGlyphs";
18
+ import localDisplayManager from "./LocalDisplayManager";
19
+ import sceneRenderer from "./SceneRenderer";
20
+ const DOTS_APP_ID = "system.gesture-dots";
21
+ /** How long a gesture stays on the indicator. */
22
+ const GESTURE_FLASH_MS = 700;
23
+ const MARGIN_X = 16;
24
+ const MARGIN_TOP = 10;
25
+ class GestureDotsService {
26
+ started = false;
27
+ flash = null;
28
+ flashTimer = null;
29
+ /** Finger currently on the touch bar (tap_start..tap_end; the CFW forwards holds as these edges). */
30
+ held = false;
31
+ /**
32
+ * The CFW's tap_then_hold fired during this press (discrete recognition
33
+ * event; the tap_end that follows on release is what clears it).
34
+ */
35
+ holdRecognized = false;
36
+ /** Whether the indicator scene currently has anything on it (needs a clear when the flash ends). */
37
+ visible = false;
38
+ unsubs = [];
39
+ start() {
40
+ if (this.started)
41
+ return;
42
+ this.started = true;
43
+ this.unsubs.push(
44
+ // The indicator owns the screen only while it is empty: drop our
45
+ // retained state when something else takes over, and clear any
46
+ // leftover flash when the display empties again.
47
+ localDisplayManager.onActiveDisplayChange((active) => {
48
+ if (active)
49
+ this.visible = false;
50
+ else
51
+ this.render();
52
+ }));
53
+ }
54
+ stop() {
55
+ for (const unsub of this.unsubs)
56
+ unsub();
57
+ this.unsubs = [];
58
+ if (this.flashTimer !== null)
59
+ BgTimer.clearTimeout(this.flashTimer);
60
+ this.flashTimer = null;
61
+ this.flash = null;
62
+ this.held = false;
63
+ this.holdRecognized = false;
64
+ this.visible = false;
65
+ this.started = false;
66
+ sceneRenderer.clearApp(DOTS_APP_ID);
67
+ }
68
+ enabled() {
69
+ return useSettingsStore.getState().getSetting(SETTINGS.gesture_dots.key) === true;
70
+ }
71
+ /**
72
+ * Flash a gesture on the indicator. Other gesture names are ignored. The
73
+ * device-event router calls this after the CFW dashboard had its chance to
74
+ * consume the gesture, so dashboard navigation does not flash.
75
+ */
76
+ noteGesture(gestureName) {
77
+ if (!this.started || !this.enabled())
78
+ return;
79
+ // Tap edges show a steady circle for as long as the finger is down (on
80
+ // CFW these arrive for holds: ET_LONG/ET_REL forwarded as
81
+ // tap_start/tap_end), with no timer — tap_end clears it.
82
+ if (gestureName === "tap_start" || gestureName === "tap_end") {
83
+ const held = gestureName === "tap_start";
84
+ if (this.held === held)
85
+ return;
86
+ this.held = held;
87
+ if (!held)
88
+ this.holdRecognized = false;
89
+ this.render();
90
+ return;
91
+ }
92
+ // CFW tap-then-hold (the stock Menu gesture): swap the steady dot for the
93
+ // bullseye until release. Recognition arrives mid-press, but set held too
94
+ // in case the tap_start edge was missed — the release edge still clears.
95
+ if (gestureName === "tap_then_hold") {
96
+ this.held = true;
97
+ this.holdRecognized = true;
98
+ this.render();
99
+ return;
100
+ }
101
+ const flash = gestureName === "single_tap"
102
+ ? "dot"
103
+ : gestureName === "swipe_up"
104
+ ? "up"
105
+ : gestureName === "swipe_down"
106
+ ? "down"
107
+ : null;
108
+ if (!flash)
109
+ return;
110
+ this.flash = flash;
111
+ if (this.flashTimer !== null)
112
+ BgTimer.clearTimeout(this.flashTimer);
113
+ this.flashTimer = BgTimer.setTimeout(() => {
114
+ this.flashTimer = null;
115
+ this.flash = null;
116
+ this.render();
117
+ }, GESTURE_FLASH_MS);
118
+ this.render();
119
+ }
120
+ /**
121
+ * Paint or clear the indicator. Nothing is drawn while anything else is on
122
+ * the glasses (miniapp frame or system screen) or when no gesture was just
123
+ * received. The top-right anchor resolves against the device's real canvas,
124
+ * so no size math here.
125
+ *
126
+ * Between flashes the element STAYS in the scene with the blank glyph:
127
+ * removing it would tear down the firmware image container and rebuild the
128
+ * page on every flash (a rebuild storm on stock firmware). Only disabling
129
+ * the setting removes the element outright.
130
+ */
131
+ render() {
132
+ if (!this.started)
133
+ return;
134
+ if (localDisplayManager.hasActiveDisplay())
135
+ return;
136
+ const shown = this.flash ?? (this.held ? (this.holdRecognized ? "hold" : "dot") : null);
137
+ if (!this.enabled()) {
138
+ if (this.visible) {
139
+ this.visible = false;
140
+ sceneRenderer.emitScene(DOTS_APP_ID, "main", []);
141
+ }
142
+ return;
143
+ }
144
+ if (!shown && !this.visible)
145
+ return;
146
+ const elements = [
147
+ {
148
+ type: "image",
149
+ id: "gesture-dot",
150
+ box: {
151
+ x: MARGIN_X,
152
+ y: MARGIN_TOP,
153
+ w: GESTURE_DOT_GLYPH_SIZE,
154
+ h: GESTURE_DOT_GLYPH_SIZE,
155
+ anchor: "top-right",
156
+ },
157
+ data: GESTURE_DOT_GLYPHS[shown ?? "blank"],
158
+ },
159
+ ];
160
+ this.visible = true;
161
+ sceneRenderer.emitScene(DOTS_APP_ID, "main", elements);
162
+ }
163
+ }
164
+ const gestureDotsService = new GestureDotsService();
165
+ export default gestureDotsService;
166
+ export { GestureDotsService };
167
+ //# sourceMappingURL=GestureDotsService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GestureDotsService.js","sourceRoot":"","sources":["../../src/services/GestureDotsService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Gesture dots — a debug indicator that flashes a small glyph top-right on the\n * glasses when a touch gesture arrives, while nothing else is on the display:\n * a filled circle for a single tap, an arrow for a swipe (cleared after a\n * short pulse), a bullseye while a CFW tap-then-hold is held. Extracted from\n * the CFW dashboard service; it renders through\n * the ordinary scene pipeline, so it works on any positioning device (stock\n * G2 included), and is gated by the gesture_dots debug setting.\n *\n * The glyphs are pre-baked images (not text): the stock firmware's builtin\n * font has no ●/↑/↓, and a single image element with a stable id and box maps\n * to ONE re-used firmware image container — flash and idle swap its bytes\n * (data-only update, no page rebuild) instead of creating/removing containers.\n */\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {GESTURE_DOT_GLYPHS, GESTURE_DOT_GLYPH_SIZE, type GestureDotGlyph} from \"./gestureDotGlyphs\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DOTS_APP_ID = \"system.gesture-dots\"\n/** How long a gesture stays on the indicator. */\nconst GESTURE_FLASH_MS = 700\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\n\nclass GestureDotsService {\n private started = false\n private flash: GestureDotGlyph | null = null\n private flashTimer: number | null = null\n /** Finger currently on the touch bar (tap_start..tap_end; the CFW forwards holds as these edges). */\n private held = false\n /**\n * The CFW's tap_then_hold fired during this press (discrete recognition\n * event; the tap_end that follows on release is what clears it).\n */\n private holdRecognized = false\n /** Whether the indicator scene currently has anything on it (needs a clear when the flash ends). */\n private visible = false\n private unsubs: Array<() => void> = []\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n // The indicator owns the screen only while it is empty: drop our\n // retained state when something else takes over, and clear any\n // leftover flash when the display empties again.\n localDisplayManager.onActiveDisplayChange((active) => {\n if (active) this.visible = false\n else this.render()\n }),\n )\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n if (this.flashTimer !== null) BgTimer.clearTimeout(this.flashTimer)\n this.flashTimer = null\n this.flash = null\n this.held = false\n this.holdRecognized = false\n this.visible = false\n this.started = false\n sceneRenderer.clearApp(DOTS_APP_ID)\n }\n\n private enabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.gesture_dots.key) === true\n }\n\n /**\n * Flash a gesture on the indicator. Other gesture names are ignored. The\n * device-event router calls this after the CFW dashboard had its chance to\n * consume the gesture, so dashboard navigation does not flash.\n */\n noteGesture(gestureName: string): void {\n if (!this.started || !this.enabled()) return\n // Tap edges show a steady circle for as long as the finger is down (on\n // CFW these arrive for holds: ET_LONG/ET_REL forwarded as\n // tap_start/tap_end), with no timer — tap_end clears it.\n if (gestureName === \"tap_start\" || gestureName === \"tap_end\") {\n const held = gestureName === \"tap_start\"\n if (this.held === held) return\n this.held = held\n if (!held) this.holdRecognized = false\n this.render()\n return\n }\n // CFW tap-then-hold (the stock Menu gesture): swap the steady dot for the\n // bullseye until release. Recognition arrives mid-press, but set held too\n // in case the tap_start edge was missed — the release edge still clears.\n if (gestureName === \"tap_then_hold\") {\n this.held = true\n this.holdRecognized = true\n this.render()\n return\n }\n const flash: GestureDotGlyph | null =\n gestureName === \"single_tap\"\n ? \"dot\"\n : gestureName === \"swipe_up\"\n ? \"up\"\n : gestureName === \"swipe_down\"\n ? \"down\"\n : null\n if (!flash) return\n this.flash = flash\n if (this.flashTimer !== null) BgTimer.clearTimeout(this.flashTimer)\n this.flashTimer = BgTimer.setTimeout(() => {\n this.flashTimer = null\n this.flash = null\n this.render()\n }, GESTURE_FLASH_MS)\n this.render()\n }\n\n /**\n * Paint or clear the indicator. Nothing is drawn while anything else is on\n * the glasses (miniapp frame or system screen) or when no gesture was just\n * received. The top-right anchor resolves against the device's real canvas,\n * so no size math here.\n *\n * Between flashes the element STAYS in the scene with the blank glyph:\n * removing it would tear down the firmware image container and rebuild the\n * page on every flash (a rebuild storm on stock firmware). Only disabling\n * the setting removes the element outright.\n */\n private render(): void {\n if (!this.started) return\n if (localDisplayManager.hasActiveDisplay()) return\n const shown = this.flash ?? (this.held ? (this.holdRecognized ? \"hold\" : \"dot\") : null)\n if (!this.enabled()) {\n if (this.visible) {\n this.visible = false\n sceneRenderer.emitScene(DOTS_APP_ID, \"main\", [])\n }\n return\n }\n if (!shown && !this.visible) return\n const elements: SceneElementInput[] = [\n {\n type: \"image\",\n id: \"gesture-dot\",\n box: {\n x: MARGIN_X,\n y: MARGIN_TOP,\n w: GESTURE_DOT_GLYPH_SIZE,\n h: GESTURE_DOT_GLYPH_SIZE,\n anchor: \"top-right\",\n },\n data: GESTURE_DOT_GLYPHS[shown ?? \"blank\"],\n },\n ]\n this.visible = true\n sceneRenderer.emitScene(DOTS_APP_ID, \"main\", elements)\n }\n}\n\nconst gestureDotsService = new GestureDotsService()\nexport default gestureDotsService\nexport {GestureDotsService}\n"]}
@@ -1,3 +1,17 @@
1
- export declare function startGlassesStatusProjection(): void;
1
+ /**
2
+ * Glasses status projection — engine-owned. Subscribes to the native bluetooth-sdk
3
+ * status events and projects them into the engine device stores, so engine's own
4
+ * services (which react to those stores) work for ANY host — not just the first-party
5
+ * Glassly app, where this used to live in MantleManager.
6
+ *
7
+ * This is the INBOUND mirror of GlassesSettingsSync: that pushes the settings store
8
+ * OUT to the device; this pulls device status IN to the stores. It's the feed that
9
+ * the rest of the engine runtime depends on (MicStateCoordinator, the settings-sync
10
+ * on-connect trigger, the facade read-models all read these stores).
11
+ *
12
+ * Started by `engine.start()`. Idempotent.
13
+ */
14
+ import { type PublicGlassesStatus } from "@glassly/bluetooth-sdk";
15
+ export declare function startGlassesStatusProjection(forwarder?: (status: Partial<PublicGlassesStatus>) => void): Promise<void>;
2
16
  export declare function stopGlassesStatusProjection(): void;
3
17
  //# sourceMappingURL=GlassesStatusProjection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GlassesStatusProjection.d.ts","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"AAqBA,wBAAgB,4BAA4B,IAAI,IAAI,CA6CnD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAIlD"}
1
+ {"version":3,"file":"GlassesStatusProjection.d.ts","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAqB,EAAC,KAAK,mBAAmB,EAAC,MAAM,wBAAwB,CAAA;AAS7E,wBAAgB,4BAA4B,CAC1C,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,KAAK,IAAI,GACzD,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAMlD"}
@@ -11,19 +11,22 @@
11
11
  *
12
12
  * Started by `engine.start()`. Idempotent.
13
13
  */
14
- import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
14
+ import BluetoothSdk from "@glassly/bluetooth-sdk";
15
15
  import { useCoreStore } from "../stores/core";
16
16
  import { useGlassesStore } from "../stores/glasses";
17
- import localMiniappRuntime from "./LocalMiniappRuntime";
18
17
  let unsubs = [];
19
18
  let projectionRunId = 0;
20
- export function startGlassesStatusProjection() {
19
+ let glassesStatusForwarder = null;
20
+ let hydrationPromise = null;
21
+ export function startGlassesStatusProjection(forwarder) {
22
+ if (forwarder)
23
+ glassesStatusForwarder = forwarder;
21
24
  if (unsubs.length)
22
- return;
25
+ return hydrationPromise ?? Promise.resolve();
23
26
  const runId = ++projectionRunId;
24
27
  let bluetoothEventSeen = false;
25
28
  let glassesEventSeen = false;
26
- void BluetoothSdk.getBluetoothStatus()
29
+ const bluetoothHydration = BluetoothSdk.getBluetoothStatus()
27
30
  .then((status) => {
28
31
  if (runId !== projectionRunId || bluetoothEventSeen)
29
32
  return;
@@ -32,7 +35,7 @@ export function startGlassesStatusProjection() {
32
35
  .catch((error) => {
33
36
  console.warn("GlassesStatusProjection: getBluetoothStatus failed", error);
34
37
  });
35
- void BluetoothSdk.getGlassesStatus()
38
+ const glassesHydration = BluetoothSdk.getGlassesStatus()
36
39
  .then((status) => {
37
40
  if (runId !== projectionRunId || glassesEventSeen)
38
41
  return;
@@ -42,24 +45,28 @@ export function startGlassesStatusProjection() {
42
45
  console.warn("GlassesStatusProjection: getGlassesStatus failed", error);
43
46
  });
44
47
  // Bluetooth-adapter status -> core store.
45
- unsubs.push(BluetoothSdk.onBluetoothStatus((changed) => {
48
+ unsubs.push(BluetoothSdk.subscribeBluetoothStatus((changed) => {
46
49
  bluetoothEventSeen = true;
47
50
  useCoreStore.getState().setCoreInfo(changed);
48
51
  }));
49
- // Glasses status -> glasses store (+ forward to local miniapps; clear any stale
50
- // OTA-available flag on disconnect).
51
- unsubs.push(BluetoothSdk.onGlassesStatus((changed) => {
52
+ // Glasses status -> glasses store (+ optional full-runtime forwarding to local
53
+ // miniapps; clear any stale OTA-available flag on disconnect).
54
+ unsubs.push(BluetoothSdk.subscribeGlassesStatus((changed) => {
52
55
  glassesEventSeen = true;
53
56
  useGlassesStore.getState().setGlassesInfo(changed);
54
- localMiniappRuntime.forwardEvent("glasses_connection_state", changed);
57
+ glassesStatusForwarder?.(changed);
55
58
  if (changed.connection?.state === "disconnected") {
56
59
  useGlassesStore.getState().setOtaUpdateAvailable(null);
57
60
  }
58
61
  }));
62
+ hydrationPromise = Promise.allSettled([bluetoothHydration, glassesHydration]).then(() => undefined);
63
+ return hydrationPromise;
59
64
  }
60
65
  export function stopGlassesStatusProjection() {
61
66
  projectionRunId++;
62
67
  unsubs.forEach((unsub) => unsub());
63
68
  unsubs = [];
69
+ glassesStatusForwarder = null;
70
+ hydrationPromise = null;
64
71
  }
65
72
  //# sourceMappingURL=GlassesStatusProjection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GlassesStatusProjection.js","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAC1D,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;AAEvD,IAAI,MAAM,GAAsB,EAAE,CAAA;AAClC,IAAI,eAAe,GAAG,CAAC,CAAA;AAEvB,MAAM,UAAU,4BAA4B;IAC1C,IAAI,MAAM,CAAC,MAAM;QAAE,OAAM;IAEzB,MAAM,KAAK,GAAG,EAAE,eAAe,CAAA;IAC/B,IAAI,kBAAkB,GAAG,KAAK,CAAA;IAC9B,IAAI,gBAAgB,GAAG,KAAK,CAAA;IAE5B,KAAK,YAAY,CAAC,kBAAkB,EAAE;SACnC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,IAAI,KAAK,KAAK,eAAe,IAAI,kBAAkB;YAAE,OAAM;QAC3D,YAAY,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAC7C,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACf,OAAO,CAAC,IAAI,CAAC,oDAAoD,EAAE,KAAK,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEJ,KAAK,YAAY,CAAC,gBAAgB,EAAE;SACjC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,IAAI,KAAK,KAAK,eAAe,IAAI,gBAAgB;YAAE,OAAM;QACzD,eAAe,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;IACnD,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACf,OAAO,CAAC,IAAI,CAAC,kDAAkD,EAAE,KAAK,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;IAEJ,0CAA0C;IAC1C,MAAM,CAAC,IAAI,CACT,YAAY,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE,EAAE;QACzC,kBAAkB,GAAG,IAAI,CAAA;QACzB,YAAY,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;IAC9C,CAAC,CAAC,CACH,CAAA;IAED,gFAAgF;IAChF,qCAAqC;IACrC,MAAM,CAAC,IAAI,CACT,YAAY,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE;QACvC,gBAAgB,GAAG,IAAI,CAAA;QACvB,eAAe,CAAC,QAAQ,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAA;QAClD,mBAAmB,CAAC,YAAY,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAA;QACrE,IAAI,OAAO,CAAC,UAAU,EAAE,KAAK,KAAK,cAAc,EAAE,CAAC;YACjD,eAAe,CAAC,QAAQ,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;QACxD,CAAC;IACH,CAAC,CAAC,CACH,CAAA;AACH,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,eAAe,EAAE,CAAA;IACjB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAA;IAClC,MAAM,GAAG,EAAE,CAAA;AACb,CAAC","sourcesContent":["/**\n * Glasses status projection — engine-owned. Subscribes to the native bluetooth-sdk\n * status events and projects them into the engine device stores, so engine's own\n * services (which react to those stores) work for ANY host — not just the first-party\n * Glassly app, where this used to live in MantleManager.\n *\n * This is the INBOUND mirror of GlassesSettingsSync: that pushes the settings store\n * OUT to the device; this pulls device status IN to the stores. It's the feed that\n * the rest of the engine runtime depends on (MicStateCoordinator, the settings-sync\n * on-connect trigger, the facade read-models all read these stores).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {useCoreStore} from \"../stores/core\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\n\nlet unsubs: Array<() => void> = []\nlet projectionRunId = 0\n\nexport function startGlassesStatusProjection(): void {\n if (unsubs.length) return\n\n const runId = ++projectionRunId\n let bluetoothEventSeen = false\n let glassesEventSeen = false\n\n void BluetoothSdk.getBluetoothStatus()\n .then((status) => {\n if (runId !== projectionRunId || bluetoothEventSeen) return\n useCoreStore.getState().setCoreInfo(status)\n })\n .catch((error) => {\n console.warn(\"GlassesStatusProjection: getBluetoothStatus failed\", error)\n })\n\n void BluetoothSdk.getGlassesStatus()\n .then((status) => {\n if (runId !== projectionRunId || glassesEventSeen) return\n useGlassesStore.getState().setGlassesInfo(status)\n })\n .catch((error) => {\n console.warn(\"GlassesStatusProjection: getGlassesStatus failed\", error)\n })\n\n // Bluetooth-adapter status -> core store.\n unsubs.push(\n BluetoothSdk.onBluetoothStatus((changed) => {\n bluetoothEventSeen = true\n useCoreStore.getState().setCoreInfo(changed)\n }),\n )\n\n // Glasses status -> glasses store (+ forward to local miniapps; clear any stale\n // OTA-available flag on disconnect).\n unsubs.push(\n BluetoothSdk.onGlassesStatus((changed) => {\n glassesEventSeen = true\n useGlassesStore.getState().setGlassesInfo(changed)\n localMiniappRuntime.forwardEvent(\"glasses_connection_state\", changed)\n if (changed.connection?.state === \"disconnected\") {\n useGlassesStore.getState().setOtaUpdateAvailable(null)\n }\n }),\n )\n}\n\nexport function stopGlassesStatusProjection(): void {\n projectionRunId++\n unsubs.forEach((unsub) => unsub())\n unsubs = []\n}\n"]}
1
+ {"version":3,"file":"GlassesStatusProjection.js","sourceRoot":"","sources":["../../src/services/GlassesStatusProjection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Glasses status projection — engine-owned. Subscribes to the native bluetooth-sdk\n * status events and projects them into the engine device stores, so engine's own\n * services (which react to those stores) work for ANY host — not just the first-party\n * Glassly app, where this used to live in MantleManager.\n *\n * This is the INBOUND mirror of GlassesSettingsSync: that pushes the settings store\n * OUT to the device; this pulls device status IN to the stores. It's the feed that\n * the rest of the engine runtime depends on (MicStateCoordinator, the settings-sync\n * on-connect trigger, the facade read-models all read these stores).\n *\n * Started by `engine.start()`. Idempotent.\n */\nimport BluetoothSdk, {type PublicGlassesStatus} from \"@glassly/bluetooth-sdk\"\nimport {useCoreStore} from \"../stores/core\"\nimport {useGlassesStore} from \"../stores/glasses\"\n\nlet unsubs: Array<() => void> = []\nlet projectionRunId = 0\nlet glassesStatusForwarder: ((status: Partial<PublicGlassesStatus>) => void) | null = null\nlet hydrationPromise: Promise<void> | null = null\n\nexport function startGlassesStatusProjection(\n forwarder?: (status: Partial<PublicGlassesStatus>) => void,\n): Promise<void> {\n if (forwarder) glassesStatusForwarder = forwarder\n if (unsubs.length) return hydrationPromise ?? Promise.resolve()\n\n const runId = ++projectionRunId\n let bluetoothEventSeen = false\n let glassesEventSeen = false\n\n const bluetoothHydration = BluetoothSdk.getBluetoothStatus()\n .then((status) => {\n if (runId !== projectionRunId || bluetoothEventSeen) return\n useCoreStore.getState().setCoreInfo(status)\n })\n .catch((error) => {\n console.warn(\"GlassesStatusProjection: getBluetoothStatus failed\", error)\n })\n\n const glassesHydration = BluetoothSdk.getGlassesStatus()\n .then((status) => {\n if (runId !== projectionRunId || glassesEventSeen) return\n useGlassesStore.getState().setGlassesInfo(status)\n })\n .catch((error) => {\n console.warn(\"GlassesStatusProjection: getGlassesStatus failed\", error)\n })\n\n // Bluetooth-adapter status -> core store.\n unsubs.push(\n BluetoothSdk.subscribeBluetoothStatus((changed) => {\n bluetoothEventSeen = true\n useCoreStore.getState().setCoreInfo(changed)\n }),\n )\n\n // Glasses status -> glasses store (+ optional full-runtime forwarding to local\n // miniapps; clear any stale OTA-available flag on disconnect).\n unsubs.push(\n BluetoothSdk.subscribeGlassesStatus((changed) => {\n glassesEventSeen = true\n useGlassesStore.getState().setGlassesInfo(changed)\n glassesStatusForwarder?.(changed)\n if (changed.connection?.state === \"disconnected\") {\n useGlassesStore.getState().setOtaUpdateAvailable(null)\n }\n }),\n )\n\n hydrationPromise = Promise.allSettled([bluetoothHydration, glassesHydration]).then(() => undefined)\n return hydrationPromise\n}\n\nexport function stopGlassesStatusProjection(): void {\n projectionRunId++\n unsubs.forEach((unsub) => unsub())\n unsubs = []\n glassesStatusForwarder = null\n hydrationPromise = null\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"HotspotOtaTransport.d.ts","sourceRoot":"","sources":["../../src/services/HotspotOtaTransport.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,4BAA4B,EAAC,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAML,KAAK,2BAA2B,EAEjC,MAAM,yBAAyB,CAAA;AAIhC,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,aAAa,GAAG,kBAAkB,GAAG,iBAAiB,GAAG,SAAS,CAAA;AAEzG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,CAAC,EAAE,2BAA2B,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B,gCAAgC,GAChC,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,CAAA;AAE3B,qBAAa,wBAAyB,SAAQ,KAAK;aAE/B,IAAI,EAAE,mBAAmB;gBAAzB,IAAI,EAAE,mBAAmB,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED,yFAAyF;AACzF,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,qBAAqB,CAAQ;IACrC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,eAAe,CAA6B;IAE9C,OAAO,CACX,WAAW,EAAE,4BAA4B,EACzC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,GAClD,OAAO,CAAC,MAAM,CAAC;IAuElB,gGAAgG;YAClF,2BAA2B;IAczC,OAAO,CAAC,mBAAmB,CAa1B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA4BhC;AAED,eAAO,MAAM,mBAAmB,qBAA4B,CAAA"}
1
+ {"version":3,"file":"HotspotOtaTransport.d.ts","sourceRoot":"","sources":["../../src/services/HotspotOtaTransport.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,4BAA4B,EAAC,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAML,KAAK,2BAA2B,EAEjC,MAAM,yBAAyB,CAAA;AAIhC,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,aAAa,GAAG,kBAAkB,GAAG,iBAAiB,GAAG,SAAS,CAAA;AAEzG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,CAAC,EAAE,2BAA2B,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAC3B,gCAAgC,GAChC,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,CAAA;AAE3B,qBAAa,wBAAyB,SAAQ,KAAK;aAE/B,IAAI,EAAE,mBAAmB;gBAAzB,IAAI,EAAE,mBAAmB,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED,yFAAyF;AACzF,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,qBAAqB,CAAQ;IACrC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,eAAe,CAA6B;IAE9C,OAAO,CACX,WAAW,EAAE,4BAA4B,EACzC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,GAClD,OAAO,CAAC,MAAM,CAAC;IAuElB,gGAAgG;YAClF,2BAA2B;IAczC,OAAO,CAAC,mBAAmB,CAa1B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA4BhC;AAED,eAAO,MAAM,mBAAmB,qBAA4B,CAAA"}
@@ -1,4 +1,5 @@
1
- import BluetoothSdk, { isEnabledHotspotStatus, OtaServer } from "@glassly/bluetooth-sdk/internal";
1
+ import BluetoothSdk, { isEnabledHotspotStatus } from "@glassly/bluetooth-sdk";
2
+ import { otaServer } from "@glassly/bluetooth-sdk/ota-transport";
2
3
  // One coordinator selects the native Android or iOS staging implementation at runtime.
3
4
  // eslint-disable-next-line react-native/split-platform-components
4
5
  import { PermissionsAndroid, Platform } from "react-native";
@@ -49,7 +50,7 @@ class HotspotOtaTransport {
49
50
  let localAddress = scopedAddress;
50
51
  if (Platform.OS === "ios") {
51
52
  try {
52
- localAddress = await OtaServer.waitForWifiAddress(hotspot.localIp, 15_000);
53
+ localAddress = await otaServer.waitForWifiAddress(hotspot.localIp, 15_000);
53
54
  }
54
55
  catch (error) {
55
56
  throw new HotspotOtaTransportError("hotspot_join_failed", error instanceof Error ? error.message : String(error));
@@ -58,10 +59,10 @@ class HotspotOtaTransport {
58
59
  const artifactPaths = Object.fromEntries(this.prepared.map((artifact) => [artifact.sha256, artifact.filePath]));
59
60
  // Start exactly one native listener to learn its selected port, then atomically replace
60
61
  // the placeholder with the immutable rewritten manifest before ota_start is sent.
61
- const server = await OtaServer.startOtaServer("{}", artifactPaths, localAddress);
62
+ const server = await otaServer.start("{}", artifactPaths, localAddress);
62
63
  this.serverStarted = true;
63
64
  const manifest = rewriteManifestForLocalServer(checkResult.manifestBody, this.prepared, server.baseUrl);
64
- const published = await OtaServer.startOtaServer(manifest, artifactPaths, server.host);
65
+ const published = await otaServer.start(manifest, artifactPaths, server.host);
65
66
  if (published.manifestUrl !== server.manifestUrl) {
66
67
  throw new Error("Local OTA server endpoint changed while publishing the manifest");
67
68
  }
@@ -98,12 +99,12 @@ class HotspotOtaTransport {
98
99
  }
99
100
  }
100
101
  downloadIosArtifact = async (entry, destination, onProgress) => {
101
- const subscription = OtaServer.addListener("artifactDownloadProgress", (event) => {
102
+ const subscription = otaServer.onArtifactDownloadProgress((event) => {
102
103
  if (event.destination === destination)
103
104
  onProgress?.(event.bytesWritten, event.contentLength);
104
105
  });
105
106
  try {
106
- return await OtaServer.downloadArtifact(entry.url, destination);
107
+ return await otaServer.downloadArtifact(entry.url, destination);
107
108
  }
108
109
  finally {
109
110
  subscription.remove();
@@ -124,7 +125,7 @@ class HotspotOtaTransport {
124
125
  }
125
126
  }
126
127
  if (this.serverStarted)
127
- await OtaServer.stopOtaServer().catch(() => { });
128
+ await otaServer.stop().catch(() => { });
128
129
  if (this.localNetworkConnected)
129
130
  await localNetworkTransport.disconnect().catch(() => { });
130
131
  await cleanupArtifacts().catch(() => { });