@geastack/core 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +36 -0
  3. package/app_frame_perf.cpp +77 -0
  4. package/css.d.ts +1 -0
  5. package/events.cpp +67 -0
  6. package/gea_app_entry.cpp +317 -0
  7. package/gea_sources.mjs +126 -0
  8. package/gea_sources.sh +36 -0
  9. package/include/app.h +60 -0
  10. package/include/apps.h +46 -0
  11. package/include/audio.h +90 -0
  12. package/include/ble.h +212 -0
  13. package/include/bluetooth/hid_profile.h +92 -0
  14. package/include/camera.h +107 -0
  15. package/include/canvas.h +290 -0
  16. package/include/display.h +390 -0
  17. package/include/display_present.h +869 -0
  18. package/include/event.h +32 -0
  19. package/include/events.h +116 -0
  20. package/include/gea/embedded.h +13 -0
  21. package/include/gea/reactive_runtime.h +32 -0
  22. package/include/gea.h +14 -0
  23. package/include/gea_perf_config.h +96 -0
  24. package/include/geaos/abi.h +165 -0
  25. package/include/geolocation.h +126 -0
  26. package/include/gpio.h +61 -0
  27. package/include/graphics/font.h +122 -0
  28. package/include/i2c.h +20 -0
  29. package/include/image.h +156 -0
  30. package/include/imu.h +23 -0
  31. package/include/input.h +33 -0
  32. package/include/memory.h +52 -0
  33. package/include/pixel.h +907 -0
  34. package/include/platform/file_cache.h +18 -0
  35. package/include/power.h +11 -0
  36. package/include/runtime.h +83 -0
  37. package/include/services/app_runner.h +10 -0
  38. package/include/services/app_state.h +14 -0
  39. package/include/services/battery_service.h +12 -0
  40. package/include/services/bluetooth_service.h +13 -0
  41. package/include/services/diagnostics.h +76 -0
  42. package/include/services/diagnostics_platform.h +54 -0
  43. package/include/services/frame_scheduler.h +71 -0
  44. package/include/services/mirror.h +49 -0
  45. package/include/services/network_services.h +25 -0
  46. package/include/services/ota.h +32 -0
  47. package/include/services/runtime_log.h +11 -0
  48. package/include/services/storage_service.h +26 -0
  49. package/include/touch.h +38 -0
  50. package/include/ui/signal.h +149 -0
  51. package/include/wifi.h +67 -0
  52. package/index.d.ts +1940 -0
  53. package/jsx.d.ts +1 -0
  54. package/package.json +70 -0
  55. package/runtime/ble.ts +49 -0
  56. package/runtime/color.ts +21 -0
  57. package/runtime/compiler.ts +43 -0
  58. package/runtime/host.ts +1083 -0
  59. package/runtime/images.ts +129 -0
  60. package/runtime/primitives.ts +49 -0
  61. package/runtime-surface.ts +180 -0
  62. package/runtime.cpp +431 -0
  63. package/runtime.ts +34 -0
  64. package/scripts/build-gea-vite-geatsc.mjs +4514 -0
  65. package/scripts/dotenv-defines.mjs +98 -0
  66. package/scripts/gea-bundle-type-hints.mjs +546 -0
  67. package/scripts/gea-embedded-compat-staging.mjs +17 -0
  68. package/scripts/gea-embedded-compat-transform.mjs +427 -0
  69. package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
  70. package/scripts/generate-gea-embedded-assets.mjs +296 -0
  71. package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
  72. package/scripts/generate-installed-app-plan.mjs +55 -0
  73. package/scripts/native-object-cache.sh +244 -0
@@ -0,0 +1,1083 @@
1
+ // These names ALSO exist as lib.dom globals, so leaving them unimported does
2
+ // not fail to compile — it silently binds the DOM interfaces instead of ours.
3
+ // That is not cosmetic: lib.dom's GeolocationCoordinates/GeolocationPosition
4
+ // carry `toJSON(): any`, a callable member, so geatsc's record identity
5
+ // declines the shape and every method returning one seals `unresolved`, which
6
+ // fails the representation coverage guard for the whole `Geolocation` literal
7
+ // below. lib.dom's GeolocationPositionError also carries PERMISSION_DENIED /
8
+ // POSITION_UNAVAILABLE / TIMEOUT, which our host object never sets.
9
+ import type {
10
+ CanvasRenderingContext2D,
11
+ GeolocationCoordinates,
12
+ GeolocationPosition,
13
+ GeolocationPositionError,
14
+ PositionOptions,
15
+ TouchSample,
16
+ CameraCaptureOptions,
17
+ CameraClip,
18
+ CameraDevice,
19
+ CameraExposureMode,
20
+ CameraExposureOptions,
21
+ CameraFacing,
22
+ CameraFlashMode,
23
+ CameraFocusMode,
24
+ CameraFocusOptions,
25
+ CameraOpenOptions,
26
+ CameraPhoto,
27
+ CameraRecordOptions,
28
+ CameraWhiteBalanceMode,
29
+ CameraWhiteBalanceOptions,
30
+ EmbeddedMemoryConfig,
31
+ EmbeddedMemoryStats,
32
+ HttpReply,
33
+ AudioContext
34
+ } from '../index'
35
+
36
+ // These 17 used to be declared HERE as well as in `../index`, byte for byte.
37
+ // A duplicate is not free: the record-alias candidate registry is keyed by
38
+ // NAME across every file in the program and the last writer wins, so one
39
+ // declaration silently shadowed the other and the struct emitted for a literal
40
+ // stopped matching the struct emitted for its annotation. Re-exported so
41
+ // existing `from './runtime/host'` importers keep working.
42
+ export type {
43
+ CameraCaptureOptions,
44
+ CameraClip,
45
+ CameraDevice,
46
+ CameraExposureMode,
47
+ CameraExposureOptions,
48
+ CameraFacing,
49
+ CameraFlashMode,
50
+ CameraFocusMode,
51
+ CameraFocusOptions,
52
+ CameraOpenOptions,
53
+ CameraPhoto,
54
+ CameraRecordOptions,
55
+ CameraWhiteBalanceMode,
56
+ CameraWhiteBalanceOptions,
57
+ EmbeddedMemoryConfig,
58
+ EmbeddedMemoryStats,
59
+ HttpReply
60
+ } from '../index'
61
+
62
+ declare const apps: {
63
+ launch(appId: string): number
64
+ }
65
+
66
+ declare const image: {
67
+ loadBytes(bytes: Uint8Array): number
68
+ loadBytesOpaque(bytes: Uint8Array): number
69
+ width(id: number): number
70
+ height(id: number): number
71
+ frameCount(id: number): number
72
+ isAnimated(id: number): boolean
73
+ setPlaying(id: number, playing: number): void
74
+ seek(id: number, frame: number): void
75
+ dispose(id: number): void
76
+ }
77
+
78
+ declare const navigator: {
79
+ geolocation: {
80
+ hasFix(): boolean
81
+ currentPosition(): GeolocationPosition
82
+ coords(): GeolocationCoordinates
83
+ latitude(): number
84
+ longitude(): number
85
+ accuracy(): number
86
+ getCurrentPosition(
87
+ success: (position: GeolocationPosition) => void,
88
+ error?: (error: GeolocationPositionError) => void,
89
+ options?: PositionOptions
90
+ ): void
91
+ watchPosition(
92
+ success: (position: GeolocationPosition) => void,
93
+ error?: (error: GeolocationPositionError) => void,
94
+ options?: PositionOptions
95
+ ): number
96
+ clearWatch(id: number): void
97
+ }
98
+ wifi: {
99
+ enabled(): boolean
100
+ setEnabled(enabled: boolean): void
101
+ connected(): boolean
102
+ rssi(): number
103
+ ssid(): string
104
+ ip(): string
105
+ mac(): string
106
+ configure(ssid: string, password: string): void
107
+ waitForConnection(timeoutMs: number): boolean
108
+ startScan(): void
109
+ scanning(): boolean
110
+ scanCount(): number
111
+ scanSsidAt(index: number): string
112
+ scanRssiAt(index: number): number
113
+ scanSecuredAt(index: number): boolean
114
+ }
115
+ bluetooth: {
116
+ keyboard: {
117
+ tap(hidCode: number): void
118
+ down(modifier: number, hidCode: number): void
119
+ up(): void
120
+ }
121
+ mouse: {
122
+ move(dx: number, dy: number, buttons?: number, wheel?: number): void
123
+ click(button: number): void
124
+ }
125
+ midi: {
126
+ enable(): void
127
+ bound(): boolean
128
+ send(bytes: number[]): void
129
+ startScan(): void
130
+ stopScan(): void
131
+ scanning(): boolean
132
+ scanCount(): number
133
+ scanNameAt(index: number): string
134
+ connect(index: number): void
135
+ disconnect(): void
136
+ }
137
+ hidHost: {
138
+ startScan(): void
139
+ stopScan(): void
140
+ scanning(): boolean
141
+ scanCount(): number
142
+ scanNameAt(index: number): string
143
+ connect(index: number): void
144
+ disconnect(): void
145
+ bound(): boolean
146
+ reportCount(): number
147
+ reportIdAt(index: number): number
148
+ reportLenAt(index: number): number
149
+ reportByteAt(index: number, byteIndex: number): number
150
+ clearReports(): void
151
+ }
152
+ connections: {
153
+ count(): number
154
+ kindAt(index: number): number
155
+ nameAt(index: number): string
156
+ }
157
+ config: {
158
+ setDocument(bytes: number[]): void
159
+ pendingLength(): number
160
+ pendingByteAt(index: number): number
161
+ consumePending(): void
162
+ pairing(): boolean
163
+ pairCode(): string
164
+ dismissPairing(): void
165
+ pushActivity(index: number): void
166
+ }
167
+ enabled(): boolean
168
+ setEnabled(enabled: boolean): void
169
+ connected(): boolean
170
+ bound(): boolean
171
+ batteryLevel(): number
172
+ mac(): string
173
+ deviceName(): string
174
+ init(deviceName: string, appearance?: number, macAddress?: string): void
175
+ startAdvertising(): void
176
+ stopAdvertising(): void
177
+ }
178
+ }
179
+
180
+ declare const __gea_Accelerometer: {
181
+ readonly x: number
182
+ readonly y: number
183
+ readonly z: number
184
+ readonly accelerationX: number
185
+ readonly accelerationY: number
186
+ readonly accelerationZ: number
187
+ readonly gyroscopeX: number
188
+ readonly gyroscopeY: number
189
+ readonly gyroscopeZ: number
190
+ readonly tiltX: number
191
+ readonly tiltY: number
192
+ start(): void
193
+ close(): void
194
+ calibrateBias(): void
195
+ }
196
+
197
+ declare const __gea_Camera: {
198
+ readonly width: number
199
+ readonly height: number
200
+ readonly orientation: number
201
+ readonly facing: string
202
+ readonly deviceCount: number
203
+ isAvailable(): boolean
204
+ hasPermission(): boolean
205
+ requestPermission(): boolean
206
+ open(facing: string, width: number, height: number): boolean
207
+ close(): void
208
+ isOpen(): boolean
209
+ draw(x: number, y: number, destWidth: number, destHeight: number): void
210
+ capture(): number
211
+ captureMirrored(): number
212
+ startRecording(path: string, fps: number): boolean
213
+ stopRecording(): number
214
+ isRecording(): boolean
215
+ setFlash(mode: string): void
216
+ setZoom(factor: number): void
217
+ setMirror(mirror: boolean): void
218
+ setExposure(mode: string, bias: number, iso: number, durationMs: number): void
219
+ setWhiteBalance(mode: string, temperature: number, tint: number): void
220
+ setFocus(mode: string, pointX: number, pointY: number): void
221
+ setTorch(mode: string, level: number): void
222
+ deviceIdAt(index: number): string
223
+ deviceFacingAt(index: number): string
224
+ }
225
+
226
+ // The audio surface is NATIVE. The geatsc gea plugin maps `AudioContext`,
227
+ // `AudioDestinationNode`, `AudioParam` and `OscillatorNode` to
228
+ // `gea::host::…` handles by NAME (host-shims.ts `nativeTypes`), so the
229
+ // `Embedded`-prefixed copies that used to live here matched nothing and were
230
+ // lowered as ordinary records instead. `AudioDestinationNode` is an EMPTY
231
+ // interface, and an empty record mints no shape at all — its field reached
232
+ // emission with no frozen representation and the whole build failed closed.
233
+ // Use the published names so the native mapping applies.
234
+ export type { AudioContext as EmbeddedAudioContext } from '../index'
235
+
236
+ declare const __gea_audioContext: AudioContext
237
+ declare const __gea_Audio: {
238
+ getVolume(): number
239
+ setVolume(volume: number): void
240
+ }
241
+
242
+ type EmbeddedDisplayOrientation = 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'
243
+
244
+ type EmbeddedDisplayOrientationSupport = EmbeddedDisplayOrientation | 'portrait' | 'landscape' | 'all'
245
+
246
+ type EmbeddedDisplayPixelFormat = 'rgb565' | 'rgb888' | 'rgb8888' | 'argb8888'
247
+
248
+ declare const __gea_Display: {
249
+ readonly ctx: CanvasRenderingContext2D
250
+ readonly width: number
251
+ readonly height: number
252
+ readonly nativeWidth: number
253
+ readonly nativeHeight: number
254
+ orientation: EmbeddedDisplayOrientation
255
+ supportedOrientations: EmbeddedDisplayOrientationSupport[]
256
+ autoRotate: boolean
257
+ pixelFormat: EmbeddedDisplayPixelFormat
258
+ readonly panelPixelFormat: EmbeddedDisplayPixelFormat
259
+ readonly supportedPixelFormats: EmbeddedDisplayPixelFormat[]
260
+ getBrightness(): number
261
+ setBrightness(brightness: number): void
262
+ getOrientation(): EmbeddedDisplayOrientation
263
+ setOrientation(orientation: EmbeddedDisplayOrientation): void
264
+ getSupportedOrientations(): EmbeddedDisplayOrientationSupport[]
265
+ setSupportedOrientations(orientations: EmbeddedDisplayOrientationSupport | EmbeddedDisplayOrientationSupport[]): void
266
+ getAutoRotate(): boolean
267
+ setAutoRotate(enabled: boolean): void
268
+ setVSync(on: boolean): void
269
+ setTextRasterCache(on: boolean): void
270
+ // Declare that text always draws over ONE solid color (0xRRGGBB). On packed
271
+ // 4-bit grayscale targets (e-paper) each glyph is then pre-blended against
272
+ // that backdrop once and stamped as straight byte copies -- no per-pixel
273
+ // framebuffer read or blend. Glyphs whose color equals the backdrop (e.g.
274
+ // inverted rows) automatically fall back to the true blend. Pass a negative
275
+ // value to disable. No-op on full-color targets.
276
+ //
277
+ // Declared in `index.d.ts` and implemented by the engine
278
+ // (`packages/host/include/host/display.h`, `packages/engine/canvas.cpp`)
279
+ // since it was added; this surface -- the one a real build type-checks
280
+ // against -- had been left out, so `e-reader`'s own `index.tsx` did not
281
+ // compile against the framework it ships with.
282
+ setTextSolidBackdrop(rrggbb: number): void
283
+ // Mark the next present as a full-screen change ("damage-all"). The present
284
+ // then skips the dirty-rect diff and its persistent previous-frame copy --
285
+ // both exist only to flush less when little changed, which is pure waste
286
+ // during a full-screen pan (the diff would conclude "everything changed"
287
+ // and full-flush anyway). Call it once per frame while actively
288
+ // panning/animating the whole screen; stop calling it when motion settles
289
+ // so idle gets the cheap diff back.
290
+ invalidate(): void
291
+ getPixelFormat(): EmbeddedDisplayPixelFormat
292
+ setPixelFormat(format: EmbeddedDisplayPixelFormat): void
293
+ getPanelPixelFormat(): EmbeddedDisplayPixelFormat
294
+ getSupportedPixelFormats(): EmbeddedDisplayPixelFormat[]
295
+ getDevicePixelRatio(): number
296
+ setDevicePixelRatio(devicePixelRatio: number): void
297
+ getFrameIntervalMs(): number
298
+ setFrameIntervalMs(intervalMs: number): void
299
+ getFrameRate(): number
300
+ setFrameRate(fps: number): void
301
+ setAA(samples: number): void
302
+ setFlushConfig(config: { rows: number; depth: number }): void
303
+ setMemoryConfig(config: { commandBufferCommands?: number; backgroundCache?: boolean }): void
304
+ // E-paper boards only (no-op elsewhere): refresh policy + custom waveforms.
305
+ // LUTs are the panel's raw 159-byte waveform tables; [] restores the vendor
306
+ // default. fastStreakWindowMs: 0 disables the fast waveform entirely.
307
+ setEpaperRefreshConfig(config: {
308
+ fullRefreshEveryPartials?: number
309
+ fullRefreshHardCapPartials?: number
310
+ fastStreakWindowMs?: number
311
+ partialLut?: number[]
312
+ fastLut?: number[]
313
+ // 4-level grayscale on supported e-paper panels (SSD1681): full refresh,
314
+ // 4 reflectance levels instead of 1-bit dither. Omit to leave unchanged.
315
+ grayscale?: boolean
316
+ // Whether a full-screen-covering partial (an e-reader page turn) is
317
+ // promoted to a flashing full refresh. Omit to leave unchanged.
318
+ fullOnCover?: boolean
319
+ }): void
320
+ // Force a full (flashing, anti-ghost) refresh of the current frame now.
321
+ epaperFullRefresh(): void
322
+ }
323
+ declare const __gea_Memory: {
324
+ internalFree(): number
325
+ internalLargestFreeBlock(): number
326
+ internalMinimumFree(): number
327
+ psramFree(): number
328
+ currentTaskStackHighWaterMark(): number
329
+ geaMainStackBytes(): number
330
+ geaInitStackBytes(): number
331
+ appFrameStackWords(): number
332
+ appFrameStackBytes(): number
333
+ displayFlushConfiguredRows(): number
334
+ displayFlushConfiguredDepth(): number
335
+ displayFlushBufferMaxBytes(): number
336
+ displayFlushRows(): number
337
+ displayFlushDepth(): number
338
+ displayFlushBufferBytes(): number
339
+ allocationSramCount(): number
340
+ allocationPsramCount(): number
341
+ allocationSramBytes(): number
342
+ allocationPsramBytes(): number
343
+ allocationSramPeakBytes(): number
344
+ allocationPsramPeakBytes(): number
345
+ }
346
+ declare const __gea_Input: {
347
+ // Reads-and-clears the one-shot back-button-pressed flag. Set by the
348
+ // platform launcher-button task (BOOT short press on ESP32) and by any
349
+ // application's `_settings_toggle()` stub when a `SettingsToggle` event
350
+ // is dispatched. Apps that own UI overlays (Settings panel) poll this in
351
+ // their rAF loop and dismiss the overlay if visible.
352
+ consumeBackButton(): boolean
353
+ }
354
+ declare const __gea_Gpio: {
355
+ // Digital pins, for the wiring a board exposes that no other facade covers:
356
+ // an LED, a relay, a button that is not the launcher button. Each call
357
+ // answers "did this board do it" — a pin the chip does not have reports
358
+ // false rather than throwing.
359
+ configureOutput(pin: number): boolean
360
+ configureInput(pin: number, pullUp: boolean): boolean
361
+ write(pin: number, level: boolean): boolean
362
+ read(pin: number): boolean
363
+ }
364
+ declare const __gea_Led: {
365
+ // A WS2812/NeoPixel strand on one pin — what "the onboard LED" is on most
366
+ // modern ESP32 modules. set() is the one-call default (attach if needed,
367
+ // write, transmit); off() is set() with black. show() returns once the strand
368
+ // has latched the buffer, so a true means the LED is showing what was asked
369
+ // for rather than that the write was queued.
370
+ set(pin: number, r: number, g: number, b: number): boolean
371
+ off(pin: number): boolean
372
+ attach(pin: number, count: number): boolean
373
+ setPixel(index: number, r: number, g: number, b: number): boolean
374
+ show(): boolean
375
+ detach(): void
376
+ }
377
+ declare const __gea_Clock: {
378
+ // Milliseconds since the Unix epoch from the platform real-time clock
379
+ // (gettimeofday). Reflects a host-set time (GEADEV SETTIME); unlike
380
+ // Date.now() — monotonic on this ESP32 build — it tracks real wall-clock
381
+ // time once synced. Returns a small value before the clock is ever set.
382
+ epochMs(): number
383
+ }
384
+ declare const __gea_Profiler: {
385
+ // Monotonic microseconds from the platform high-resolution timer.
386
+ nowUs(): number
387
+ }
388
+ declare const __gea_Battery: {
389
+ // Battery charge percentage (0-100) from the platform PMU.
390
+ level(): number
391
+ }
392
+ declare const __gea_Notify: {
393
+ // Transient host->app notification channel. text() is the latest message;
394
+ // seq() increments on each post so an app can detect a new one.
395
+ text(): string
396
+ seq(): number
397
+ }
398
+ declare const __gea_DeviceControl: {
399
+ // Run a host shell command, return combined stdout+stderr (macOS companion).
400
+ exec(command: string): string
401
+ }
402
+
403
+ // The `__gea_embedded_touch` global this used to reach for is registered by
404
+ // NOTHING in this repository — `touch.read()` has always returned the zero
405
+ // sample on every board. Reaching for it was not free, though: the
406
+ // `globalThis as EmbeddedHostGlobals` cast minted a file-scope record out of
407
+ // the entire global surface, and the hook's own `read()` is a callable member,
408
+ // which D0 record identity declines by design (a callable key is coarser than
409
+ // the physical ABI). The field therefore reached emission with no frozen
410
+ // representation authority and `widenValueClassFieldForFileScope` failed
411
+ // closed, taking every embedded build down with it.
412
+ //
413
+ // Keep the public surface (`export declare const touch: TouchHost`) and the
414
+ // observable behaviour exactly as they were. When a board does grow a raw
415
+ // touch source, give it a real ambient `declare const __gea_*` host object
416
+ // like every other one in this file rather than a globalThis intersection.
417
+
418
+ export const defaults = {}
419
+
420
+ export const touch = {
421
+ read(): TouchSample {
422
+ return { touching: false, x: 0, y: 0 }
423
+ }
424
+ }
425
+
426
+ export const Apps = {
427
+ launch(appId: string): number {
428
+ return apps.launch(appId)
429
+ }
430
+ }
431
+
432
+ // Case-variant aliases for host accessors. Keep these lazy: direct top-level
433
+ // `navigator.*` aliases survive bundling as bare reads even in Display-only
434
+ // apps, which bloats embedded canvas demos.
435
+ export const BLE: typeof navigator.bluetooth = {
436
+ keyboard: {
437
+ tap(hidCode: number): void {
438
+ navigator.bluetooth.keyboard.tap(hidCode)
439
+ },
440
+ down(modifier: number, hidCode: number): void {
441
+ navigator.bluetooth.keyboard.down(modifier, hidCode)
442
+ },
443
+ up(): void {
444
+ navigator.bluetooth.keyboard.up()
445
+ }
446
+ },
447
+ mouse: {
448
+ move(dx: number, dy: number, buttons?: number, wheel?: number): void {
449
+ navigator.bluetooth.mouse.move(dx, dy, buttons, wheel)
450
+ },
451
+ click(button: number): void {
452
+ navigator.bluetooth.mouse.click(button)
453
+ }
454
+ },
455
+ // BLE-MIDI (MIDI over GATT). `enable()` registers the MIDI service +
456
+ // advertises its UUID; `send()` transmits ONE already-framed BLE-MIDI
457
+ // packet (header + timestamp + MIDI bytes) as a notification.
458
+ midi: {
459
+ enable(): void {
460
+ navigator.bluetooth.midi.enable()
461
+ },
462
+ bound(): boolean {
463
+ return navigator.bluetooth.midi.bound()
464
+ },
465
+ send(bytes: number[]): void {
466
+ navigator.bluetooth.midi.send(bytes)
467
+ },
468
+ // Central role (scan for WIDI adapters / BLE-MIDI pedals and connect as
469
+ // a GATT client). Scan results follow the WiFi scan polling idiom.
470
+ startScan(): void {
471
+ navigator.bluetooth.midi.startScan()
472
+ },
473
+ stopScan(): void {
474
+ navigator.bluetooth.midi.stopScan()
475
+ },
476
+ scanning(): boolean {
477
+ return navigator.bluetooth.midi.scanning()
478
+ },
479
+ scanCount(): number {
480
+ return navigator.bluetooth.midi.scanCount()
481
+ },
482
+ scanNameAt(index: number): string {
483
+ return navigator.bluetooth.midi.scanNameAt(index)
484
+ },
485
+ connect(index: number): void {
486
+ navigator.bluetooth.midi.connect(index)
487
+ },
488
+ disconnect(): void {
489
+ navigator.bluetooth.midi.disconnect()
490
+ }
491
+ },
492
+ // HID host role: this device is the central; a remote HID peripheral (a
493
+ // keyboard/macro pad like the XPPen ACK05) is the input source. Raw input
494
+ // reports queue driver-side; the app polls and decodes them itself.
495
+ hidHost: {
496
+ startScan(): void {
497
+ navigator.bluetooth.hidHost.startScan()
498
+ },
499
+ stopScan(): void {
500
+ navigator.bluetooth.hidHost.stopScan()
501
+ },
502
+ scanning(): boolean {
503
+ return navigator.bluetooth.hidHost.scanning()
504
+ },
505
+ scanCount(): number {
506
+ return navigator.bluetooth.hidHost.scanCount()
507
+ },
508
+ scanNameAt(index: number): string {
509
+ return navigator.bluetooth.hidHost.scanNameAt(index)
510
+ },
511
+ connect(index: number): void {
512
+ navigator.bluetooth.hidHost.connect(index)
513
+ },
514
+ disconnect(): void {
515
+ navigator.bluetooth.hidHost.disconnect()
516
+ },
517
+ bound(): boolean {
518
+ return navigator.bluetooth.hidHost.bound()
519
+ },
520
+ reportCount(): number {
521
+ return navigator.bluetooth.hidHost.reportCount()
522
+ },
523
+ reportIdAt(index: number): number {
524
+ return navigator.bluetooth.hidHost.reportIdAt(index)
525
+ },
526
+ reportLenAt(index: number): number {
527
+ return navigator.bluetooth.hidHost.reportLenAt(index)
528
+ },
529
+ reportByteAt(index: number, byteIndex: number): number {
530
+ return navigator.bluetooth.hidHost.reportByteAt(index, byteIndex)
531
+ },
532
+ clearReports(): void {
533
+ navigator.bluetooth.hidHost.clearReports()
534
+ }
535
+ },
536
+ // Live connection registry across every concurrent BLE role. kind: 0 =
537
+ // HID central peer (desktop), 1 = MIDI central peer (DAW), 2 = HID
538
+ // peripheral we host (macro pad), 3 = BLE-MIDI peripheral we drive
539
+ // (pedal/WIDI adapter).
540
+ connections: {
541
+ count(): number {
542
+ return navigator.bluetooth.connections.count()
543
+ },
544
+ kindAt(index: number): number {
545
+ return navigator.bluetooth.connections.kindAt(index)
546
+ },
547
+ nameAt(index: number): string {
548
+ return navigator.bluetooth.connections.nameAt(index)
549
+ }
550
+ },
551
+ // Config service (custom GATT). A Web Bluetooth portal live-programs the
552
+ // device: it reads the app's current config blob and, once paired with the
553
+ // 4-digit code shown on-device, writes a new one. `setDocument` publishes
554
+ // the blob the portal reads back; the app polls `pendingLength` each frame
555
+ // and drains a committed inbound blob via `pendingByteAt`/`consumePending`.
556
+ // The pairing trio drives the on-device pairing overlay.
557
+ config: {
558
+ setDocument(bytes: number[]): void {
559
+ navigator.bluetooth.config.setDocument(bytes)
560
+ },
561
+ pendingLength(): number {
562
+ return navigator.bluetooth.config.pendingLength()
563
+ },
564
+ pendingByteAt(index: number): number {
565
+ return navigator.bluetooth.config.pendingByteAt(index)
566
+ },
567
+ consumePending(): void {
568
+ navigator.bluetooth.config.consumePending()
569
+ },
570
+ pairing(): boolean {
571
+ return navigator.bluetooth.config.pairing()
572
+ },
573
+ pairCode(): string {
574
+ return navigator.bluetooth.config.pairCode()
575
+ },
576
+ dismissPairing(): void {
577
+ navigator.bluetooth.config.dismissPairing()
578
+ },
579
+ // Device -> portal activity push: when a control fires on the device, tell
580
+ // any subscribed portal which control (by index) lit up, so the portal can
581
+ // highlight it live. No-op on the device when no portal is subscribed.
582
+ pushActivity(index: number): void {
583
+ navigator.bluetooth.config.pushActivity(index)
584
+ }
585
+ },
586
+ enabled(): boolean {
587
+ return navigator.bluetooth.enabled()
588
+ },
589
+ setEnabled(enabled: boolean): void {
590
+ navigator.bluetooth.setEnabled(enabled)
591
+ },
592
+ connected(): boolean {
593
+ return navigator.bluetooth.connected()
594
+ },
595
+ bound(): boolean {
596
+ return navigator.bluetooth.bound()
597
+ },
598
+ batteryLevel(): number {
599
+ return navigator.bluetooth.batteryLevel()
600
+ },
601
+ mac(): string {
602
+ return navigator.bluetooth.mac()
603
+ },
604
+ deviceName(): string {
605
+ return navigator.bluetooth.deviceName()
606
+ },
607
+ init(deviceName: string, appearance?: number, macAddress?: string): void {
608
+ navigator.bluetooth.init(deviceName, appearance, macAddress)
609
+ },
610
+ startAdvertising(): void {
611
+ navigator.bluetooth.startAdvertising()
612
+ },
613
+ stopAdvertising(): void {
614
+ navigator.bluetooth.stopAdvertising()
615
+ }
616
+ }
617
+ export const bluetooth = BLE
618
+ export const WiFi: typeof navigator.wifi = {
619
+ enabled(): boolean {
620
+ return navigator.wifi.enabled()
621
+ },
622
+ setEnabled(enabled: boolean): void {
623
+ navigator.wifi.setEnabled(enabled)
624
+ },
625
+ connected(): boolean {
626
+ return navigator.wifi.connected()
627
+ },
628
+ rssi(): number {
629
+ return navigator.wifi.rssi()
630
+ },
631
+ ssid(): string {
632
+ return navigator.wifi.ssid()
633
+ },
634
+ ip(): string {
635
+ return navigator.wifi.ip()
636
+ },
637
+ mac(): string {
638
+ return navigator.wifi.mac()
639
+ },
640
+ configure(ssid: string, password: string): void {
641
+ navigator.wifi.configure(ssid, password)
642
+ },
643
+ waitForConnection(timeoutMs: number): boolean {
644
+ return navigator.wifi.waitForConnection(timeoutMs)
645
+ },
646
+ startScan(): void {
647
+ navigator.wifi.startScan()
648
+ },
649
+ scanning(): boolean {
650
+ return navigator.wifi.scanning()
651
+ },
652
+ scanCount(): number {
653
+ return navigator.wifi.scanCount()
654
+ },
655
+ scanSsidAt(index: number): string {
656
+ return navigator.wifi.scanSsidAt(index)
657
+ },
658
+ scanRssiAt(index: number): number {
659
+ return navigator.wifi.scanRssiAt(index)
660
+ },
661
+ scanSecuredAt(index: number): boolean {
662
+ return navigator.wifi.scanSecuredAt(index)
663
+ }
664
+ }
665
+ export const wifi = WiFi
666
+
667
+ // Device-hosted HTTP server. On hardware, geatsc lowers `http.createServer(...)`
668
+ // to the native esp_http_server facade (packages/core/host/http.cpp); this JS
669
+ // stub is the web/simulator fallback — browsers can't open a listening socket,
670
+ // so it accepts the handler and reports a no-op server (listen() returns false).
671
+ export interface HttpIncomingMessage {
672
+ method: string
673
+ url: string
674
+ path: string
675
+ query: string
676
+ }
677
+ export interface HttpServerInstance {
678
+ listen(port: number): boolean
679
+ close(): void
680
+ id(): number
681
+ }
682
+ export const http = {
683
+ createServer(_handler: (req: HttpIncomingMessage) => HttpReply): HttpServerInstance {
684
+ return {
685
+ listen(_port: number): boolean {
686
+ return false
687
+ },
688
+ close(): void {},
689
+ id(): number {
690
+ return 0
691
+ }
692
+ }
693
+ },
694
+ close(_handle: number): void {}
695
+ }
696
+ export const Geolocation: typeof navigator.geolocation = {
697
+ hasFix(): boolean {
698
+ return navigator.geolocation.hasFix()
699
+ },
700
+ currentPosition(): GeolocationPosition {
701
+ return navigator.geolocation.currentPosition()
702
+ },
703
+ coords(): GeolocationCoordinates {
704
+ return navigator.geolocation.coords()
705
+ },
706
+ latitude(): number {
707
+ return navigator.geolocation.latitude()
708
+ },
709
+ longitude(): number {
710
+ return navigator.geolocation.longitude()
711
+ },
712
+ accuracy(): number {
713
+ return navigator.geolocation.accuracy()
714
+ },
715
+ getCurrentPosition(
716
+ success: (position: GeolocationPosition) => void,
717
+ error?: (error: GeolocationPositionError) => void,
718
+ options?: PositionOptions
719
+ ): void {
720
+ navigator.geolocation.getCurrentPosition(success, error, options)
721
+ },
722
+ watchPosition(
723
+ success: (position: GeolocationPosition) => void,
724
+ error?: (error: GeolocationPositionError) => void,
725
+ options?: PositionOptions
726
+ ): number {
727
+ return navigator.geolocation.watchPosition(success, error, options)
728
+ },
729
+ clearWatch(id: number): void {
730
+ navigator.geolocation.clearWatch(id)
731
+ }
732
+ }
733
+ export const geolocation = Geolocation
734
+ // `__gea_Accelerometer` is only injected by the vite plugin when an app actually
735
+ // uses the accelerometer (the imu-capability gate keys on `Accelerometer.` calls).
736
+ // Referencing it unconditionally here threw `ReferenceError: __gea_Accelerometer
737
+ // is not defined` at module load for every app that doesn't use it — blanking the
738
+ // whole page in the browser runtime. `typeof` on the bare identifier is safe
739
+ // whether or not the global was injected, and apps that do use it still get the
740
+ // real binding.
741
+ export const Accelerometer =
742
+ typeof __gea_Accelerometer !== 'undefined' ? __gea_Accelerometer : (undefined as unknown as typeof __gea_Accelerometer)
743
+
744
+ // Remembers the most recent recording sink so stopRecording can report it on
745
+ // the resolved CameraClip (the host only returns a duration).
746
+ let cameraRecordingPath = ''
747
+
748
+ // Live preview + still-capture + recording + controls API. Same shape on every
749
+ // target that ships a Camera backend (esp32-p4, geaos; iOS follows). The
750
+ // `<camera>` JSX element provides the matching CSS-sized preview surface; the
751
+ // optional <CameraView /> wrapper lives in @geastack/elements.
752
+ export const Camera = {
753
+ isAvailable(): boolean {
754
+ return __gea_Camera.isAvailable()
755
+ },
756
+ hasPermission(): boolean {
757
+ return __gea_Camera.hasPermission()
758
+ },
759
+ requestPermission(): Promise<boolean> {
760
+ return Promise.resolve(__gea_Camera.requestPermission())
761
+ },
762
+ open(options?: CameraOpenOptions | CameraFacing): boolean {
763
+ if (typeof options === 'string') return __gea_Camera.open(options, 0, 0)
764
+ if (!options) return __gea_Camera.open('back', 0, 0)
765
+ const facing = options.facing ?? 'back'
766
+ const width = options.width ?? 0
767
+ const height = options.height ?? 0
768
+ return __gea_Camera.open(facing, width, height)
769
+ },
770
+ close(): void {
771
+ __gea_Camera.close()
772
+ },
773
+ isOpen(): boolean {
774
+ return __gea_Camera.isOpen()
775
+ },
776
+ get facing(): CameraFacing {
777
+ return __gea_Camera.facing as CameraFacing
778
+ },
779
+ get width(): number {
780
+ return __gea_Camera.width
781
+ },
782
+ get height(): number {
783
+ return __gea_Camera.height
784
+ },
785
+ get orientation(): number {
786
+ return __gea_Camera.orientation
787
+ },
788
+ draw(x: number, y: number, destWidth?: number, destHeight?: number): void {
789
+ __gea_Camera.draw(x, y, destWidth ?? 0, destHeight ?? 0)
790
+ },
791
+ capture(options?: CameraCaptureOptions): Promise<CameraPhoto> {
792
+ const id = options?.mirror ? __gea_Camera.captureMirrored() : __gea_Camera.capture()
793
+ const photo: CameraPhoto = {
794
+ imageId: id,
795
+ width: __gea_Camera.width,
796
+ height: __gea_Camera.height,
797
+ orientation: __gea_Camera.orientation,
798
+ dispose(): void {
799
+ image.dispose(id)
800
+ }
801
+ }
802
+ return Promise.resolve(photo)
803
+ },
804
+ capturePhoto(options?: CameraCaptureOptions): Promise<CameraPhoto> {
805
+ return this.capture(options)
806
+ },
807
+ startRecording(options: CameraRecordOptions): Promise<void> {
808
+ cameraRecordingPath = options.path
809
+ __gea_Camera.startRecording(options.path, options.fps ?? 0)
810
+ return Promise.resolve()
811
+ },
812
+ stopRecording(): Promise<CameraClip> {
813
+ // durationMs is -1 when no recording was active; callers can check it.
814
+ const durationMs = __gea_Camera.stopRecording()
815
+ const clip: CameraClip = { durationMs, path: cameraRecordingPath }
816
+ return Promise.resolve(clip)
817
+ },
818
+ isRecording(): boolean {
819
+ return __gea_Camera.isRecording()
820
+ },
821
+ setFlash(mode: CameraFlashMode): void {
822
+ __gea_Camera.setFlash(mode)
823
+ },
824
+ setZoom(factor: number): void {
825
+ __gea_Camera.setZoom(factor)
826
+ },
827
+ setMirror(mirror: boolean): void {
828
+ __gea_Camera.setMirror(mirror)
829
+ },
830
+ setExposure(options: CameraExposureOptions): void {
831
+ __gea_Camera.setExposure(options.mode ?? 'continuous', options.bias ?? 0, options.iso ?? 0, options.durationMs ?? 0)
832
+ },
833
+ setWhiteBalance(options: CameraWhiteBalanceOptions): void {
834
+ __gea_Camera.setWhiteBalance(options.mode ?? 'auto', options.temperature ?? 0, options.tint ?? 0)
835
+ },
836
+ setFocus(options: CameraFocusOptions): void {
837
+ __gea_Camera.setFocus(options.mode ?? 'continuous', options.point?.x ?? 0, options.point?.y ?? 0)
838
+ },
839
+ setTorch(mode: 'off' | 'on' | 'auto', level?: number): void {
840
+ __gea_Camera.setTorch(mode, level ?? 0)
841
+ },
842
+ switchCamera(target: CameraFacing | string): boolean {
843
+ if (this.isOpen()) __gea_Camera.close()
844
+ return __gea_Camera.open(target, 0, 0)
845
+ },
846
+ getDevices(): CameraDevice[] {
847
+ const count = __gea_Camera.deviceCount
848
+ const out: CameraDevice[] = []
849
+ for (let i = 0; i < count; i++) {
850
+ out.push({
851
+ id: __gea_Camera.deviceIdAt(i),
852
+ facing: __gea_Camera.deviceFacingAt(i) as CameraFacing
853
+ })
854
+ }
855
+ return out
856
+ }
857
+ }
858
+
859
+ // Same guard as Accelerometer above: these embedded host globals are not injected
860
+ // in the browser runtime (which renders via real web APIs), so a bare top-level
861
+ // reference ReferenceErrors at module load and blanks the page. `typeof` keeps the
862
+ // real binding on embedded targets where the plugin injects them.
863
+ export const audioContext: AudioContext =
864
+ typeof __gea_audioContext !== 'undefined' ? __gea_audioContext : (undefined as unknown as AudioContext)
865
+ // Method wrapper, NOT the `typeof … ? … : undefined as …` guard the
866
+ // property-bearing host objects above use. Two reasons. (1) The guard exists
867
+ // only to stop a bare top-level reference from ReferenceError-ing at module
868
+ // load in the browser runtime; a reference inside a method body is evaluated
869
+ // on call, so it needs no guard — which is why `Memory`/`Input`/`Clock` below
870
+ // are already written this way. (2) `typeof __gea_Audio` is an object whose
871
+ // every member is callable, and the conditional over it has no plannable
872
+ // representation ("coverage gap for ConditionalExpression"). The plugin cannot
873
+ // paper over that with a `hostNamespaces` entry either: the name `Audio` is
874
+ // already claimed by `embeddedHostClasses` for the DOM `new Audio()`
875
+ // constructor (gea::host::HTMLAudioElement).
876
+ export const Audio = {
877
+ getVolume(): number {
878
+ return __gea_Audio.getVolume()
879
+ },
880
+ setVolume(volume: number): void {
881
+ __gea_Audio.setVolume(volume)
882
+ }
883
+ }
884
+ export const Display = typeof __gea_Display !== 'undefined' ? __gea_Display : (undefined as unknown as typeof __gea_Display)
885
+ export const Memory = {
886
+ internalFree(): number {
887
+ return __gea_Memory.internalFree()
888
+ },
889
+ internalLargestFreeBlock(): number {
890
+ return __gea_Memory.internalLargestFreeBlock()
891
+ },
892
+ internalMinimumFree(): number {
893
+ return __gea_Memory.internalMinimumFree()
894
+ },
895
+ psramFree(): number {
896
+ return __gea_Memory.psramFree()
897
+ },
898
+ currentTaskStackHighWaterMark(): number {
899
+ return __gea_Memory.currentTaskStackHighWaterMark()
900
+ },
901
+ geaMainStackBytes(): number {
902
+ return __gea_Memory.geaMainStackBytes()
903
+ },
904
+ geaInitStackBytes(): number {
905
+ return __gea_Memory.geaInitStackBytes()
906
+ },
907
+ appFrameStackWords(): number {
908
+ return __gea_Memory.appFrameStackWords()
909
+ },
910
+ appFrameStackBytes(): number {
911
+ return __gea_Memory.appFrameStackBytes()
912
+ },
913
+ displayFlushConfiguredRows(): number {
914
+ return __gea_Memory.displayFlushConfiguredRows()
915
+ },
916
+ displayFlushConfiguredDepth(): number {
917
+ return __gea_Memory.displayFlushConfiguredDepth()
918
+ },
919
+ displayFlushBufferMaxBytes(): number {
920
+ return __gea_Memory.displayFlushBufferMaxBytes()
921
+ },
922
+ displayFlushRows(): number {
923
+ return __gea_Memory.displayFlushRows()
924
+ },
925
+ displayFlushDepth(): number {
926
+ return __gea_Memory.displayFlushDepth()
927
+ },
928
+ displayFlushBufferBytes(): number {
929
+ return __gea_Memory.displayFlushBufferBytes()
930
+ },
931
+ allocationSramCount(): number {
932
+ return __gea_Memory.allocationSramCount()
933
+ },
934
+ allocationPsramCount(): number {
935
+ return __gea_Memory.allocationPsramCount()
936
+ },
937
+ allocationSramBytes(): number {
938
+ return __gea_Memory.allocationSramBytes()
939
+ },
940
+ allocationPsramBytes(): number {
941
+ return __gea_Memory.allocationPsramBytes()
942
+ },
943
+ allocationSramPeakBytes(): number {
944
+ return __gea_Memory.allocationSramPeakBytes()
945
+ },
946
+ allocationPsramPeakBytes(): number {
947
+ return __gea_Memory.allocationPsramPeakBytes()
948
+ },
949
+ stats(): EmbeddedMemoryStats {
950
+ return {
951
+ internalFree: __gea_Memory.internalFree(),
952
+ internalLargestFreeBlock: __gea_Memory.internalLargestFreeBlock(),
953
+ internalMinimumFree: __gea_Memory.internalMinimumFree(),
954
+ psramFree: __gea_Memory.psramFree(),
955
+ currentTaskStackHighWaterMark: __gea_Memory.currentTaskStackHighWaterMark(),
956
+ allocationSramCount: __gea_Memory.allocationSramCount(),
957
+ allocationPsramCount: __gea_Memory.allocationPsramCount(),
958
+ allocationSramBytes: __gea_Memory.allocationSramBytes(),
959
+ allocationPsramBytes: __gea_Memory.allocationPsramBytes(),
960
+ allocationSramPeakBytes: __gea_Memory.allocationSramPeakBytes(),
961
+ allocationPsramPeakBytes: __gea_Memory.allocationPsramPeakBytes()
962
+ }
963
+ },
964
+ config(): EmbeddedMemoryConfig {
965
+ return {
966
+ geaMainStackBytes: __gea_Memory.geaMainStackBytes(),
967
+ geaInitStackBytes: __gea_Memory.geaInitStackBytes(),
968
+ appFrameStackWords: __gea_Memory.appFrameStackWords(),
969
+ appFrameStackBytes: __gea_Memory.appFrameStackBytes(),
970
+ displayFlushConfiguredRows: __gea_Memory.displayFlushConfiguredRows(),
971
+ displayFlushConfiguredDepth: __gea_Memory.displayFlushConfiguredDepth(),
972
+ displayFlushBufferMaxBytes: __gea_Memory.displayFlushBufferMaxBytes(),
973
+ displayFlushRows: __gea_Memory.displayFlushRows(),
974
+ displayFlushDepth: __gea_Memory.displayFlushDepth(),
975
+ displayFlushBufferBytes: __gea_Memory.displayFlushBufferBytes()
976
+ }
977
+ }
978
+ }
979
+ export const Input = {
980
+ consumeBackButton(): boolean {
981
+ return __gea_Input.consumeBackButton()
982
+ }
983
+ }
984
+ export const Gpio = {
985
+ configureOutput(pin: number): boolean {
986
+ return __gea_Gpio.configureOutput(pin)
987
+ },
988
+ configureInput(pin: number, pullUp: boolean): boolean {
989
+ return __gea_Gpio.configureInput(pin, pullUp)
990
+ },
991
+ write(pin: number, level: boolean): boolean {
992
+ return __gea_Gpio.write(pin, level)
993
+ },
994
+ read(pin: number): boolean {
995
+ return __gea_Gpio.read(pin)
996
+ }
997
+ }
998
+ export const Led = {
999
+ set(pin: number, r: number, g: number, b: number): boolean {
1000
+ return __gea_Led.set(pin, r, g, b)
1001
+ },
1002
+ off(pin: number): boolean {
1003
+ return __gea_Led.off(pin)
1004
+ },
1005
+ attach(pin: number, count: number): boolean {
1006
+ return __gea_Led.attach(pin, count)
1007
+ },
1008
+ setPixel(index: number, r: number, g: number, b: number): boolean {
1009
+ return __gea_Led.setPixel(index, r, g, b)
1010
+ },
1011
+ show(): boolean {
1012
+ return __gea_Led.show()
1013
+ },
1014
+ detach(): void {
1015
+ __gea_Led.detach()
1016
+ }
1017
+ }
1018
+ export const Clock = {
1019
+ epochMs(): number {
1020
+ return __gea_Clock.epochMs()
1021
+ }
1022
+ }
1023
+ // Same method-wrapper reasoning as `Audio` above: the host global is only
1024
+ // touched when the method runs, and the conditional form has no plannable
1025
+ // representation. The `Date.now()` fallback is preserved for the runtimes that
1026
+ // do not inject the global.
1027
+ export const Profiler = {
1028
+ nowUs(): number {
1029
+ if (typeof __gea_Profiler === 'undefined') return Date.now() * 1000
1030
+ return __gea_Profiler.nowUs()
1031
+ }
1032
+ }
1033
+ export const Battery = {
1034
+ level(): number {
1035
+ return __gea_Battery.level()
1036
+ }
1037
+ }
1038
+ export const Notify = {
1039
+ text(): string {
1040
+ return __gea_Notify.text()
1041
+ },
1042
+ seq(): number {
1043
+ return __gea_Notify.seq()
1044
+ }
1045
+ }
1046
+ export const DeviceControl = {
1047
+ exec(command: string): string {
1048
+ return __gea_DeviceControl.exec(command)
1049
+ }
1050
+ }
1051
+ // Method wrapper for the same two reasons as `Audio`/`Profiler` above: a
1052
+ // reference inside a method body cannot ReferenceError at module load, and the
1053
+ // `typeof … ? … : undefined as …` conditional over an all-callable object type
1054
+ // has no plannable representation.
1055
+ export const imageHost = {
1056
+ loadBytes(bytes: Uint8Array): number {
1057
+ return image.loadBytes(bytes)
1058
+ },
1059
+ loadBytesOpaque(bytes: Uint8Array): number {
1060
+ return image.loadBytesOpaque(bytes)
1061
+ },
1062
+ width(id: number): number {
1063
+ return image.width(id)
1064
+ },
1065
+ height(id: number): number {
1066
+ return image.height(id)
1067
+ },
1068
+ frameCount(id: number): number {
1069
+ return image.frameCount(id)
1070
+ },
1071
+ isAnimated(id: number): boolean {
1072
+ return image.isAnimated(id)
1073
+ },
1074
+ setPlaying(id: number, playing: number): void {
1075
+ image.setPlaying(id, playing)
1076
+ },
1077
+ seek(id: number, frame: number): void {
1078
+ image.seek(id, frame)
1079
+ },
1080
+ dispose(id: number): void {
1081
+ image.dispose(id)
1082
+ }
1083
+ }