@glassly/miniapp 0.3.0-dev.2 → 0.3.0-dev.462
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/README.md +219 -74
- package/dist/background/index.d.ts +14 -8
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +4 -0
- package/dist/background/index.js.map +1 -1
- package/dist/background/register.d.ts +1 -1
- package/dist/background/register.js +1 -1
- package/dist/display/index.d.ts +23 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +17 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/measurer/TextMeasurer.d.ts +160 -0
- package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
- package/dist/display/measurer/TextMeasurer.js +272 -0
- package/dist/display/measurer/TextMeasurer.js.map +1 -0
- package/dist/display/measurer/index.d.ts +10 -0
- package/dist/display/measurer/index.d.ts.map +1 -0
- package/dist/display/measurer/index.js +11 -0
- package/dist/display/measurer/index.js.map +1 -0
- package/dist/display/measurer/script-detection.d.ts +53 -0
- package/dist/display/measurer/script-detection.d.ts.map +1 -0
- package/dist/display/measurer/script-detection.js +184 -0
- package/dist/display/measurer/script-detection.js.map +1 -0
- package/dist/display/pixelArt.d.ts +22 -0
- package/dist/display/pixelArt.d.ts.map +1 -0
- package/dist/display/pixelArt.js +126 -0
- package/dist/display/pixelArt.js.map +1 -0
- package/dist/display/profiles/g1.d.ts +33 -0
- package/dist/display/profiles/g1.d.ts.map +1 -0
- package/dist/display/profiles/g1.js +214 -0
- package/dist/display/profiles/g1.js.map +1 -0
- package/dist/display/profiles/g2.d.ts +13 -0
- package/dist/display/profiles/g2.d.ts.map +1 -0
- package/dist/display/profiles/g2.js +34 -0
- package/dist/display/profiles/g2.js.map +1 -0
- package/dist/display/profiles/index.d.ts +12 -0
- package/dist/display/profiles/index.d.ts.map +1 -0
- package/dist/display/profiles/index.js +15 -0
- package/dist/display/profiles/index.js.map +1 -0
- package/dist/display/profiles/nex.d.ts +10 -0
- package/dist/display/profiles/nex.d.ts.map +1 -0
- package/dist/display/profiles/nex.js +150 -0
- package/dist/display/profiles/nex.js.map +1 -0
- package/dist/display/profiles/types.d.ts +103 -0
- package/dist/display/profiles/types.d.ts.map +1 -0
- package/dist/display/profiles/types.js +2 -0
- package/dist/display/profiles/types.js.map +1 -0
- package/dist/display/profiles/z100.d.ts +33 -0
- package/dist/display/profiles/z100.d.ts.map +1 -0
- package/dist/display/profiles/z100.js +182 -0
- package/dist/display/profiles/z100.js.map +1 -0
- package/dist/display/scene/types.d.ts +292 -0
- package/dist/display/scene/types.d.ts.map +1 -0
- package/dist/display/scene/types.js +0 -0
- package/dist/display/scene/types.js.map +1 -0
- package/dist/display/wrapper/TextWrapper.d.ts +102 -0
- package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
- package/dist/display/wrapper/TextWrapper.js +469 -0
- package/dist/display/wrapper/TextWrapper.js.map +1 -0
- package/dist/display/wrapper/index.d.ts +12 -0
- package/dist/display/wrapper/index.d.ts.map +1 -0
- package/dist/display/wrapper/index.js +12 -0
- package/dist/display/wrapper/index.js.map +1 -0
- package/dist/display/wrapper/types.d.ts +75 -0
- package/dist/display/wrapper/types.d.ts.map +1 -0
- package/dist/display/wrapper/types.js +11 -0
- package/dist/display/wrapper/types.js.map +1 -0
- package/dist/globals.d.ts +5 -5
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/hardware.d.ts +4 -18
- package/dist/hardware.d.ts.map +1 -1
- package/dist/hardware.js +2 -13
- package/dist/hardware.js.map +1 -1
- package/dist/index.d.ts +16 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/knownApps.d.ts +26 -0
- package/dist/knownApps.d.ts.map +1 -0
- package/dist/knownApps.js +140 -0
- package/dist/knownApps.js.map +1 -0
- package/dist/mirror/canvas.d.ts +22 -0
- package/dist/mirror/canvas.d.ts.map +1 -0
- package/dist/mirror/canvas.js +311 -0
- package/dist/mirror/canvas.js.map +1 -0
- package/dist/mirror/geometry.d.ts +69 -0
- package/dist/mirror/geometry.d.ts.map +1 -0
- package/dist/mirror/geometry.js +75 -0
- package/dist/mirror/geometry.js.map +1 -0
- package/dist/mirror/index.d.ts +3 -0
- package/dist/mirror/index.d.ts.map +1 -0
- package/dist/mirror/index.js +2 -0
- package/dist/mirror/index.js.map +1 -0
- package/dist/mirror/types.d.ts +40 -0
- package/dist/mirror/types.d.ts.map +1 -0
- package/dist/mirror/types.js +2 -0
- package/dist/mirror/types.js.map +1 -0
- package/dist/modules/auth.d.ts +1 -1
- package/dist/modules/auth.d.ts.map +1 -1
- package/dist/modules/auth.js +2 -2
- package/dist/modules/auth.js.map +1 -1
- package/dist/modules/compass.d.ts +47 -0
- package/dist/modules/compass.d.ts.map +1 -0
- package/dist/modules/compass.js +39 -0
- package/dist/modules/compass.js.map +1 -0
- package/dist/modules/display.d.ts +192 -8
- package/dist/modules/display.d.ts.map +1 -1
- package/dist/modules/display.js +40 -3
- package/dist/modules/display.js.map +1 -1
- package/dist/modules/events.d.ts +30 -2
- package/dist/modules/events.d.ts.map +1 -1
- package/dist/modules/events.js.map +1 -1
- package/dist/modules/input.d.ts +40 -1
- package/dist/modules/input.d.ts.map +1 -1
- package/dist/modules/input.js +45 -1
- package/dist/modules/input.js.map +1 -1
- package/dist/modules/latest-render-scheduler.d.ts +19 -0
- package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
- package/dist/modules/latest-render-scheduler.js +54 -0
- package/dist/modules/latest-render-scheduler.js.map +1 -0
- package/dist/modules/lightSensor.d.ts +44 -0
- package/dist/modules/lightSensor.d.ts.map +1 -0
- package/dist/modules/lightSensor.js +33 -0
- package/dist/modules/lightSensor.js.map +1 -0
- package/dist/modules/mic.d.ts +3 -2
- package/dist/modules/mic.d.ts.map +1 -1
- package/dist/modules/mic.js +3 -2
- package/dist/modules/mic.js.map +1 -1
- package/dist/modules/miniapps.d.ts +2 -0
- package/dist/modules/miniapps.d.ts.map +1 -1
- package/dist/modules/miniapps.js.map +1 -1
- package/dist/modules/mirror.d.ts +16 -0
- package/dist/modules/mirror.d.ts.map +1 -0
- package/dist/modules/mirror.js +40 -0
- package/dist/modules/mirror.js.map +1 -0
- package/dist/modules/mono-text-rows.d.ts +20 -0
- package/dist/modules/mono-text-rows.d.ts.map +1 -0
- package/dist/modules/mono-text-rows.js +26 -0
- package/dist/modules/mono-text-rows.js.map +1 -0
- package/dist/modules/navigation.d.ts.map +1 -1
- package/dist/modules/navigation.js +21 -14
- package/dist/modules/navigation.js.map +1 -1
- package/dist/modules/phone.d.ts +26 -1
- package/dist/modules/phone.d.ts.map +1 -1
- package/dist/modules/phone.js +28 -0
- package/dist/modules/phone.js.map +1 -1
- package/dist/modules/settings.d.ts +45 -0
- package/dist/modules/settings.d.ts.map +1 -0
- package/dist/modules/settings.js +54 -0
- package/dist/modules/settings.js.map +1 -0
- package/dist/modules/system.d.ts +21 -0
- package/dist/modules/system.d.ts.map +1 -1
- package/dist/modules/system.js +12 -0
- package/dist/modules/system.js.map +1 -1
- package/dist/modules/transcription.d.ts +3 -2
- package/dist/modules/transcription.d.ts.map +1 -1
- package/dist/modules/transcription.js +3 -2
- package/dist/modules/transcription.js.map +1 -1
- package/dist/protocol.d.ts +65 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +66 -5
- package/dist/protocol.js.map +1 -1
- package/dist/react/MirrorCanvas.d.ts +10 -0
- package/dist/react/MirrorCanvas.d.ts.map +1 -0
- package/dist/react/MirrorCanvas.js +23 -0
- package/dist/react/MirrorCanvas.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useColorScheme.d.ts +2 -3
- package/dist/react/useColorScheme.d.ts.map +1 -1
- package/dist/react/useColorScheme.js +15 -8
- package/dist/react/useColorScheme.js.map +1 -1
- package/dist/react/useRpc.js +2 -2
- package/dist/react/useRpc.js.map +1 -1
- package/dist/react/useSafeArea.d.ts.map +1 -1
- package/dist/react/useSafeArea.js +2 -2
- package/dist/react/useSafeArea.js.map +1 -1
- package/dist/session.d.ts +50 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -6
- package/dist/session.js.map +1 -1
- package/dist/transport/auto.d.ts +2 -2
- package/dist/transport/auto.js +2 -2
- package/dist/transport/dispatch.d.ts +1 -1
- package/dist/transport/dispatch.js +1 -1
- package/dist/transport/mock.d.ts +4 -4
- package/dist/transport/mock.d.ts.map +1 -1
- package/dist/transport/mock.js +31 -7
- package/dist/transport/mock.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +19 -3
- package/src/background/index.ts +26 -4
- package/src/background/register.ts +1 -1
- package/src/display/index.ts +83 -0
- package/src/display/measurer/TextMeasurer.ts +346 -0
- package/src/display/measurer/index.ts +21 -0
- package/src/display/measurer/script-detection.ts +218 -0
- package/src/display/pixelArt.ts +141 -0
- package/src/display/profiles/g1.ts +237 -0
- package/src/display/profiles/g2.ts +39 -0
- package/src/display/profiles/index.ts +28 -0
- package/src/display/profiles/nex.ts +166 -0
- package/src/display/profiles/types.ts +132 -0
- package/src/display/profiles/z100.ts +198 -0
- package/src/display/scene/types.ts +335 -0
- package/src/display/wrapper/TextWrapper.ts +546 -0
- package/src/display/wrapper/index.ts +15 -0
- package/src/display/wrapper/types.ts +102 -0
- package/src/globals.ts +5 -5
- package/src/hardware.ts +6 -21
- package/src/index.ts +34 -6
- package/src/knownApps.ts +170 -0
- package/src/mirror/canvas.ts +322 -0
- package/src/mirror/geometry.ts +128 -0
- package/src/mirror/index.ts +2 -0
- package/src/mirror/types.ts +38 -0
- package/src/modules/auth.ts +2 -2
- package/src/modules/compass.ts +69 -0
- package/src/modules/display.ts +222 -12
- package/src/modules/events.ts +27 -1
- package/src/modules/input.ts +55 -2
- package/src/modules/latest-render-scheduler.ts +55 -0
- package/src/modules/lightSensor.ts +61 -0
- package/src/modules/mic.ts +3 -2
- package/src/modules/miniapps.ts +2 -0
- package/src/modules/mirror.ts +42 -0
- package/src/modules/mono-text-rows.ts +30 -0
- package/src/modules/navigation.ts +20 -14
- package/src/modules/phone.ts +40 -1
- package/src/modules/settings.ts +64 -0
- package/src/modules/system.ts +30 -1
- package/src/modules/transcription.ts +3 -2
- package/src/protocol.ts +72 -5
- package/src/react/MirrorCanvas.tsx +35 -0
- package/src/react/index.ts +3 -0
- package/src/react/useColorScheme.ts +17 -8
- package/src/react/useRpc.ts +2 -2
- package/src/react/useSafeArea.ts +2 -2
- package/src/session.ts +78 -8
- package/src/transport/auto.ts +2 -2
- package/src/transport/dispatch.ts +1 -1
- package/src/transport/mock.ts +42 -11
- package/src/ui/index.ts +3 -0
- package/dist/modules/heading.d.ts +0 -16
- package/dist/modules/heading.d.ts.map +0 -1
- package/dist/modules/heading.js +0 -20
- package/dist/modules/heading.js.map +0 -1
- package/src/modules/heading.ts +0 -23
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export const SHAPE_TYPES = new Set(["rect", "circle", "arc", "pie", "line", "triangle", "quad", "bezier", "svg"])
|
|
2
|
+
|
|
3
|
+
/** The mirror consumes processed frames; it does not own native scene state. */
|
|
4
|
+
export interface MirrorSceneElement {
|
|
5
|
+
id?: string
|
|
6
|
+
type?: string
|
|
7
|
+
box?: {x?: number; y?: number; w?: number; h?: number}
|
|
8
|
+
points?: readonly {x: number; y: number}[]
|
|
9
|
+
style?: {
|
|
10
|
+
color?: number
|
|
11
|
+
width?: number
|
|
12
|
+
border?: number
|
|
13
|
+
radius?: number
|
|
14
|
+
fill?: boolean
|
|
15
|
+
font?: string
|
|
16
|
+
fontSize?: number
|
|
17
|
+
}
|
|
18
|
+
startDeg?: number
|
|
19
|
+
endDeg?: number
|
|
20
|
+
rotation?: number
|
|
21
|
+
pivot?: {x: number; y: number}
|
|
22
|
+
pathScale?: number
|
|
23
|
+
d?: string
|
|
24
|
+
fillRule?: string
|
|
25
|
+
text?: string
|
|
26
|
+
data?: string
|
|
27
|
+
change?: string
|
|
28
|
+
transition?: {durationMs: number; easing?: string}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Tweenable state of one scene element, in canvas pixels. */
|
|
32
|
+
export interface SceneGeometry {
|
|
33
|
+
x: number
|
|
34
|
+
y: number
|
|
35
|
+
w: number
|
|
36
|
+
h: number
|
|
37
|
+
/** Ink intensity derived from the gray level (0..15 → 0..1). */
|
|
38
|
+
level: number
|
|
39
|
+
/** Stroke width in canvas px; 0 = no outline. */
|
|
40
|
+
stroke: number
|
|
41
|
+
radius: number
|
|
42
|
+
/** Flat x0, y0, x1, y1, … for point shapes. */
|
|
43
|
+
points: number[]
|
|
44
|
+
startDeg: number
|
|
45
|
+
endDeg: number
|
|
46
|
+
rotation: number
|
|
47
|
+
pivotX: number
|
|
48
|
+
pivotY: number
|
|
49
|
+
pathScale: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Gray level 0..15 → normalized ink intensity. */
|
|
53
|
+
export function levelOpacity(color: unknown): number {
|
|
54
|
+
const level = Number(color)
|
|
55
|
+
return Number.isFinite(level) ? Math.max(0, Math.min(15, level)) / 15 : 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function sceneGeometry(element: MirrorSceneElement): SceneGeometry {
|
|
59
|
+
const box = element?.box ?? {}
|
|
60
|
+
const style = element?.style ?? {}
|
|
61
|
+
const type = element?.type
|
|
62
|
+
const stroke =
|
|
63
|
+
type !== "rect" && SHAPE_TYPES.has(type ?? "")
|
|
64
|
+
? Number(style.width) || Number(style.border) || 1
|
|
65
|
+
: Number(style.border) || (type === "rect" && style.fill !== true ? 1 : 0)
|
|
66
|
+
const points: number[] = Array.isArray(element?.points)
|
|
67
|
+
? element.points.flatMap((p) => [Number(p?.x) || 0, Number(p?.y) || 0])
|
|
68
|
+
: []
|
|
69
|
+
return {
|
|
70
|
+
x: Number(box.x) || 0,
|
|
71
|
+
y: Number(box.y) || 0,
|
|
72
|
+
w: Math.max(0, Number(box.w) || 0),
|
|
73
|
+
h: Math.max(0, Number(box.h) || 0),
|
|
74
|
+
level: levelOpacity(style.color),
|
|
75
|
+
stroke,
|
|
76
|
+
radius: Number(style.radius) || 0,
|
|
77
|
+
points,
|
|
78
|
+
startDeg: Number(element?.startDeg) || 0,
|
|
79
|
+
endDeg: Number(element?.endDeg) || 0,
|
|
80
|
+
rotation: Number(element?.rotation) || 0,
|
|
81
|
+
pivotX: Number(element?.pivot?.x ?? Number(box.x || 0) + Number(box.w || 0) / 2),
|
|
82
|
+
pivotY: Number(element?.pivot?.y ?? Number(box.y || 0) + Number(box.h || 0) / 2),
|
|
83
|
+
pathScale: Number(element?.pathScale ?? 256) / 256,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function geometryEqual(a: SceneGeometry, b: SceneGeometry): boolean {
|
|
88
|
+
return (
|
|
89
|
+
a.x === b.x &&
|
|
90
|
+
a.y === b.y &&
|
|
91
|
+
a.w === b.w &&
|
|
92
|
+
a.h === b.h &&
|
|
93
|
+
a.level === b.level &&
|
|
94
|
+
a.stroke === b.stroke &&
|
|
95
|
+
a.radius === b.radius &&
|
|
96
|
+
a.startDeg === b.startDeg &&
|
|
97
|
+
a.endDeg === b.endDeg &&
|
|
98
|
+
a.pathScale === b.pathScale &&
|
|
99
|
+
a.points.length === b.points.length &&
|
|
100
|
+
a.points.every((p, i) => p === b.points[i])
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Linear blend of two geometries at eased progress `t` (0..1). */
|
|
105
|
+
export function tweenAt(from: SceneGeometry, to: SceneGeometry, t: number): SceneGeometry {
|
|
106
|
+
"worklet"
|
|
107
|
+
if (t >= 1) return to
|
|
108
|
+
if (t <= 0) return from
|
|
109
|
+
const mix = (a: number, b: number) => a + (b - a) * t
|
|
110
|
+
const points = from.points.length === to.points.length ? to.points.map((p, i) => mix(from.points[i], p)) : to.points
|
|
111
|
+
return {
|
|
112
|
+
x: mix(from.x, to.x),
|
|
113
|
+
y: mix(from.y, to.y),
|
|
114
|
+
w: mix(from.w, to.w),
|
|
115
|
+
h: mix(from.h, to.h),
|
|
116
|
+
level: mix(from.level, to.level),
|
|
117
|
+
stroke: mix(from.stroke, to.stroke),
|
|
118
|
+
radius: mix(from.radius, to.radius),
|
|
119
|
+
points,
|
|
120
|
+
startDeg: mix(from.startDeg, to.startDeg),
|
|
121
|
+
endDeg: mix(from.endDeg, to.endDeg),
|
|
122
|
+
rotation: mix(from.rotation, to.rotation),
|
|
123
|
+
// Firmware changes the fixed pivot immediately when a rotation retargets.
|
|
124
|
+
pivotX: to.pivotX,
|
|
125
|
+
pivotY: to.pivotY,
|
|
126
|
+
pathScale: mix(from.pathScale, to.pathScale),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FrameElement,
|
|
3
|
+
SceneTextStyle,
|
|
4
|
+
SceneRectStyle,
|
|
5
|
+
SceneShapeStyle,
|
|
6
|
+
SceneStrokeStyle,
|
|
7
|
+
SceneImageStyle,
|
|
8
|
+
} from "../display/scene/types"
|
|
9
|
+
|
|
10
|
+
type ReadonlyValue<T> = T extends object ? {readonly [K in keyof T]: ReadonlyValue<T[K]>} : T
|
|
11
|
+
|
|
12
|
+
/** Ordered, processed scene element. Coordinates are glasses canvas pixels. */
|
|
13
|
+
export type MirrorElement = ReadonlyValue<
|
|
14
|
+
Omit<FrameElement, "path" | "contentHash" | "style"> & {
|
|
15
|
+
style?: SceneTextStyle & SceneRectStyle & SceneShapeStyle & SceneStrokeStyle & SceneImageStyle
|
|
16
|
+
}
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
export type MirrorContent =
|
|
20
|
+
| {readonly kind: "scene"; readonly elements: readonly MirrorElement[]; readonly epoch: number}
|
|
21
|
+
| {readonly kind: "bitmap"; readonly data: string}
|
|
22
|
+
| {readonly kind: "empty"}
|
|
23
|
+
| {readonly kind: "unavailable"; readonly reason: string}
|
|
24
|
+
|
|
25
|
+
/** Serializable intended output after display arbitration; never a hardware screenshot. */
|
|
26
|
+
export interface MirrorSnapshot {
|
|
27
|
+
/** Monotonic within this host runtime; useful when mixing requests and updates. */
|
|
28
|
+
readonly revision: number
|
|
29
|
+
readonly updatedAt: number
|
|
30
|
+
readonly connected: boolean
|
|
31
|
+
readonly model: string | null
|
|
32
|
+
readonly canvas: {readonly width: number; readonly height: number} | null
|
|
33
|
+
readonly view: "main" | "dashboard" | null
|
|
34
|
+
readonly appId: string | null
|
|
35
|
+
readonly content: MirrorContent
|
|
36
|
+
/** Rendered advances for the code points in this snapshot (builtin text). */
|
|
37
|
+
readonly glyphWidths: Readonly<Record<string, number>>
|
|
38
|
+
}
|
package/src/modules/auth.ts
CHANGED
|
@@ -18,8 +18,8 @@ export class AuthModule {
|
|
|
18
18
|
return this.session._getAuth()
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
get
|
|
22
|
-
return this.current?.
|
|
21
|
+
get userId(): string | null {
|
|
22
|
+
return this.current?.userId ?? null
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
get oemId(): string | null {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glasses compass. Subscribing starts the sensor like other glasses streams:
|
|
3
|
+
* on G2 custom firmware the raw compass API runs whenever any miniapp is
|
|
4
|
+
* subscribed; on stock firmware headings only arrive while the glasses' native
|
|
5
|
+
* navigation UI is open. Elsewhere the subscription succeeds and stays silent.
|
|
6
|
+
*/
|
|
7
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
8
|
+
import type {MiniappSession} from "../session"
|
|
9
|
+
import type {UnsubscribeFn} from "./events"
|
|
10
|
+
|
|
11
|
+
export interface CompassOptions {
|
|
12
|
+
/** 50..2000 ms, default 1000. */
|
|
13
|
+
intervalMs?: number
|
|
14
|
+
/** Heading change threshold, 0..359 degrees, default 5. */
|
|
15
|
+
minChangeDegrees?: number
|
|
16
|
+
}
|
|
17
|
+
export interface CompassDiagnostics {
|
|
18
|
+
accuracy: 0 | 1 | 2 | 3 | null
|
|
19
|
+
anomalies: 0 | 1 | 2 | null
|
|
20
|
+
source: "unknown" | "grv" | "gmrv" | "rv"
|
|
21
|
+
sampleMatched: boolean
|
|
22
|
+
grvValid: boolean
|
|
23
|
+
gmrvValid: boolean
|
|
24
|
+
rvValid: boolean
|
|
25
|
+
magneticDataFresh: boolean
|
|
26
|
+
/** Firmware uptime, wraps at 2^32 ms. */
|
|
27
|
+
sampleTimestampMs: number
|
|
28
|
+
}
|
|
29
|
+
export interface CompassData {
|
|
30
|
+
/** Magnetic heading in degrees, 0 = north, 90 = east. Fused heading, not raw magnetometer XYZ. */
|
|
31
|
+
degrees: number
|
|
32
|
+
/** Phone reception time, Unix milliseconds. */
|
|
33
|
+
timestamp: number
|
|
34
|
+
/** Fusion quality; custom firmware only. */
|
|
35
|
+
diagnostics?: CompassDiagnostics
|
|
36
|
+
}
|
|
37
|
+
export interface CompassCalibrationData {
|
|
38
|
+
status: "start" | "complete"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class CompassModule {
|
|
42
|
+
constructor(private readonly session: MiniappSession) {}
|
|
43
|
+
onUpdate(handler: (data: CompassData) => void): UnsubscribeFn {
|
|
44
|
+
return this.session._subscribe(MiniappStreamType.GLASSES_COMPASS, handler as (data: unknown) => void)
|
|
45
|
+
}
|
|
46
|
+
onCalibration(handler: (data: CompassCalibrationData) => void): UnsubscribeFn {
|
|
47
|
+
return this.session._subscribe(MiniappStreamType.COMPASS_CALIBRATION, handler as (data: unknown) => void)
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Override the sampling this miniapp asks for. Subscribers already run with
|
|
51
|
+
* firmware defaults; the phone serves every subscriber the smallest interval
|
|
52
|
+
* and smallest change threshold requested across miniapps. Custom firmware only.
|
|
53
|
+
*/
|
|
54
|
+
start(options: CompassOptions = {}): Promise<void> {
|
|
55
|
+
return this.control("start", options)
|
|
56
|
+
}
|
|
57
|
+
/** Drop this miniapp's sampling override. Live subscriptions keep the sensor running. */
|
|
58
|
+
stop(): Promise<void> {
|
|
59
|
+
return this.control("stop")
|
|
60
|
+
}
|
|
61
|
+
private control(operation: string, options: CompassOptions = {}): Promise<void> {
|
|
62
|
+
return this.session.sendRequest({
|
|
63
|
+
type: MiniappRequestType.GLASSES_SENSOR_CONTROL,
|
|
64
|
+
sensor: "compass",
|
|
65
|
+
operation,
|
|
66
|
+
options,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/modules/display.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* from bundles packed with older SDKs.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {MiniappRequestType} from "../protocol"
|
|
12
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
13
13
|
import {MiniappSession} from "../session"
|
|
14
|
+
import type {UnsubscribeFn} from "./events"
|
|
14
15
|
|
|
15
16
|
export type ViewType = "main" | "dashboard"
|
|
16
17
|
|
|
@@ -57,26 +58,155 @@ export interface RenderTextStyle {
|
|
|
57
58
|
* of 1.2 × size, and NOT re-wrapped by the host — lines are taken as given.
|
|
58
59
|
* For grids (terminals, tables). Devices without a phone-side rasterizer
|
|
59
60
|
* (stock G2 firmware) fall back to their own proportional font.
|
|
61
|
+
*
|
|
62
|
+
* "builtin": the glasses draw the text themselves with their built-in 20 px
|
|
63
|
+
* proportional font. On the Glassly CFW G2 the default face is drawn the
|
|
64
|
+
* same way, so text updates are a few bytes each and animate like shapes;
|
|
65
|
+
* lines are clipped to the box and the render window is pinned to the
|
|
66
|
+
* middle depth level. Other devices and stock firmware use the phone's
|
|
67
|
+
* proportional face.
|
|
60
68
|
*/
|
|
61
|
-
font?: "default" | "mono"
|
|
69
|
+
font?: "default" | "mono" | "builtin"
|
|
62
70
|
/** Font size in px for `font: "mono"`. Default 20. */
|
|
63
71
|
fontSize?: number
|
|
72
|
+
/** Gray level 0..15 (15 = brightest) on devices that draw text themselves. Default 15. */
|
|
73
|
+
color?: number
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
export interface RenderRectStyle {
|
|
67
77
|
border?: number
|
|
68
78
|
radius?: number
|
|
79
|
+
/** Solid fill instead of an outline. */
|
|
80
|
+
fill?: boolean
|
|
81
|
+
/** Gray level 0..15 (15 = brightest). Default 15. */
|
|
82
|
+
color?: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Closed shapes (circle, triangle, quad): an outline `border` px wide, or filled. */
|
|
86
|
+
export interface RenderShapeStyle {
|
|
87
|
+
border?: number
|
|
88
|
+
fill?: boolean
|
|
89
|
+
/** Gray level 0..15 (15 = brightest). Default 15. */
|
|
90
|
+
color?: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Open strokes (line, bezier, arc). */
|
|
94
|
+
export interface RenderStrokeStyle {
|
|
95
|
+
/** Stroke width in px. Default 1. */
|
|
96
|
+
width?: number
|
|
97
|
+
/** Gray level 0..15 (15 = brightest). Default 15. */
|
|
98
|
+
color?: number
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A point in raw canvas pixels (no anchoring). */
|
|
102
|
+
export interface RenderPoint {
|
|
103
|
+
x: number
|
|
104
|
+
y: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type RenderEasing = "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out"
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Animate an element's geometry between renders. When an element with a
|
|
111
|
+
* stable `id` moves, resizes, or changes its points/angles/color, devices
|
|
112
|
+
* with `capabilities.display.animation` tween it on the glasses over
|
|
113
|
+
* `durationMs`; other devices jump. Content changes (new text or image
|
|
114
|
+
* pixels) always switch immediately.
|
|
115
|
+
*/
|
|
116
|
+
export interface RenderTransition {
|
|
117
|
+
durationMs: number
|
|
118
|
+
/** Default "ease". */
|
|
119
|
+
easing?: RenderEasing
|
|
69
120
|
}
|
|
70
121
|
|
|
71
122
|
/**
|
|
72
123
|
* One element of a rendered scene. `id` is optional but recommended for
|
|
73
124
|
* anything that updates over time: elements with a stable id update in place on
|
|
74
125
|
* the glasses (no flicker); the host matches unnamed elements by geometry.
|
|
126
|
+
*
|
|
127
|
+
* Shapes beyond `rect` need `capabilities.display.shapes` to list the type;
|
|
128
|
+
* a device without it drops the element (reported via `dropped`). Circles,
|
|
129
|
+
* arcs and pies are inscribed in their box (radius = half the shorter side;
|
|
130
|
+
* angles in degrees, 0 = right, increasing clockwise). Point shapes take
|
|
131
|
+
* raw canvas coordinates and are clipped by the device; `bezier` is
|
|
132
|
+
* quadratic with 3 points or cubic with 4.
|
|
75
133
|
*/
|
|
76
|
-
export type RenderElement =
|
|
77
|
-
| {type: "text"; id?: string; box: RenderBox; text: string; style?: RenderTextStyle}
|
|
78
|
-
| {type: "image"; id?: string; box: RenderBox; data: string}
|
|
79
|
-
| {type: "rect"; id?: string; box: RenderBox; style?: RenderRectStyle}
|
|
134
|
+
export type RenderElement = (
|
|
135
|
+
| {type: "text"; id?: string; box: RenderBox; text: string; style?: RenderTextStyle; transition?: RenderTransition}
|
|
136
|
+
| {type: "image"; id?: string; box: RenderBox; data: string; transition?: RenderTransition}
|
|
137
|
+
| {type: "rect"; id?: string; box: RenderBox; style?: RenderRectStyle; transition?: RenderTransition}
|
|
138
|
+
| {type: "circle"; id?: string; box: RenderBox; style?: RenderShapeStyle; transition?: RenderTransition}
|
|
139
|
+
| {
|
|
140
|
+
type: "arc"
|
|
141
|
+
id?: string
|
|
142
|
+
box: RenderBox
|
|
143
|
+
startDeg: number
|
|
144
|
+
endDeg: number
|
|
145
|
+
style?: RenderStrokeStyle
|
|
146
|
+
transition?: RenderTransition
|
|
147
|
+
}
|
|
148
|
+
| {
|
|
149
|
+
type: "pie"
|
|
150
|
+
id?: string
|
|
151
|
+
box: RenderBox
|
|
152
|
+
startDeg: number
|
|
153
|
+
endDeg: number
|
|
154
|
+
style?: {color?: number}
|
|
155
|
+
transition?: RenderTransition
|
|
156
|
+
}
|
|
157
|
+
| {
|
|
158
|
+
type: "line"
|
|
159
|
+
id?: string
|
|
160
|
+
points: [RenderPoint, RenderPoint]
|
|
161
|
+
style?: RenderStrokeStyle
|
|
162
|
+
transition?: RenderTransition
|
|
163
|
+
}
|
|
164
|
+
| {
|
|
165
|
+
type: "triangle"
|
|
166
|
+
id?: string
|
|
167
|
+
points: [RenderPoint, RenderPoint, RenderPoint]
|
|
168
|
+
style?: RenderShapeStyle
|
|
169
|
+
transition?: RenderTransition
|
|
170
|
+
}
|
|
171
|
+
| {
|
|
172
|
+
type: "quad"
|
|
173
|
+
id?: string
|
|
174
|
+
points: [RenderPoint, RenderPoint, RenderPoint, RenderPoint]
|
|
175
|
+
style?: RenderShapeStyle
|
|
176
|
+
transition?: RenderTransition
|
|
177
|
+
}
|
|
178
|
+
| {
|
|
179
|
+
type: "bezier"
|
|
180
|
+
id?: string
|
|
181
|
+
points: [RenderPoint, RenderPoint, RenderPoint] | [RenderPoint, RenderPoint, RenderPoint, RenderPoint]
|
|
182
|
+
style?: RenderStrokeStyle
|
|
183
|
+
transition?: RenderTransition
|
|
184
|
+
}
|
|
185
|
+
| {
|
|
186
|
+
type: "svg"
|
|
187
|
+
id?: string
|
|
188
|
+
box: RenderBox
|
|
189
|
+
d: string
|
|
190
|
+
scale?: number
|
|
191
|
+
fillRule?: "nonzero" | "evenodd"
|
|
192
|
+
style?: {color?: number}
|
|
193
|
+
transition?: RenderTransition
|
|
194
|
+
}
|
|
195
|
+
) & {
|
|
196
|
+
/** Clockwise degrees, unwrapped (0 to 360 makes a full turn). Geometric shapes and SVG only. */
|
|
197
|
+
rotation?: number
|
|
198
|
+
/** Fixed pivot in canvas pixels; defaults to the target box center. */
|
|
199
|
+
pivot?: RenderPoint
|
|
200
|
+
/**
|
|
201
|
+
* Stereo depth in canvas pixels, -16..16: positive floats the element in
|
|
202
|
+
* front of the rest of the frame, negative sinks it behind. Devices with
|
|
203
|
+
* `capabilities.display.depth` shift it by `depth` px on the left lens and
|
|
204
|
+
* `-depth` px on the right; others draw it flat. Elements paint in array
|
|
205
|
+
* order, so a card that should sit on top goes last AND at a positive depth.
|
|
206
|
+
* A depth change with a `transition` glides toward or away from the viewer.
|
|
207
|
+
*/
|
|
208
|
+
depth?: number
|
|
209
|
+
}
|
|
80
210
|
|
|
81
211
|
export interface RenderOptions {
|
|
82
212
|
view?: ViewType
|
|
@@ -84,6 +214,38 @@ export interface RenderOptions {
|
|
|
84
214
|
durationMs?: number
|
|
85
215
|
}
|
|
86
216
|
|
|
217
|
+
// ============================================================================
|
|
218
|
+
// The dashboard view — the overlay slot above the main content
|
|
219
|
+
// ============================================================================
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* "shown" = the requested view is showing (already showing counts).
|
|
223
|
+
* "unsupported" = the host refused: the glasses cannot show the dashboard
|
|
224
|
+
* view right now, or this app is not the designated dashboard app.
|
|
225
|
+
*/
|
|
226
|
+
export interface ShowViewResult {
|
|
227
|
+
status: "shown" | "unsupported"
|
|
228
|
+
reason?: string
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Why the showing view changed. "shown" and "hidden" answer this app's own
|
|
233
|
+
* showView calls; "replaced" means another overlay app took the view;
|
|
234
|
+
* "ineligible" means the glasses stopped being able to show the dashboard
|
|
235
|
+
* view (disconnected, dashboard turned off); "app_stopped" is implied when
|
|
236
|
+
* the app itself stops and never arrives as an event.
|
|
237
|
+
*/
|
|
238
|
+
export type DisplayViewChangeReason = "shown" | "hidden" | "replaced" | "ineligible" | "app_stopped"
|
|
239
|
+
|
|
240
|
+
export interface DisplayViewChange {
|
|
241
|
+
/** The view now showing. */
|
|
242
|
+
view: ViewType
|
|
243
|
+
reason: DisplayViewChangeReason
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Wire shape of the display_view stream. */
|
|
247
|
+
type DisplayViewEvent = {kind: "change"; view: ViewType; reason: DisplayViewChangeReason}
|
|
248
|
+
|
|
87
249
|
// ============================================================================
|
|
88
250
|
// Native navigation UI (capability `hasNativeNavigation`, e.g. G2)
|
|
89
251
|
// ============================================================================
|
|
@@ -115,10 +277,12 @@ export interface NativeNavResult {
|
|
|
115
277
|
}
|
|
116
278
|
|
|
117
279
|
/**
|
|
118
|
-
* Outcome of a render request
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
280
|
+
* Outcome of a render request. "displayed" = the host accepted the frame;
|
|
281
|
+
* on scene devices it resolves once the glasses have the frame and its
|
|
282
|
+
* transitions have finished playing (`presented`), or once the host knows
|
|
283
|
+
* the frame won't show right now (`presented: false`, `reason` says why —
|
|
284
|
+
* dashboard covering the view, screen off, nav UI up, glasses busy).
|
|
285
|
+
* "blocked" = another app owns the display (or the request failed).
|
|
122
286
|
*/
|
|
123
287
|
export interface RenderResult {
|
|
124
288
|
status: "displayed" | "blocked"
|
|
@@ -126,6 +290,13 @@ export interface RenderResult {
|
|
|
126
290
|
degraded?: boolean
|
|
127
291
|
/** Ids of elements the host dropped (budget/bounds/device limits) — dropped is never silent. */
|
|
128
292
|
dropped?: string[]
|
|
293
|
+
/**
|
|
294
|
+
* The glasses showed this frame's content before the promise resolved.
|
|
295
|
+
* Absent on legacy/degrade paths, where "displayed" still just means sent.
|
|
296
|
+
*/
|
|
297
|
+
presented?: boolean
|
|
298
|
+
/** A newer render() from this app landed before this one was reported; its content is on screen. */
|
|
299
|
+
superseded?: boolean
|
|
129
300
|
reason?: string
|
|
130
301
|
}
|
|
131
302
|
|
|
@@ -146,14 +317,21 @@ export class DisplayManager {
|
|
|
146
317
|
* over-budget elements are dropped tail-first; both are reported via the
|
|
147
318
|
* result, never silently.
|
|
148
319
|
*
|
|
149
|
-
* Awaiting is OPT-IN — a plain fire-and-forget call is fine.
|
|
150
|
-
* promise
|
|
320
|
+
* Awaiting is OPT-IN — a plain fire-and-forget call is fine. Awaited, the
|
|
321
|
+
* promise resolves once the glasses have the frame and any transitions it
|
|
322
|
+
* started have finished (see `RenderResult.presented`), so `await` in a
|
|
323
|
+
* loop paces you at the real display rate. Frames that arrive faster than
|
|
324
|
+
* the host sends them are folded into the next one; each still resolves
|
|
325
|
+
* when its content is on screen. The returned promise never rejects.
|
|
151
326
|
*
|
|
152
327
|
* @example
|
|
153
328
|
* const result = await session.display.render([
|
|
154
329
|
* {type: "text", id: "stats", box: {x: 12, y: 9, w: 200, h: 28}, text: "863 m 11 min"},
|
|
155
330
|
* {type: "image", id: "map", box: {x: 335, y: 14, w: 150, h: 150}, data: mapPng},
|
|
156
331
|
* {type: "rect", box: {x: 0, y: 0, w: 500, h: 220}, style: {radius: 4}},
|
|
332
|
+
* // Re-render with a new box and the ball glides there over 300 ms on animating devices.
|
|
333
|
+
* {type: "circle", id: "ball", box: {x: 40, y: 100, w: 30, h: 30}, style: {fill: true},
|
|
334
|
+
* transition: {durationMs: 300, easing: "ease-in-out"}},
|
|
157
335
|
* ])
|
|
158
336
|
* if (result.degraded) console.warn("dropped:", result.dropped)
|
|
159
337
|
*/
|
|
@@ -174,6 +352,38 @@ export class DisplayManager {
|
|
|
174
352
|
}))
|
|
175
353
|
}
|
|
176
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Ask the host to show a view. `"dashboard"` takes the overlay slot above
|
|
357
|
+
* the main content: frames rendered with `{view: "dashboard"}` reach the
|
|
358
|
+
* glasses while it shows. Claim the gestures you handle there with
|
|
359
|
+
* `session.input.setClaimedGestures`. `"main"` releases the slot; the
|
|
360
|
+
* retained main content replays. Both are idempotent. Only running apps
|
|
361
|
+
* whose manifest `type` is `"system_dashboard"` may show the dashboard
|
|
362
|
+
* view, and only while `capabilities.display.views` lists it; the most
|
|
363
|
+
* recent of them to ask holds it and the previous one hears "replaced".
|
|
364
|
+
*/
|
|
365
|
+
showView(view: ViewType): Promise<ShowViewResult> {
|
|
366
|
+
return this.session.sendRequest<ShowViewResult>({type: MiniappRequestType.SHOW_VIEW, view}).catch((err) => ({
|
|
367
|
+
status: "unsupported" as const,
|
|
368
|
+
reason:
|
|
369
|
+
typeof err === "object" && err !== null && "message" in err
|
|
370
|
+
? String((err as {message: unknown}).message)
|
|
371
|
+
: String(err),
|
|
372
|
+
}))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* The showing view changed: the host granted the dashboard view, this app
|
|
377
|
+
* released it, another overlay app took it, or the glasses stopped being
|
|
378
|
+
* able to show it. Fires only for system_dashboard apps.
|
|
379
|
+
*/
|
|
380
|
+
onViewChange(handler: (change: DisplayViewChange) => void): UnsubscribeFn {
|
|
381
|
+
return this.session._subscribe(MiniappStreamType.DISPLAY_VIEW, (data) => {
|
|
382
|
+
const event = data as DisplayViewEvent | null
|
|
383
|
+
if (event?.kind === "change") handler({view: event.view, reason: event.reason})
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
|
|
177
387
|
/** Update the native nav UI's turn-by-turn card. */
|
|
178
388
|
sendNativeNavInfo(info: NativeNavInfo): Promise<NativeNavResult> {
|
|
179
389
|
return this.sendNativeNav({action: "info", info})
|
package/src/modules/events.ts
CHANGED
|
@@ -106,7 +106,7 @@ export interface LocationData {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
export interface HeadingData {
|
|
109
|
-
/**
|
|
109
|
+
/** Phone compass heading in degrees, 0 = north, 90 = east. */
|
|
110
110
|
degrees: number
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -162,6 +162,9 @@ export interface TouchData {
|
|
|
162
162
|
* The gesture: single_tap, double_tap, triple_tap, long_press, swipe_up, or
|
|
163
163
|
* swipe_down — plus the raw press edges tap_start (finger down) and tap_end
|
|
164
164
|
* (finger up), which bracket every gesture on hardware that reports them.
|
|
165
|
+
* tap_then_hold is the G2 2.2.9 tap-then-hold (Menu) gesture. touch_down is
|
|
166
|
+
* the R1 ring's finger-down, forwarded by the Glassly CFW (GLASSLYCFW/37 on
|
|
167
|
+
* G2 2.3.0.24); only the ring produces it, the temples report tap_start.
|
|
165
168
|
*/
|
|
166
169
|
kind:
|
|
167
170
|
| "single_tap"
|
|
@@ -172,9 +175,32 @@ export interface TouchData {
|
|
|
172
175
|
| "swipe_down"
|
|
173
176
|
| "tap_start"
|
|
174
177
|
| "tap_end"
|
|
178
|
+
| "tap_then_hold"
|
|
179
|
+
| "touch_down"
|
|
175
180
|
| string
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
/**
|
|
184
|
+
* One key transition from a physical keyboard paired to the glasses (G2
|
|
185
|
+
* Glassly CFW revision 39+). Shapes follow the DOM: `key` is the logical key
|
|
186
|
+
* under the host's layout (US for now: "a", "A", "Enter", "ArrowUp", "F5",
|
|
187
|
+
* "AudioVolumeUp", …), `code` the physical key ("KeyA", "Digit1", "Space",
|
|
188
|
+
* "ControlLeft", …). `text` is the printable character a plain press types
|
|
189
|
+
* (down and repeat only, absent for Ctrl/Alt/Meta chords and named keys).
|
|
190
|
+
* `repeat` marks the host's synthesized auto-repeat while a key stays held;
|
|
191
|
+
* every gap in the link releases held keys with matching `up` events.
|
|
192
|
+
*/
|
|
193
|
+
export interface KeyboardEventData {
|
|
194
|
+
phase: "down" | "up"
|
|
195
|
+
key: string
|
|
196
|
+
code: string
|
|
197
|
+
modifiers: {shift: boolean; ctrl: boolean; alt: boolean; meta: boolean}
|
|
198
|
+
text?: string
|
|
199
|
+
repeat: boolean
|
|
200
|
+
/** Phone receipt time, unix ms. */
|
|
201
|
+
timestamp: number
|
|
202
|
+
}
|
|
203
|
+
|
|
178
204
|
export interface AudioChunkData {
|
|
179
205
|
/** PCM or LC3, base64-encoded. Format depends on phone's mic mode. */
|
|
180
206
|
data: string
|
package/src/modules/input.ts
CHANGED
|
@@ -15,17 +15,70 @@
|
|
|
15
15
|
* the phone runtime fans out alongside the bare `touch_event` stream.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {MiniappStreamType} from "../protocol"
|
|
18
|
+
import {MiniappRequestType, MiniappStreamType} from "../protocol"
|
|
19
19
|
import {MiniappSession} from "../session"
|
|
20
|
-
import type {ButtonPressData, TouchData, UnsubscribeFn} from "./events"
|
|
20
|
+
import type {ButtonPressData, KeyboardEventData, TouchData, UnsubscribeFn} from "./events"
|
|
21
|
+
|
|
22
|
+
export interface GestureClaimChange {
|
|
23
|
+
gesture: string
|
|
24
|
+
/** True when this app now holds the gesture, false when another app took it. */
|
|
25
|
+
held: boolean
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
export class InputModule {
|
|
23
29
|
constructor(private readonly session: MiniappSession) {}
|
|
24
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Declare the gestures this miniapp is handling itself, replacing any
|
|
33
|
+
* previous claim set in the selected scope. Claims stack: the most recent app to claim a gesture
|
|
34
|
+
* holds it and receives it alone through `onTouch`, and the host skips its
|
|
35
|
+
* own default for it (a double tap otherwise leaves the visible miniapp or
|
|
36
|
+
* opens the glasses' dashboard). When the holder releases the gesture, or
|
|
37
|
+
* stops, the previous claimer holds it again; `onClaimChange` reports both
|
|
38
|
+
* sides of every handoff.
|
|
39
|
+
*
|
|
40
|
+
* Pass `[]` to release that scope. Omit `scope` for the default registration.
|
|
41
|
+
* Separate scopes preserve persistent shortcuts while a temporary page claims
|
|
42
|
+
* input: releasing the page scope restores the previous receiver without
|
|
43
|
+
* re-registering the shortcuts. All scopes are cleared automatically when
|
|
44
|
+
* the miniapp stops. One exception stands above the claims: an unclaimed
|
|
45
|
+
* double tap on a miniapp whose frame is visibly on the glasses leaves that
|
|
46
|
+
* app, so claim `double_tap` yourself while you are on screen if you use it.
|
|
47
|
+
*
|
|
48
|
+
* session.input.setClaimedGestures(["double_tap"]) // in-app back gesture
|
|
49
|
+
* session.input.setClaimedGestures([]) // hand it back
|
|
50
|
+
*/
|
|
51
|
+
setClaimedGestures(gestures: string[], options?: {scope?: string}): Promise<void> {
|
|
52
|
+
return this.session
|
|
53
|
+
.sendRequest<void>({
|
|
54
|
+
type: MiniappRequestType.CLAIM_GESTURES,
|
|
55
|
+
gestures,
|
|
56
|
+
...(options?.scope === undefined ? {} : {scope: options.scope}),
|
|
57
|
+
})
|
|
58
|
+
.then(() => undefined)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** This app gained or lost a gesture it claimed (another app claimed it, or released it back). */
|
|
62
|
+
onClaimChange(handler: (change: GestureClaimChange) => void): UnsubscribeFn {
|
|
63
|
+
return this.session._subscribe(MiniappStreamType.GESTURE_CLAIM, handler as (data: unknown) => void)
|
|
64
|
+
}
|
|
65
|
+
|
|
25
66
|
onButtonPress(handler: (data: ButtonPressData) => void): UnsubscribeFn {
|
|
26
67
|
return this.session._subscribe(MiniappStreamType.BUTTON_PRESS, handler as (data: unknown) => void)
|
|
27
68
|
}
|
|
28
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Subscribe to a physical keyboard paired to the glasses (G2 Glassly CFW
|
|
72
|
+
* revision 39+; nothing arrives on other hardware). Every subscriber
|
|
73
|
+
* receives every key, like button presses: check `phase`, use `text` to
|
|
74
|
+
* type printable characters and `key`/`modifiers` for commands.
|
|
75
|
+
*
|
|
76
|
+
* session.input.onKey((e) => { if (e.phase === "down" && e.text) type(e.text) })
|
|
77
|
+
*/
|
|
78
|
+
onKey(handler: (data: KeyboardEventData) => void): UnsubscribeFn {
|
|
79
|
+
return this.session._subscribe(MiniappStreamType.KEYBOARD_EVENT, handler as (data: unknown) => void)
|
|
80
|
+
}
|
|
81
|
+
|
|
29
82
|
/**
|
|
30
83
|
* Subscribe to touch events.
|
|
31
84
|
*
|