@geastack/core 0.1.39 → 0.1.41
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 +15 -0
- package/events.cpp +2 -0
- package/gea_app_entry.cpp +2 -2
- package/gea_sources.mjs +4 -3
- package/include/audio.h +11 -1
- package/include/audio_oscillator.h +52 -0
- package/include/camera.h +21 -0
- package/include/captive_dns.h +71 -0
- package/include/display_present.h +66 -28
- package/include/events.h +31 -3
- package/include/gea/embedded.h +2 -0
- package/include/graphics/font.h +19 -0
- package/include/image.h +39 -1
- package/include/input.h +12 -1
- package/include/platform/atomic_file.h +72 -0
- package/include/platform/file_cache.h +6 -3
- package/include/power.h +2 -0
- package/include/services/frame_scheduler.h +6 -0
- package/include/wifi.h +60 -45
- package/index.d.ts +229 -45
- package/package.json +3 -3
- package/runtime/audio-worklet.ts +7 -11
- package/runtime/host.ts +132 -77
- package/runtime/images.ts +5 -0
- package/runtime-surface.ts +2 -0
- package/runtime.cpp +23 -5
- package/scripts/bitmap-font-source.mjs +155 -0
- package/scripts/build-gea-vite-geatsc.mjs +97 -6
- package/scripts/gea-embedded-asset-policy.mjs +38 -0
- package/scripts/gea-embedded-compat-transform.mjs +4 -2
- package/scripts/gea-vite-module-graph-plugin.mjs +64 -15
- package/scripts/generate-gea-embedded-assets.mjs +17 -3
- package/scripts/generate-gea-embedded-fonts.mjs +986 -670
package/runtime/host.ts
CHANGED
|
@@ -30,7 +30,11 @@ import type {
|
|
|
30
30
|
EmbeddedMemoryConfig,
|
|
31
31
|
EmbeddedMemoryStats,
|
|
32
32
|
HttpReply,
|
|
33
|
-
|
|
33
|
+
IncomingMessage,
|
|
34
|
+
HttpModule,
|
|
35
|
+
HttpServer,
|
|
36
|
+
AudioContext,
|
|
37
|
+
WiFiController,
|
|
34
38
|
} from '../index'
|
|
35
39
|
|
|
36
40
|
// These 17 used to be declared HERE as well as in `../index`, byte for byte.
|
|
@@ -56,7 +60,7 @@ export type {
|
|
|
56
60
|
CameraWhiteBalanceOptions,
|
|
57
61
|
EmbeddedMemoryConfig,
|
|
58
62
|
EmbeddedMemoryStats,
|
|
59
|
-
HttpReply
|
|
63
|
+
HttpReply,
|
|
60
64
|
} from '../index'
|
|
61
65
|
|
|
62
66
|
declare const apps: {
|
|
@@ -86,32 +90,16 @@ declare const navigator: {
|
|
|
86
90
|
getCurrentPosition(
|
|
87
91
|
success: (position: GeolocationPosition) => void,
|
|
88
92
|
error?: (error: GeolocationPositionError) => void,
|
|
89
|
-
options?: PositionOptions
|
|
93
|
+
options?: PositionOptions,
|
|
90
94
|
): void
|
|
91
95
|
watchPosition(
|
|
92
96
|
success: (position: GeolocationPosition) => void,
|
|
93
97
|
error?: (error: GeolocationPositionError) => void,
|
|
94
|
-
options?: PositionOptions
|
|
98
|
+
options?: PositionOptions,
|
|
95
99
|
): number
|
|
96
100
|
clearWatch(id: number): void
|
|
97
101
|
}
|
|
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
|
-
}
|
|
102
|
+
wifi: WiFiController
|
|
115
103
|
bluetooth: {
|
|
116
104
|
keyboard: {
|
|
117
105
|
tap(hidCode: number): void
|
|
@@ -209,6 +197,7 @@ declare const __gea_Camera: {
|
|
|
209
197
|
draw(x: number, y: number, destWidth: number, destHeight: number): void
|
|
210
198
|
capture(): number
|
|
211
199
|
captureMirrored(): number
|
|
200
|
+
captureFrame(): string
|
|
212
201
|
startRecording(path: string, fps: number): boolean
|
|
213
202
|
stopRecording(): number
|
|
214
203
|
isRecording(): boolean
|
|
@@ -239,9 +228,11 @@ declare const __gea_Audio: {
|
|
|
239
228
|
setVolume(volume: number): void
|
|
240
229
|
}
|
|
241
230
|
|
|
242
|
-
type EmbeddedDisplayOrientation =
|
|
231
|
+
type EmbeddedDisplayOrientation =
|
|
232
|
+
'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'
|
|
243
233
|
|
|
244
|
-
type EmbeddedDisplayOrientationSupport =
|
|
234
|
+
type EmbeddedDisplayOrientationSupport =
|
|
235
|
+
EmbeddedDisplayOrientation | 'portrait' | 'landscape' | 'all'
|
|
245
236
|
|
|
246
237
|
type EmbeddedDisplayPixelFormat = 'rgb565' | 'rgb888' | 'rgb8888' | 'argb8888'
|
|
247
238
|
|
|
@@ -262,7 +253,9 @@ declare const __gea_Display: {
|
|
|
262
253
|
getOrientation(): EmbeddedDisplayOrientation
|
|
263
254
|
setOrientation(orientation: EmbeddedDisplayOrientation): void
|
|
264
255
|
getSupportedOrientations(): EmbeddedDisplayOrientationSupport[]
|
|
265
|
-
setSupportedOrientations(
|
|
256
|
+
setSupportedOrientations(
|
|
257
|
+
orientations: EmbeddedDisplayOrientationSupport | EmbeddedDisplayOrientationSupport[],
|
|
258
|
+
): void
|
|
266
259
|
getAutoRotate(): boolean
|
|
267
260
|
setAutoRotate(enabled: boolean): void
|
|
268
261
|
setVSync(on: boolean): void
|
|
@@ -375,6 +368,7 @@ declare const __gea_Led: {
|
|
|
375
368
|
detach(): void
|
|
376
369
|
}
|
|
377
370
|
declare const __gea_Clock: {
|
|
371
|
+
setEpochMs(timestamp: number): boolean
|
|
378
372
|
// Milliseconds since the Unix epoch from the platform real-time clock
|
|
379
373
|
// (gettimeofday). Reflects a host-set time (GEADEV SETTIME); unlike
|
|
380
374
|
// Date.now() — monotonic on this ESP32 build — it tracks real wall-clock
|
|
@@ -384,8 +378,14 @@ declare const __gea_Clock: {
|
|
|
384
378
|
declare const __gea_Profiler: {
|
|
385
379
|
// Monotonic microseconds from the platform high-resolution timer.
|
|
386
380
|
nowUs(): number
|
|
381
|
+
// Hardware cycle count on ESP32; nanoseconds on desktop hosts.
|
|
382
|
+
nowCycles(): number
|
|
383
|
+
}
|
|
384
|
+
declare const __gea_Haptics: {
|
|
385
|
+
vibrate(durationMs: number, strength: number): boolean
|
|
387
386
|
}
|
|
388
387
|
declare const __gea_Battery: {
|
|
388
|
+
charging(): boolean
|
|
389
389
|
// Battery charge percentage (0-100) from the platform PMU.
|
|
390
390
|
level(): number
|
|
391
391
|
}
|
|
@@ -420,13 +420,13 @@ export const defaults = {}
|
|
|
420
420
|
export const touch = {
|
|
421
421
|
read(): TouchSample {
|
|
422
422
|
return { touching: false, x: 0, y: 0 }
|
|
423
|
-
}
|
|
423
|
+
},
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
export const Apps = {
|
|
427
427
|
launch(appId: string): number {
|
|
428
428
|
return apps.launch(appId)
|
|
429
|
-
}
|
|
429
|
+
},
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// Case-variant aliases for host accessors. Keep these lazy: direct top-level
|
|
@@ -442,7 +442,7 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
442
442
|
},
|
|
443
443
|
up(): void {
|
|
444
444
|
navigator.bluetooth.keyboard.up()
|
|
445
|
-
}
|
|
445
|
+
},
|
|
446
446
|
},
|
|
447
447
|
mouse: {
|
|
448
448
|
move(dx: number, dy: number, buttons?: number, wheel?: number): void {
|
|
@@ -450,7 +450,7 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
450
450
|
},
|
|
451
451
|
click(button: number): void {
|
|
452
452
|
navigator.bluetooth.mouse.click(button)
|
|
453
|
-
}
|
|
453
|
+
},
|
|
454
454
|
},
|
|
455
455
|
// BLE-MIDI (MIDI over GATT). `enable()` registers the MIDI service +
|
|
456
456
|
// advertises its UUID; `send()` transmits ONE already-framed BLE-MIDI
|
|
@@ -487,7 +487,7 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
487
487
|
},
|
|
488
488
|
disconnect(): void {
|
|
489
489
|
navigator.bluetooth.midi.disconnect()
|
|
490
|
-
}
|
|
490
|
+
},
|
|
491
491
|
},
|
|
492
492
|
// HID host role: this device is the central; a remote HID peripheral (a
|
|
493
493
|
// keyboard/macro pad like the XPPen ACK05) is the input source. Raw input
|
|
@@ -531,7 +531,7 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
531
531
|
},
|
|
532
532
|
clearReports(): void {
|
|
533
533
|
navigator.bluetooth.hidHost.clearReports()
|
|
534
|
-
}
|
|
534
|
+
},
|
|
535
535
|
},
|
|
536
536
|
// Live connection registry across every concurrent BLE role. kind: 0 =
|
|
537
537
|
// HID central peer (desktop), 1 = MIDI central peer (DAW), 2 = HID
|
|
@@ -546,7 +546,7 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
546
546
|
},
|
|
547
547
|
nameAt(index: number): string {
|
|
548
548
|
return navigator.bluetooth.connections.nameAt(index)
|
|
549
|
-
}
|
|
549
|
+
},
|
|
550
550
|
},
|
|
551
551
|
// Config service (custom GATT). A Web Bluetooth portal live-programs the
|
|
552
552
|
// device: it reads the app's current config blob and, once paired with the
|
|
@@ -581,7 +581,7 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
581
581
|
// highlight it live. No-op on the device when no portal is subscribed.
|
|
582
582
|
pushActivity(index: number): void {
|
|
583
583
|
navigator.bluetooth.config.pushActivity(index)
|
|
584
|
-
}
|
|
584
|
+
},
|
|
585
585
|
},
|
|
586
586
|
enabled(): boolean {
|
|
587
587
|
return navigator.bluetooth.enabled()
|
|
@@ -612,10 +612,29 @@ export const BLE: typeof navigator.bluetooth = {
|
|
|
612
612
|
},
|
|
613
613
|
stopAdvertising(): void {
|
|
614
614
|
navigator.bluetooth.stopAdvertising()
|
|
615
|
-
}
|
|
615
|
+
},
|
|
616
616
|
}
|
|
617
617
|
export const bluetooth = BLE
|
|
618
618
|
export const WiFi: typeof navigator.wifi = {
|
|
619
|
+
accessPointMac(): string {
|
|
620
|
+
return navigator.wifi.accessPointMac()
|
|
621
|
+
},
|
|
622
|
+
|
|
623
|
+
startCaptivePortal(): boolean {
|
|
624
|
+
return navigator.wifi.startCaptivePortal()
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
stopCaptivePortal(): void {
|
|
628
|
+
navigator.wifi.stopCaptivePortal()
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
startAccessPoint(ssid: string, password = ''): boolean {
|
|
632
|
+
return navigator.wifi.startAccessPoint(ssid, password)
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
stopAccessPoint(): void {
|
|
636
|
+
navigator.wifi.stopAccessPoint()
|
|
637
|
+
},
|
|
619
638
|
enabled(): boolean {
|
|
620
639
|
return navigator.wifi.enabled()
|
|
621
640
|
},
|
|
@@ -660,39 +679,33 @@ export const WiFi: typeof navigator.wifi = {
|
|
|
660
679
|
},
|
|
661
680
|
scanSecuredAt(index: number): boolean {
|
|
662
681
|
return navigator.wifi.scanSecuredAt(index)
|
|
663
|
-
}
|
|
682
|
+
},
|
|
664
683
|
}
|
|
665
684
|
export const wifi = WiFi
|
|
666
685
|
|
|
667
686
|
// Device-hosted HTTP server. On hardware, geatsc lowers `http.createServer(...)`
|
|
668
687
|
// to the native esp_http_server facade (packages/core/host/http.cpp); this JS
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
export interface HttpIncomingMessage {
|
|
672
|
-
method: string
|
|
673
|
-
url: string
|
|
674
|
-
path: string
|
|
675
|
-
query: string
|
|
676
|
-
}
|
|
688
|
+
// fallback reports unsupported use because browsers cannot open a listening socket.
|
|
689
|
+
export type HttpIncomingMessage = IncomingMessage
|
|
677
690
|
export interface HttpServerInstance {
|
|
678
691
|
listen(port: number): boolean
|
|
679
692
|
close(): void
|
|
680
693
|
id(): number
|
|
681
694
|
}
|
|
695
|
+
declare const __gea_http: HttpModule
|
|
696
|
+
|
|
682
697
|
export const http = {
|
|
683
|
-
createServer(
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return false
|
|
687
|
-
},
|
|
688
|
-
close(): void {},
|
|
689
|
-
id(): number {
|
|
690
|
-
return 0
|
|
691
|
-
}
|
|
698
|
+
createServer(handler: (req: HttpIncomingMessage) => HttpReply): HttpServer {
|
|
699
|
+
if (typeof __gea_http !== 'undefined') {
|
|
700
|
+
return __gea_http.createServer(handler)
|
|
692
701
|
}
|
|
702
|
+
throw new Error('HTTP servers are unavailable in this browser runtime')
|
|
703
|
+
},
|
|
704
|
+
close(handle: number): void {
|
|
705
|
+
if (typeof __gea_http !== 'undefined') __gea_http.close(handle)
|
|
693
706
|
},
|
|
694
|
-
close(_handle: number): void {}
|
|
695
707
|
}
|
|
708
|
+
|
|
696
709
|
export const Geolocation: typeof navigator.geolocation = {
|
|
697
710
|
hasFix(): boolean {
|
|
698
711
|
return navigator.geolocation.hasFix()
|
|
@@ -715,20 +728,20 @@ export const Geolocation: typeof navigator.geolocation = {
|
|
|
715
728
|
getCurrentPosition(
|
|
716
729
|
success: (position: GeolocationPosition) => void,
|
|
717
730
|
error?: (error: GeolocationPositionError) => void,
|
|
718
|
-
options?: PositionOptions
|
|
731
|
+
options?: PositionOptions,
|
|
719
732
|
): void {
|
|
720
733
|
navigator.geolocation.getCurrentPosition(success, error, options)
|
|
721
734
|
},
|
|
722
735
|
watchPosition(
|
|
723
736
|
success: (position: GeolocationPosition) => void,
|
|
724
737
|
error?: (error: GeolocationPositionError) => void,
|
|
725
|
-
options?: PositionOptions
|
|
738
|
+
options?: PositionOptions,
|
|
726
739
|
): number {
|
|
727
740
|
return navigator.geolocation.watchPosition(success, error, options)
|
|
728
741
|
},
|
|
729
742
|
clearWatch(id: number): void {
|
|
730
743
|
navigator.geolocation.clearWatch(id)
|
|
731
|
-
}
|
|
744
|
+
},
|
|
732
745
|
}
|
|
733
746
|
export const geolocation = Geolocation
|
|
734
747
|
// `__gea_Accelerometer` is only injected by the vite plugin when an app actually
|
|
@@ -739,7 +752,9 @@ export const geolocation = Geolocation
|
|
|
739
752
|
// whether or not the global was injected, and apps that do use it still get the
|
|
740
753
|
// real binding.
|
|
741
754
|
export const Accelerometer =
|
|
742
|
-
typeof __gea_Accelerometer !== 'undefined'
|
|
755
|
+
typeof __gea_Accelerometer !== 'undefined'
|
|
756
|
+
? __gea_Accelerometer
|
|
757
|
+
: (undefined as unknown as typeof __gea_Accelerometer)
|
|
743
758
|
|
|
744
759
|
// Remembers the most recent recording sink so stopRecording can report it on
|
|
745
760
|
// the resolved CameraClip (the host only returns a duration).
|
|
@@ -797,13 +812,16 @@ export const Camera = {
|
|
|
797
812
|
orientation: __gea_Camera.orientation,
|
|
798
813
|
dispose(): void {
|
|
799
814
|
image.dispose(id)
|
|
800
|
-
}
|
|
815
|
+
},
|
|
801
816
|
}
|
|
802
817
|
return Promise.resolve(photo)
|
|
803
818
|
},
|
|
804
819
|
capturePhoto(options?: CameraCaptureOptions): Promise<CameraPhoto> {
|
|
805
820
|
return this.capture(options)
|
|
806
821
|
},
|
|
822
|
+
captureFrame(): string {
|
|
823
|
+
return __gea_Camera.captureFrame()
|
|
824
|
+
},
|
|
807
825
|
startRecording(options: CameraRecordOptions): Promise<void> {
|
|
808
826
|
cameraRecordingPath = options.path
|
|
809
827
|
__gea_Camera.startRecording(options.path, options.fps ?? 0)
|
|
@@ -828,13 +846,26 @@ export const Camera = {
|
|
|
828
846
|
__gea_Camera.setMirror(mirror)
|
|
829
847
|
},
|
|
830
848
|
setExposure(options: CameraExposureOptions): void {
|
|
831
|
-
__gea_Camera.setExposure(
|
|
849
|
+
__gea_Camera.setExposure(
|
|
850
|
+
options.mode ?? 'continuous',
|
|
851
|
+
options.bias ?? 0,
|
|
852
|
+
options.iso ?? 0,
|
|
853
|
+
options.durationMs ?? 0,
|
|
854
|
+
)
|
|
832
855
|
},
|
|
833
856
|
setWhiteBalance(options: CameraWhiteBalanceOptions): void {
|
|
834
|
-
__gea_Camera.setWhiteBalance(
|
|
857
|
+
__gea_Camera.setWhiteBalance(
|
|
858
|
+
options.mode ?? 'auto',
|
|
859
|
+
options.temperature ?? 0,
|
|
860
|
+
options.tint ?? 0,
|
|
861
|
+
)
|
|
835
862
|
},
|
|
836
863
|
setFocus(options: CameraFocusOptions): void {
|
|
837
|
-
__gea_Camera.setFocus(
|
|
864
|
+
__gea_Camera.setFocus(
|
|
865
|
+
options.mode ?? 'continuous',
|
|
866
|
+
options.point?.x ?? 0,
|
|
867
|
+
options.point?.y ?? 0,
|
|
868
|
+
)
|
|
838
869
|
},
|
|
839
870
|
setTorch(mode: 'off' | 'on' | 'auto', level?: number): void {
|
|
840
871
|
__gea_Camera.setTorch(mode, level ?? 0)
|
|
@@ -849,11 +880,11 @@ export const Camera = {
|
|
|
849
880
|
for (let i = 0; i < count; i++) {
|
|
850
881
|
out.push({
|
|
851
882
|
id: __gea_Camera.deviceIdAt(i),
|
|
852
|
-
facing: __gea_Camera.deviceFacingAt(i) as CameraFacing
|
|
883
|
+
facing: __gea_Camera.deviceFacingAt(i) as CameraFacing,
|
|
853
884
|
})
|
|
854
885
|
}
|
|
855
886
|
return out
|
|
856
|
-
}
|
|
887
|
+
},
|
|
857
888
|
}
|
|
858
889
|
|
|
859
890
|
// Same guard as Accelerometer above: these embedded host globals are not injected
|
|
@@ -861,7 +892,9 @@ export const Camera = {
|
|
|
861
892
|
// reference ReferenceErrors at module load and blanks the page. `typeof` keeps the
|
|
862
893
|
// real binding on embedded targets where the plugin injects them.
|
|
863
894
|
export const audioContext: AudioContext =
|
|
864
|
-
typeof __gea_audioContext !== 'undefined'
|
|
895
|
+
typeof __gea_audioContext !== 'undefined'
|
|
896
|
+
? __gea_audioContext
|
|
897
|
+
: (undefined as unknown as AudioContext)
|
|
865
898
|
// Method wrapper, NOT the `typeof … ? … : undefined as …` guard the
|
|
866
899
|
// property-bearing host objects above use. Two reasons. (1) The guard exists
|
|
867
900
|
// only to stop a bare top-level reference from ReferenceError-ing at module
|
|
@@ -879,9 +912,12 @@ export const Audio = {
|
|
|
879
912
|
},
|
|
880
913
|
setVolume(volume: number): void {
|
|
881
914
|
__gea_Audio.setVolume(volume)
|
|
882
|
-
}
|
|
915
|
+
},
|
|
883
916
|
}
|
|
884
|
-
export const Display =
|
|
917
|
+
export const Display =
|
|
918
|
+
typeof __gea_Display !== 'undefined'
|
|
919
|
+
? __gea_Display
|
|
920
|
+
: (undefined as unknown as typeof __gea_Display)
|
|
885
921
|
export const Memory = {
|
|
886
922
|
internalFree(): number {
|
|
887
923
|
return __gea_Memory.internalFree()
|
|
@@ -958,7 +994,7 @@ export const Memory = {
|
|
|
958
994
|
allocationSramBytes: __gea_Memory.allocationSramBytes(),
|
|
959
995
|
allocationPsramBytes: __gea_Memory.allocationPsramBytes(),
|
|
960
996
|
allocationSramPeakBytes: __gea_Memory.allocationSramPeakBytes(),
|
|
961
|
-
allocationPsramPeakBytes: __gea_Memory.allocationPsramPeakBytes()
|
|
997
|
+
allocationPsramPeakBytes: __gea_Memory.allocationPsramPeakBytes(),
|
|
962
998
|
}
|
|
963
999
|
},
|
|
964
1000
|
config(): EmbeddedMemoryConfig {
|
|
@@ -972,14 +1008,14 @@ export const Memory = {
|
|
|
972
1008
|
displayFlushBufferMaxBytes: __gea_Memory.displayFlushBufferMaxBytes(),
|
|
973
1009
|
displayFlushRows: __gea_Memory.displayFlushRows(),
|
|
974
1010
|
displayFlushDepth: __gea_Memory.displayFlushDepth(),
|
|
975
|
-
displayFlushBufferBytes: __gea_Memory.displayFlushBufferBytes()
|
|
1011
|
+
displayFlushBufferBytes: __gea_Memory.displayFlushBufferBytes(),
|
|
976
1012
|
}
|
|
977
|
-
}
|
|
1013
|
+
},
|
|
978
1014
|
}
|
|
979
1015
|
export const Input = {
|
|
980
1016
|
consumeBackButton(): boolean {
|
|
981
1017
|
return __gea_Input.consumeBackButton()
|
|
982
|
-
}
|
|
1018
|
+
},
|
|
983
1019
|
}
|
|
984
1020
|
export const Gpio = {
|
|
985
1021
|
configureOutput(pin: number): boolean {
|
|
@@ -993,7 +1029,7 @@ export const Gpio = {
|
|
|
993
1029
|
},
|
|
994
1030
|
read(pin: number): boolean {
|
|
995
1031
|
return __gea_Gpio.read(pin)
|
|
996
|
-
}
|
|
1032
|
+
},
|
|
997
1033
|
}
|
|
998
1034
|
export const Led = {
|
|
999
1035
|
set(pin: number, r: number, g: number, b: number): boolean {
|
|
@@ -1013,12 +1049,16 @@ export const Led = {
|
|
|
1013
1049
|
},
|
|
1014
1050
|
detach(): void {
|
|
1015
1051
|
__gea_Led.detach()
|
|
1016
|
-
}
|
|
1052
|
+
},
|
|
1017
1053
|
}
|
|
1018
1054
|
export const Clock = {
|
|
1055
|
+
setEpochMs(timestamp: number): boolean {
|
|
1056
|
+
return __gea_Clock.setEpochMs(timestamp)
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1019
1059
|
epochMs(): number {
|
|
1020
1060
|
return __gea_Clock.epochMs()
|
|
1021
|
-
}
|
|
1061
|
+
},
|
|
1022
1062
|
}
|
|
1023
1063
|
// Same method-wrapper reasoning as `Audio` above: the host global is only
|
|
1024
1064
|
// touched when the method runs, and the conditional form has no plannable
|
|
@@ -1028,12 +1068,27 @@ export const Profiler = {
|
|
|
1028
1068
|
nowUs(): number {
|
|
1029
1069
|
if (typeof __gea_Profiler === 'undefined') return Date.now() * 1000
|
|
1030
1070
|
return __gea_Profiler.nowUs()
|
|
1031
|
-
}
|
|
1071
|
+
},
|
|
1072
|
+
|
|
1073
|
+
nowCycles(): number {
|
|
1074
|
+
// Desktop native hosts also expose nanoseconds instead of CPU cycles.
|
|
1075
|
+
if (typeof __gea_Profiler === 'undefined') return Date.now() * 1000000
|
|
1076
|
+
return __gea_Profiler.nowCycles()
|
|
1077
|
+
},
|
|
1078
|
+
}
|
|
1079
|
+
export const Haptics = {
|
|
1080
|
+
vibrate(durationMs: number, strength = 100): boolean {
|
|
1081
|
+
return __gea_Haptics.vibrate(durationMs, strength)
|
|
1082
|
+
},
|
|
1032
1083
|
}
|
|
1033
1084
|
export const Battery = {
|
|
1085
|
+
charging(): boolean {
|
|
1086
|
+
return __gea_Battery.charging()
|
|
1087
|
+
},
|
|
1088
|
+
|
|
1034
1089
|
level(): number {
|
|
1035
1090
|
return __gea_Battery.level()
|
|
1036
|
-
}
|
|
1091
|
+
},
|
|
1037
1092
|
}
|
|
1038
1093
|
export const Notify = {
|
|
1039
1094
|
text(): string {
|
|
@@ -1041,12 +1096,12 @@ export const Notify = {
|
|
|
1041
1096
|
},
|
|
1042
1097
|
seq(): number {
|
|
1043
1098
|
return __gea_Notify.seq()
|
|
1044
|
-
}
|
|
1099
|
+
},
|
|
1045
1100
|
}
|
|
1046
1101
|
export const DeviceControl = {
|
|
1047
1102
|
exec(command: string): string {
|
|
1048
1103
|
return __gea_DeviceControl.exec(command)
|
|
1049
|
-
}
|
|
1104
|
+
},
|
|
1050
1105
|
}
|
|
1051
1106
|
// Method wrapper for the same two reasons as `Audio`/`Profiler` above: a
|
|
1052
1107
|
// reference inside a method body cannot ReferenceError at module load, and the
|
|
@@ -1079,5 +1134,5 @@ export const imageHost = {
|
|
|
1079
1134
|
},
|
|
1080
1135
|
dispose(id: number): void {
|
|
1081
1136
|
image.dispose(id)
|
|
1082
|
-
}
|
|
1137
|
+
},
|
|
1083
1138
|
}
|
package/runtime/images.ts
CHANGED
|
@@ -20,6 +20,7 @@ declare const image: {
|
|
|
20
20
|
writeFile(path: string, bytes: Uint8Array): boolean
|
|
21
21
|
// Read a whole file's raw bytes (e.g. a .pmtiles on microSD). Empty when the
|
|
22
22
|
// file is missing or no storage is mounted.
|
|
23
|
+
removeFile(path: string): boolean
|
|
23
24
|
readFile(path: string): Uint8Array
|
|
24
25
|
// Read a .pmtiles archive flashed into the spare ota_1 partition. Empty when
|
|
25
26
|
// none present (or off-device).
|
|
@@ -127,3 +128,7 @@ export function fetchText(url: string): string {
|
|
|
127
128
|
export function imageFromId(id: number): GeaEmbeddedImage {
|
|
128
129
|
return image.make(id)
|
|
129
130
|
}
|
|
131
|
+
|
|
132
|
+
export function removeCacheFile(path: string): boolean {
|
|
133
|
+
return image.removeFile(path)
|
|
134
|
+
}
|
package/runtime-surface.ts
CHANGED
|
@@ -70,6 +70,7 @@ export type {
|
|
|
70
70
|
AudioParam,
|
|
71
71
|
AudioProps,
|
|
72
72
|
BatteryController,
|
|
73
|
+
HapticsController,
|
|
73
74
|
BluetoothConfig,
|
|
74
75
|
BluetoothConnections,
|
|
75
76
|
BluetoothController,
|
|
@@ -181,6 +182,7 @@ export type {
|
|
|
181
182
|
VirtualListElement,
|
|
182
183
|
WebSocketCloseEvent,
|
|
183
184
|
WebSocketConstructor,
|
|
185
|
+
WebSocketOptions,
|
|
184
186
|
WebSocketErrorEvent,
|
|
185
187
|
WebSocketInstance,
|
|
186
188
|
WebSocketMessageEvent,
|
package/runtime.cpp
CHANGED
|
@@ -88,10 +88,11 @@ void dispatch_rotary_input()
|
|
|
88
88
|
void dispatch_key_input()
|
|
89
89
|
{
|
|
90
90
|
auto &tree = gea::embedded::ui::Tree::instance();
|
|
91
|
-
|
|
91
|
+
input::HardwareKeyEvent key{};
|
|
92
|
+
while (input::consumeKeyEvent(key)) {
|
|
92
93
|
events::PointerEvent event{};
|
|
93
|
-
event.type = events::PointerEventType::KeyDown;
|
|
94
|
-
event.keyCode = keyCode;
|
|
94
|
+
event.type = key.pressed ? events::PointerEventType::KeyDown : events::PointerEventType::KeyUp;
|
|
95
|
+
event.keyCode = key.keyCode;
|
|
95
96
|
event.bubbles = true;
|
|
96
97
|
event.cancelable = true;
|
|
97
98
|
|
|
@@ -105,7 +106,8 @@ void dispatch_key_input()
|
|
|
105
106
|
}
|
|
106
107
|
if (!stopped) {
|
|
107
108
|
event.propagationStopped = false;
|
|
108
|
-
gea::embedded::ui::dispatchDocumentKeyDown(event);
|
|
109
|
+
if (key.pressed) gea::embedded::ui::dispatchDocumentKeyDown(event);
|
|
110
|
+
else gea::embedded::ui::dispatchDocumentKeyUp(event);
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -196,6 +198,9 @@ void dispatch_event(const events::Event &event, const RuntimeOptions &options)
|
|
|
196
198
|
// A finger held still posts no moves, so gestureActiveWithin() lapses to
|
|
197
199
|
// false after the window and the loop quiesces — no busy-render on a hold.
|
|
198
200
|
for (;;) {
|
|
201
|
+
#if defined(ESP_PLATFORM) && defined(GEA_NATIVE_DEBUGGER) && GEA_NATIVE_DEBUGGER
|
|
202
|
+
if (services::FrameScheduler::debuggerFrameRate() > 0) break;
|
|
203
|
+
#endif
|
|
199
204
|
const bool catchUp = services::FrameScheduler::takeCatchUpRequest();
|
|
200
205
|
const bool activeDrag =
|
|
201
206
|
GEA_EMBEDDED_CONTINUOUS_TOUCH_FRAMES &&
|
|
@@ -405,6 +410,15 @@ bool Runtime::boot(const RuntimeOptions &options)
|
|
|
405
410
|
(void)options;
|
|
406
411
|
services::RuntimeLog::appStarted(networkReady);
|
|
407
412
|
#else
|
|
413
|
+
#if defined(GEA_EMBEDDED_BOOT_STAGING_RESERVE) && GEA_EMBEDDED_BOOT_STAGING_RESERVE
|
|
414
|
+
// Opt-in (an app sets GEA_EMBEDDED_BOOT_STAGING_RESERVE=1 in gea.defines).
|
|
415
|
+
// TS initialization can start flash workers and audio before the event loop
|
|
416
|
+
// is available to resize staging. Lend surplus DMA staging now, retaining
|
|
417
|
+
// the panel's minimum pipeline until application and runtime tasks exist.
|
|
418
|
+
gea::platform::display::Display::reserveInternal(
|
|
419
|
+
static_cast<std::size_t>(gea::platform::display::Display::flushBufferBytes()));
|
|
420
|
+
gea::platform::display::Display::applyPendingInternalReserve();
|
|
421
|
+
#endif
|
|
408
422
|
if (!services::AppRunner::initApplication(options.width, options.height)) return false;
|
|
409
423
|
services::HeapProbe::log("runtime:after_app_init");
|
|
410
424
|
services::StackProbe::logCurrentTask("runtime:after_app_init");
|
|
@@ -417,7 +431,11 @@ bool Runtime::boot(const RuntimeOptions &options)
|
|
|
417
431
|
services::HeapProbe::log("runtime:after_frame_start");
|
|
418
432
|
services::StackProbe::logCurrentTask("runtime:after_frame_start");
|
|
419
433
|
services::HeapTaskSummary::log("runtime:after_frame_start");
|
|
420
|
-
#if
|
|
434
|
+
#if defined(GEA_EMBEDDED_BOOT_STAGING_RESERVE) && GEA_EMBEDDED_BOOT_STAGING_RESERVE
|
|
435
|
+
// Initialization owns its allocations now. Grow staging into the remaining
|
|
436
|
+
// heap when the event loop resumes; allocation retains the DMA reserve.
|
|
437
|
+
gea::platform::display::Display::reserveInternal(0);
|
|
438
|
+
#elif GEA_EMBEDDED_WIFI_EARLY_CONNECT
|
|
421
439
|
// The early-connect loan protects radio, app, and task initialization.
|
|
422
440
|
// They now own their allocations; let the frame task grow staging into
|
|
423
441
|
// the remaining heap instead of retaining the two-row boot floor forever.
|