@glassly/engine 0.3.5-dev.745 → 0.3.5-dev.746
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 +3 -0
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +4 -0
- package/build/engine.js.map +1 -1
- package/build/facades/glasses.d.ts +5 -1
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +5 -0
- package/build/facades/glasses.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +19 -4
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +68 -125
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/actionInvoke.d.ts +36 -0
- package/build/services/actionInvoke.d.ts.map +1 -0
- package/build/services/actionInvoke.js +89 -0
- package/build/services/actionInvoke.js.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.js +259 -0
- package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +16 -0
- package/build/services/controllers.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +9 -0
- package/build/stores/settings.js.map +1 -1
- package/build/types/enums.d.ts +2 -1
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -0
- package/build/types/enums.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +4 -0
- package/src/facades/glasses.ts +7 -0
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/LocalMiniappRuntime.ts +103 -125
- package/src/services/actionInvoke.ts +104 -0
- package/src/services/appleWatch/AppleWatchLink.ts +297 -0
- package/src/services/controllers.ts +17 -0
- package/src/stores/settings.ts +9 -0
- package/src/types/enums.ts +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ClientApp } from "../types/applet";
|
|
2
|
+
export type ActionError = {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
} & Record<string, unknown>;
|
|
6
|
+
export type ActionOutcome = {
|
|
7
|
+
ok: true;
|
|
8
|
+
data: unknown;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
error: ActionError;
|
|
12
|
+
};
|
|
13
|
+
export declare const actionFailure: (code: string, message: string) => ActionOutcome;
|
|
14
|
+
/** Installed, compatible, and declares the action. */
|
|
15
|
+
export declare function appDeclaresAction(app: ClientApp | undefined, actionId: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Gates shared by miniapp->miniapp invoke and host invoke. Declared actions are
|
|
18
|
+
* public by default; `visibility: "system"` ones need a system caller.
|
|
19
|
+
*/
|
|
20
|
+
export declare function checkActionAccess(app: ClientApp | undefined, target: string | undefined, actionId: string | undefined, callerIsSystem: boolean): ActionError | null;
|
|
21
|
+
export interface ActionTimers {
|
|
22
|
+
setTimeout(cb: () => void, ms: number): number;
|
|
23
|
+
clearTimeout(id: number): void;
|
|
24
|
+
}
|
|
25
|
+
/** Outstanding ACTION_CALLs awaiting an ACTION_RESULT, keyed by host-generated callId. */
|
|
26
|
+
export declare class PendingActionCalls {
|
|
27
|
+
private readonly timers;
|
|
28
|
+
private readonly payloadTooLarge;
|
|
29
|
+
private readonly calls;
|
|
30
|
+
constructor(timers: ActionTimers, payloadTooLarge: (value: unknown) => boolean);
|
|
31
|
+
/** Register a call; resolves with its outcome, or ACTION_TIMEOUT. */
|
|
32
|
+
register(callId: string, target: string, actionId: string, timeoutMs: number): Promise<ActionOutcome>;
|
|
33
|
+
/** Settle from a target's ACTION_RESULT payload. Only the invoked target may resolve a call. */
|
|
34
|
+
handleResult(targetPackageName: string, payload: Record<string, unknown>): void;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=actionInvoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionInvoke.d.ts","sourceRoot":"","sources":["../../src/services/actionInvoke.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAA;AAE9C,MAAM,MAAM,WAAW,GAAG;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AACnF,MAAM,MAAM,aAAa,GAAG;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAC,GAAG;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAC,CAAA;AAEvF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,aAAsD,CAAA;AAEpH,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIvF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,SAAS,GAAG,SAAS,EAC1B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,cAAc,EAAE,OAAO,GACtB,WAAW,GAAG,IAAI,CAyBpB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9C,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED,0FAA0F;AAC1F,qBAAa,kBAAkB;IAI3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAJlC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyF;gBAG5F,MAAM,EAAE,YAAY,EACpB,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO;IAG/D,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAUrG,gGAAgG;IAChG,YAAY,CAAC,iBAAiB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAyBhF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { MiniappErrorCode } from "@glassly/miniapp";
|
|
2
|
+
export const actionFailure = (code, message) => ({ ok: false, error: { code, message } });
|
|
3
|
+
/** Installed, compatible, and declares the action. */
|
|
4
|
+
export function appDeclaresAction(app, actionId) {
|
|
5
|
+
if (!app)
|
|
6
|
+
return false;
|
|
7
|
+
if (app.compatibility && app.compatibility.isCompatible === false)
|
|
8
|
+
return false;
|
|
9
|
+
return (app.actions ?? []).some((a) => a.id === actionId);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Gates shared by miniapp->miniapp invoke and host invoke. Declared actions are
|
|
13
|
+
* public by default; `visibility: "system"` ones need a system caller.
|
|
14
|
+
*/
|
|
15
|
+
export function checkActionAccess(app, target, actionId, callerIsSystem) {
|
|
16
|
+
if (!target || !app) {
|
|
17
|
+
return {
|
|
18
|
+
code: MiniappErrorCode.APP_NOT_FOUND,
|
|
19
|
+
message: `Miniapp not found: ${target ?? "(missing targetPackageName)"}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (!actionId)
|
|
23
|
+
return { code: MiniappErrorCode.ACTION_NOT_FOUND, message: "invoke requires an actionId" };
|
|
24
|
+
if (app.compatibility && app.compatibility.isCompatible === false) {
|
|
25
|
+
return {
|
|
26
|
+
code: MiniappErrorCode.APP_NOT_COMPATIBLE,
|
|
27
|
+
message: `${target} is not compatible with the connected glasses`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const declared = (app.actions ?? []).find((a) => a.id === actionId);
|
|
31
|
+
if (!declared) {
|
|
32
|
+
return { code: MiniappErrorCode.ACTION_NOT_FOUND, message: `${target} does not declare action "${actionId}"` };
|
|
33
|
+
}
|
|
34
|
+
if (declared.visibility === "system" && !callerIsSystem) {
|
|
35
|
+
return {
|
|
36
|
+
code: MiniappErrorCode.NOT_PERMITTED,
|
|
37
|
+
message: `Action "${actionId}" on ${target} is restricted to system apps`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
/** Outstanding ACTION_CALLs awaiting an ACTION_RESULT, keyed by host-generated callId. */
|
|
43
|
+
export class PendingActionCalls {
|
|
44
|
+
timers;
|
|
45
|
+
payloadTooLarge;
|
|
46
|
+
calls = new Map();
|
|
47
|
+
constructor(timers, payloadTooLarge) {
|
|
48
|
+
this.timers = timers;
|
|
49
|
+
this.payloadTooLarge = payloadTooLarge;
|
|
50
|
+
}
|
|
51
|
+
/** Register a call; resolves with its outcome, or ACTION_TIMEOUT. */
|
|
52
|
+
register(callId, target, actionId, timeoutMs) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const timer = this.timers.setTimeout(() => {
|
|
55
|
+
this.calls.delete(callId);
|
|
56
|
+
resolve(actionFailure(MiniappErrorCode.ACTION_TIMEOUT, `action "${actionId}" timed out after ${timeoutMs}ms`));
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
this.calls.set(callId, { target, settle: resolve, timer });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** Settle from a target's ACTION_RESULT payload. Only the invoked target may resolve a call. */
|
|
62
|
+
handleResult(targetPackageName, payload) {
|
|
63
|
+
const callId = payload.callId;
|
|
64
|
+
if (!callId)
|
|
65
|
+
return;
|
|
66
|
+
const pending = this.calls.get(callId);
|
|
67
|
+
// callIds are guessable (timestamp + seq): a different miniapp must not spoof someone else's result.
|
|
68
|
+
if (!pending || pending.target !== targetPackageName)
|
|
69
|
+
return;
|
|
70
|
+
this.calls.delete(callId);
|
|
71
|
+
this.timers.clearTimeout(pending.timer);
|
|
72
|
+
if (payload.ok === true) {
|
|
73
|
+
if (this.payloadTooLarge(payload.result)) {
|
|
74
|
+
pending.settle(actionFailure(MiniappErrorCode.PAYLOAD_TOO_LARGE, "action result exceeded the 256 KB cap"));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
pending.settle({ ok: true, data: payload.result ?? null });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
pending.settle({
|
|
81
|
+
ok: false,
|
|
82
|
+
error: payload.error ?? {
|
|
83
|
+
code: MiniappErrorCode.INTERNAL,
|
|
84
|
+
message: "action handler error",
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=actionInvoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionInvoke.js","sourceRoot":"","sources":["../../src/services/actionInvoke.ts"],"names":[],"mappings":"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","sourcesContent":["import {MiniappErrorCode} from \"@glassly/miniapp\"\n\nimport type {ClientApp} from \"../types/applet\"\n\nexport type ActionError = {code: string; message: string} & Record<string, unknown>\nexport type ActionOutcome = {ok: true; data: unknown} | {ok: false; error: ActionError}\n\nexport const actionFailure = (code: string, message: string): ActionOutcome => ({ok: false, error: {code, message}})\n\n/** Installed, compatible, and declares the action. */\nexport function appDeclaresAction(app: ClientApp | undefined, actionId: string): boolean {\n if (!app) return false\n if (app.compatibility && app.compatibility.isCompatible === false) return false\n return (app.actions ?? []).some((a) => a.id === actionId)\n}\n\n/**\n * Gates shared by miniapp->miniapp invoke and host invoke. Declared actions are\n * public by default; `visibility: \"system\"` ones need a system caller.\n */\nexport function checkActionAccess(\n app: ClientApp | undefined,\n target: string | undefined,\n actionId: string | undefined,\n callerIsSystem: boolean,\n): ActionError | null {\n if (!target || !app) {\n return {\n code: MiniappErrorCode.APP_NOT_FOUND,\n message: `Miniapp not found: ${target ?? \"(missing targetPackageName)\"}`,\n }\n }\n if (!actionId) return {code: MiniappErrorCode.ACTION_NOT_FOUND, message: \"invoke requires an actionId\"}\n if (app.compatibility && app.compatibility.isCompatible === false) {\n return {\n code: MiniappErrorCode.APP_NOT_COMPATIBLE,\n message: `${target} is not compatible with the connected glasses`,\n }\n }\n const declared = (app.actions ?? []).find((a) => a.id === actionId)\n if (!declared) {\n return {code: MiniappErrorCode.ACTION_NOT_FOUND, message: `${target} does not declare action \"${actionId}\"`}\n }\n if (declared.visibility === \"system\" && !callerIsSystem) {\n return {\n code: MiniappErrorCode.NOT_PERMITTED,\n message: `Action \"${actionId}\" on ${target} is restricted to system apps`,\n }\n }\n return null\n}\n\nexport interface ActionTimers {\n setTimeout(cb: () => void, ms: number): number\n clearTimeout(id: number): void\n}\n\n/** Outstanding ACTION_CALLs awaiting an ACTION_RESULT, keyed by host-generated callId. */\nexport class PendingActionCalls {\n private readonly calls = new Map<string, {target: string; settle: (o: ActionOutcome) => void; timer: number}>()\n\n constructor(\n private readonly timers: ActionTimers,\n private readonly payloadTooLarge: (value: unknown) => boolean,\n ) {}\n\n /** Register a call; resolves with its outcome, or ACTION_TIMEOUT. */\n register(callId: string, target: string, actionId: string, timeoutMs: number): Promise<ActionOutcome> {\n return new Promise((resolve) => {\n const timer = this.timers.setTimeout(() => {\n this.calls.delete(callId)\n resolve(actionFailure(MiniappErrorCode.ACTION_TIMEOUT, `action \"${actionId}\" timed out after ${timeoutMs}ms`))\n }, timeoutMs)\n this.calls.set(callId, {target, settle: resolve, timer})\n })\n }\n\n /** Settle from a target's ACTION_RESULT payload. Only the invoked target may resolve a call. */\n handleResult(targetPackageName: string, payload: Record<string, unknown>): void {\n const callId = payload.callId as string | undefined\n if (!callId) return\n const pending = this.calls.get(callId)\n // callIds are guessable (timestamp + seq): a different miniapp must not spoof someone else's result.\n if (!pending || pending.target !== targetPackageName) return\n this.calls.delete(callId)\n this.timers.clearTimeout(pending.timer)\n\n if (payload.ok === true) {\n if (this.payloadTooLarge(payload.result)) {\n pending.settle(actionFailure(MiniappErrorCode.PAYLOAD_TOO_LARGE, \"action result exceeded the 256 KB cap\"))\n return\n }\n pending.settle({ok: true, data: payload.result ?? null})\n return\n }\n pending.settle({\n ok: false,\n error: (payload.error as ActionError | undefined) ?? {\n code: MiniappErrorCode.INTERNAL,\n message: \"action handler error\",\n },\n })\n }\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Apple Watch remote's phone half: the watch app's status context (glasses, the miniapp in
|
|
3
|
+
* front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames
|
|
4
|
+
* while the setting is on, and the commands the watch sends back. Native adds whether the active
|
|
5
|
+
* watch is paired and keeps the newest frame for when the watch app opens. Gestures ride
|
|
6
|
+
* `touch_event` like any controller's.
|
|
7
|
+
*/
|
|
8
|
+
import type { AppleWatchCommandEvent } from "@glassly/bluetooth-sdk";
|
|
9
|
+
import type { MirrorSnapshot } from "@glassly/miniapp";
|
|
10
|
+
import type { ClientApp } from "../../types";
|
|
11
|
+
export declare const ASSIST_PACKAGE = "com.glassly.assistant";
|
|
12
|
+
export declare const ASSIST_ACTION = "ask";
|
|
13
|
+
export interface WatchStateInput {
|
|
14
|
+
glasses: {
|
|
15
|
+
connected: boolean;
|
|
16
|
+
model: string;
|
|
17
|
+
battery: number;
|
|
18
|
+
};
|
|
19
|
+
apps: readonly ClientApp[];
|
|
20
|
+
assist: boolean;
|
|
21
|
+
mirror: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Miniapps the watch can open: shown, compatible, and drawing on the glasses. */
|
|
24
|
+
export declare function launchableApps(apps: readonly ClientApp[]): {
|
|
25
|
+
packageName: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}[];
|
|
28
|
+
export declare function watchState({ glasses, apps, assist, mirror }: WatchStateInput): Record<string, unknown>;
|
|
29
|
+
/** The glasses frame in the watch's drawing vocabulary; null when there is nothing to draw. */
|
|
30
|
+
export declare function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> | null;
|
|
31
|
+
export interface AppleWatchCommandDeps {
|
|
32
|
+
apps: () => readonly ClientApp[];
|
|
33
|
+
start: (app: ClientApp) => Promise<unknown>;
|
|
34
|
+
stop: (packageName: string) => Promise<unknown>;
|
|
35
|
+
ask: (query: string) => Promise<unknown>;
|
|
36
|
+
type: (stream: string, payload: unknown) => void;
|
|
37
|
+
now: () => number;
|
|
38
|
+
}
|
|
39
|
+
export declare function handleAppleWatchCommand(event: AppleWatchCommandEvent, deps?: AppleWatchCommandDeps): Promise<void>;
|
|
40
|
+
export declare function startAppleWatchLink(): void;
|
|
41
|
+
export declare function stopAppleWatchLink(): void;
|
|
42
|
+
//# sourceMappingURL=AppleWatchLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppleWatchLink.d.ts","sourceRoot":"","sources":["../../../src/services/appleWatch/AppleWatchLink.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,wBAAwB,CAAA;AAElE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAOpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,aAAa,CAAA;AAO1C,eAAO,MAAM,cAAc,0BAA0B,CAAA;AACrD,eAAO,MAAM,aAAa,QAAQ,CAAA;AAQlC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAA;IAC7D,IAAI,EAAE,SAAS,SAAS,EAAE,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,GAAG;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,EAAE,CAMhG;AAED,wBAAgB,UAAU,CAAC,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAC,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUpG;AAOD,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA4EpF;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,SAAS,SAAS,EAAE,CAAA;IAChC,KAAK,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/C,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,GAAG,EAAE,MAAM,MAAM,CAAA;CAClB;AAWD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,sBAAsB,EAC7B,IAAI,GAAE,qBAAmC,GACxC,OAAO,CAAC,IAAI,CAAC,CA4Bf;AA6DD,wBAAgB,mBAAmB,IAAI,IAAI,CAqC1C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import BluetoothSdk from "@glassly/bluetooth-sdk/internal";
|
|
2
|
+
import { MiniappStreamType } from "@glassly/miniapp";
|
|
3
|
+
import { Platform } from "react-native";
|
|
4
|
+
import { useAppStatusStore } from "../../stores/apps";
|
|
5
|
+
import { useGlassesStore } from "../../stores/glasses";
|
|
6
|
+
import { SETTINGS, useSettingsStore } from "../../stores/settings";
|
|
7
|
+
import { BgTimer } from "../../utils/timers";
|
|
8
|
+
import { isGlassesConnected } from "../GlassesReadiness";
|
|
9
|
+
import localMiniappRuntime from "../LocalMiniappRuntime";
|
|
10
|
+
import { mirrorService } from "../MirrorService";
|
|
11
|
+
import { keyEventsForTextChange } from "../neuralBand/NeuralBandInput";
|
|
12
|
+
export const ASSIST_PACKAGE = "com.glassly.assistant";
|
|
13
|
+
export const ASSIST_ACTION = "ask";
|
|
14
|
+
/** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */
|
|
15
|
+
const MAX_FRAME_BYTES = 48_000;
|
|
16
|
+
const MAX_IMAGE_CHARS = 16_000;
|
|
17
|
+
const MAX_APPS = 24;
|
|
18
|
+
const STATE_DEBOUNCE_MS = 250;
|
|
19
|
+
const MIRROR_INTERVAL_MS = 500;
|
|
20
|
+
/** Miniapps the watch can open: shown, compatible, and drawing on the glasses. */
|
|
21
|
+
export function launchableApps(apps) {
|
|
22
|
+
return apps
|
|
23
|
+
.filter((app) => app.type === "standard" && !app.hidden && app.compatibility?.isCompatible !== false)
|
|
24
|
+
.filter((app) => app.packageName && !app.packageName.includes("@empty"))
|
|
25
|
+
.slice(0, MAX_APPS)
|
|
26
|
+
.map((app) => ({ packageName: app.packageName, name: app.name }));
|
|
27
|
+
}
|
|
28
|
+
export function watchState({ glasses, apps, assist, mirror }) {
|
|
29
|
+
const front = apps.find((app) => app.foregrounded && app.running && app.type === "standard");
|
|
30
|
+
const state = {
|
|
31
|
+
glasses: { connected: glasses.connected, name: glasses.model || "Glasses", battery: glasses.battery },
|
|
32
|
+
apps: launchableApps(apps),
|
|
33
|
+
assist,
|
|
34
|
+
mirror,
|
|
35
|
+
};
|
|
36
|
+
if (front)
|
|
37
|
+
state.app = { packageName: front.packageName, name: front.name };
|
|
38
|
+
return state;
|
|
39
|
+
}
|
|
40
|
+
const level = (color, fallback = 15) => typeof color === "number" ? Math.max(0, Math.min(15, color)) : fallback;
|
|
41
|
+
/** The glasses frame in the watch's drawing vocabulary; null when there is nothing to draw. */
|
|
42
|
+
export function mirrorFrame(snapshot) {
|
|
43
|
+
const canvas = snapshot.canvas;
|
|
44
|
+
if (!canvas)
|
|
45
|
+
return null;
|
|
46
|
+
const content = snapshot.content;
|
|
47
|
+
if (content.kind === "bitmap") {
|
|
48
|
+
if (content.data.length > MAX_FRAME_BYTES)
|
|
49
|
+
return null;
|
|
50
|
+
return { width: canvas.width, height: canvas.height, bitmap: content.data, elements: [] };
|
|
51
|
+
}
|
|
52
|
+
if (content.kind !== "scene")
|
|
53
|
+
return null;
|
|
54
|
+
const elements = [];
|
|
55
|
+
for (const element of content.elements) {
|
|
56
|
+
const { x, y, w, h } = element.box;
|
|
57
|
+
const style = element.style ?? {};
|
|
58
|
+
switch (element.type) {
|
|
59
|
+
case "text":
|
|
60
|
+
if (!element.text)
|
|
61
|
+
break;
|
|
62
|
+
elements.push({
|
|
63
|
+
type: "text",
|
|
64
|
+
x,
|
|
65
|
+
y,
|
|
66
|
+
w,
|
|
67
|
+
h,
|
|
68
|
+
text: element.text,
|
|
69
|
+
size: style.fontSize ?? 20,
|
|
70
|
+
color: level(style.color),
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
case "rect":
|
|
74
|
+
elements.push({
|
|
75
|
+
type: "rect",
|
|
76
|
+
x,
|
|
77
|
+
y,
|
|
78
|
+
w,
|
|
79
|
+
h,
|
|
80
|
+
fill: style.fill === true,
|
|
81
|
+
border: style.border ?? 1,
|
|
82
|
+
radius: style.radius ?? 0,
|
|
83
|
+
color: level(style.color),
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
case "circle":
|
|
87
|
+
elements.push({
|
|
88
|
+
type: "circle",
|
|
89
|
+
x,
|
|
90
|
+
y,
|
|
91
|
+
w,
|
|
92
|
+
h,
|
|
93
|
+
fill: style.fill === true,
|
|
94
|
+
border: style.border ?? 1,
|
|
95
|
+
color: level(style.color),
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case "line":
|
|
99
|
+
case "triangle":
|
|
100
|
+
case "quad": {
|
|
101
|
+
const points = (element.points ?? []).flatMap((point) => [point.x, point.y]);
|
|
102
|
+
if (points.length < 4)
|
|
103
|
+
break;
|
|
104
|
+
const closed = element.type !== "line";
|
|
105
|
+
elements.push({
|
|
106
|
+
type: "line",
|
|
107
|
+
points,
|
|
108
|
+
width: style.width ?? style.border ?? 1,
|
|
109
|
+
color: level(style.color),
|
|
110
|
+
closed,
|
|
111
|
+
fill: closed && style.fill === true,
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "image":
|
|
116
|
+
if (element.data && element.data.length <= MAX_IMAGE_CHARS)
|
|
117
|
+
elements.push({ type: "image", x, y, w, h, data: element.data });
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const frame = { width: canvas.width, height: canvas.height, elements };
|
|
122
|
+
return JSON.stringify(frame).length > MAX_FRAME_BYTES ? null : frame;
|
|
123
|
+
}
|
|
124
|
+
const defaultDeps = {
|
|
125
|
+
apps: () => useAppStatusStore.getState().apps,
|
|
126
|
+
start: (app) => useAppStatusStore.getState().start(app),
|
|
127
|
+
stop: (packageName) => useAppStatusStore.getState().stop(packageName),
|
|
128
|
+
ask: (query) => localMiniappRuntime.invokeActionFromHost(ASSIST_PACKAGE, ASSIST_ACTION, { query }),
|
|
129
|
+
type: (stream, payload) => localMiniappRuntime.forwardEvent(stream, payload),
|
|
130
|
+
now: () => Date.now(),
|
|
131
|
+
};
|
|
132
|
+
export async function handleAppleWatchCommand(event, deps = defaultDeps) {
|
|
133
|
+
switch (event.command) {
|
|
134
|
+
case "launch": {
|
|
135
|
+
const app = launchableApps(deps.apps()).some((entry) => entry.packageName === event.packageName)
|
|
136
|
+
? deps.apps().find((entry) => entry.packageName === event.packageName)
|
|
137
|
+
: undefined;
|
|
138
|
+
if (!app) {
|
|
139
|
+
console.warn(`AppleWatch: launch of unknown miniapp ${event.packageName}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
await deps.start(app);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
case "stop": {
|
|
146
|
+
const front = deps.apps().find((app) => app.foregrounded && app.running && app.type === "standard");
|
|
147
|
+
if (front)
|
|
148
|
+
await deps.stop(front.packageName);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
case "assist": {
|
|
152
|
+
const query = event.text.trim();
|
|
153
|
+
if (query)
|
|
154
|
+
await deps.ask(query);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
case "text":
|
|
158
|
+
for (const key of keyEventsForTextChange("", event.text, deps.now()))
|
|
159
|
+
deps.type(MiniappStreamType.KEYBOARD_EVENT, key);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
let stops = [];
|
|
164
|
+
let stopMirror = null;
|
|
165
|
+
function currentState() {
|
|
166
|
+
const glasses = useGlassesStore.getState();
|
|
167
|
+
return watchState({
|
|
168
|
+
glasses: {
|
|
169
|
+
connected: isGlassesConnected(glasses.connection),
|
|
170
|
+
model: glasses.deviceModel,
|
|
171
|
+
battery: glasses.batteryLevel,
|
|
172
|
+
},
|
|
173
|
+
apps: useAppStatusStore.getState().apps,
|
|
174
|
+
assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),
|
|
175
|
+
mirror: useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function syncMirror(enabled) {
|
|
179
|
+
if (!enabled) {
|
|
180
|
+
if (stopMirror) {
|
|
181
|
+
stopMirror();
|
|
182
|
+
stopMirror = null;
|
|
183
|
+
void BluetoothSdk.setWatchMirror(null).catch(() => { });
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (stopMirror)
|
|
188
|
+
return;
|
|
189
|
+
let lastSentAt = 0;
|
|
190
|
+
let pending = null;
|
|
191
|
+
let timer = null;
|
|
192
|
+
const send = (snapshot) => {
|
|
193
|
+
lastSentAt = Date.now();
|
|
194
|
+
void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) => console.warn("AppleWatch: mirror push failed:", err));
|
|
195
|
+
};
|
|
196
|
+
const unsubscribe = mirrorService.subscribe((snapshot) => {
|
|
197
|
+
const wait = lastSentAt + MIRROR_INTERVAL_MS - Date.now();
|
|
198
|
+
if (wait <= 0 && !timer) {
|
|
199
|
+
send(snapshot);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
pending = snapshot;
|
|
203
|
+
timer ??= BgTimer.setTimeout(() => {
|
|
204
|
+
timer = null;
|
|
205
|
+
if (pending)
|
|
206
|
+
send(pending);
|
|
207
|
+
pending = null;
|
|
208
|
+
}, Math.max(wait, 0));
|
|
209
|
+
});
|
|
210
|
+
stopMirror = () => {
|
|
211
|
+
unsubscribe();
|
|
212
|
+
if (timer)
|
|
213
|
+
BgTimer.clearTimeout(timer);
|
|
214
|
+
timer = null;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export function startAppleWatchLink() {
|
|
218
|
+
if (Platform.OS !== "ios" || stops.length > 0)
|
|
219
|
+
return;
|
|
220
|
+
let lastKey = "";
|
|
221
|
+
let timer = null;
|
|
222
|
+
const push = () => {
|
|
223
|
+
timer = null;
|
|
224
|
+
const state = currentState();
|
|
225
|
+
const key = JSON.stringify(state);
|
|
226
|
+
if (key === lastKey)
|
|
227
|
+
return;
|
|
228
|
+
lastKey = key;
|
|
229
|
+
void BluetoothSdk.setWatchState(state).catch((err) => console.warn("AppleWatch: state push failed:", err));
|
|
230
|
+
};
|
|
231
|
+
const schedule = () => {
|
|
232
|
+
timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS);
|
|
233
|
+
};
|
|
234
|
+
const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true;
|
|
235
|
+
const commands = BluetoothSdk.addListener("apple_watch_command", (event) => {
|
|
236
|
+
handleAppleWatchCommand(event).catch((err) => console.warn(`AppleWatch: ${event.command} failed:`, err));
|
|
237
|
+
});
|
|
238
|
+
stops = [
|
|
239
|
+
() => commands.remove(),
|
|
240
|
+
useGlassesStore.subscribe(schedule),
|
|
241
|
+
useAppStatusStore.subscribe(schedule),
|
|
242
|
+
useSettingsStore.subscribe((s) => s.getSetting(SETTINGS.apple_watch_mirror.key), () => {
|
|
243
|
+
syncMirror(mirrorEnabled());
|
|
244
|
+
schedule();
|
|
245
|
+
}),
|
|
246
|
+
() => {
|
|
247
|
+
if (timer)
|
|
248
|
+
BgTimer.clearTimeout(timer);
|
|
249
|
+
timer = null;
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
syncMirror(mirrorEnabled());
|
|
253
|
+
schedule();
|
|
254
|
+
}
|
|
255
|
+
export function stopAppleWatchLink() {
|
|
256
|
+
stops.splice(0).forEach((stop) => stop());
|
|
257
|
+
syncMirror(false);
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=AppleWatchLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppleWatchLink.js","sourceRoot":"","sources":["../../../src/services/appleWatch/AppleWatchLink.ts"],"names":[],"mappings":"AAQA,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAE1D,OAAO,EAAC,iBAAiB,EAAC,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAA;AAErC,OAAO,EAAC,iBAAiB,EAAC,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AAEhE,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AACtD,OAAO,mBAAmB,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAC,sBAAsB,EAAC,MAAM,+BAA+B,CAAA;AAEpE,MAAM,CAAC,MAAM,cAAc,GAAG,uBAAuB,CAAA;AACrD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAA;AAClC,kGAAkG;AAClG,MAAM,eAAe,GAAG,MAAM,CAAA;AAC9B,MAAM,eAAe,GAAG,MAAM,CAAA;AAC9B,MAAM,QAAQ,GAAG,EAAE,CAAA;AACnB,MAAM,iBAAiB,GAAG,GAAG,CAAA;AAC7B,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAS9B,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,IAA0B;IACvD,OAAO,IAAI;SACR,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,aAAa,EAAE,YAAY,KAAK,KAAK,CAAC;SACpG,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;SACvE,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;SAClB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAC,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAC,CAAC,CAAC,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAkB;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,CAAC,CAAA;IAC5F,MAAM,KAAK,GAA4B;QACrC,OAAO,EAAE,EAAC,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAC;QACnG,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC;QAC1B,MAAM;QACN,MAAM;KACP,CAAA;IACD,IAAI,KAAK;QAAE,KAAK,CAAC,GAAG,GAAG,EAAC,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAC,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC;AAID,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,QAAQ,GAAG,EAAE,EAAE,EAAE,CAC9C,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AAEzE,+FAA+F;AAC/F,MAAM,UAAU,WAAW,CAAC,QAAwB;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAA;IAChC,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,eAAe;YAAE,OAAO,IAAI,CAAA;QACtD,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAC,CAAA;IACzF,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAA;IACzC,MAAM,QAAQ,GAAmB,EAAE,CAAA;IACnC,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAC,GAAG,OAAO,CAAC,GAAG,CAAA;QAChC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAA;QACjC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,MAAM;gBACT,IAAI,CAAC,OAAO,CAAC,IAAI;oBAAE,MAAK;gBACxB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,CAAC;oBACD,CAAC;oBACD,CAAC;oBACD,CAAC;oBACD,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;oBAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;iBAC1B,CAAC,CAAA;gBACF,MAAK;YACP,KAAK,MAAM;gBACT,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,CAAC;oBACD,CAAC;oBACD,CAAC;oBACD,CAAC;oBACD,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,IAAI;oBACzB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;oBACzB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;oBACzB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;iBAC1B,CAAC,CAAA;gBACF,MAAK;YACP,KAAK,QAAQ;gBACX,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,QAAQ;oBACd,CAAC;oBACD,CAAC;oBACD,CAAC;oBACD,CAAC;oBACD,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,IAAI;oBACzB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;oBACzB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;iBAC1B,CAAC,CAAA;gBACF,MAAK;YACP,KAAK,MAAM,CAAC;YACZ,KAAK,UAAU,CAAC;YAChB,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC5E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;oBAAE,MAAK;gBAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,KAAK,MAAM,CAAA;gBACtC,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,MAAM;oBACN,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;oBACvC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;oBACzB,MAAM;oBACN,IAAI,EAAE,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI;iBACpC,CAAC,CAAA;gBACF,MAAK;YACP,CAAC;YACD,KAAK,OAAO;gBACV,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,eAAe;oBACxD,QAAQ,CAAC,IAAI,CAAC,EAAC,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAC,CAAC,CAAA;gBAChE,MAAK;QACT,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAC,CAAA;IACpE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAA;AACtE,CAAC;AAWD,MAAM,WAAW,GAA0B;IACzC,IAAI,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI;IAC7C,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;IACvD,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC;IACrE,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,CAAC,oBAAoB,CAAC,cAAc,EAAE,aAAa,EAAE,EAAC,KAAK,EAAC,CAAC;IAChG,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,mBAAmB,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5E,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;CACtB,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,KAA6B,EAC7B,OAA8B,WAAW;IAEzC,QAAQ,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,CAAC;gBAC9F,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,CAAC;gBACtE,CAAC,CAAC,SAAS,CAAA;YACb,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,CAAC,IAAI,CAAC,yCAAyC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;gBAC1E,OAAM;YACR,CAAC;YACD,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACrB,OAAM;QACR,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,CAAC,CAAA;YACnG,IAAI,KAAK;gBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;YAC7C,OAAM;QACR,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;YAC/B,IAAI,KAAK;gBAAE,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YAChC,OAAM;QACR,CAAC;QACD,KAAK,MAAM;YACT,KAAK,MAAM,GAAG,IAAI,sBAAsB,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;YAClD,OAAM;IACV,CAAC;AACH,CAAC;AAED,IAAI,KAAK,GAAmB,EAAE,CAAA;AAC9B,IAAI,UAAU,GAAwB,IAAI,CAAA;AAE1C,SAAS,YAAY;IACnB,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IAC1C,OAAO,UAAU,CAAC;QAChB,OAAO,EAAE;YACP,SAAS,EAAE,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC;YACjD,KAAK,EAAE,OAAO,CAAC,WAAW;YAC1B,OAAO,EAAE,OAAO,CAAC,YAAY;SAC9B;QACD,IAAI,EAAE,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI;QACvC,MAAM,EAAE,mBAAmB,CAAC,mBAAmB,CAAC,cAAc,EAAE,aAAa,CAAC;QAC9E,MAAM,EAAE,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,IAAI;KACzF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,OAAgB;IAClC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,EAAE,CAAA;YACZ,UAAU,GAAG,IAAI,CAAA;YACjB,KAAK,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACxD,CAAC;QACD,OAAM;IACR,CAAC;IACD,IAAI,UAAU;QAAE,OAAM;IACtB,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,OAAO,GAA0B,IAAI,CAAA;IACzC,IAAI,KAAK,GAAiD,IAAI,CAAA;IAC9D,MAAM,IAAI,GAAG,CAAC,QAAwB,EAAE,EAAE;QACxC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACvB,KAAK,YAAY,CAAC,cAAc,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CACpE,OAAO,CAAC,IAAI,CAAC,iCAAiC,EAAE,GAAG,CAAC,CACrD,CAAA;IACH,CAAC,CAAA;IACD,MAAM,WAAW,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE;QACvD,MAAM,IAAI,GAAG,UAAU,GAAG,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACzD,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,CAAA;YACd,OAAM;QACR,CAAC;QACD,OAAO,GAAG,QAAQ,CAAA;QAClB,KAAK,KAAK,OAAO,CAAC,UAAU,CAC1B,GAAG,EAAE;YACH,KAAK,GAAG,IAAI,CAAA;YACZ,IAAI,OAAO;gBAAE,IAAI,CAAC,OAAO,CAAC,CAAA;YAC1B,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAClB,CAAA;IACH,CAAC,CAAC,CAAA;IACF,UAAU,GAAG,GAAG,EAAE;QAChB,WAAW,EAAE,CAAA;QACb,IAAI,KAAK;YAAE,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;QACtC,KAAK,GAAG,IAAI,CAAA;IACd,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAM;IACrD,IAAI,OAAO,GAAG,EAAE,CAAA;IAChB,IAAI,KAAK,GAAiD,IAAI,CAAA;IAC9D,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,KAAK,GAAG,IAAI,CAAA;QACZ,MAAM,KAAK,GAAG,YAAY,EAAE,CAAA;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QACjC,IAAI,GAAG,KAAK,OAAO;YAAE,OAAM;QAC3B,OAAO,GAAG,GAAG,CAAA;QACb,KAAK,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,gCAAgC,EAAE,GAAG,CAAC,CAAC,CAAA;IAC5G,CAAC,CAAA;IACD,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,KAAK,KAAK,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAA;IACvD,CAAC,CAAA;IACD,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,IAAI,CAAA;IAC5G,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,EAAE;QACzE,uBAAuB,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,OAAO,UAAU,EAAE,GAAG,CAAC,CAAC,CAAA;IAC1G,CAAC,CAAC,CAAA;IACF,KAAK,GAAG;QACN,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE;QACvB,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC;QACnC,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAC;QACrC,gBAAgB,CAAC,SAAS,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,EACpD,GAAG,EAAE;YACH,UAAU,CAAC,aAAa,EAAE,CAAC,CAAA;YAC3B,QAAQ,EAAE,CAAA;QACZ,CAAC,CACF;QACD,GAAG,EAAE;YACH,IAAI,KAAK;gBAAE,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;YACtC,KAAK,GAAG,IAAI,CAAA;QACd,CAAC;KACF,CAAA;IACD,UAAU,CAAC,aAAa,EAAE,CAAC,CAAA;IAC3B,QAAQ,EAAE,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;IACzC,UAAU,CAAC,KAAK,CAAC,CAAA;AACnB,CAAC","sourcesContent":["/**\n * The Apple Watch remote's phone half: the watch app's status context (glasses, the miniapp in\n * front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames\n * while the setting is on, and the commands the watch sends back. Native adds whether the active\n * watch is paired and keeps the newest frame for when the watch app opens. Gestures ride\n * `touch_event` like any controller's.\n */\nimport type {AppleWatchCommandEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport type {MirrorSnapshot} from \"@glassly/miniapp\"\nimport {MiniappStreamType} from \"@glassly/miniapp\"\nimport {Platform} from \"react-native\"\n\nimport {useAppStatusStore} from \"../../stores/apps\"\nimport {useGlassesStore} from \"../../stores/glasses\"\nimport {SETTINGS, useSettingsStore} from \"../../stores/settings\"\nimport type {ClientApp} from \"../../types\"\nimport {BgTimer} from \"../../utils/timers\"\nimport {isGlassesConnected} from \"../GlassesReadiness\"\nimport localMiniappRuntime from \"../LocalMiniappRuntime\"\nimport {mirrorService} from \"../MirrorService\"\nimport {keyEventsForTextChange} from \"../neuralBand/NeuralBandInput\"\n\nexport const ASSIST_PACKAGE = \"com.glassly.assistant\"\nexport const ASSIST_ACTION = \"ask\"\n/** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */\nconst MAX_FRAME_BYTES = 48_000\nconst MAX_IMAGE_CHARS = 16_000\nconst MAX_APPS = 24\nconst STATE_DEBOUNCE_MS = 250\nconst MIRROR_INTERVAL_MS = 500\n\nexport interface WatchStateInput {\n glasses: {connected: boolean; model: string; battery: number}\n apps: readonly ClientApp[]\n assist: boolean\n mirror: boolean\n}\n\n/** Miniapps the watch can open: shown, compatible, and drawing on the glasses. */\nexport function launchableApps(apps: readonly ClientApp[]): {packageName: string; name: string}[] {\n return apps\n .filter((app) => app.type === \"standard\" && !app.hidden && app.compatibility?.isCompatible !== false)\n .filter((app) => app.packageName && !app.packageName.includes(\"@empty\"))\n .slice(0, MAX_APPS)\n .map((app) => ({packageName: app.packageName, name: app.name}))\n}\n\nexport function watchState({glasses, apps, assist, mirror}: WatchStateInput): Record<string, unknown> {\n const front = apps.find((app) => app.foregrounded && app.running && app.type === \"standard\")\n const state: Record<string, unknown> = {\n glasses: {connected: glasses.connected, name: glasses.model || \"Glasses\", battery: glasses.battery},\n apps: launchableApps(apps),\n assist,\n mirror,\n }\n if (front) state.app = {packageName: front.packageName, name: front.name}\n return state\n}\n\ntype WatchElement = Record<string, string | number | boolean | number[]>\n\nconst level = (color: unknown, fallback = 15) =>\n typeof color === \"number\" ? Math.max(0, Math.min(15, color)) : fallback\n\n/** The glasses frame in the watch's drawing vocabulary; null when there is nothing to draw. */\nexport function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> | null {\n const canvas = snapshot.canvas\n if (!canvas) return null\n const content = snapshot.content\n if (content.kind === \"bitmap\") {\n if (content.data.length > MAX_FRAME_BYTES) return null\n return {width: canvas.width, height: canvas.height, bitmap: content.data, elements: []}\n }\n if (content.kind !== \"scene\") return null\n const elements: WatchElement[] = []\n for (const element of content.elements) {\n const {x, y, w, h} = element.box\n const style = element.style ?? {}\n switch (element.type) {\n case \"text\":\n if (!element.text) break\n elements.push({\n type: \"text\",\n x,\n y,\n w,\n h,\n text: element.text,\n size: style.fontSize ?? 20,\n color: level(style.color),\n })\n break\n case \"rect\":\n elements.push({\n type: \"rect\",\n x,\n y,\n w,\n h,\n fill: style.fill === true,\n border: style.border ?? 1,\n radius: style.radius ?? 0,\n color: level(style.color),\n })\n break\n case \"circle\":\n elements.push({\n type: \"circle\",\n x,\n y,\n w,\n h,\n fill: style.fill === true,\n border: style.border ?? 1,\n color: level(style.color),\n })\n break\n case \"line\":\n case \"triangle\":\n case \"quad\": {\n const points = (element.points ?? []).flatMap((point) => [point.x, point.y])\n if (points.length < 4) break\n const closed = element.type !== \"line\"\n elements.push({\n type: \"line\",\n points,\n width: style.width ?? style.border ?? 1,\n color: level(style.color),\n closed,\n fill: closed && style.fill === true,\n })\n break\n }\n case \"image\":\n if (element.data && element.data.length <= MAX_IMAGE_CHARS)\n elements.push({type: \"image\", x, y, w, h, data: element.data})\n break\n }\n }\n const frame = {width: canvas.width, height: canvas.height, elements}\n return JSON.stringify(frame).length > MAX_FRAME_BYTES ? null : frame\n}\n\nexport interface AppleWatchCommandDeps {\n apps: () => readonly ClientApp[]\n start: (app: ClientApp) => Promise<unknown>\n stop: (packageName: string) => Promise<unknown>\n ask: (query: string) => Promise<unknown>\n type: (stream: string, payload: unknown) => void\n now: () => number\n}\n\nconst defaultDeps: AppleWatchCommandDeps = {\n apps: () => useAppStatusStore.getState().apps,\n start: (app) => useAppStatusStore.getState().start(app),\n stop: (packageName) => useAppStatusStore.getState().stop(packageName),\n ask: (query) => localMiniappRuntime.invokeActionFromHost(ASSIST_PACKAGE, ASSIST_ACTION, {query}),\n type: (stream, payload) => localMiniappRuntime.forwardEvent(stream, payload),\n now: () => Date.now(),\n}\n\nexport async function handleAppleWatchCommand(\n event: AppleWatchCommandEvent,\n deps: AppleWatchCommandDeps = defaultDeps,\n): Promise<void> {\n switch (event.command) {\n case \"launch\": {\n const app = launchableApps(deps.apps()).some((entry) => entry.packageName === event.packageName)\n ? deps.apps().find((entry) => entry.packageName === event.packageName)\n : undefined\n if (!app) {\n console.warn(`AppleWatch: launch of unknown miniapp ${event.packageName}`)\n return\n }\n await deps.start(app)\n return\n }\n case \"stop\": {\n const front = deps.apps().find((app) => app.foregrounded && app.running && app.type === \"standard\")\n if (front) await deps.stop(front.packageName)\n return\n }\n case \"assist\": {\n const query = event.text.trim()\n if (query) await deps.ask(query)\n return\n }\n case \"text\":\n for (const key of keyEventsForTextChange(\"\", event.text, deps.now()))\n deps.type(MiniappStreamType.KEYBOARD_EVENT, key)\n return\n }\n}\n\nlet stops: (() => void)[] = []\nlet stopMirror: (() => void) | null = null\n\nfunction currentState(): Record<string, unknown> {\n const glasses = useGlassesStore.getState()\n return watchState({\n glasses: {\n connected: isGlassesConnected(glasses.connection),\n model: glasses.deviceModel,\n battery: glasses.batteryLevel,\n },\n apps: useAppStatusStore.getState().apps,\n assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),\n mirror: useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true,\n })\n}\n\nfunction syncMirror(enabled: boolean): void {\n if (!enabled) {\n if (stopMirror) {\n stopMirror()\n stopMirror = null\n void BluetoothSdk.setWatchMirror(null).catch(() => {})\n }\n return\n }\n if (stopMirror) return\n let lastSentAt = 0\n let pending: MirrorSnapshot | null = null\n let timer: ReturnType<typeof BgTimer.setTimeout> | null = null\n const send = (snapshot: MirrorSnapshot) => {\n lastSentAt = Date.now()\n void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) =>\n console.warn(\"AppleWatch: mirror push failed:\", err),\n )\n }\n const unsubscribe = mirrorService.subscribe((snapshot) => {\n const wait = lastSentAt + MIRROR_INTERVAL_MS - Date.now()\n if (wait <= 0 && !timer) {\n send(snapshot)\n return\n }\n pending = snapshot\n timer ??= BgTimer.setTimeout(\n () => {\n timer = null\n if (pending) send(pending)\n pending = null\n },\n Math.max(wait, 0),\n )\n })\n stopMirror = () => {\n unsubscribe()\n if (timer) BgTimer.clearTimeout(timer)\n timer = null\n }\n}\n\nexport function startAppleWatchLink(): void {\n if (Platform.OS !== \"ios\" || stops.length > 0) return\n let lastKey = \"\"\n let timer: ReturnType<typeof BgTimer.setTimeout> | null = null\n const push = () => {\n timer = null\n const state = currentState()\n const key = JSON.stringify(state)\n if (key === lastKey) return\n lastKey = key\n void BluetoothSdk.setWatchState(state).catch((err) => console.warn(\"AppleWatch: state push failed:\", err))\n }\n const schedule = () => {\n timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS)\n }\n const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.apple_watch_mirror.key) === true\n const commands = BluetoothSdk.addListener(\"apple_watch_command\", (event) => {\n handleAppleWatchCommand(event).catch((err) => console.warn(`AppleWatch: ${event.command} failed:`, err))\n })\n stops = [\n () => commands.remove(),\n useGlassesStore.subscribe(schedule),\n useAppStatusStore.subscribe(schedule),\n useSettingsStore.subscribe(\n (s) => s.getSetting(SETTINGS.apple_watch_mirror.key),\n () => {\n syncMirror(mirrorEnabled())\n schedule()\n },\n ),\n () => {\n if (timer) BgTimer.clearTimeout(timer)\n timer = null\n },\n ]\n syncMirror(mirrorEnabled())\n schedule()\n}\n\nexport function stopAppleWatchLink(): void {\n stops.splice(0).forEach((stop) => stop())\n syncMirror(false)\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controllers.d.ts","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAE9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"controllers.d.ts","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAE9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAyC7E,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAErE;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAUxE;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAE/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,gBAAgB,EAAE,CAE3G;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,CAKlG"}
|
|
@@ -14,9 +14,25 @@ const NEURAL_BAND_SURFACE = {
|
|
|
14
14
|
"DIAL_DOWN",
|
|
15
15
|
],
|
|
16
16
|
};
|
|
17
|
+
/** The Apple Watch trackpad's taps and four-way swipes, and the Digital Crown as the dial. */
|
|
18
|
+
const APPLE_WATCH_SURFACE = {
|
|
19
|
+
type: "swipe2d",
|
|
20
|
+
events: [
|
|
21
|
+
"TAP",
|
|
22
|
+
"DOUBLE_TAP",
|
|
23
|
+
"PRESS_HOLD",
|
|
24
|
+
"SWIPE_UP",
|
|
25
|
+
"SWIPE_DOWN",
|
|
26
|
+
"SWIPE_LEFT",
|
|
27
|
+
"SWIPE_RIGHT",
|
|
28
|
+
"DIAL_UP",
|
|
29
|
+
"DIAL_DOWN",
|
|
30
|
+
],
|
|
31
|
+
};
|
|
17
32
|
// The R1 swipes up/down like the temples it stands in for, so it adds nothing.
|
|
18
33
|
const INPUT_SURFACES = {
|
|
19
34
|
[ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],
|
|
35
|
+
[ControllerTypes.APPLE_WATCH]: [APPLE_WATCH_SURFACE],
|
|
20
36
|
[ControllerTypes.R1]: [],
|
|
21
37
|
};
|
|
22
38
|
/** The input surfaces a connected controller of `model` adds to the glasses'. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controllers.js","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAA;AAI9C,wDAAwD;AACxD,MAAM,mBAAmB,GAAiB;IACxC,IAAI,EAAE,SAAS;IACf,MAAM,EAAE;QACN,KAAK;QACL,YAAY;QACZ,YAAY;QACZ,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,SAAS;QACT,WAAW;KACZ;CACF,CAAA;AAED,+EAA+E;AAC/E,MAAM,cAAc,GAAsD;IACxE,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,mBAAmB,CAAC;IACpD,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE;CACzB,CAAA;AAED,iFAAiF;AACjF,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAC,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAC,CAAC,CAAC,CAAA;AACpG,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAA;AACpF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,OAAO,KAAK,KAAK,SAAS,IAAK,MAAM,CAAC,MAAM,CAAC,eAAe,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC5F,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,OAAO,GAAuB,EAAE,CAAA;IACtC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACjD,MAAM,EAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAC,GAAG,KAAgC,CAAA;QAC1D,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACxE,OAAO,CAAC,IAAI,CAAC,EAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAC,CAAC,CAAA;IACvE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,MAAoC;IACpE,OAAO,MAAM,EAAE,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAsD;IACrF,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAA;AAChE,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,mBAAmB,CAAC,QAAsD;IACxF,OAAO,gBAAgB,CAAC,QAAQ,CAAC;SAC9B,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;SAC/C,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["/**\n * Controller registry read-model: the paired records (the native-authoritative\n * `controllers` setting) and the live driver statuses (glasses status\n * `controllers`), plus what each model adds to the input surfaces. Pure.\n */\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport type {ButtonCapabilities} from \"../types\"\nimport {ControllerTypes} from \"../types/enums\"\n\nexport type InputSurface = NonNullable<ButtonCapabilities[\"buttons\"]>[number]\n\n/** The Neural Band's thumb swipes, pinches and dial. */\nconst NEURAL_BAND_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n// The R1 swipes up/down like the temples it stands in for, so it adds nothing.\nconst INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {\n [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],\n [ControllerTypes.R1]: [],\n}\n\n/** The input surfaces a connected controller of `model` adds to the glasses'. */\nexport function controllerInputSurfaces(model: string): InputSurface[] {\n return (INPUT_SURFACES[model] ?? []).map((surface) => ({...surface, events: [...surface.events]}))\n}\n\n/** The model swipes in two dimensions. */\nexport function hasInput2d(model: string): boolean {\n return (INPUT_SURFACES[model] ?? []).some((surface) => surface.type === \"swipe2d\")\n}\n\nexport function isControllerModel(model: string | undefined): boolean {\n return model !== undefined && (Object.values(ControllerTypes) as string[]).includes(model)\n}\n\n/** The `controllers` setting as records; malformed entries are dropped. */\nexport function pairedControllersFrom(value: unknown): PairedController[] {\n if (!Array.isArray(value)) return []\n const records: PairedController[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue\n const {id, model, name} = entry as Record<string, unknown>\n if (typeof id !== \"string\" || !id || typeof model !== \"string\") continue\n records.push({id, model, name: typeof name === \"string\" ? name : id})\n }\n return records\n}\n\n/** Delivering input: link up and the route or claim done. */\nexport function isControllerReady(status: ControllerStatus | undefined): boolean {\n return status?.connected === true && status.fullyBooted === true\n}\n\nexport function readyControllers(statuses: Record<string, ControllerStatus> | undefined): ControllerStatus[] {\n return Object.values(statuses ?? {}).filter(isControllerReady)\n}\n\n/** Change key over the controllers delivering input. */\nexport function readyControllersKey(statuses: Record<string, ControllerStatus> | undefined): string {\n return readyControllers(statuses)\n .map((status) => `${status.id}=${status.model}`)\n .sort()\n .join(\",\")\n}\n"]}
|
|
1
|
+
{"version":3,"file":"controllers.js","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Controller registry read-model: the paired records (the native-authoritative\n * `controllers` setting) and the live driver statuses (glasses status\n * `controllers`), plus what each model adds to the input surfaces. Pure.\n */\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport type {ButtonCapabilities} from \"../types\"\nimport {ControllerTypes} from \"../types/enums\"\n\nexport type InputSurface = NonNullable<ButtonCapabilities[\"buttons\"]>[number]\n\n/** The Neural Band's thumb swipes, pinches and dial. */\nconst NEURAL_BAND_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n/** The Apple Watch trackpad's taps and four-way swipes, and the Digital Crown as the dial. */\nconst APPLE_WATCH_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n// The R1 swipes up/down like the temples it stands in for, so it adds nothing.\nconst INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {\n [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],\n [ControllerTypes.APPLE_WATCH]: [APPLE_WATCH_SURFACE],\n [ControllerTypes.R1]: [],\n}\n\n/** The input surfaces a connected controller of `model` adds to the glasses'. */\nexport function controllerInputSurfaces(model: string): InputSurface[] {\n return (INPUT_SURFACES[model] ?? []).map((surface) => ({...surface, events: [...surface.events]}))\n}\n\n/** The model swipes in two dimensions. */\nexport function hasInput2d(model: string): boolean {\n return (INPUT_SURFACES[model] ?? []).some((surface) => surface.type === \"swipe2d\")\n}\n\nexport function isControllerModel(model: string | undefined): boolean {\n return model !== undefined && (Object.values(ControllerTypes) as string[]).includes(model)\n}\n\n/** The `controllers` setting as records; malformed entries are dropped. */\nexport function pairedControllersFrom(value: unknown): PairedController[] {\n if (!Array.isArray(value)) return []\n const records: PairedController[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue\n const {id, model, name} = entry as Record<string, unknown>\n if (typeof id !== \"string\" || !id || typeof model !== \"string\") continue\n records.push({id, model, name: typeof name === \"string\" ? name : id})\n }\n return records\n}\n\n/** Delivering input: link up and the route or claim done. */\nexport function isControllerReady(status: ControllerStatus | undefined): boolean {\n return status?.connected === true && status.fullyBooted === true\n}\n\nexport function readyControllers(statuses: Record<string, ControllerStatus> | undefined): ControllerStatus[] {\n return Object.values(statuses ?? {}).filter(isControllerReady)\n}\n\n/** Change key over the controllers delivering input. */\nexport function readyControllersKey(statuses: Record<string, ControllerStatus> | undefined): string {\n return readyControllers(statuses)\n .map((status) => `${status.id}=${status.model}`)\n .sort()\n .join(\",\")\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/stores/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAwB,MAAM,aAAa,CAAA;AAQ9D,OAAO,EAAC,wBAAwB,EAAE,gCAAgC,EAAC,MAAM,wBAAwB,CAAA;AAEjG,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,GAAG,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IAGrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAEjC,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAA;IAEpB,OAAO,EAAE,OAAO,CAAA;IAQhB,mBAAmB,CAAC,EAAE,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAA;CAC7B;AAKD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/stores/settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAwB,MAAM,aAAa,CAAA;AAQ9D,OAAO,EAAC,wBAAwB,EAAE,gCAAgC,EAAC,MAAM,wBAAwB,CAAA;AAEjG,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,GAAG,CAAA;IACvB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IAGrB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAEjC,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAA;IAEpB,OAAO,EAAE,OAAO,CAAA;IAQhB,mBAAmB,CAAC,EAAE,IAAI,CAAA;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAA;CAC7B;AAKD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAslCnC,CAAA;AAEV,eAAO,MAAM,eAAe,EAAE,MAAM,EAAuD,CAAA;AAI3F,eAAO,MAAM,sBAAsB,EAAE,MAAM,EA2D1C,CAAA;AAeD,eAAO,MAAM,qBAAqB,EAAE,MAAM,EAEV,CAAA;AAIhC,MAAM,WAAW,aAAa;IAE5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE7B,aAAa,EAAE,OAAO,CAAA;IAEtB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzF,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,GAAG,CAAA;IAEhC,eAAe,EAAE,MAAM,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAE/C,oBAAoB,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC/C,uBAAuB,EAAE,MAAM,IAAI,CAAA;CACpC;AAoBD,eAAO,MAAM,gBAAgB;;;;;;;;EA+U5B,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,GAAG,GAAG,EAAE,KAAK,MAAM,KAAG,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAI3F,CAAA"}
|
package/build/stores/settings.js
CHANGED
|
@@ -787,6 +787,15 @@ export const SETTINGS = {
|
|
|
787
787
|
saveOnServer: false,
|
|
788
788
|
persist: true,
|
|
789
789
|
},
|
|
790
|
+
// Apple Watch: draw the glasses' current frame behind the watch trackpad. Frames
|
|
791
|
+
// cross WatchConnectivity while the watch app is up, so it stays off unless asked for.
|
|
792
|
+
apple_watch_mirror: {
|
|
793
|
+
key: "apple_watch_mirror",
|
|
794
|
+
defaultValue: () => false,
|
|
795
|
+
writable: true,
|
|
796
|
+
saveOnServer: false,
|
|
797
|
+
persist: true,
|
|
798
|
+
},
|
|
790
799
|
// Maximum rate at which display updates leave the phone for the glasses.
|
|
791
800
|
// The Bluetooth SDK paces every scene frame and legacy layout to this,
|
|
792
801
|
// coalescing faster updates last-wins; clears bypass it. No effect on G2
|