@glassly/engine 0.3.7 → 0.3.8-beta.1013
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 +4 -0
- package/build/engine.d.ts +18 -2
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +7 -4
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +3 -0
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -0
- package/build/facades/ai.js.map +1 -1
- package/build/facades/backup.d.ts +14 -0
- package/build/facades/backup.d.ts.map +1 -0
- package/build/facades/backup.js +22 -0
- package/build/facades/backup.js.map +1 -0
- package/build/facades/glasses.d.ts +13 -5
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +17 -4
- package/build/facades/glasses.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/services/AppRegistry.d.ts +2 -1
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +38 -8
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AppStateBackup.d.ts +98 -0
- package/build/services/AppStateBackup.d.ts.map +1 -0
- package/build/services/AppStateBackup.js +224 -0
- package/build/services/AppStateBackup.js.map +1 -0
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +5 -12
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CloudClientService.d.ts +3 -1
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +42 -2
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +1 -0
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/DisplayDiagnostics.d.ts.map +1 -1
- package/build/services/DisplayDiagnostics.js +4 -2
- package/build/services/DisplayDiagnostics.js.map +1 -1
- package/build/services/DisplayProcessor.d.ts +2 -0
- package/build/services/DisplayProcessor.d.ts.map +1 -1
- package/build/services/DisplayProcessor.js +15 -1
- package/build/services/DisplayProcessor.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +4 -2
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/JSCrashloopReportService.d.ts.map +1 -1
- package/build/services/JSCrashloopReportService.js +5 -0
- package/build/services/JSCrashloopReportService.js.map +1 -1
- package/build/services/JSRouter.d.ts +11 -3
- package/build/services/JSRouter.d.ts.map +1 -1
- package/build/services/JSRouter.js +36 -6
- package/build/services/JSRouter.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +5 -2
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +13 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +61 -22
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.js +2 -0
- package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
- package/build/services/LocalTranslationService.d.ts +2 -0
- package/build/services/LocalTranslationService.d.ts.map +1 -1
- package/build/services/LocalTranslationService.js +22 -0
- package/build/services/LocalTranslationService.js.map +1 -1
- package/build/services/MiniappEngine.d.ts +9 -0
- package/build/services/MiniappEngine.d.ts.map +1 -1
- package/build/services/MiniappEngine.js +60 -27
- package/build/services/MiniappEngine.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +2 -0
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +6 -0
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/NavigationService.d.ts +1 -0
- package/build/services/NavigationService.d.ts.map +1 -1
- package/build/services/NavigationService.js +20 -6
- package/build/services/NavigationService.js.map +1 -1
- package/build/services/OfflineActivity.d.ts +54 -0
- package/build/services/OfflineActivity.d.ts.map +1 -0
- package/build/services/OfflineActivity.js +86 -0
- package/build/services/OfflineActivity.js.map +1 -0
- package/build/services/PhoneCalendarService.d.ts.map +1 -1
- package/build/services/PhoneCalendarService.js +8 -0
- package/build/services/PhoneCalendarService.js.map +1 -1
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +7 -3
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/STTModelManager.d.ts +1 -0
- package/build/services/STTModelManager.d.ts.map +1 -1
- package/build/services/STTModelManager.js +6 -0
- package/build/services/STTModelManager.js.map +1 -1
- package/build/services/SceneRenderer.js +1 -1
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/appStateBackupFile.d.ts +32 -0
- package/build/services/appStateBackupFile.d.ts.map +1 -0
- package/build/services/appStateBackupFile.js +135 -0
- package/build/services/appStateBackupFile.js.map +1 -0
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +13 -3
- package/build/services/controllers.js.map +1 -1
- package/build/services/effectiveCapabilities.js +6 -1
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts +12 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.js +27 -0
- package/build/services/neuralBand/NeuralBandInput.js.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.js +6 -0
- package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
- package/build/services/pages/pageEligibility.d.ts.map +1 -1
- package/build/services/pages/pageEligibility.js +8 -1
- package/build/services/pages/pageEligibility.js.map +1 -1
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +1 -0
- package/build/services/settingsBridge.js.map +1 -1
- package/build/services/{appleWatch/AppleWatchLink.d.ts → watch/WatchLink.d.ts} +8 -7
- package/build/services/watch/WatchLink.d.ts.map +1 -0
- package/build/services/{appleWatch/AppleWatchLink.js → watch/WatchLink.js} +14 -15
- package/build/services/watch/WatchLink.js.map +1 -0
- package/build/stores/appVisibility.d.ts.map +1 -1
- package/build/stores/appVisibility.js +0 -1
- package/build/stores/appVisibility.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +22 -10
- package/build/stores/apps.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +71 -6
- package/build/stores/settings.js.map +1 -1
- package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g1.js +3 -0
- package/build/types/capabilities/even-realities-g1.js.map +1 -1
- package/build/types/capabilities/memomind-one.d.ts +3 -0
- package/build/types/capabilities/memomind-one.d.ts.map +1 -0
- package/build/types/capabilities/memomind-one.js +42 -0
- package/build/types/capabilities/memomind-one.js.map +1 -0
- package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
- package/build/types/capabilities/rayneo-io.js +3 -2
- package/build/types/capabilities/rayneo-io.js.map +1 -1
- package/build/types/enums.d.ts +8 -2
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +6 -0
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +3 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +3 -1
- package/build/types/hardware.js.map +1 -1
- package/build/utils/display/index.d.ts +2 -1
- package/build/utils/display/index.d.ts.map +1 -1
- package/build/utils/display/index.js +3 -2
- package/build/utils/display/index.js.map +1 -1
- package/build/utils/display/profiles/index.d.ts +2 -1
- package/build/utils/display/profiles/index.d.ts.map +1 -1
- package/build/utils/display/profiles/index.js +3 -2
- package/build/utils/display/profiles/index.js.map +1 -1
- package/build/utils/display/profiles/rayneo-io.d.ts +1 -1
- package/build/utils/display/profiles/rayneo-io.d.ts.map +1 -1
- package/build/utils/display/profiles/rayneo-io.js +1 -1
- package/build/utils/display/profiles/rayneo-io.js.map +1 -1
- package/build/utils/display/scene/index.d.ts +1 -1
- package/build/utils/display/scene/index.d.ts.map +1 -1
- package/build/utils/display/scene/index.js +1 -1
- package/build/utils/display/scene/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts +6 -0
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +22 -7
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/miniappGlobals.d.ts +13 -17
- package/build/utils/miniappGlobals.d.ts.map +1 -1
- package/build/utils/miniappGlobals.js +24 -56
- package/build/utils/miniappGlobals.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +7 -4
- package/src/facades/ai.ts +4 -0
- package/src/facades/backup.ts +28 -0
- package/src/facades/glasses.ts +22 -5
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +10 -0
- package/src/services/AppRegistry.ts +36 -9
- package/src/services/AppStateBackup.ts +283 -0
- package/src/services/BlobStore.ts +5 -11
- package/src/services/CloudClientService.ts +43 -4
- package/src/services/DeviceEventRouter.ts +1 -0
- package/src/services/DisplayDiagnostics.ts +4 -2
- package/src/services/DisplayProcessor.ts +20 -8
- package/src/services/GlassesTouchInput.ts +6 -2
- package/src/services/JSCrashloopReportService.ts +5 -0
- package/src/services/JSRouter.ts +37 -6
- package/src/services/LocalDisplayManager.ts +5 -2
- package/src/services/LocalMiniappRuntime.ts +61 -22
- package/src/services/LocalSttFallbackCoordinator.ts +4 -0
- package/src/services/LocalTranslationService.ts +20 -0
- package/src/services/MiniappEngine.ts +62 -27
- package/src/services/MiniappLauncher.ts +7 -0
- package/src/services/NavigationService.ts +28 -8
- package/src/services/OfflineActivity.ts +131 -0
- package/src/services/PhoneCalendarService.ts +7 -0
- package/src/services/PhoneNotificationsSync.ts +8 -3
- package/src/services/STTModelManager.ts +6 -0
- package/src/services/SceneRenderer.ts +1 -1
- package/src/services/appStateBackupFile.ts +171 -0
- package/src/services/controllers.ts +14 -3
- package/src/services/effectiveCapabilities.ts +6 -1
- package/src/services/neuralBand/NeuralBandInput.ts +37 -1
- package/src/services/neuralBand/NeuralBandSettingsSync.ts +12 -0
- package/src/services/pages/pageEligibility.ts +9 -1
- package/src/services/settingsBridge.ts +1 -0
- package/src/services/{appleWatch/AppleWatchLink.ts → watch/WatchLink.ts} +20 -20
- package/src/stores/appVisibility.ts +0 -1
- package/src/stores/apps.ts +22 -11
- package/src/stores/settings.ts +78 -10
- package/src/types/capabilities/even-realities-g1.ts +4 -0
- package/src/types/capabilities/memomind-one.ts +43 -0
- package/src/types/capabilities/rayneo-io.ts +3 -2
- package/src/types/enums.ts +6 -0
- package/src/types/hardware.ts +14 -1
- package/src/utils/display/index.ts +4 -2
- package/src/utils/display/profiles/index.ts +9 -2
- package/src/utils/display/profiles/rayneo-io.ts +6 -1
- package/src/utils/display/scene/index.ts +1 -1
- package/src/utils/display/scene/process.ts +22 -7
- package/src/utils/miniappGlobals.ts +32 -64
- package/build/services/appleWatch/AppleWatchLink.d.ts.map +0 -1
- package/build/services/appleWatch/AppleWatchLink.js.map +0 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the phone is running on-device right now (transcription, translation,
|
|
3
|
+
* LLM) and why, for the home offline card. Producers push in; it imports
|
|
4
|
+
* nothing at runtime so their test stubs stay unaffected.
|
|
5
|
+
*/
|
|
6
|
+
import type {LocalLlmReason} from "./LlmRouting"
|
|
7
|
+
import type {LocalSttFallbackReason} from "./LocalSttFallbackCoordinator"
|
|
8
|
+
|
|
9
|
+
export type OfflineActivityReason = LocalSttFallbackReason | LocalLlmReason
|
|
10
|
+
|
|
11
|
+
export interface OfflineTranscriptionActivity {
|
|
12
|
+
reason: LocalSttFallbackReason
|
|
13
|
+
model: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OfflineTranslationPair {
|
|
17
|
+
source: string
|
|
18
|
+
target: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OfflineTranslationActivity {
|
|
22
|
+
reason: OfflineActivityReason
|
|
23
|
+
pairs: OfflineTranslationPair[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OfflineLlmActivity {
|
|
27
|
+
reason: LocalLlmReason
|
|
28
|
+
model: string
|
|
29
|
+
requests: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OfflineActivity {
|
|
33
|
+
transcription: OfflineTranscriptionActivity | null
|
|
34
|
+
translation: OfflineTranslationActivity | null
|
|
35
|
+
llm: OfflineLlmActivity | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ServedTranslationPair extends OfflineTranslationPair {
|
|
39
|
+
forceLocal: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const EMPTY: OfflineActivity = {transcription: null, translation: null, llm: null}
|
|
43
|
+
|
|
44
|
+
class OfflineActivityTracker {
|
|
45
|
+
private transcription: OfflineTranscriptionActivity | null = null
|
|
46
|
+
private llmRuns: Array<{reason: LocalLlmReason; model: string}> = []
|
|
47
|
+
private translationSource: (() => ServedTranslationPair[]) | null = null
|
|
48
|
+
private snapshot: OfflineActivity = EMPTY
|
|
49
|
+
private snapshotKey = JSON.stringify(EMPTY)
|
|
50
|
+
private listeners = new Set<(activity: OfflineActivity) => void>()
|
|
51
|
+
|
|
52
|
+
current(): OfflineActivity {
|
|
53
|
+
return this.snapshot
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
subscribe(listener: (activity: OfflineActivity) => void): () => void {
|
|
57
|
+
this.listeners.add(listener)
|
|
58
|
+
return () => {
|
|
59
|
+
this.listeners.delete(listener)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setTranscription(transcription: OfflineTranscriptionActivity | null): void {
|
|
64
|
+
this.transcription = transcription
|
|
65
|
+
this.refresh()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
beginLlm(reason: LocalLlmReason, model: string): () => void {
|
|
69
|
+
const run = {reason, model}
|
|
70
|
+
this.llmRuns.push(run)
|
|
71
|
+
this.refresh()
|
|
72
|
+
let ended = false
|
|
73
|
+
return () => {
|
|
74
|
+
if (ended) return
|
|
75
|
+
ended = true
|
|
76
|
+
this.llmRuns = this.llmRuns.filter((r) => r !== run)
|
|
77
|
+
this.refresh()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setTranslationSource(source: (() => ServedTranslationPair[]) | null): void {
|
|
82
|
+
this.translationSource = source
|
|
83
|
+
this.refresh()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
refresh(): void {
|
|
87
|
+
const next: OfflineActivity = {
|
|
88
|
+
transcription: this.transcription,
|
|
89
|
+
translation: this.translation(),
|
|
90
|
+
llm: this.llm(),
|
|
91
|
+
}
|
|
92
|
+
const key = JSON.stringify(next)
|
|
93
|
+
if (key === this.snapshotKey) return
|
|
94
|
+
this.snapshotKey = key
|
|
95
|
+
this.snapshot = next
|
|
96
|
+
for (const listener of this.listeners) {
|
|
97
|
+
try {
|
|
98
|
+
listener(next)
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn("OfflineActivity: listener failed:", error)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
reset(): void {
|
|
106
|
+
this.transcription = null
|
|
107
|
+
this.llmRuns = []
|
|
108
|
+
this.translationSource = null
|
|
109
|
+
this.refresh()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private translation(): OfflineTranslationActivity | null {
|
|
113
|
+
if (!this.transcription || !this.translationSource) return null
|
|
114
|
+
const pairs = this.translationSource()
|
|
115
|
+
if (pairs.length === 0) return null
|
|
116
|
+
return {
|
|
117
|
+
reason: pairs.some((p) => p.forceLocal) ? "force_local" : this.transcription.reason,
|
|
118
|
+
pairs: pairs.map(({source, target}) => ({source, target})),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private llm(): OfflineLlmActivity | null {
|
|
123
|
+
const latest = this.llmRuns[this.llmRuns.length - 1]
|
|
124
|
+
if (!latest) return null
|
|
125
|
+
return {reason: latest.reason, model: latest.model, requests: this.llmRuns.length}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {OfflineActivityTracker}
|
|
130
|
+
const offlineActivity = new OfflineActivityTracker()
|
|
131
|
+
export default offlineActivity
|
|
@@ -49,9 +49,16 @@ export async function listPhoneCalendarEvents(request: CalendarListRequest): Pro
|
|
|
49
49
|
if (calendars.length === 0) return {events: [], truncated: false}
|
|
50
50
|
|
|
51
51
|
const rawEvents = await Calendar.listEvents(calendars, startsAt, endsAt)
|
|
52
|
+
const seen = new Set<string>()
|
|
52
53
|
const events = rawEvents
|
|
53
54
|
.map(normalizeCalendarEvent)
|
|
54
55
|
.filter((event) => calendarEventOverlapsWindow(event, startsAt, endsAt))
|
|
56
|
+
.filter((event) => {
|
|
57
|
+
const key = `${event.title}\n${event.startsAt}\n${event.endsAt}\n${event.allDay}`
|
|
58
|
+
if (seen.has(key)) return false
|
|
59
|
+
seen.add(key)
|
|
60
|
+
return true
|
|
61
|
+
})
|
|
55
62
|
.sort((a, b) => Date.parse(a.startsAt) - Date.parse(b.startsAt) || Date.parse(a.endsAt) - Date.parse(b.endsAt))
|
|
56
63
|
|
|
57
64
|
return {
|
|
@@ -91,6 +91,11 @@ export function nativeNotificationsSupported(): boolean {
|
|
|
91
91
|
return getModelCapabilities(model).hasNativeNotifications === true
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function notificationRelaySupported(): boolean {
|
|
95
|
+
const model = useGlassesStore.getState().deviceModel as DeviceTypes
|
|
96
|
+
return Platform.OS === "ios" && getModelCapabilities(model).hasNotificationRelay === true
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
/** Popup durations the firmware accepts, or an empty list when it takes any. */
|
|
95
100
|
export function nativeNotificationPopupSeconds(): number[] {
|
|
96
101
|
const model = useGlassesStore.getState().deviceModel as DeviceTypes
|
|
@@ -141,10 +146,10 @@ export function currentPresentationOwner(hostActive: boolean): PresentationOwner
|
|
|
141
146
|
*/
|
|
142
147
|
export function captureAuthorization(): "granted" | "denied" | "unknown" | "not_needed" {
|
|
143
148
|
if (Platform.OS !== "ios") return "not_needed"
|
|
144
|
-
if (!nativeNotificationsSupported()) return "not_needed"
|
|
149
|
+
if (!nativeNotificationsSupported() && !notificationRelaySupported()) return "not_needed"
|
|
145
150
|
const glasses = useGlassesStore.getState()
|
|
146
151
|
if (!isGlassesConnected(glasses.connection)) return "unknown"
|
|
147
|
-
return glasses.ancsAuthorized ? "granted" : "denied"
|
|
152
|
+
return glasses.ancsAuthorized ? "granted" : notificationRelaySupported() ? "unknown" : "denied"
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
/**
|
|
@@ -178,7 +183,7 @@ function pushListenerConfig(): void {
|
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
function pushGlassesConfig(): void {
|
|
181
|
-
if (!nativeNotificationsSupported()) return
|
|
186
|
+
if (!nativeNotificationsSupported() && !notificationRelaySupported()) return
|
|
182
187
|
if (!isGlassesConnected(useGlassesStore.getState().connection)) return
|
|
183
188
|
void Promise.resolve(BluetoothSdk.setGlassesNotificationConfig(desiredGlassesNotificationConfig())).catch(
|
|
184
189
|
(err: unknown) =>
|
|
@@ -228,6 +228,12 @@ class STTModelManager {
|
|
|
228
228
|
return this.currentLanguage
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
currentModelLabel(): string {
|
|
232
|
+
const language = this.config(this.currentLanguage)
|
|
233
|
+
if (!language) return this.currentLanguage
|
|
234
|
+
return language.type === "apple" ? `Apple · ${language.displayName}` : language.displayName
|
|
235
|
+
}
|
|
236
|
+
|
|
231
237
|
setCurrentLanguage(code: string): void {
|
|
232
238
|
if (this.config(code)) {
|
|
233
239
|
this.currentLanguage = code
|
|
@@ -250,7 +250,7 @@ class SceneRenderer {
|
|
|
250
250
|
return {kind: "legacy", layout, degraded, dropped}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
const processed = processScene(elements, caps, displayProcessor.
|
|
253
|
+
const processed = processScene(elements, caps, displayProcessor.getSceneProfile())
|
|
254
254
|
// A stale baseline means the glasses were cleared since the last commit —
|
|
255
255
|
// diff from empty so everything repaints (the retained frame stays valid
|
|
256
256
|
// as the replay source only).
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App state backup files: the share-sheet export and file-picker restore
|
|
3
|
+
* behind Settings > Profile > Backup. What goes in the file is decided in
|
|
4
|
+
* AppStateBackup.ts; this side reads and writes the live stores.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {MemoryImportResult} from "@glassly/miniapp"
|
|
8
|
+
import {File, Paths} from "expo-file-system"
|
|
9
|
+
import {Platform} from "react-native"
|
|
10
|
+
import Share from "react-native-share"
|
|
11
|
+
|
|
12
|
+
import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
13
|
+
import {useAppStatusStore} from "../stores/apps"
|
|
14
|
+
import {storage} from "../utils/storage/storage"
|
|
15
|
+
import appRegistry, {saveAlwaysOnOverride, saveWidgetExpandChoice} from "./AppRegistry"
|
|
16
|
+
import {
|
|
17
|
+
AppStateBackupError,
|
|
18
|
+
collectAppStateBackup,
|
|
19
|
+
miniappPrefs,
|
|
20
|
+
miniappsWithStorage,
|
|
21
|
+
parseAppStateBackup,
|
|
22
|
+
restorableSettings,
|
|
23
|
+
restoreMiniappStorage,
|
|
24
|
+
type AppStateBackupBackend,
|
|
25
|
+
} from "./AppStateBackup"
|
|
26
|
+
import {cloudClientService} from "./CloudClientService"
|
|
27
|
+
import {memoryStore} from "./memoryStoreInstance"
|
|
28
|
+
import {respawnMiniapp, runningMiniappPackages, stopMiniapp} from "./MiniappEngine"
|
|
29
|
+
|
|
30
|
+
export interface AppStateExportResult {
|
|
31
|
+
success: boolean
|
|
32
|
+
cancelled?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AppStateRestoreResult {
|
|
36
|
+
/** Settings written. */
|
|
37
|
+
settings: number
|
|
38
|
+
/** Miniapps whose storage was restored. */
|
|
39
|
+
miniapps: number
|
|
40
|
+
memory: MemoryImportResult | null
|
|
41
|
+
/** Installed when the backup was made but not on this phone now. */
|
|
42
|
+
missing: string[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AppStateRestoreOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Install what the backup had installed and this phone lacks. Runs before
|
|
48
|
+
* the restored miniapps start again, so the dashboard comes back with every
|
|
49
|
+
* widget provider its saved layout names: it drops unknown widgets from the
|
|
50
|
+
* layout the next time it saves it.
|
|
51
|
+
*/
|
|
52
|
+
reinstall?: (packageNames: string[]) => Promise<void>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const PICK_MIME_TYPES =
|
|
56
|
+
Platform.OS === "android" ? ["application/json", "application/octet-stream"] : ["application/json"]
|
|
57
|
+
|
|
58
|
+
const backend: AppStateBackupBackend = {
|
|
59
|
+
get(key) {
|
|
60
|
+
const result = storage.load<unknown>(key)
|
|
61
|
+
return result.is_ok() ? result.value : null
|
|
62
|
+
},
|
|
63
|
+
set(key, value) {
|
|
64
|
+
const result = storage.save(key, value)
|
|
65
|
+
if (result.is_error()) throw result.error
|
|
66
|
+
},
|
|
67
|
+
keys: () => storage.getAllKeys(),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function exportAppStateFile(): Promise<AppStateExportResult> {
|
|
71
|
+
const userId = await cloudClientService.resolveUserId()
|
|
72
|
+
const installed: Array<{packageName: string; version: string; source?: string}> = []
|
|
73
|
+
for (const app of await appRegistry.getInstalledMiniapps()) {
|
|
74
|
+
const version = await appRegistry.getActiveVersion(app.packageName)
|
|
75
|
+
if (!version) continue
|
|
76
|
+
installed.push({
|
|
77
|
+
packageName: app.packageName,
|
|
78
|
+
version,
|
|
79
|
+
source: appRegistry.getReleaseIdentity(app.packageName, version)?.source,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
const backup = collectAppStateBackup({
|
|
83
|
+
backend,
|
|
84
|
+
userId,
|
|
85
|
+
settings: Object.values(SETTINGS),
|
|
86
|
+
installed,
|
|
87
|
+
memory: await memoryStore.exportBackup(),
|
|
88
|
+
now: Date.now(),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const filename = `glassly-backup-${localDate(backup.exportedAt)}.json`
|
|
92
|
+
const file = new File(Paths.cache, filename)
|
|
93
|
+
file.writeSync(JSON.stringify(backup))
|
|
94
|
+
try {
|
|
95
|
+
await Share.open({url: file.uri, type: "application/json", filename, title: "Glassly backup"})
|
|
96
|
+
return {success: true}
|
|
97
|
+
} catch (error: any) {
|
|
98
|
+
// react-native-share rejects when the user dismisses the sheet.
|
|
99
|
+
if (error?.message?.includes("User did not share")) return {success: false, cancelled: true}
|
|
100
|
+
throw error
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Resolves null when the user cancels the picker. */
|
|
105
|
+
export async function importAppStateFile(opts: AppStateRestoreOptions = {}): Promise<AppStateRestoreResult | null> {
|
|
106
|
+
const picked = await File.pickFileAsync({mimeTypes: PICK_MIME_TYPES})
|
|
107
|
+
if (picked.canceled) return null
|
|
108
|
+
let data: unknown
|
|
109
|
+
try {
|
|
110
|
+
data = JSON.parse(await picked.result.text())
|
|
111
|
+
} catch {
|
|
112
|
+
throw new AppStateBackupError("Not a Glassly app state backup")
|
|
113
|
+
}
|
|
114
|
+
const backup = parseAppStateBackup(data)
|
|
115
|
+
const userId = await cloudClientService.resolveUserId()
|
|
116
|
+
|
|
117
|
+
// First, so a memory section this build rejects fails the restore before anything is written.
|
|
118
|
+
const memory = backup.memory ? await memoryStore.importBackup(backup.memory) : null
|
|
119
|
+
|
|
120
|
+
const {plain, indexed} = restorableSettings(backup, Object.values(SETTINGS))
|
|
121
|
+
const written = await useSettingsStore.getState().setManyLocally(plain)
|
|
122
|
+
if (written.is_error()) throw written.error
|
|
123
|
+
// setManyLocally only takes registry keys; `key:index` values go to storage and the store directly.
|
|
124
|
+
for (const [key, value] of Object.entries(indexed)) backend.set(key, value)
|
|
125
|
+
if (Object.keys(indexed).length > 0) {
|
|
126
|
+
useSettingsStore.setState((state) => ({settings: {...state.settings, ...indexed}}))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// A running miniapp holds its old values in memory and could write them back over the
|
|
130
|
+
// restore, so the ones getting storage stop now and start again at the end.
|
|
131
|
+
const running = new Set(runningMiniappPackages())
|
|
132
|
+
const restarting = miniappsWithStorage(backup).filter((packageName) => running.has(packageName))
|
|
133
|
+
for (const packageName of restarting) await stopMiniapp(packageName)
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const restored = restoreMiniappStorage(backend, userId, backup)
|
|
137
|
+
|
|
138
|
+
const apps = useAppStatusStore.getState()
|
|
139
|
+
for (const [packageName, record] of Object.entries(backup.miniapps)) {
|
|
140
|
+
const prefs = miniappPrefs(record)
|
|
141
|
+
if (prefs.hidden !== undefined) apps.setHiddenStatus(packageName, prefs.hidden)
|
|
142
|
+
if (prefs.alwaysOn !== undefined) saveAlwaysOnOverride(packageName, prefs.alwaysOn)
|
|
143
|
+
for (const [widgetId, expand] of Object.entries(prefs.widgetExpand)) {
|
|
144
|
+
saveWidgetExpandChoice(packageName, widgetId, expand)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
appRegistry.markRefreshNeeded()
|
|
148
|
+
await apps.refresh()
|
|
149
|
+
|
|
150
|
+
const installedNow = new Set((await appRegistry.getInstalledMiniapps()).map((app) => app.packageName))
|
|
151
|
+
const missing = Object.entries(backup.miniapps)
|
|
152
|
+
.filter(([packageName, record]) => record.installed && !installedNow.has(packageName))
|
|
153
|
+
.map(([packageName]) => packageName)
|
|
154
|
+
if (missing.length > 0) await opts.reinstall?.(missing)
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
settings: Object.keys(plain).length + Object.keys(indexed).length,
|
|
158
|
+
miniapps: restored.packages.length,
|
|
159
|
+
memory,
|
|
160
|
+
missing,
|
|
161
|
+
}
|
|
162
|
+
} finally {
|
|
163
|
+
for (const packageName of restarting) await respawnMiniapp(packageName)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function localDate(ms: number): string {
|
|
168
|
+
const d = new Date(ms)
|
|
169
|
+
const pad = (n: number) => String(n).padStart(2, "0")
|
|
170
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
|
171
|
+
}
|
|
@@ -26,8 +26,8 @@ const NEURAL_BAND_SURFACE: InputSurface = {
|
|
|
26
26
|
],
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
const
|
|
29
|
+
/** A watch trackpad's taps and four-way swipes, and the crown (Apple) or rotary bezel (Wear OS) as the dial. */
|
|
30
|
+
const WATCH_SURFACE: InputSurface = {
|
|
31
31
|
type: "swipe2d",
|
|
32
32
|
events: [
|
|
33
33
|
"TAP",
|
|
@@ -42,10 +42,21 @@ const APPLE_WATCH_SURFACE: InputSurface = {
|
|
|
42
42
|
],
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/** The Gamebrick's D-pad is four absolute swipes; A is the press lifecycle (tap and hold) and B a double tap. */
|
|
46
|
+
const GAMEBRICK_SURFACE: InputSurface = {
|
|
47
|
+
type: "swipe2d",
|
|
48
|
+
events: ["TAP", "DOUBLE_TAP", "PRESS_HOLD", "SWIPE_UP", "SWIPE_DOWN", "SWIPE_LEFT", "SWIPE_RIGHT"],
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
// The R1 swipes up/down like the temples it stands in for, so it adds nothing.
|
|
46
52
|
const INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {
|
|
47
53
|
[ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],
|
|
48
|
-
[ControllerTypes.APPLE_WATCH]: [
|
|
54
|
+
[ControllerTypes.APPLE_WATCH]: [WATCH_SURFACE],
|
|
55
|
+
[ControllerTypes.WEAR_OS]: [WATCH_SURFACE],
|
|
56
|
+
[ControllerTypes.GAMEBRICK]: [GAMEBRICK_SURFACE],
|
|
57
|
+
[ControllerTypes.IINE_ANANKE]: [GAMEBRICK_SURFACE],
|
|
58
|
+
[ControllerTypes.ZERO2]: [GAMEBRICK_SURFACE],
|
|
59
|
+
[ControllerTypes.MICRO]: [GAMEBRICK_SURFACE],
|
|
49
60
|
[ControllerTypes.R1]: [],
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -61,6 +61,8 @@ function glassesCapabilities(model: DeviceTypes): Capabilities {
|
|
|
61
61
|
// Glassly CFW supports the complete vector contract, including SVG and rotation.
|
|
62
62
|
// Upstream g2flash publishes a raster canvas with sceneVector=false.
|
|
63
63
|
const vector = useGlassesStore.getState().sceneVector === true
|
|
64
|
+
// Depth is a disparity between the lenses; one panel (MemoMind One) draws every element flat.
|
|
65
|
+
const stereo = (base.display.count ?? 1) >= 2
|
|
64
66
|
return {
|
|
65
67
|
...(volumeGate ? withVolumeGate(base) : base),
|
|
66
68
|
hasNativeNavigation: resolveNativeNavigation(base.hasNativeNavigation, true, forceNativeNavigationOnCfw()),
|
|
@@ -68,6 +70,9 @@ function glassesCapabilities(model: DeviceTypes): Capabilities {
|
|
|
68
70
|
...base.display,
|
|
69
71
|
width: canvas.width,
|
|
70
72
|
height: canvas.height,
|
|
73
|
+
// A Glassly CFW that publishes a canvas takes positioned elements, whatever
|
|
74
|
+
// the stock profile says (the G1's stock firmware degrades scenes to text walls).
|
|
75
|
+
canPosition: true,
|
|
71
76
|
maxTextElements: Math.max(
|
|
72
77
|
base.display.maxTextElements ?? 0,
|
|
73
78
|
vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_TEXT_ELEMENTS,
|
|
@@ -78,7 +83,7 @@ function glassesCapabilities(model: DeviceTypes): Capabilities {
|
|
|
78
83
|
vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_IMAGE_ELEMENTS,
|
|
79
84
|
),
|
|
80
85
|
...(vector
|
|
81
|
-
? {shapes: [...ALL_DISPLAY_SHAPES, "svg" as const], animation: true, rotation: true, depth:
|
|
86
|
+
? {shapes: [...ALL_DISPLAY_SHAPES, "svg" as const], animation: true, rotation: true, depth: stereo}
|
|
82
87
|
: {}),
|
|
83
88
|
},
|
|
84
89
|
}
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* latest claim is also kept on its own, since the pairing screen runs before
|
|
7
7
|
* the band's id reaches JS.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
NeuralBandClaimEvent,
|
|
11
|
+
NeuralBandFirmwareEvent,
|
|
12
|
+
NeuralBandHandwritingEvent,
|
|
13
|
+
NeuralBandPointerEvent,
|
|
14
|
+
} from "@glassly/bluetooth-sdk"
|
|
10
15
|
import {MiniappStreamType} from "@glassly/miniapp"
|
|
11
16
|
|
|
12
17
|
import type {KeyboardEventPayload} from "../GlassesKeyboardInput"
|
|
@@ -34,6 +39,8 @@ export interface NeuralBandHandwritingUpdate {
|
|
|
34
39
|
status: NeuralBandHandwritingStatus
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
export type NeuralBandFirmwareStatus = Omit<NeuralBandFirmwareEvent, "type" | "controllerId"> & {known: boolean}
|
|
43
|
+
|
|
37
44
|
export interface NeuralBandInputOptions {
|
|
38
45
|
forward?: (stream: string, payload: unknown) => void
|
|
39
46
|
now?: () => number
|
|
@@ -67,13 +74,25 @@ function codeFor(char: string): string {
|
|
|
67
74
|
|
|
68
75
|
const IDLE_CLAIM: NeuralBandClaimStatus = {stage: "idle", sessionExpired: false}
|
|
69
76
|
const IDLE_HANDWRITING: NeuralBandHandwritingStatus = {phase: "idle", message: "", text: ""}
|
|
77
|
+
const IDLE_FIRMWARE: NeuralBandFirmwareStatus = {
|
|
78
|
+
known: false,
|
|
79
|
+
connected: false,
|
|
80
|
+
hasMetaSession: false,
|
|
81
|
+
checked: false,
|
|
82
|
+
updateAvailable: false,
|
|
83
|
+
phase: "idle",
|
|
84
|
+
progress: 0,
|
|
85
|
+
message: "",
|
|
86
|
+
}
|
|
70
87
|
|
|
71
88
|
export class NeuralBandInput {
|
|
72
89
|
private latestClaim: NeuralBandClaimStatus = IDLE_CLAIM
|
|
73
90
|
private claims = new Map<string, NeuralBandClaimStatus>()
|
|
74
91
|
private handwriting = new Map<string, NeuralBandHandwritingStatus>()
|
|
92
|
+
private firmware = new Map<string, NeuralBandFirmwareStatus>()
|
|
75
93
|
private claimListeners = new Set<(status: NeuralBandClaimStatus) => void>()
|
|
76
94
|
private handwritingListeners = new Set<(update: NeuralBandHandwritingUpdate) => void>()
|
|
95
|
+
private firmwareListeners = new Set<(update: {id: string; status: NeuralBandFirmwareStatus}) => void>()
|
|
77
96
|
private readonly forward: (stream: string, payload: unknown) => void
|
|
78
97
|
private readonly now: () => number
|
|
79
98
|
|
|
@@ -102,6 +121,15 @@ export class NeuralBandInput {
|
|
|
102
121
|
return () => this.handwritingListeners.delete(cb)
|
|
103
122
|
}
|
|
104
123
|
|
|
124
|
+
firmwareStatus(controllerId: string): NeuralBandFirmwareStatus {
|
|
125
|
+
return this.firmware.get(controllerId) ?? IDLE_FIRMWARE
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onFirmwareStatus(cb: (update: {id: string; status: NeuralBandFirmwareStatus}) => void): () => void {
|
|
129
|
+
this.firmwareListeners.add(cb)
|
|
130
|
+
return () => this.firmwareListeners.delete(cb)
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
/** A new pairing attempt starts from a clean slate. */
|
|
106
134
|
resetClaim(): void {
|
|
107
135
|
this.latestClaim = IDLE_CLAIM
|
|
@@ -112,6 +140,14 @@ export class NeuralBandInput {
|
|
|
112
140
|
forget(controllerId: string): void {
|
|
113
141
|
this.claims.delete(controllerId)
|
|
114
142
|
this.handwriting.delete(controllerId)
|
|
143
|
+
this.firmware.delete(controllerId)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleFirmware(event: NeuralBandFirmwareEvent): void {
|
|
147
|
+
const {type: _type, controllerId, ...rest} = event
|
|
148
|
+
const status: NeuralBandFirmwareStatus = {...rest, known: true}
|
|
149
|
+
this.firmware.set(controllerId, status)
|
|
150
|
+
for (const cb of this.firmwareListeners) cb({id: controllerId, status})
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
handleClaim(event: NeuralBandClaimEvent): void {
|
|
@@ -32,6 +32,13 @@ function pushHand(id: string): void {
|
|
|
32
32
|
)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function pushVibration(id: string): void {
|
|
36
|
+
const enabled = useSettingsStore.getState().getSetting(SETTINGS.neural_band_vibration.key) !== false
|
|
37
|
+
void BluetoothSdk.neuralBandSetGestureHaptics(id, enabled).catch((err) =>
|
|
38
|
+
console.warn("NeuralBandSettingsSync: vibration push failed:", err),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
export function startNeuralBandSettingsSync(): void {
|
|
36
43
|
if (unsubscribers.length > 0) return
|
|
37
44
|
let connected = new Set(connectedBandIds())
|
|
@@ -44,6 +51,7 @@ export function startNeuralBandSettingsSync(): void {
|
|
|
44
51
|
if (connected.has(id)) continue
|
|
45
52
|
pushAirMouse(id)
|
|
46
53
|
pushHand(id)
|
|
54
|
+
pushVibration(id)
|
|
47
55
|
}
|
|
48
56
|
connected = next
|
|
49
57
|
},
|
|
@@ -56,6 +64,10 @@ export function startNeuralBandSettingsSync(): void {
|
|
|
56
64
|
(s) => s.getSetting(SETTINGS.neural_band_hand.key),
|
|
57
65
|
() => connectedBandIds().forEach(pushHand),
|
|
58
66
|
),
|
|
67
|
+
useSettingsStore.subscribe(
|
|
68
|
+
(s) => s.getSetting(SETTINGS.neural_band_vibration.key),
|
|
69
|
+
() => connectedBandIds().forEach(pushVibration),
|
|
70
|
+
),
|
|
59
71
|
]
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -13,7 +13,10 @@ import {isGlassesConnected} from "../GlassesReadiness"
|
|
|
13
13
|
|
|
14
14
|
/** The SGC appends this marker after the firmware advertises Glassly CFW capabilities (G2, RayNeo iO). */
|
|
15
15
|
export function isCfwFirmwareVersion(firmwareVersion: string | null | undefined): boolean {
|
|
16
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
firmwareVersion?.toLowerCase().includes("glassly cfw") === true ||
|
|
18
|
+
/^GLASSLYCFW\/[0-9]+$/.test(firmwareVersion ?? "")
|
|
19
|
+
)
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
/**
|
|
@@ -23,6 +26,11 @@ export function isCfwFirmwareVersion(firmwareVersion: string | null | undefined)
|
|
|
23
26
|
*/
|
|
24
27
|
export function isCustomDashboardEligible(info: {model?: string | null; firmwareVersion?: string | null}): boolean {
|
|
25
28
|
if (!info.model) return false
|
|
29
|
+
if (info.model === DeviceTypes.MEMOMIND_ONE) {
|
|
30
|
+
const match = /^GLASSLYCFW\/([0-9]+)$/.exec(info.firmwareVersion ?? "")
|
|
31
|
+
const revision = match ? Number(match[1]) : 0
|
|
32
|
+
return Number.isSafeInteger(revision) && revision >= 10
|
|
33
|
+
}
|
|
26
34
|
return (
|
|
27
35
|
getModelCapabilities(info.model as DeviceTypes).hasCustomFirmware === true &&
|
|
28
36
|
isCfwFirmwareVersion(info.firmwareVersion)
|
|
@@ -38,6 +38,7 @@ export const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>
|
|
|
38
38
|
notifications_enabled: "write",
|
|
39
39
|
notifications_popups: "write",
|
|
40
40
|
notifications_popup_seconds: "write",
|
|
41
|
+
notifications_batch_seconds: "write",
|
|
41
42
|
notifications_hold_during_apps: "write",
|
|
42
43
|
notifications_ignore_while_active: "write",
|
|
43
44
|
notifications_blocklist: "write",
|