@gd-kirie/platform 0.2.0

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 ADDED
@@ -0,0 +1,64 @@
1
+ # `@gd-kirie/platform`
2
+
3
+ `@gd-kirie/platform` exposes browser-side desktop capabilities supplied by the
4
+ Godot host. Create the client from an existing `@gd-kirie/ipc-eventa` context.
5
+
6
+ ```ts
7
+ import { createContext } from "@gd-kirie/ipc-eventa";
8
+ import { createPlatformClient } from "@gd-kirie/platform";
9
+
10
+ const eventa = createContext();
11
+ const platform = createPlatformClient(eventa.context);
12
+
13
+ await platform.hostWindow.setAlwaysOnTop(true);
14
+
15
+ const windowBounds = await platform.hostWindow.getBounds();
16
+ const displayBounds = await platform.hostWindow.getCurrentDisplayBounds();
17
+ const pointer = await platform.hostWindow.getPointerPosition();
18
+ console.log(windowBounds, displayBounds, pointer);
19
+ eventa.dispose();
20
+ ```
21
+
22
+ ## Host window
23
+
24
+ - window position and size in screen coordinates
25
+ - current display position and size in screen coordinates
26
+ - pointer position relative to the host window, including while it is
27
+ click-through
28
+ - pointer passthrough
29
+ - native move and resize gestures
30
+ - always-on-top
31
+ - centering on the current display
32
+
33
+ Pointer coordinates use host-window pixels and are not normalized to the
34
+ browser viewport. `getPointerPosition()` returns a single snapshot.
35
+
36
+ ## Global shortcuts
37
+
38
+ The macOS host can register a shortcut that remains active while the Godot
39
+ window is hidden or unfocused:
40
+
41
+ ```ts
42
+ const shortcut = {
43
+ keycode: 0x4b, // Godot Key.K
44
+ shiftPressed: false,
45
+ altPressed: false,
46
+ ctrlPressed: false,
47
+ metaPressed: false,
48
+ commandOrControlAutoremap: true,
49
+ };
50
+
51
+ await platform.globalShortcuts.register(shortcut, ({ state }) => {
52
+ console.log(state); // "pressed" or "released"
53
+ });
54
+
55
+ await platform.globalShortcuts.unregister(shortcut);
56
+ ```
57
+
58
+ `keycode` uses Godot's logical `Key` values. Every modifier field is required;
59
+ `commandOrControlAutoremap` selects Command on macOS. One `onKeyEvent` handler
60
+ receives both states; keyboard auto-repeat does not produce extra calls.
61
+
62
+ The Windows and Linux backends are planned by
63
+ [ADR-0002](../../docs/decisions/0002-add-system-wide-global-shortcuts.md) but
64
+ have not been implemented yet.
@@ -0,0 +1,48 @@
1
+ import { KirieEventaContext } from "@gd-kirie/ipc-eventa";
2
+
3
+ //#region src/index.d.ts
4
+ type ResizeEdge = "top" | "right" | "bottom" | "left" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
5
+ interface HostWindowPointerPosition {
6
+ x: number;
7
+ y: number;
8
+ inside: boolean;
9
+ }
10
+ interface PlatformBounds {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ interface HostWindowClient {
17
+ getBounds: () => Promise<PlatformBounds>;
18
+ getCurrentDisplayBounds: () => Promise<PlatformBounds>;
19
+ getPointerPosition: () => Promise<HostWindowPointerPosition>;
20
+ setPointerPassthrough: (enabled: boolean) => Promise<void>;
21
+ beginMove: () => Promise<void>;
22
+ beginResize: (edge: ResizeEdge) => Promise<void>;
23
+ setAlwaysOnTop: (enabled: boolean) => Promise<void>;
24
+ centerOnCurrentDisplay: () => Promise<void>;
25
+ }
26
+ interface GlobalShortcut {
27
+ keycode: number;
28
+ shiftPressed: boolean;
29
+ altPressed: boolean;
30
+ ctrlPressed: boolean;
31
+ metaPressed: boolean;
32
+ commandOrControlAutoremap: boolean;
33
+ }
34
+ interface GlobalShortcutKeyEvent {
35
+ state: "pressed" | "released";
36
+ }
37
+ interface GlobalShortcutsClient {
38
+ register: (shortcut: GlobalShortcut, onKeyEvent: (event: GlobalShortcutKeyEvent) => void) => Promise<void>;
39
+ unregister: (shortcut: GlobalShortcut) => Promise<void>;
40
+ }
41
+ interface PlatformClient {
42
+ hostWindow: HostWindowClient;
43
+ globalShortcuts: GlobalShortcutsClient;
44
+ }
45
+ declare function createPlatformClient(context: KirieEventaContext): PlatformClient;
46
+ //#endregion
47
+ export { GlobalShortcut, GlobalShortcutKeyEvent, GlobalShortcutsClient, HostWindowClient, HostWindowPointerPosition, PlatformBounds, PlatformClient, ResizeEdge, createPlatformClient };
48
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;KAGY,UAAA;AAAA,UAUK,yBAAA;EACf,CAAA;EACA,CAAA;EACA,MAAA;AAAA;AAAA,UAGe,cAAA;EACf,CAAA;EACA,CAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,SAAA,QAAiB,OAAA,CAAQ,cAAA;EACzB,uBAAA,QAA+B,OAAA,CAAQ,cAAA;EACvC,kBAAA,QAA0B,OAAA,CAAQ,yBAAA;EAClC,qBAAA,GAAwB,OAAA,cAAqB,OAAA;EAC7C,SAAA,QAAiB,OAAA;EACjB,WAAA,GAAc,IAAA,EAAM,UAAA,KAAe,OAAA;EACnC,cAAA,GAAiB,OAAA,cAAqB,OAAA;EACtC,sBAAA,QAA8B,OAAA;AAAA;AAAA,UAGf,cAAA;EACf,OAAA;EACA,YAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;EACA,yBAAA;AAAA;AAAA,UAGe,sBAAA;EACf,KAAA;AAAA;AAAA,UAGe,qBAAA;EACf,QAAA,GACE,QAAA,EAAU,cAAA,EACV,UAAA,GAAa,KAAA,EAAO,sBAAA,cACjB,OAAA;EACL,UAAA,GAAa,QAAA,EAAU,cAAA,KAAmB,OAAA;AAAA;AAAA,UAG3B,cAAA;EACf,UAAA,EAAY,gBAAA;EACZ,eAAA,EAAiB,qBAAA;AAAA;AAAA,iBAwDH,oBAAA,CAAqB,OAAA,EAAS,kBAAA,GAAqB,cAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ import { defineInboundEventa, defineInvokeEventa, defineInvokes } from "@moeru/eventa";
2
+ //#region src/index.ts
3
+ const events = {
4
+ beginMove: defineInvokeEventa("kirie:platform:host-window:begin-move"),
5
+ beginResize: defineInvokeEventa("kirie:platform:host-window:begin-resize"),
6
+ center: defineInvokeEventa("kirie:platform:host-window:center"),
7
+ getBounds: defineInvokeEventa("kirie:platform:host-window:get-bounds"),
8
+ getCurrentDisplayBounds: defineInvokeEventa("kirie:platform:host-window:get-current-display-bounds"),
9
+ getPointerPosition: defineInvokeEventa("kirie:platform:host-window:get-pointer-position"),
10
+ setAlwaysOnTop: defineInvokeEventa("kirie:platform:host-window:set-always-on-top"),
11
+ setPointerPassthrough: defineInvokeEventa("kirie:platform:host-window:set-pointer-passthrough"),
12
+ registerGlobalShortcut: defineInvokeEventa("kirie:platform:global-shortcut:register"),
13
+ unregisterGlobalShortcut: defineInvokeEventa("kirie:platform:global-shortcut:unregister")
14
+ };
15
+ const globalShortcutStateChanged = defineInboundEventa("kirie:platform:global-shortcut:state-changed");
16
+ function globalShortcutKey(shortcut) {
17
+ return [
18
+ shortcut.keycode,
19
+ shortcut.shiftPressed,
20
+ shortcut.altPressed,
21
+ shortcut.ctrlPressed,
22
+ shortcut.metaPressed,
23
+ shortcut.commandOrControlAutoremap
24
+ ].join(":");
25
+ }
26
+ function createPlatformClient(context) {
27
+ const invokes = defineInvokes(context, events);
28
+ const globalShortcutRegistrations = /* @__PURE__ */ new Map();
29
+ context.on(globalShortcutStateChanged, ({ body }) => {
30
+ if (!body) return;
31
+ globalShortcutRegistrations.get(globalShortcutKey(body.shortcut))?.({ state: body.state });
32
+ });
33
+ return {
34
+ hostWindow: {
35
+ getBounds() {
36
+ return invokes.getBounds({});
37
+ },
38
+ getCurrentDisplayBounds() {
39
+ return invokes.getCurrentDisplayBounds({});
40
+ },
41
+ getPointerPosition() {
42
+ return invokes.getPointerPosition({});
43
+ },
44
+ async setPointerPassthrough(enabled) {
45
+ await invokes.setPointerPassthrough(enabled);
46
+ },
47
+ async beginMove() {
48
+ await invokes.beginMove({});
49
+ },
50
+ async beginResize(edge) {
51
+ await invokes.beginResize(edge);
52
+ },
53
+ async setAlwaysOnTop(enabled) {
54
+ await invokes.setAlwaysOnTop(enabled);
55
+ },
56
+ async centerOnCurrentDisplay() {
57
+ await invokes.center({});
58
+ }
59
+ },
60
+ globalShortcuts: {
61
+ async register(shortcut, onKeyEvent) {
62
+ const key = globalShortcutKey(shortcut);
63
+ if (globalShortcutRegistrations.has(key)) throw new Error("Global shortcut is already registered.");
64
+ globalShortcutRegistrations.set(key, onKeyEvent);
65
+ try {
66
+ await invokes.registerGlobalShortcut(shortcut);
67
+ } catch (error) {
68
+ if (globalShortcutRegistrations.get(key) === onKeyEvent) globalShortcutRegistrations.delete(key);
69
+ throw error;
70
+ }
71
+ },
72
+ async unregister(shortcut) {
73
+ const key = globalShortcutKey(shortcut);
74
+ const onKeyEvent = globalShortcutRegistrations.get(key);
75
+ if (!onKeyEvent) throw new Error("Global shortcut is not registered.");
76
+ await invokes.unregisterGlobalShortcut(shortcut);
77
+ if (globalShortcutRegistrations.get(key) === onKeyEvent) globalShortcutRegistrations.delete(key);
78
+ }
79
+ }
80
+ };
81
+ }
82
+ //#endregion
83
+ export { createPlatformClient };
84
+
85
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { KirieEventaContext } from \"@gd-kirie/ipc-eventa\";\nimport { defineInboundEventa, defineInvokeEventa, defineInvokes } from \"@moeru/eventa\";\n\nexport type ResizeEdge =\n | \"top\"\n | \"right\"\n | \"bottom\"\n | \"left\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\";\n\nexport interface HostWindowPointerPosition {\n x: number;\n y: number;\n inside: boolean;\n}\n\nexport interface PlatformBounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface HostWindowClient {\n getBounds: () => Promise<PlatformBounds>;\n getCurrentDisplayBounds: () => Promise<PlatformBounds>;\n getPointerPosition: () => Promise<HostWindowPointerPosition>;\n setPointerPassthrough: (enabled: boolean) => Promise<void>;\n beginMove: () => Promise<void>;\n beginResize: (edge: ResizeEdge) => Promise<void>;\n setAlwaysOnTop: (enabled: boolean) => Promise<void>;\n centerOnCurrentDisplay: () => Promise<void>;\n}\n\nexport interface GlobalShortcut {\n keycode: number;\n shiftPressed: boolean;\n altPressed: boolean;\n ctrlPressed: boolean;\n metaPressed: boolean;\n commandOrControlAutoremap: boolean;\n}\n\nexport interface GlobalShortcutKeyEvent {\n state: \"pressed\" | \"released\";\n}\n\nexport interface GlobalShortcutsClient {\n register: (\n shortcut: GlobalShortcut,\n onKeyEvent: (event: GlobalShortcutKeyEvent) => void,\n ) => Promise<void>;\n unregister: (shortcut: GlobalShortcut) => Promise<void>;\n}\n\nexport interface PlatformClient {\n hostWindow: HostWindowClient;\n globalShortcuts: GlobalShortcutsClient;\n}\n\ntype EmptyPayload = Record<string, never>;\n\ninterface GlobalShortcutStateChanged {\n shortcut: GlobalShortcut;\n state: GlobalShortcutKeyEvent[\"state\"];\n}\n\nconst events = {\n beginMove: defineInvokeEventa<EmptyPayload, EmptyPayload>(\n \"kirie:platform:host-window:begin-move\",\n ),\n beginResize: defineInvokeEventa<EmptyPayload, ResizeEdge>(\n \"kirie:platform:host-window:begin-resize\",\n ),\n center: defineInvokeEventa<EmptyPayload, EmptyPayload>(\"kirie:platform:host-window:center\"),\n getBounds: defineInvokeEventa<PlatformBounds, EmptyPayload>(\n \"kirie:platform:host-window:get-bounds\",\n ),\n getCurrentDisplayBounds: defineInvokeEventa<PlatformBounds, EmptyPayload>(\n \"kirie:platform:host-window:get-current-display-bounds\",\n ),\n getPointerPosition: defineInvokeEventa<HostWindowPointerPosition, EmptyPayload>(\n \"kirie:platform:host-window:get-pointer-position\",\n ),\n setAlwaysOnTop: defineInvokeEventa<EmptyPayload, boolean>(\n \"kirie:platform:host-window:set-always-on-top\",\n ),\n setPointerPassthrough: defineInvokeEventa<EmptyPayload, boolean>(\n \"kirie:platform:host-window:set-pointer-passthrough\",\n ),\n registerGlobalShortcut: defineInvokeEventa<EmptyPayload, GlobalShortcut>(\n \"kirie:platform:global-shortcut:register\",\n ),\n unregisterGlobalShortcut: defineInvokeEventa<EmptyPayload, GlobalShortcut>(\n \"kirie:platform:global-shortcut:unregister\",\n ),\n};\n\nconst globalShortcutStateChanged = defineInboundEventa<GlobalShortcutStateChanged>(\n \"kirie:platform:global-shortcut:state-changed\",\n);\n\nfunction globalShortcutKey(shortcut: GlobalShortcut): string {\n return [\n shortcut.keycode,\n shortcut.shiftPressed,\n shortcut.altPressed,\n shortcut.ctrlPressed,\n shortcut.metaPressed,\n shortcut.commandOrControlAutoremap,\n ].join(\":\");\n}\n\nexport function createPlatformClient(context: KirieEventaContext): PlatformClient {\n const invokes = defineInvokes(context, events);\n const globalShortcutRegistrations = new Map<string, (event: GlobalShortcutKeyEvent) => void>();\n\n context.on(globalShortcutStateChanged, ({ body }) => {\n if (!body) {\n return;\n }\n\n globalShortcutRegistrations.get(globalShortcutKey(body.shortcut))?.({\n state: body.state,\n });\n });\n\n return {\n hostWindow: {\n getBounds() {\n return invokes.getBounds({});\n },\n getCurrentDisplayBounds() {\n return invokes.getCurrentDisplayBounds({});\n },\n getPointerPosition() {\n return invokes.getPointerPosition({});\n },\n async setPointerPassthrough(enabled) {\n await invokes.setPointerPassthrough(enabled);\n },\n async beginMove() {\n await invokes.beginMove({});\n },\n async beginResize(edge) {\n await invokes.beginResize(edge);\n },\n async setAlwaysOnTop(enabled) {\n await invokes.setAlwaysOnTop(enabled);\n },\n async centerOnCurrentDisplay() {\n await invokes.center({});\n },\n },\n globalShortcuts: {\n async register(shortcut, onKeyEvent) {\n const key = globalShortcutKey(shortcut);\n\n if (globalShortcutRegistrations.has(key)) {\n throw new Error(\"Global shortcut is already registered.\");\n }\n\n globalShortcutRegistrations.set(key, onKeyEvent);\n\n try {\n await invokes.registerGlobalShortcut(shortcut);\n } catch (error) {\n if (globalShortcutRegistrations.get(key) === onKeyEvent) {\n globalShortcutRegistrations.delete(key);\n }\n throw error;\n }\n },\n async unregister(shortcut) {\n const key = globalShortcutKey(shortcut);\n const onKeyEvent = globalShortcutRegistrations.get(key);\n\n if (!onKeyEvent) {\n throw new Error(\"Global shortcut is not registered.\");\n }\n\n await invokes.unregisterGlobalShortcut(shortcut);\n\n if (globalShortcutRegistrations.get(key) === onKeyEvent) {\n globalShortcutRegistrations.delete(key);\n }\n },\n },\n };\n}\n"],"mappings":";;AAsEA,MAAM,SAAS;CACb,WAAW,mBACT,uCACF;CACA,aAAa,mBACX,yCACF;CACA,QAAQ,mBAA+C,mCAAmC;CAC1F,WAAW,mBACT,uCACF;CACA,yBAAyB,mBACvB,uDACF;CACA,oBAAoB,mBAClB,iDACF;CACA,gBAAgB,mBACd,8CACF;CACA,uBAAuB,mBACrB,oDACF;CACA,wBAAwB,mBACtB,yCACF;CACA,0BAA0B,mBACxB,2CACF;AACF;AAEA,MAAM,6BAA6B,oBACjC,8CACF;AAEA,SAAS,kBAAkB,UAAkC;CAC3D,OAAO;EACL,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;CACX,CAAC,CAAC,KAAK,GAAG;AACZ;AAEA,SAAgB,qBAAqB,SAA6C;CAChF,MAAM,UAAU,cAAc,SAAS,MAAM;CAC7C,MAAM,8CAA8B,IAAI,IAAqD;CAE7F,QAAQ,GAAG,6BAA6B,EAAE,WAAW;EACnD,IAAI,CAAC,MACH;EAGF,4BAA4B,IAAI,kBAAkB,KAAK,QAAQ,CAAC,CAAC,GAAG,EAClE,OAAO,KAAK,MACd,CAAC;CACH,CAAC;CAED,OAAO;EACL,YAAY;GACV,YAAY;IACV,OAAO,QAAQ,UAAU,CAAC,CAAC;GAC7B;GACA,0BAA0B;IACxB,OAAO,QAAQ,wBAAwB,CAAC,CAAC;GAC3C;GACA,qBAAqB;IACnB,OAAO,QAAQ,mBAAmB,CAAC,CAAC;GACtC;GACA,MAAM,sBAAsB,SAAS;IACnC,MAAM,QAAQ,sBAAsB,OAAO;GAC7C;GACA,MAAM,YAAY;IAChB,MAAM,QAAQ,UAAU,CAAC,CAAC;GAC5B;GACA,MAAM,YAAY,MAAM;IACtB,MAAM,QAAQ,YAAY,IAAI;GAChC;GACA,MAAM,eAAe,SAAS;IAC5B,MAAM,QAAQ,eAAe,OAAO;GACtC;GACA,MAAM,yBAAyB;IAC7B,MAAM,QAAQ,OAAO,CAAC,CAAC;GACzB;EACF;EACA,iBAAiB;GACf,MAAM,SAAS,UAAU,YAAY;IACnC,MAAM,MAAM,kBAAkB,QAAQ;IAEtC,IAAI,4BAA4B,IAAI,GAAG,GACrC,MAAM,IAAI,MAAM,wCAAwC;IAG1D,4BAA4B,IAAI,KAAK,UAAU;IAE/C,IAAI;KACF,MAAM,QAAQ,uBAAuB,QAAQ;IAC/C,SAAS,OAAO;KACd,IAAI,4BAA4B,IAAI,GAAG,MAAM,YAC3C,4BAA4B,OAAO,GAAG;KAExC,MAAM;IACR;GACF;GACA,MAAM,WAAW,UAAU;IACzB,MAAM,MAAM,kBAAkB,QAAQ;IACtC,MAAM,aAAa,4BAA4B,IAAI,GAAG;IAEtD,IAAI,CAAC,YACH,MAAM,IAAI,MAAM,oCAAoC;IAGtD,MAAM,QAAQ,yBAAyB,QAAQ;IAE/C,IAAI,4BAA4B,IAAI,GAAG,MAAM,YAC3C,4BAA4B,OAAO,GAAG;GAE1C;EACF;CACF;AACF"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@gd-kirie/platform",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/moeru-ai/godot-kirie.git",
8
+ "directory": "packages/platform"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./src/index.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsdown",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@gd-kirie/ipc-eventa": "workspace:*",
26
+ "@moeru/eventa": "catalog:"
27
+ },
28
+ "devDependencies": {
29
+ "tsdown": "catalog:",
30
+ "typescript": "catalog:"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/index.js"
39
+ }
40
+ }
41
+ }
42
+ }