@glassly/engine 0.3.3-dev.670 → 0.3.4-beta.685
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.
- package/build/engine.d.ts +38 -14
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +3 -0
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +32 -0
- package/build/facades/ai.d.ts.map +1 -0
- package/build/facades/ai.js +38 -0
- package/build/facades/ai.js.map +1 -0
- package/build/facades/glasses.d.ts +30 -24
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +49 -30
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/glassesSettings.d.ts.map +1 -1
- package/build/facades/glassesSettings.js +1 -0
- package/build/facades/glassesSettings.js.map +1 -1
- package/build/facades/pairing.d.ts +6 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +14 -14
- package/build/facades/pairing.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +5 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +9 -1
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/GestureDotsService.d.ts +14 -3
- package/build/services/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +48 -13
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +11 -9
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LlmRouting.d.ts +35 -0
- package/build/services/LlmRouting.d.ts.map +1 -0
- package/build/services/LlmRouting.js +25 -0
- package/build/services/LlmRouting.js.map +1 -0
- package/build/services/LocalLlmModelManager.d.ts +79 -0
- package/build/services/LocalLlmModelManager.d.ts.map +1 -0
- package/build/services/LocalLlmModelManager.js +278 -0
- package/build/services/LocalLlmModelManager.js.map +1 -0
- package/build/services/LocalLlmPrompt.d.ts +90 -0
- package/build/services/LocalLlmPrompt.d.ts.map +1 -0
- package/build/services/LocalLlmPrompt.js +189 -0
- package/build/services/LocalLlmPrompt.js.map +1 -0
- package/build/services/LocalLlmProvider.d.ts +35 -0
- package/build/services/LocalLlmProvider.d.ts.map +1 -0
- package/build/services/LocalLlmProvider.js +218 -0
- package/build/services/LocalLlmProvider.js.map +1 -0
- package/build/services/LocalMiniappRuntime.d.ts +9 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +91 -17
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/STTModelManager.d.ts +25 -2
- package/build/services/STTModelManager.d.ts.map +1 -1
- package/build/services/STTModelManager.js +137 -6
- package/build/services/STTModelManager.js.map +1 -1
- package/build/services/controllers.d.ts +21 -0
- package/build/services/controllers.d.ts.map +1 -0
- package/build/services/controllers.js +62 -0
- package/build/services/controllers.js.map +1 -0
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +18 -24
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts +17 -6
- package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.js +40 -23
- package/build/services/neuralBand/NeuralBandInput.js.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.js +23 -26
- package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
- package/build/services/settingsBridge.d.ts +26 -18
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +36 -15
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +3 -1
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.js +0 -1
- package/build/stores/core.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +2 -6
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +29 -3
- package/build/stores/settings.js.map +1 -1
- package/package.json +12 -8
- package/src/engine.ts +3 -0
- package/src/facades/ai.ts +43 -0
- package/src/facades/glasses.ts +60 -31
- package/src/facades/glassesSettings.ts +1 -0
- package/src/facades/pairing.ts +18 -14
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +16 -2
- package/src/services/DeviceEventRouter.ts +8 -1
- package/src/services/GestureDotsService.ts +52 -15
- package/src/services/GlassesTouchInput.ts +11 -9
- package/src/services/LlmRouting.ts +49 -0
- package/src/services/LocalLlmModelManager.ts +327 -0
- package/src/services/LocalLlmPrompt.ts +246 -0
- package/src/services/LocalLlmProvider.ts +290 -0
- package/src/services/LocalMiniappRuntime.ts +137 -21
- package/src/services/STTModelManager.ts +140 -8
- package/src/services/controllers.ts +77 -0
- package/src/services/effectiveCapabilities.ts +16 -32
- package/src/services/neuralBand/NeuralBandInput.ts +50 -26
- package/src/services/neuralBand/NeuralBandSettingsSync.ts +28 -26
- package/src/services/settingsBridge.ts +60 -22
- package/src/stores/bluetoothSettingKeys.ts +3 -1
- package/src/stores/core.ts +0 -1
- package/src/stores/glasses.ts +2 -6
- package/src/stores/settings.ts +29 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effectiveCapabilities.js","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAC7D,OAAO,
|
|
1
|
+
{"version":3,"file":"effectiveCapabilities.js","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Effective device capabilities — the static model profile plus what the\n * connected firmware reports at runtime. The runtime override is the scene\n * canvas: Glassly CFW on the G2 rasterizes scenes phone-side straight into\n * the 640×480 panel, so the drawable canvas grows past stock's 576×288; on\n * the RayNeo iO it opens a 540×180 page that stock firmware has no\n * equivalent of. Any model whose profile declares `hasCustomFirmware` reads it.\n * Native publishes the canvas into the glasses status (0 = none). Because\n * that path is phone-side rasterization, the stock firmware's container\n * pools (6 text / 4 image) don't bound a CFW frame either — element caps are\n * lifted alongside the canvas, bounded only to keep frames sane. Firmware\n * that draws vector shapes keeps 128 scene slots, so shape-heavy frames\n * (a game board, a chart) get a larger shared text/shape budget.\n *\n * Every connected controller adds its model's input surfaces to\n * `button.buttons` (the Meta Neural Band a `swipe2d` entry), so a miniapp can\n * tell four-way swipes from the temples' and the R1 ring's up/down.\n */\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {ALL_DISPLAY_SHAPES, DeviceTypes, getModelCapabilities, type Capabilities} from \"../types\"\nimport {controllerInputSurfaces, readyControllers, type InputSurface} from \"./controllers\"\nimport {resolveNativeNavigation} from \"./nativeNavigationGate\"\n\nconst CFW_MAX_TEXT_ELEMENTS = 24\nconst CFW_MAX_IMAGE_ELEMENTS = 16\nconst CFW_VECTOR_MAX_ELEMENTS = 120\n\nexport interface SceneCanvasSize {\n width: number\n height: number\n}\n\nexport function runtimeSceneCanvas(): SceneCanvasSize | null {\n const {sceneCanvasWidth, sceneCanvasHeight} = useGlassesStore.getState()\n if (!sceneCanvasWidth || !sceneCanvasHeight || sceneCanvasWidth <= 0 || sceneCanvasHeight <= 0) return null\n return {width: sceneCanvasWidth, height: sceneCanvasHeight}\n}\n\nfunction forceNativeNavigationOnCfw(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.cfw_native_navigation.key) === true\n}\n\n/** The connected firmware gates microphone streaming on volume (G2 Glassly CFW revision 40+). */\nexport function runtimeVolumeGate(): boolean {\n return useGlassesStore.getState().volumeGate === true\n}\n\nexport function getEffectiveCapabilities(model: DeviceTypes): Capabilities {\n return withControllerInput(glassesCapabilities(model))\n}\n\nfunction glassesCapabilities(model: DeviceTypes): Capabilities {\n const base = getModelCapabilities(model)\n // Only models with a Glassly CFW publish a canvas or a volume gate; never\n // let a stale value reshape another model.\n const canvas = base.hasCustomFirmware ? runtimeSceneCanvas() : null\n const volumeGate = base.hasCustomFirmware && base.hasMicrophone && runtimeVolumeGate()\n if (!canvas) return volumeGate ? withVolumeGate(base) : base\n if (!base.display) return volumeGate ? withVolumeGate(base) : base\n // Glassly CFW supports the complete vector contract, including SVG and rotation.\n // Upstream g2flash publishes a raster canvas with sceneVector=false.\n const vector = useGlassesStore.getState().sceneVector === true\n return {\n ...(volumeGate ? withVolumeGate(base) : base),\n hasNativeNavigation: resolveNativeNavigation(base.hasNativeNavigation, true, forceNativeNavigationOnCfw()),\n display: {\n ...base.display,\n width: canvas.width,\n height: canvas.height,\n maxTextElements: Math.max(\n base.display.maxTextElements ?? 0,\n vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_TEXT_ELEMENTS,\n ),\n // Vector firmware draws an image as one cached slot, the same as a shape.\n maxImageElements: Math.max(\n base.display.maxImageElements ?? 0,\n vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_IMAGE_ELEMENTS,\n ),\n ...(vector\n ? {shapes: [...ALL_DISPLAY_SHAPES, \"svg\" as const], animation: true, rotation: true, depth: true}\n : {}),\n },\n }\n}\n\nfunction withControllerInput(caps: Capabilities): Capabilities {\n const buttons = caps.button?.buttons ?? []\n const types = new Set(buttons.map((surface) => surface.type))\n const added: InputSurface[] = []\n for (const status of readyControllers(useGlassesStore.getState().controllers)) {\n for (const surface of controllerInputSurfaces(status.model)) {\n if (types.has(surface.type)) continue\n types.add(surface.type)\n added.push(surface)\n }\n }\n if (added.length === 0) return caps\n return {\n ...caps,\n hasButton: true,\n button: {\n ...caps.button,\n count: (caps.button?.count ?? buttons.length) + added.length,\n buttons: [...buttons, ...added],\n },\n }\n}\n\nfunction withVolumeGate(base: Capabilities): Capabilities {\n return {...base, microphone: {...(base.microphone ?? {}), volumeGate: true}}\n}\n"]}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* The Meta Neural Band's own event streams, past the gestures that ride `touch_event`:
|
|
3
3
|
* claim progress (for the pairing screens), the handwriting model (typed into the
|
|
4
4
|
* miniapp keyboard stream as synthesized key presses) and the air mouse (forwarded
|
|
5
|
-
* to miniapps on the pointer stream).
|
|
5
|
+
* to miniapps on the pointer stream). State is per band (`controllerId`); the
|
|
6
|
+
* latest claim is also kept on its own, since the pairing screen runs before
|
|
7
|
+
* the band's id reaches JS.
|
|
6
8
|
*/
|
|
7
9
|
import type { NeuralBandClaimEvent, NeuralBandHandwritingEvent, NeuralBandPointerEvent } from "@glassly/bluetooth-sdk";
|
|
8
10
|
import type { KeyboardEventPayload } from "../GlassesKeyboardInput";
|
|
@@ -11,6 +13,8 @@ export interface NeuralBandClaimStatus {
|
|
|
11
13
|
message?: string;
|
|
12
14
|
sessionExpired: boolean;
|
|
13
15
|
bandName?: string;
|
|
16
|
+
/** The band being claimed; absent until a claim event arrives. */
|
|
17
|
+
controllerId?: string;
|
|
14
18
|
}
|
|
15
19
|
export interface NeuralBandHandwritingStatus {
|
|
16
20
|
phase: "idle" | "preparing" | "ready" | "restoring" | "finished";
|
|
@@ -19,6 +23,10 @@ export interface NeuralBandHandwritingStatus {
|
|
|
19
23
|
/** The last text the band produced since handwriting was switched on. */
|
|
20
24
|
text: string;
|
|
21
25
|
}
|
|
26
|
+
export interface NeuralBandHandwritingUpdate {
|
|
27
|
+
id: string;
|
|
28
|
+
status: NeuralBandHandwritingStatus;
|
|
29
|
+
}
|
|
22
30
|
export interface NeuralBandInputOptions {
|
|
23
31
|
forward?: (stream: string, payload: unknown) => void;
|
|
24
32
|
now?: () => number;
|
|
@@ -26,23 +34,26 @@ export interface NeuralBandInputOptions {
|
|
|
26
34
|
/** One handwriting change as the key presses that would have typed it. */
|
|
27
35
|
export declare function keyEventsForTextChange(previous: string, next: string, timestamp: number): KeyboardEventPayload[];
|
|
28
36
|
export declare class NeuralBandInput {
|
|
29
|
-
private
|
|
37
|
+
private latestClaim;
|
|
38
|
+
private claims;
|
|
30
39
|
private handwriting;
|
|
31
40
|
private claimListeners;
|
|
32
41
|
private handwritingListeners;
|
|
33
42
|
private readonly forward;
|
|
34
43
|
private readonly now;
|
|
35
44
|
constructor(options?: NeuralBandInputOptions);
|
|
36
|
-
|
|
45
|
+
/** One band's claim, or the latest claim of any band without an id. */
|
|
46
|
+
claimStatus(controllerId?: string): NeuralBandClaimStatus;
|
|
37
47
|
onClaimStatus(cb: (status: NeuralBandClaimStatus) => void): () => void;
|
|
38
|
-
handwritingStatus(): NeuralBandHandwritingStatus;
|
|
39
|
-
onHandwritingStatus(cb: (
|
|
48
|
+
handwritingStatus(controllerId: string): NeuralBandHandwritingStatus;
|
|
49
|
+
onHandwritingStatus(cb: (update: NeuralBandHandwritingUpdate) => void): () => void;
|
|
40
50
|
/** A new pairing attempt starts from a clean slate. */
|
|
41
51
|
resetClaim(): void;
|
|
52
|
+
/** Drop an unpaired band's state. */
|
|
53
|
+
forget(controllerId: string): void;
|
|
42
54
|
handleClaim(event: NeuralBandClaimEvent): void;
|
|
43
55
|
handleHandwriting(event: NeuralBandHandwritingEvent): void;
|
|
44
56
|
handlePointer(event: NeuralBandPointerEvent): void;
|
|
45
|
-
private setClaim;
|
|
46
57
|
private setHandwriting;
|
|
47
58
|
}
|
|
48
59
|
declare const neuralBandInput: NeuralBandInput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuralBandInput.d.ts","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandInput.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"NeuralBandInput.d.ts","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandInput.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAC,oBAAoB,EAAE,0BAA0B,EAAE,sBAAsB,EAAC,MAAM,wBAAwB,CAAA;AAGpH,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAA;AAGjE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAA;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,CAAA;IAChE,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,2BAA2B,CAAA;CACpC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACpD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAID,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAehH;AAWD,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,WAAW,CAAiD;IACpE,OAAO,CAAC,cAAc,CAAqD;IAC3E,OAAO,CAAC,oBAAoB,CAA2D;IACvF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;IACpE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEtB,OAAO,GAAE,sBAA2B;IAKhD,uEAAuE;IACvE,WAAW,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,qBAAqB;IAKzD,aAAa,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKtE,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,2BAA2B;IAIpE,mBAAmB,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,2BAA2B,KAAK,IAAI,GAAG,MAAM,IAAI;IAKlF,uDAAuD;IACvD,UAAU,IAAI,IAAI;IAKlB,qCAAqC;IACrC,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAKlC,WAAW,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAa9C,iBAAiB,CAAC,KAAK,EAAE,0BAA0B,GAAG,IAAI;IAgB1D,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI;IAmBlD,OAAO,CAAC,cAAc;CAIvB;AAED,QAAA,MAAM,eAAe,iBAAwB,CAAA;AAC7C,eAAe,eAAe,CAAA"}
|
|
@@ -30,9 +30,12 @@ function codeFor(char) {
|
|
|
30
30
|
return `Digit${char}`;
|
|
31
31
|
return "Unidentified";
|
|
32
32
|
}
|
|
33
|
+
const IDLE_CLAIM = { stage: "idle", sessionExpired: false };
|
|
34
|
+
const IDLE_HANDWRITING = { phase: "idle", message: "", text: "" };
|
|
33
35
|
export class NeuralBandInput {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
latestClaim = IDLE_CLAIM;
|
|
37
|
+
claims = new Map();
|
|
38
|
+
handwriting = new Map();
|
|
36
39
|
claimListeners = new Set();
|
|
37
40
|
handwritingListeners = new Set();
|
|
38
41
|
forward;
|
|
@@ -41,15 +44,18 @@ export class NeuralBandInput {
|
|
|
41
44
|
this.forward = options.forward ?? ((stream, payload) => localMiniappRuntime.forwardEvent(stream, payload));
|
|
42
45
|
this.now = options.now ?? (() => Date.now());
|
|
43
46
|
}
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
/** One band's claim, or the latest claim of any band without an id. */
|
|
48
|
+
claimStatus(controllerId) {
|
|
49
|
+
if (controllerId === undefined)
|
|
50
|
+
return this.latestClaim;
|
|
51
|
+
return this.claims.get(controllerId) ?? IDLE_CLAIM;
|
|
46
52
|
}
|
|
47
53
|
onClaimStatus(cb) {
|
|
48
54
|
this.claimListeners.add(cb);
|
|
49
55
|
return () => this.claimListeners.delete(cb);
|
|
50
56
|
}
|
|
51
|
-
handwritingStatus() {
|
|
52
|
-
return this.handwriting;
|
|
57
|
+
handwritingStatus(controllerId) {
|
|
58
|
+
return this.handwriting.get(controllerId) ?? IDLE_HANDWRITING;
|
|
53
59
|
}
|
|
54
60
|
onHandwritingStatus(cb) {
|
|
55
61
|
this.handwritingListeners.add(cb);
|
|
@@ -57,29 +63,43 @@ export class NeuralBandInput {
|
|
|
57
63
|
}
|
|
58
64
|
/** A new pairing attempt starts from a clean slate. */
|
|
59
65
|
resetClaim() {
|
|
60
|
-
this.
|
|
66
|
+
this.latestClaim = IDLE_CLAIM;
|
|
67
|
+
for (const cb of this.claimListeners)
|
|
68
|
+
cb(IDLE_CLAIM);
|
|
69
|
+
}
|
|
70
|
+
/** Drop an unpaired band's state. */
|
|
71
|
+
forget(controllerId) {
|
|
72
|
+
this.claims.delete(controllerId);
|
|
73
|
+
this.handwriting.delete(controllerId);
|
|
61
74
|
}
|
|
62
75
|
handleClaim(event) {
|
|
63
|
-
|
|
76
|
+
const status = {
|
|
64
77
|
stage: event.stage,
|
|
65
78
|
message: event.message,
|
|
66
79
|
sessionExpired: event.sessionExpired === true,
|
|
67
80
|
bandName: event.bandName,
|
|
68
|
-
|
|
81
|
+
controllerId: event.controllerId,
|
|
82
|
+
};
|
|
83
|
+
if (event.controllerId)
|
|
84
|
+
this.claims.set(event.controllerId, status);
|
|
85
|
+
this.latestClaim = status;
|
|
86
|
+
for (const cb of this.claimListeners)
|
|
87
|
+
cb(status);
|
|
69
88
|
}
|
|
70
89
|
handleHandwriting(event) {
|
|
90
|
+
const id = event.controllerId;
|
|
91
|
+
const current = this.handwritingStatus(id);
|
|
71
92
|
if (event.kind === "state") {
|
|
72
|
-
const text = event.phase === "preparing" ? "" :
|
|
73
|
-
this.setHandwriting({ phase: event.phase, message: event.message, problem: event.problem, text });
|
|
93
|
+
const text = event.phase === "preparing" ? "" : current.text;
|
|
94
|
+
this.setHandwriting(id, { phase: event.phase, message: event.message, problem: event.problem, text });
|
|
74
95
|
return;
|
|
75
96
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for (const key of keyEventsForTextChange(previous, event.text, this.now())) {
|
|
97
|
+
if (event.text !== current.text) {
|
|
98
|
+
for (const key of keyEventsForTextChange(current.text, event.text, this.now())) {
|
|
79
99
|
this.forward(MiniappStreamType.KEYBOARD_EVENT, key);
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
|
-
this.setHandwriting({ ...
|
|
102
|
+
this.setHandwriting(id, { ...current, text: event.text });
|
|
83
103
|
}
|
|
84
104
|
handlePointer(event) {
|
|
85
105
|
if (event.kind === "move") {
|
|
@@ -88,6 +108,7 @@ export class NeuralBandInput {
|
|
|
88
108
|
dx: event.dx,
|
|
89
109
|
dy: event.dy,
|
|
90
110
|
timestamp: event.timestamp,
|
|
111
|
+
controllerId: event.controllerId,
|
|
91
112
|
});
|
|
92
113
|
}
|
|
93
114
|
else {
|
|
@@ -95,18 +116,14 @@ export class NeuralBandInput {
|
|
|
95
116
|
kind: "buttons",
|
|
96
117
|
buttons: event.buttons,
|
|
97
118
|
timestamp: event.timestamp,
|
|
119
|
+
controllerId: event.controllerId,
|
|
98
120
|
});
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
for (const cb of this.claimListeners)
|
|
104
|
-
cb(status);
|
|
105
|
-
}
|
|
106
|
-
setHandwriting(status) {
|
|
107
|
-
this.handwriting = status;
|
|
123
|
+
setHandwriting(id, status) {
|
|
124
|
+
this.handwriting.set(id, status);
|
|
108
125
|
for (const cb of this.handwritingListeners)
|
|
109
|
-
cb(status);
|
|
126
|
+
cb({ id, status });
|
|
110
127
|
}
|
|
111
128
|
}
|
|
112
129
|
const neuralBandInput = new NeuralBandInput();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuralBandInput.js","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandInput.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Meta Neural Band's own event streams, past the gestures that ride `touch_event`:\n * claim progress (for the pairing screens), the handwriting model (typed into the\n * miniapp keyboard stream as synthesized key presses) and the air mouse (forwarded\n * to miniapps on the pointer stream).\n */\nimport type {NeuralBandClaimEvent, NeuralBandHandwritingEvent, NeuralBandPointerEvent} from \"@glassly/bluetooth-sdk\"\nimport {MiniappStreamType} from \"@glassly/miniapp\"\n\nimport type {KeyboardEventPayload} from \"../GlassesKeyboardInput\"\nimport localMiniappRuntime from \"../LocalMiniappRuntime\"\n\nexport interface NeuralBandClaimStatus {\n stage: \"idle\" | \"progress\" | \"completed\" | \"failed\"\n message?: string\n sessionExpired: boolean\n bandName?: string\n}\n\nexport interface NeuralBandHandwritingStatus {\n phase: \"idle\" | \"preparing\" | \"ready\" | \"restoring\" | \"finished\"\n message: string\n problem?: string\n /** The last text the band produced since handwriting was switched on. */\n text: string\n}\n\nexport interface NeuralBandInputOptions {\n forward?: (stream: string, payload: unknown) => void\n now?: () => number\n}\n\nconst MODIFIERS = {shift: false, ctrl: false, alt: false, meta: false}\n\n/** One handwriting change as the key presses that would have typed it. */\nexport function keyEventsForTextChange(previous: string, next: string, timestamp: number): KeyboardEventPayload[] {\n let common = 0\n while (common < previous.length && common < next.length && previous[common] === next[common]) common++\n const events: KeyboardEventPayload[] = []\n const press = (key: string, code: string, text?: string) => {\n events.push({phase: \"down\", key, code, modifiers: MODIFIERS, text, repeat: false, timestamp})\n events.push({phase: \"up\", key, code, modifiers: MODIFIERS, repeat: false, timestamp})\n }\n for (let i = common; i < previous.length; i++) press(\"Backspace\", \"Backspace\")\n for (const char of Array.from(next.slice(common))) {\n if (char === \"\\n\") press(\"Enter\", \"Enter\")\n else if (char === \" \") press(\" \", \"Space\", \" \")\n else press(char, codeFor(char), char)\n }\n return events\n}\n\nfunction codeFor(char: string): string {\n if (/^[a-z]$/i.test(char)) return `Key${char.toUpperCase()}`\n if (/^[0-9]$/.test(char)) return `Digit${char}`\n return \"Unidentified\"\n}\n\nexport class NeuralBandInput {\n private claim: NeuralBandClaimStatus = {stage: \"idle\", sessionExpired: false}\n private handwriting: NeuralBandHandwritingStatus = {phase: \"idle\", message: \"\", text: \"\"}\n private claimListeners = new Set<(status: NeuralBandClaimStatus) => void>()\n private handwritingListeners = new Set<(status: NeuralBandHandwritingStatus) => void>()\n private readonly forward: (stream: string, payload: unknown) => void\n private readonly now: () => number\n\n constructor(options: NeuralBandInputOptions = {}) {\n this.forward = options.forward ?? ((stream, payload) => localMiniappRuntime.forwardEvent(stream, payload))\n this.now = options.now ?? (() => Date.now())\n }\n\n claimStatus(): NeuralBandClaimStatus {\n return this.claim\n }\n\n onClaimStatus(cb: (status: NeuralBandClaimStatus) => void): () => void {\n this.claimListeners.add(cb)\n return () => this.claimListeners.delete(cb)\n }\n\n handwritingStatus(): NeuralBandHandwritingStatus {\n return this.handwriting\n }\n\n onHandwritingStatus(cb: (status: NeuralBandHandwritingStatus) => void): () => void {\n this.handwritingListeners.add(cb)\n return () => this.handwritingListeners.delete(cb)\n }\n\n /** A new pairing attempt starts from a clean slate. */\n resetClaim(): void {\n this.setClaim({stage: \"idle\", sessionExpired: false})\n }\n\n handleClaim(event: NeuralBandClaimEvent): void {\n this.setClaim({\n stage: event.stage,\n message: event.message,\n sessionExpired: event.sessionExpired === true,\n bandName: event.bandName,\n })\n }\n\n handleHandwriting(event: NeuralBandHandwritingEvent): void {\n if (event.kind === \"state\") {\n const text = event.phase === \"preparing\" ? \"\" : this.handwriting.text\n this.setHandwriting({phase: event.phase, message: event.message, problem: event.problem, text})\n return\n }\n const previous = this.handwriting.text\n if (event.text !== previous) {\n for (const key of keyEventsForTextChange(previous, event.text, this.now())) {\n this.forward(MiniappStreamType.KEYBOARD_EVENT, key)\n }\n }\n this.setHandwriting({...this.handwriting, text: event.text})\n }\n\n handlePointer(event: NeuralBandPointerEvent): void {\n if (event.kind === \"move\") {\n this.forward(MiniappStreamType.POINTER_EVENT, {\n kind: \"move\",\n dx: event.dx,\n dy: event.dy,\n timestamp: event.timestamp,\n })\n } else {\n this.forward(MiniappStreamType.POINTER_EVENT, {\n kind: \"buttons\",\n buttons: event.buttons,\n timestamp: event.timestamp,\n })\n }\n }\n\n private setClaim(status: NeuralBandClaimStatus): void {\n this.claim = status\n for (const cb of this.claimListeners) cb(status)\n }\n\n private setHandwriting(status: NeuralBandHandwritingStatus): void {\n this.handwriting = status\n for (const cb of this.handwritingListeners) cb(status)\n }\n}\n\nconst neuralBandInput = new NeuralBandInput()\nexport default neuralBandInput\n"]}
|
|
1
|
+
{"version":3,"file":"NeuralBandInput.js","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandInput.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The Meta Neural Band's own event streams, past the gestures that ride `touch_event`:\n * claim progress (for the pairing screens), the handwriting model (typed into the\n * miniapp keyboard stream as synthesized key presses) and the air mouse (forwarded\n * to miniapps on the pointer stream). State is per band (`controllerId`); the\n * latest claim is also kept on its own, since the pairing screen runs before\n * the band's id reaches JS.\n */\nimport type {NeuralBandClaimEvent, NeuralBandHandwritingEvent, NeuralBandPointerEvent} from \"@glassly/bluetooth-sdk\"\nimport {MiniappStreamType} from \"@glassly/miniapp\"\n\nimport type {KeyboardEventPayload} from \"../GlassesKeyboardInput\"\nimport localMiniappRuntime from \"../LocalMiniappRuntime\"\n\nexport interface NeuralBandClaimStatus {\n stage: \"idle\" | \"progress\" | \"completed\" | \"failed\"\n message?: string\n sessionExpired: boolean\n bandName?: string\n /** The band being claimed; absent until a claim event arrives. */\n controllerId?: string\n}\n\nexport interface NeuralBandHandwritingStatus {\n phase: \"idle\" | \"preparing\" | \"ready\" | \"restoring\" | \"finished\"\n message: string\n problem?: string\n /** The last text the band produced since handwriting was switched on. */\n text: string\n}\n\nexport interface NeuralBandHandwritingUpdate {\n id: string\n status: NeuralBandHandwritingStatus\n}\n\nexport interface NeuralBandInputOptions {\n forward?: (stream: string, payload: unknown) => void\n now?: () => number\n}\n\nconst MODIFIERS = {shift: false, ctrl: false, alt: false, meta: false}\n\n/** One handwriting change as the key presses that would have typed it. */\nexport function keyEventsForTextChange(previous: string, next: string, timestamp: number): KeyboardEventPayload[] {\n let common = 0\n while (common < previous.length && common < next.length && previous[common] === next[common]) common++\n const events: KeyboardEventPayload[] = []\n const press = (key: string, code: string, text?: string) => {\n events.push({phase: \"down\", key, code, modifiers: MODIFIERS, text, repeat: false, timestamp})\n events.push({phase: \"up\", key, code, modifiers: MODIFIERS, repeat: false, timestamp})\n }\n for (let i = common; i < previous.length; i++) press(\"Backspace\", \"Backspace\")\n for (const char of Array.from(next.slice(common))) {\n if (char === \"\\n\") press(\"Enter\", \"Enter\")\n else if (char === \" \") press(\" \", \"Space\", \" \")\n else press(char, codeFor(char), char)\n }\n return events\n}\n\nfunction codeFor(char: string): string {\n if (/^[a-z]$/i.test(char)) return `Key${char.toUpperCase()}`\n if (/^[0-9]$/.test(char)) return `Digit${char}`\n return \"Unidentified\"\n}\n\nconst IDLE_CLAIM: NeuralBandClaimStatus = {stage: \"idle\", sessionExpired: false}\nconst IDLE_HANDWRITING: NeuralBandHandwritingStatus = {phase: \"idle\", message: \"\", text: \"\"}\n\nexport class NeuralBandInput {\n private latestClaim: NeuralBandClaimStatus = IDLE_CLAIM\n private claims = new Map<string, NeuralBandClaimStatus>()\n private handwriting = new Map<string, NeuralBandHandwritingStatus>()\n private claimListeners = new Set<(status: NeuralBandClaimStatus) => void>()\n private handwritingListeners = new Set<(update: NeuralBandHandwritingUpdate) => void>()\n private readonly forward: (stream: string, payload: unknown) => void\n private readonly now: () => number\n\n constructor(options: NeuralBandInputOptions = {}) {\n this.forward = options.forward ?? ((stream, payload) => localMiniappRuntime.forwardEvent(stream, payload))\n this.now = options.now ?? (() => Date.now())\n }\n\n /** One band's claim, or the latest claim of any band without an id. */\n claimStatus(controllerId?: string): NeuralBandClaimStatus {\n if (controllerId === undefined) return this.latestClaim\n return this.claims.get(controllerId) ?? IDLE_CLAIM\n }\n\n onClaimStatus(cb: (status: NeuralBandClaimStatus) => void): () => void {\n this.claimListeners.add(cb)\n return () => this.claimListeners.delete(cb)\n }\n\n handwritingStatus(controllerId: string): NeuralBandHandwritingStatus {\n return this.handwriting.get(controllerId) ?? IDLE_HANDWRITING\n }\n\n onHandwritingStatus(cb: (update: NeuralBandHandwritingUpdate) => void): () => void {\n this.handwritingListeners.add(cb)\n return () => this.handwritingListeners.delete(cb)\n }\n\n /** A new pairing attempt starts from a clean slate. */\n resetClaim(): void {\n this.latestClaim = IDLE_CLAIM\n for (const cb of this.claimListeners) cb(IDLE_CLAIM)\n }\n\n /** Drop an unpaired band's state. */\n forget(controllerId: string): void {\n this.claims.delete(controllerId)\n this.handwriting.delete(controllerId)\n }\n\n handleClaim(event: NeuralBandClaimEvent): void {\n const status: NeuralBandClaimStatus = {\n stage: event.stage,\n message: event.message,\n sessionExpired: event.sessionExpired === true,\n bandName: event.bandName,\n controllerId: event.controllerId,\n }\n if (event.controllerId) this.claims.set(event.controllerId, status)\n this.latestClaim = status\n for (const cb of this.claimListeners) cb(status)\n }\n\n handleHandwriting(event: NeuralBandHandwritingEvent): void {\n const id = event.controllerId\n const current = this.handwritingStatus(id)\n if (event.kind === \"state\") {\n const text = event.phase === \"preparing\" ? \"\" : current.text\n this.setHandwriting(id, {phase: event.phase, message: event.message, problem: event.problem, text})\n return\n }\n if (event.text !== current.text) {\n for (const key of keyEventsForTextChange(current.text, event.text, this.now())) {\n this.forward(MiniappStreamType.KEYBOARD_EVENT, key)\n }\n }\n this.setHandwriting(id, {...current, text: event.text})\n }\n\n handlePointer(event: NeuralBandPointerEvent): void {\n if (event.kind === \"move\") {\n this.forward(MiniappStreamType.POINTER_EVENT, {\n kind: \"move\",\n dx: event.dx,\n dy: event.dy,\n timestamp: event.timestamp,\n controllerId: event.controllerId,\n })\n } else {\n this.forward(MiniappStreamType.POINTER_EVENT, {\n kind: \"buttons\",\n buttons: event.buttons,\n timestamp: event.timestamp,\n controllerId: event.controllerId,\n })\n }\n }\n\n private setHandwriting(id: string, status: NeuralBandHandwritingStatus): void {\n this.handwriting.set(id, status)\n for (const cb of this.handwritingListeners) cb({id, status})\n }\n}\n\nconst neuralBandInput = new NeuralBandInput()\nexport default neuralBandInput\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuralBandSettingsSync.d.ts","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandSettingsSync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NeuralBandSettingsSync.d.ts","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandSettingsSync.ts"],"names":[],"mappings":"AAkCA,wBAAgB,2BAA2B,IAAI,IAAI,CAyBlD;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD"}
|
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pushes the Neural Band
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Pushes the Neural Band user settings (air mouse, wearing hand) to every
|
|
3
|
+
* connected band: to each band as it connects, and to all of them whenever
|
|
4
|
+
* either setting changes. The native side keeps nothing across connections.
|
|
5
5
|
*/
|
|
6
6
|
import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
|
|
7
7
|
import { useGlassesStore } from "../../stores/glasses";
|
|
8
8
|
import { SETTINGS, useSettingsStore } from "../../stores/settings";
|
|
9
9
|
import { ControllerTypes } from "../../types/enums";
|
|
10
|
+
import { readyControllers } from "../controllers";
|
|
10
11
|
let unsubscribers = [];
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
glasses.controllerFullyBooted === true &&
|
|
16
|
-
settings.getSetting(SETTINGS.default_controller.key) === ControllerTypes.NEURAL_BAND);
|
|
12
|
+
function connectedBandIds() {
|
|
13
|
+
return readyControllers(useGlassesStore.getState().controllers)
|
|
14
|
+
.filter((status) => status.model === ControllerTypes.NEURAL_BAND)
|
|
15
|
+
.map((status) => status.id);
|
|
17
16
|
}
|
|
18
|
-
function pushAirMouse() {
|
|
19
|
-
if (!bandConnected())
|
|
20
|
-
return;
|
|
17
|
+
function pushAirMouse(id) {
|
|
21
18
|
const enabled = useSettingsStore.getState().getSetting(SETTINGS.neural_band_air_mouse.key) === true;
|
|
22
|
-
void BluetoothSdk.neuralBandSetPointer(enabled).catch((err) => console.warn("NeuralBandSettingsSync: air mouse push failed:", err));
|
|
19
|
+
void BluetoothSdk.neuralBandSetPointer(id, enabled).catch((err) => console.warn("NeuralBandSettingsSync: air mouse push failed:", err));
|
|
23
20
|
}
|
|
24
|
-
function pushHand() {
|
|
25
|
-
if (!bandConnected())
|
|
26
|
-
return;
|
|
21
|
+
function pushHand(id) {
|
|
27
22
|
const hand = useSettingsStore.getState().getSetting(SETTINGS.neural_band_hand.key) === "left" ? "left" : "right";
|
|
28
|
-
void BluetoothSdk.neuralBandSetHand(hand).catch((err) => console.warn("NeuralBandSettingsSync: hand push failed:", err));
|
|
23
|
+
void BluetoothSdk.neuralBandSetHand(id, hand).catch((err) => console.warn("NeuralBandSettingsSync: hand push failed:", err));
|
|
29
24
|
}
|
|
30
25
|
export function startNeuralBandSettingsSync() {
|
|
31
26
|
if (unsubscribers.length > 0)
|
|
32
27
|
return;
|
|
33
|
-
let
|
|
28
|
+
let connected = new Set(connectedBandIds());
|
|
34
29
|
unsubscribers = [
|
|
35
|
-
useGlassesStore.subscribe((
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
useGlassesStore.subscribe(() => connectedBandIds().sort().join(","), () => {
|
|
31
|
+
const next = new Set(connectedBandIds());
|
|
32
|
+
for (const id of next) {
|
|
33
|
+
if (connected.has(id))
|
|
34
|
+
continue;
|
|
35
|
+
pushAirMouse(id);
|
|
36
|
+
pushHand(id);
|
|
40
37
|
}
|
|
41
|
-
|
|
38
|
+
connected = next;
|
|
42
39
|
}),
|
|
43
|
-
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.neural_band_air_mouse.key), pushAirMouse),
|
|
44
|
-
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.neural_band_hand.key), pushHand),
|
|
40
|
+
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.neural_band_air_mouse.key), () => connectedBandIds().forEach(pushAirMouse)),
|
|
41
|
+
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.neural_band_hand.key), () => connectedBandIds().forEach(pushHand)),
|
|
45
42
|
];
|
|
46
43
|
}
|
|
47
44
|
export function stopNeuralBandSettingsSync() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeuralBandSettingsSync.js","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandSettingsSync.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAE1D,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"NeuralBandSettingsSync.js","sourceRoot":"","sources":["../../../src/services/neuralBand/NeuralBandSettingsSync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pushes the Neural Band user settings (air mouse, wearing hand) to every\n * connected band: to each band as it connects, and to all of them whenever\n * either setting changes. The native side keeps nothing across connections.\n */\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport {ControllerTypes} from \"../../types/enums\"\nimport {readyControllers} from \"../controllers\"\n\nlet unsubscribers: (() => void)[] = []\n\nfunction connectedBandIds(): string[] {\n return readyControllers(useGlassesStore.getState().controllers)\n .filter((status) => status.model === ControllerTypes.NEURAL_BAND)\n .map((status) => status.id)\n}\n\nfunction pushAirMouse(id: string): void {\n const enabled = useSettingsStore.getState().getSetting(SETTINGS.neural_band_air_mouse.key) === true\n void BluetoothSdk.neuralBandSetPointer(id, enabled).catch((err) =>\n console.warn(\"NeuralBandSettingsSync: air mouse push failed:\", err),\n )\n}\n\nfunction pushHand(id: string): void {\n const hand = useSettingsStore.getState().getSetting(SETTINGS.neural_band_hand.key) === \"left\" ? \"left\" : \"right\"\n void BluetoothSdk.neuralBandSetHand(id, hand).catch((err) =>\n console.warn(\"NeuralBandSettingsSync: hand push failed:\", err),\n )\n}\n\nexport function startNeuralBandSettingsSync(): void {\n if (unsubscribers.length > 0) return\n let connected = new Set(connectedBandIds())\n unsubscribers = [\n useGlassesStore.subscribe(\n () => connectedBandIds().sort().join(\",\"),\n () => {\n const next = new Set(connectedBandIds())\n for (const id of next) {\n if (connected.has(id)) continue\n pushAirMouse(id)\n pushHand(id)\n }\n connected = next\n },\n ),\n useSettingsStore.subscribe(\n (s) => s.getSetting(SETTINGS.neural_band_air_mouse.key),\n () => connectedBandIds().forEach(pushAirMouse),\n ),\n useSettingsStore.subscribe(\n (s) => s.getSetting(SETTINGS.neural_band_hand.key),\n () => connectedBandIds().forEach(pushHand),\n ),\n ]\n}\n\nexport function stopNeuralBandSettingsSync(): void {\n for (const unsubscribe of unsubscribers) unsubscribe()\n unsubscribers = []\n}\n"]}
|
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
* of `menu_apps` and the position behave as if a phone screen wrote them. A dashboard's own look is not here; it keeps
|
|
12
12
|
* that in its storage.
|
|
13
13
|
*/
|
|
14
|
+
import type { ControllerStatus, PairedController } from "@glassly/bluetooth-sdk";
|
|
14
15
|
export type SettingsBridgeAccess = "read" | "write";
|
|
15
16
|
/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */
|
|
16
17
|
export declare const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>>;
|
|
17
18
|
/**
|
|
18
19
|
* Read-only keys the engine computes: whether the wearer can see the phone,
|
|
19
|
-
* whether the foreground miniapp is drawing on the glasses, the
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* whether the foreground miniapp is drawing on the glasses, and the paired
|
|
21
|
+
* controllers with their battery and input. They change on their own clocks;
|
|
22
|
+
* the runtime pushes them like stored keys.
|
|
22
23
|
*/
|
|
23
|
-
export declare const DERIVED_SETTINGS_KEYS: readonly ["phone_active", "foreground_app_on_screen", "
|
|
24
|
+
export declare const DERIVED_SETTINGS_KEYS: readonly ["phone_active", "foreground_app_on_screen", "controllers"];
|
|
24
25
|
export type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number];
|
|
25
26
|
export declare function isDerivedSettingsKey(key: string): key is DerivedSettingsKey;
|
|
26
27
|
/** What a permitted app may do with `key`, or null when the bridge does not serve it. */
|
|
@@ -44,24 +45,31 @@ export type SettingsSetValidation = {
|
|
|
44
45
|
};
|
|
45
46
|
/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */
|
|
46
47
|
export declare function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation;
|
|
47
|
-
/**
|
|
48
|
-
export interface
|
|
48
|
+
/** One paired controller as the `controllers` derived key serves it. */
|
|
49
|
+
export interface ControllerSettingEntry {
|
|
50
|
+
id: string;
|
|
51
|
+
model: string;
|
|
52
|
+
name: string;
|
|
53
|
+
/** Delivering input. */
|
|
49
54
|
connected: boolean;
|
|
50
|
-
/** -1 when unknown. */
|
|
51
|
-
|
|
55
|
+
/** level -1 when unknown. */
|
|
56
|
+
battery: {
|
|
57
|
+
level: number;
|
|
58
|
+
charging: boolean;
|
|
59
|
+
};
|
|
60
|
+
/** Swipes sideways as well as up and down (the Meta Neural Band). */
|
|
61
|
+
input2d: boolean;
|
|
52
62
|
}
|
|
53
|
-
|
|
63
|
+
/** What the glasses report about the ring linked to them (G2 with the R1 routed through it). */
|
|
64
|
+
export interface RelayedRing {
|
|
54
65
|
ringConnected: boolean;
|
|
55
66
|
ringBatteryLevel: number;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}): RingBattery;
|
|
67
|
+
ringCharging: boolean;
|
|
68
|
+
}
|
|
59
69
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
70
|
+
* One entry per paired record, registry order. While the glasses report a
|
|
71
|
+
* linked ring, that report is that ring's link and battery: a ring routed
|
|
72
|
+
* through the G2 reports there, not to the phone.
|
|
63
73
|
*/
|
|
64
|
-
export declare function
|
|
65
|
-
controllerConnected: boolean;
|
|
66
|
-
}, controllerType: unknown): boolean;
|
|
74
|
+
export declare function controllersSettingFrom(records: PairedController[], statuses: Record<string, ControllerStatus> | undefined, relayed: RelayedRing): ControllerSettingEntry[];
|
|
67
75
|
//# sourceMappingURL=settingsBridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settingsBridge.d.ts","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"settingsBridge.d.ts","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAK9E,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEnD,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAkB/E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sEAAuE,CAAA;AACzG,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,kBAAkB,CAE3E;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAG7E;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAIrE;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAC,GACvC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAE5E,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,qBAAqB,CAY3F;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAA;IAC3C,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,OAAO,CAAA;CACtB;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,EACtD,OAAO,EAAE,WAAW,GACnB,sBAAsB,EAAE,CAqB1B"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* that in its storage.
|
|
13
13
|
*/
|
|
14
14
|
import { ControllerTypes } from "../types/enums";
|
|
15
|
+
import { hasInput2d, isControllerReady } from "./controllers";
|
|
15
16
|
/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */
|
|
16
17
|
export const SETTINGS_BRIDGE_KEYS = {
|
|
17
18
|
// The dashboard's master switch is a phone-side debug knob.
|
|
@@ -34,11 +35,11 @@ export const SETTINGS_BRIDGE_KEYS = {
|
|
|
34
35
|
};
|
|
35
36
|
/**
|
|
36
37
|
* Read-only keys the engine computes: whether the wearer can see the phone,
|
|
37
|
-
* whether the foreground miniapp is drawing on the glasses, the
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* whether the foreground miniapp is drawing on the glasses, and the paired
|
|
39
|
+
* controllers with their battery and input. They change on their own clocks;
|
|
40
|
+
* the runtime pushes them like stored keys.
|
|
40
41
|
*/
|
|
41
|
-
export const DERIVED_SETTINGS_KEYS = ["phone_active", "foreground_app_on_screen", "
|
|
42
|
+
export const DERIVED_SETTINGS_KEYS = ["phone_active", "foreground_app_on_screen", "controllers"];
|
|
42
43
|
export function isDerivedSettingsKey(key) {
|
|
43
44
|
return DERIVED_SETTINGS_KEYS.includes(key);
|
|
44
45
|
}
|
|
@@ -80,19 +81,39 @@ export function validateSettingsSet(payload) {
|
|
|
80
81
|
}
|
|
81
82
|
return { ok: true, key, value: payload.value };
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
/**
|
|
85
|
+
* The R1 the glasses' ring report is about: a ring routed through the G2 keeps
|
|
86
|
+
* its phone link for telemetry, so prefer a linked ring, else the first one.
|
|
87
|
+
*/
|
|
88
|
+
function relayedRingId(records, statuses) {
|
|
89
|
+
const rings = records.filter((record) => record.model === ControllerTypes.R1);
|
|
90
|
+
return (rings.find((ring) => statuses?.[ring.id]?.connected === true) ?? rings[0])?.id;
|
|
89
91
|
}
|
|
90
92
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
93
|
+
* One entry per paired record, registry order. While the glasses report a
|
|
94
|
+
* linked ring, that report is that ring's link and battery: a ring routed
|
|
95
|
+
* through the G2 reports there, not to the phone.
|
|
94
96
|
*/
|
|
95
|
-
export function
|
|
96
|
-
|
|
97
|
+
export function controllersSettingFrom(records, statuses, relayed) {
|
|
98
|
+
const relayedId = relayed.ringConnected ? relayedRingId(records, statuses) : undefined;
|
|
99
|
+
return records.map((record) => {
|
|
100
|
+
const status = statuses?.[record.id];
|
|
101
|
+
let connected = isControllerReady(status);
|
|
102
|
+
let battery = { level: status?.batteryLevel ?? -1, charging: status?.charging === true };
|
|
103
|
+
if (record.id === relayedId) {
|
|
104
|
+
connected = true;
|
|
105
|
+
if (relayed.ringBatteryLevel >= 0) {
|
|
106
|
+
battery = { level: relayed.ringBatteryLevel, charging: relayed.ringCharging };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
id: record.id,
|
|
111
|
+
model: record.model,
|
|
112
|
+
name: record.name,
|
|
113
|
+
connected,
|
|
114
|
+
battery,
|
|
115
|
+
input2d: connected && hasInput2d(record.model),
|
|
116
|
+
};
|
|
117
|
+
});
|
|
97
118
|
}
|
|
98
119
|
//# sourceMappingURL=settingsBridge.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settingsBridge.js","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings bridge's vocabulary (session.settings): which engine settings\n * a miniapp with the PLATFORM_SETTINGS permission may read or write, the\n * derived read-only keys the engine computes rather than stores, and the\n * request validation. Pure, so the runtime's handlers stay thin and this can\n * be tested without the native import surface the runtime pulls in.\n *\n * A dashboard miniapp is the usual consumer: it edits the notification,\n * glasses-menu and display-position keys the phone's settings screens used\n * to, through the store, so persistence, server sync and the Bluetooth sync\n * of `menu_apps` and the position behave as if a phone screen wrote them. A dashboard's own look is not here; it keeps\n * that in its storage.\n */\n\nimport {ControllerTypes} from \"../types/enums\"\n\nexport type SettingsBridgeAccess = \"read\" | \"write\"\n\n/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */\nexport const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {\n // The dashboard's master switch is a phone-side debug knob.\n custom_dashboard_enabled: \"read\",\n contextual_dashboard: \"write\",\n head_up_angle: \"write\",\n // Where the glasses draw; each model's steps are its display.positionRange.\n dashboard_depth: \"write\",\n dashboard_height: \"write\",\n twelve_hour_time: \"write\",\n menu_apps: \"write\",\n notifications_enabled: \"write\",\n notifications_popups: \"write\",\n notifications_popup_seconds: \"write\",\n notifications_hold_during_apps: \"write\",\n notifications_ignore_while_active: \"write\",\n notifications_blocklist: \"write\",\n // Apps the glasses reported posting notifications; the engine records it.\n notification_seen_apps: \"read\",\n}\n\n/**\n * Read-only keys the engine computes: whether the wearer can see the phone,\n * whether the foreground miniapp is drawing on the glasses, the ring's\n * battery, and whether a 2D controller is connected. They change on their\n * own clocks; the runtime pushes them like stored keys.\n */\nexport const DERIVED_SETTINGS_KEYS = [\"phone_active\", \"foreground_app_on_screen\", \"ring_battery\", \"input_2d\"] as const\nexport type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]\n\nexport function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {\n return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)\n}\n\n/** What a permitted app may do with `key`, or null when the bridge does not serve it. */\nexport function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {\n if (isDerivedSettingsKey(key)) return \"read\"\n return SETTINGS_BRIDGE_KEYS[key] ?? null\n}\n\n/** Every key the bridge serves, stored then derived. */\nexport function allSettingsBridgeKeys(): string[] {\n return [...Object.keys(SETTINGS_BRIDGE_KEYS), ...DERIVED_SETTINGS_KEYS]\n}\n\n/**\n * The keys a SETTINGS_GET asks for: everything when `requested` is absent,\n * else the known ones among the requested strings (unknown keys are left\n * out, never rejected).\n */\nexport function selectSettingsBridgeKeys(requested: unknown): string[] {\n if (requested === undefined || requested === null) return allSettingsBridgeKeys()\n if (!Array.isArray(requested)) return []\n return requested.filter((key): key is string => typeof key === \"string\" && settingsBridgeAccess(key) !== null)\n}\n\nexport type SettingsSetValidation =\n | {ok: true; key: string; value: unknown}\n | {ok: false; code: \"INVALID_ARGUMENT\" | \"NOT_PERMITTED\"; message: string}\n\n/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */\nexport function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation {\n const key = payload.key\n if (typeof key !== \"string\" || !key) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a key\"}\n }\n const access = settingsBridgeAccess(key)\n if (access === null) return {ok: false, code: \"INVALID_ARGUMENT\", message: `Unknown setting \"${key}\"`}\n if (access !== \"write\") return {ok: false, code: \"NOT_PERMITTED\", message: `Setting \"${key}\" is read-only`}\n if (!(\"value\" in payload) || payload.value === undefined) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a value\"}\n }\n return {ok: true, key, value: payload.value}\n}\n\n/** The ring's battery as the dashboard shows it: the relayed ring first, else the controller. */\nexport interface RingBattery {\n connected: boolean\n /** -1 when unknown. */\n level: number\n}\n\nexport function ringBatteryFrom(glasses: {\n ringConnected: boolean\n ringBatteryLevel: number\n controllerConnected: boolean\n controllerBatteryLevel: number\n}): RingBattery {\n const relayed = glasses.ringConnected && glasses.ringBatteryLevel >= 0\n return {\n connected: relayed || glasses.controllerConnected,\n level: relayed ? glasses.ringBatteryLevel : glasses.controllerBatteryLevel,\n }\n}\n\n/**\n * Whether the connected controller swipes in two dimensions (sideways as well\n * as up and down): the Meta Neural Band's thumb swipes. The R1 ring and the\n * glasses' temples are one-dimensional.\n */\nexport function input2dFrom(glasses: {controllerConnected: boolean}, controllerType: unknown): boolean {\n return glasses.controllerConnected && controllerType === ControllerTypes.NEURAL_BAND\n}\n"]}
|
|
1
|
+
{"version":3,"file":"settingsBridge.js","sourceRoot":"","sources":["../../src/services/settingsBridge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings bridge's vocabulary (session.settings): which engine settings\n * a miniapp with the PLATFORM_SETTINGS permission may read or write, the\n * derived read-only keys the engine computes rather than stores, and the\n * request validation. Pure, so the runtime's handlers stay thin and this can\n * be tested without the native import surface the runtime pulls in.\n *\n * A dashboard miniapp is the usual consumer: it edits the notification,\n * glasses-menu and display-position keys the phone's settings screens used\n * to, through the store, so persistence, server sync and the Bluetooth sync\n * of `menu_apps` and the position behave as if a phone screen wrote them. A dashboard's own look is not here; it keeps\n * that in its storage.\n */\n\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport {ControllerTypes} from \"../types/enums\"\nimport {hasInput2d, isControllerReady} from \"./controllers\"\n\nexport type SettingsBridgeAccess = \"read\" | \"write\"\n\n/** Stored settings the bridge serves, by key, with what a permitted app may do to each. */\nexport const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>> = {\n // The dashboard's master switch is a phone-side debug knob.\n custom_dashboard_enabled: \"read\",\n contextual_dashboard: \"write\",\n head_up_angle: \"write\",\n // Where the glasses draw; each model's steps are its display.positionRange.\n dashboard_depth: \"write\",\n dashboard_height: \"write\",\n twelve_hour_time: \"write\",\n menu_apps: \"write\",\n notifications_enabled: \"write\",\n notifications_popups: \"write\",\n notifications_popup_seconds: \"write\",\n notifications_hold_during_apps: \"write\",\n notifications_ignore_while_active: \"write\",\n notifications_blocklist: \"write\",\n // Apps the glasses reported posting notifications; the engine records it.\n notification_seen_apps: \"read\",\n}\n\n/**\n * Read-only keys the engine computes: whether the wearer can see the phone,\n * whether the foreground miniapp is drawing on the glasses, and the paired\n * controllers with their battery and input. They change on their own clocks;\n * the runtime pushes them like stored keys.\n */\nexport const DERIVED_SETTINGS_KEYS = [\"phone_active\", \"foreground_app_on_screen\", \"controllers\"] as const\nexport type DerivedSettingsKey = (typeof DERIVED_SETTINGS_KEYS)[number]\n\nexport function isDerivedSettingsKey(key: string): key is DerivedSettingsKey {\n return (DERIVED_SETTINGS_KEYS as readonly string[]).includes(key)\n}\n\n/** What a permitted app may do with `key`, or null when the bridge does not serve it. */\nexport function settingsBridgeAccess(key: string): SettingsBridgeAccess | null {\n if (isDerivedSettingsKey(key)) return \"read\"\n return SETTINGS_BRIDGE_KEYS[key] ?? null\n}\n\n/** Every key the bridge serves, stored then derived. */\nexport function allSettingsBridgeKeys(): string[] {\n return [...Object.keys(SETTINGS_BRIDGE_KEYS), ...DERIVED_SETTINGS_KEYS]\n}\n\n/**\n * The keys a SETTINGS_GET asks for: everything when `requested` is absent,\n * else the known ones among the requested strings (unknown keys are left\n * out, never rejected).\n */\nexport function selectSettingsBridgeKeys(requested: unknown): string[] {\n if (requested === undefined || requested === null) return allSettingsBridgeKeys()\n if (!Array.isArray(requested)) return []\n return requested.filter((key): key is string => typeof key === \"string\" && settingsBridgeAccess(key) !== null)\n}\n\nexport type SettingsSetValidation =\n | {ok: true; key: string; value: unknown}\n | {ok: false; code: \"INVALID_ARGUMENT\" | \"NOT_PERMITTED\"; message: string}\n\n/** A SETTINGS_SET payload: a writable key and a JSON value (undefined is not a value). */\nexport function validateSettingsSet(payload: Record<string, unknown>): SettingsSetValidation {\n const key = payload.key\n if (typeof key !== \"string\" || !key) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a key\"}\n }\n const access = settingsBridgeAccess(key)\n if (access === null) return {ok: false, code: \"INVALID_ARGUMENT\", message: `Unknown setting \"${key}\"`}\n if (access !== \"write\") return {ok: false, code: \"NOT_PERMITTED\", message: `Setting \"${key}\" is read-only`}\n if (!(\"value\" in payload) || payload.value === undefined) {\n return {ok: false, code: \"INVALID_ARGUMENT\", message: \"settings.set requires a value\"}\n }\n return {ok: true, key, value: payload.value}\n}\n\n/** One paired controller as the `controllers` derived key serves it. */\nexport interface ControllerSettingEntry {\n id: string\n model: string\n name: string\n /** Delivering input. */\n connected: boolean\n /** level -1 when unknown. */\n battery: {level: number; charging: boolean}\n /** Swipes sideways as well as up and down (the Meta Neural Band). */\n input2d: boolean\n}\n\n/** What the glasses report about the ring linked to them (G2 with the R1 routed through it). */\nexport interface RelayedRing {\n ringConnected: boolean\n ringBatteryLevel: number\n ringCharging: boolean\n}\n\n/**\n * The R1 the glasses' ring report is about: a ring routed through the G2 keeps\n * its phone link for telemetry, so prefer a linked ring, else the first one.\n */\nfunction relayedRingId(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n): string | undefined {\n const rings = records.filter((record) => record.model === ControllerTypes.R1)\n return (rings.find((ring) => statuses?.[ring.id]?.connected === true) ?? rings[0])?.id\n}\n\n/**\n * One entry per paired record, registry order. While the glasses report a\n * linked ring, that report is that ring's link and battery: a ring routed\n * through the G2 reports there, not to the phone.\n */\nexport function controllersSettingFrom(\n records: PairedController[],\n statuses: Record<string, ControllerStatus> | undefined,\n relayed: RelayedRing,\n): ControllerSettingEntry[] {\n const relayedId = relayed.ringConnected ? relayedRingId(records, statuses) : undefined\n return records.map((record) => {\n const status = statuses?.[record.id]\n let connected = isControllerReady(status)\n let battery = {level: status?.batteryLevel ?? -1, charging: status?.charging === true}\n if (record.id === relayedId) {\n connected = true\n if (relayed.ringBatteryLevel >= 0) {\n battery = {level: relayed.ringBatteryLevel, charging: relayed.ringCharging}\n }\n }\n return {\n id: record.id,\n model: record.model,\n name: record.name,\n connected,\n battery,\n input2d: connected && hasInput2d(record.model),\n }\n })\n}\n"]}
|