@liminal-hq/plugin-desktop-integration 0.1.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,132 @@
1
+ # @liminal-hq/plugin-desktop-integration
2
+
3
+ Desktop activation helpers for Linux Tauri apps: native X11 window activation and a
4
+ unified global-shortcut API that picks the right binding path (X11 direct grab vs.
5
+ Wayland portal) automatically based on session type.
6
+
7
+ - Requests native GTK window presentation with a real event timestamp and stamps
8
+ `_NET_WM_USER_TIME` through `gdkx11`, so fresh windows look like legitimate
9
+ user-driven activations under X11 window managers.
10
+ - Wraps `tauri-plugin-global-shortcut` on X11 and
11
+ [`@liminal-hq/plugin-xdg-portal`](../xdg-portal)'s `GlobalShortcuts` portal binding
12
+ on Wayland behind one `DesktopIntegrationExt` trait, so calling apps don't need to
13
+ branch on session type themselves.
14
+ - On non-Linux platforms, the activation helper is a documented no-op — see
15
+ [Platform Support](#platform-support).
16
+
17
+ ## Installation
18
+
19
+ ### Rust
20
+
21
+ ```toml
22
+ [dependencies]
23
+ tauri-plugin-desktop-integration = "0.1"
24
+
25
+ # Alternatively with Git:
26
+ tauri-plugin-desktop-integration = { git = "https://github.com/liminal-hq/tauri-plugins-workspace", branch = "main" }
27
+ ```
28
+
29
+ ### JavaScript
30
+
31
+ ```bash
32
+ pnpm add @liminal-hq/plugin-desktop-integration
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Rust
38
+
39
+ ```rust
40
+ use tauri_plugin_desktop_integration::DesktopIntegrationExt;
41
+
42
+ fn main() {
43
+ tauri::Builder::default()
44
+ .plugin(tauri_plugin_desktop_integration::init())
45
+ .plugin(tauri_plugin_global_shortcut::Builder::new().build())
46
+ .plugin(tauri_plugin_xdg_portal::init())
47
+ .setup(|app| {
48
+ let handle = app.handle().clone();
49
+ handle.register_shortcut(
50
+ "your-app-toggle", // stable Wayland portal session id
51
+ "Toggle Your App", // shown in the compositor's shortcut dialog
52
+ "Alt+Shift+T",
53
+ move || { /* shortcut activated */ },
54
+ );
55
+ Ok(())
56
+ })
57
+ .run(tauri::generate_context!())
58
+ .expect("error while running tauri application");
59
+ }
60
+ ```
61
+
62
+ On Wayland, the portal `BindShortcuts` call requires a parent window for its
63
+ confirmation dialog. Call `set_shortcut_window(&window)` once your first window is
64
+ shown to kick off the deferred binding, and listen for the `shortcut-binding-result`
65
+ event to know when it resolves.
66
+
67
+ ### JavaScript
68
+
69
+ JS-only apps (no custom Rust command of their own) can register shortcuts directly —
70
+ activation is delivered as a `shortcut-activated` event under the hood, but
71
+ `registerShortcut` hides that and takes a plain callback:
72
+
73
+ ```typescript
74
+ import { desktopIntegration } from '@liminal-hq/plugin-desktop-integration';
75
+
76
+ await desktopIntegration.registerShortcut(
77
+ 'your-app-toggle', // stable Wayland portal session id
78
+ 'Toggle Your App', // shown in the compositor's shortcut dialog
79
+ 'Alt+Shift+T',
80
+ () => {
81
+ /* shortcut activated */
82
+ }
83
+ );
84
+
85
+ const complete = await desktopIntegration.checkShortcutBindingComplete();
86
+ const error = await desktopIntegration.checkShortcutBindingError();
87
+ ```
88
+
89
+ Rust consumers should prefer calling `DesktopIntegrationExt::register_shortcut` directly
90
+ from `setup()` — it delivers activation via a real closure instead of an event
91
+ round-trip. The `register_shortcut` command exists specifically for JS-only consumers.
92
+
93
+ ### Generated types
94
+
95
+ `ShortcutBindingResult` and `ShortcutActivatedPayload` (the payloads of the
96
+ `shortcut-binding-result` and `shortcut-activated` events) are generated from their Rust
97
+ definitions via [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs) into
98
+ `guest-js/bindings/` and re-exported from the package root, so the JS/Rust shapes can't
99
+ drift:
100
+
101
+ ```typescript
102
+ import type {
103
+ ShortcutActivatedPayload,
104
+ ShortcutBindingResult,
105
+ } from '@liminal-hq/plugin-desktop-integration';
106
+ ```
107
+
108
+ The bindings regenerate automatically as part of `cargo test` (each type's `#[ts(export)]`
109
+ attribute creates a test that writes its `.ts` file) — run `cargo test -p
110
+ tauri-plugin-desktop-integration` after changing either struct and commit the result.
111
+
112
+ ## Permissions
113
+
114
+ This plugin requires these permissions:
115
+
116
+ - `allow-register-shortcut`: Grants access to `register_shortcut`
117
+ - `allow-check-shortcut-binding-complete`: Grants access to `check_shortcut_binding_complete`
118
+ - `allow-check-shortcut-binding-error`: Grants access to `check_shortcut_binding_error`
119
+
120
+ ## Platform Support
121
+
122
+ | Platform | Support Level | Notes |
123
+ | -------- | ------------- | ------------------------------------------------------------- |
124
+ | Windows | None | X11/Wayland activation helpers only |
125
+ | Linux | Full | X11 activation via `gdkx11`, Wayland shortcuts via the portal |
126
+ | macOS | None | X11/Wayland activation helpers only |
127
+ | Android | None | X11/Wayland activation helpers only |
128
+ | iOS | None | X11/Wayland activation helpers only |
129
+
130
+ ## Licence
131
+
132
+ Apache-2.0 OR MIT
package/api-iife.js ADDED
@@ -0,0 +1,59 @@
1
+ if ('__TAURI__' in window) {
2
+ var __TAURI_PLUGIN_DESKTOP_INTEGRATION__ = (function (exports, core, event) {
3
+ 'use strict';
4
+
5
+ // Exposes guest-side bindings for the desktop-integration plugin
6
+ //
7
+ // (c) Copyright 2026 Liminal HQ, Scott Morris
8
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
9
+ const PREFIX = 'plugin:desktop-integration|';
10
+ function cmd(name, args) {
11
+ return core.invoke(`${PREFIX}${name}`, args);
12
+ }
13
+ let activationCallback = null;
14
+ let activationListening = null;
15
+ function ensureActivationListener() {
16
+ if (activationListening)
17
+ return;
18
+ activationListening = event.listen('shortcut-activated', () => {
19
+ activationCallback?.();
20
+ });
21
+ }
22
+ const desktopIntegration = {
23
+ /**
24
+ * Registers a global shortcut. On X11 it's bound immediately; on Wayland,
25
+ * binding is deferred until the compositor confirms it — see
26
+ * checkShortcutBindingComplete/checkShortcutBindingError.
27
+ *
28
+ * `sessionId` and `sessionDescription` identify the Wayland portal session:
29
+ * `sessionId` should be a stable, app-specific string, and `sessionDescription`
30
+ * is shown to the user in the compositor's shortcut binding dialog.
31
+ *
32
+ * `onActivated` fires each time the shortcut is pressed. Registering a new
33
+ * shortcut replaces both the binding and the callback.
34
+ */
35
+ registerShortcut: (sessionId, sessionDescription, shortcut, onActivated) => {
36
+ activationCallback = onActivated;
37
+ ensureActivationListener();
38
+ return cmd('register_shortcut', { sessionId, sessionDescription, shortcut });
39
+ },
40
+ /**
41
+ * Returns true once the portal BindShortcuts call has completed successfully.
42
+ * On X11 this is always true immediately after startup.
43
+ * Use this as a race guard after registering the shortcut-binding-result listener.
44
+ */
45
+ checkShortcutBindingComplete: () => cmd('check_shortcut_binding_complete'),
46
+ /**
47
+ * Returns the error message if BindShortcuts failed, or null if still pending
48
+ * or successful. Use this as a race guard after registering the
49
+ * shortcut-binding-result listener — complements checkShortcutBindingComplete.
50
+ */
51
+ checkShortcutBindingError: () => cmd('check_shortcut_binding_error'),
52
+ };
53
+
54
+ exports.desktopIntegration = desktopIntegration;
55
+
56
+ return exports;
57
+
58
+ })({}, __TAURI__.core, __TAURI__.event);
59
+ Object.defineProperty(window.__TAURI__, 'desktopIntegration', { value: __TAURI_PLUGIN_DESKTOP_INTEGRATION__ }) }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Payload emitted on the `shortcut-activated` event, fired when the shortcut
3
+ * registered via the `register_shortcut` command is pressed. Rust consumers using
4
+ * `DesktopIntegrationExt::register_shortcut` directly get a real closure instead —
5
+ * this event exists so JS-only consumers can use the plugin without writing Rust.
6
+ */
7
+ export type ShortcutActivatedPayload = {
8
+ sessionId: string;
9
+ };
10
+ //# sourceMappingURL=ShortcutActivatedPayload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShortcutActivatedPayload.d.ts","sourceRoot":"","sources":["../../guest-js/bindings/ShortcutActivatedPayload.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;CAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Payload emitted on the `shortcut-binding-result` event.
3
+ */
4
+ export type ShortcutBindingResult = {
5
+ success: boolean;
6
+ error: string | null;
7
+ };
8
+ //# sourceMappingURL=ShortcutBindingResult.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShortcutBindingResult.d.ts","sourceRoot":"","sources":["../../guest-js/bindings/ShortcutBindingResult.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAAE,CAAC"}
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ var core = require('@tauri-apps/api/core');
4
+ var event = require('@tauri-apps/api/event');
5
+
6
+ // Exposes guest-side bindings for the desktop-integration plugin
7
+ //
8
+ // (c) Copyright 2026 Liminal HQ, Scott Morris
9
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
10
+ const PREFIX = 'plugin:desktop-integration|';
11
+ function cmd(name, args) {
12
+ return core.invoke(`${PREFIX}${name}`, args);
13
+ }
14
+ let activationCallback = null;
15
+ let activationListening = null;
16
+ function ensureActivationListener() {
17
+ if (activationListening)
18
+ return;
19
+ activationListening = event.listen('shortcut-activated', () => {
20
+ activationCallback?.();
21
+ });
22
+ }
23
+ const desktopIntegration = {
24
+ /**
25
+ * Registers a global shortcut. On X11 it's bound immediately; on Wayland,
26
+ * binding is deferred until the compositor confirms it — see
27
+ * checkShortcutBindingComplete/checkShortcutBindingError.
28
+ *
29
+ * `sessionId` and `sessionDescription` identify the Wayland portal session:
30
+ * `sessionId` should be a stable, app-specific string, and `sessionDescription`
31
+ * is shown to the user in the compositor's shortcut binding dialog.
32
+ *
33
+ * `onActivated` fires each time the shortcut is pressed. Registering a new
34
+ * shortcut replaces both the binding and the callback.
35
+ */
36
+ registerShortcut: (sessionId, sessionDescription, shortcut, onActivated) => {
37
+ activationCallback = onActivated;
38
+ ensureActivationListener();
39
+ return cmd('register_shortcut', { sessionId, sessionDescription, shortcut });
40
+ },
41
+ /**
42
+ * Returns true once the portal BindShortcuts call has completed successfully.
43
+ * On X11 this is always true immediately after startup.
44
+ * Use this as a race guard after registering the shortcut-binding-result listener.
45
+ */
46
+ checkShortcutBindingComplete: () => cmd('check_shortcut_binding_complete'),
47
+ /**
48
+ * Returns the error message if BindShortcuts failed, or null if still pending
49
+ * or successful. Use this as a race guard after registering the
50
+ * shortcut-binding-result listener — complements checkShortcutBindingComplete.
51
+ */
52
+ checkShortcutBindingError: () => cmd('check_shortcut_binding_error'),
53
+ };
54
+
55
+ exports.desktopIntegration = desktopIntegration;
56
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../guest-js/index.ts"],"sourcesContent":[null],"names":["invoke","listen"],"mappings":";;;;;AAAA;AACA;AACA;AACA;AASA,MAAM,MAAM,GAAG,6BAA6B;AAE5C,SAAS,GAAG,CAAI,IAAY,EAAE,IAA8B,EAAA;IAC3D,OAAOA,WAAM,CAAI,CAAA,EAAG,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,EAAE,IAAI,CAAC;AAC3C;AAEA,IAAI,kBAAkB,GAAwB,IAAI;AAClD,IAAI,mBAAmB,GAA4B,IAAI;AAEvD,SAAS,wBAAwB,GAAA;AAChC,IAAA,IAAI,mBAAmB;QAAE;AACzB,IAAA,mBAAmB,GAAGC,YAAM,CAA2B,oBAAoB,EAAE,MAAK;QACjF,kBAAkB,IAAI;AACvB,IAAA,CAAC,CAAC;AACH;AAEO,MAAM,kBAAkB,GAAG;AACjC;;;;;;;;;;;AAWG;IACH,gBAAgB,EAAE,CACjB,SAAiB,EACjB,kBAA0B,EAC1B,QAAgB,EAChB,WAAuB,KACL;QAClB,kBAAkB,GAAG,WAAW;AAChC,QAAA,wBAAwB,EAAE;AAC1B,QAAA,OAAO,GAAG,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC;IAC7E,CAAC;AAED;;;;AAIG;AACH,IAAA,4BAA4B,EAAE,MAAM,GAAG,CAAU,iCAAiC,CAAC;AAEnF;;;;AAIG;AACH,IAAA,yBAAyB,EAAE,MAAM,GAAG,CAAgB,8BAA8B,CAAC;;;;;"}
@@ -0,0 +1,31 @@
1
+ import type { ShortcutActivatedPayload } from './bindings/ShortcutActivatedPayload';
2
+ import type { ShortcutBindingResult } from './bindings/ShortcutBindingResult';
3
+ export type { ShortcutActivatedPayload, ShortcutBindingResult };
4
+ export declare const desktopIntegration: {
5
+ /**
6
+ * Registers a global shortcut. On X11 it's bound immediately; on Wayland,
7
+ * binding is deferred until the compositor confirms it — see
8
+ * checkShortcutBindingComplete/checkShortcutBindingError.
9
+ *
10
+ * `sessionId` and `sessionDescription` identify the Wayland portal session:
11
+ * `sessionId` should be a stable, app-specific string, and `sessionDescription`
12
+ * is shown to the user in the compositor's shortcut binding dialog.
13
+ *
14
+ * `onActivated` fires each time the shortcut is pressed. Registering a new
15
+ * shortcut replaces both the binding and the callback.
16
+ */
17
+ registerShortcut: (sessionId: string, sessionDescription: string, shortcut: string, onActivated: () => void) => Promise<void>;
18
+ /**
19
+ * Returns true once the portal BindShortcuts call has completed successfully.
20
+ * On X11 this is always true immediately after startup.
21
+ * Use this as a race guard after registering the shortcut-binding-result listener.
22
+ */
23
+ checkShortcutBindingComplete: () => Promise<boolean>;
24
+ /**
25
+ * Returns the error message if BindShortcuts failed, or null if still pending
26
+ * or successful. Use this as a race guard after registering the
27
+ * shortcut-binding-result listener — complements checkShortcutBindingComplete.
28
+ */
29
+ checkShortcutBindingError: () => Promise<string | null>;
30
+ };
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../guest-js/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AACpF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAE9E,YAAY,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,CAAC;AAkBhE,eAAO,MAAM,kBAAkB;IAC9B;;;;;;;;;;;OAWG;kCAES,MAAM,sBACG,MAAM,YAChB,MAAM,eACH,MAAM,IAAI,KACrB,OAAO,CAAC,IAAI,CAAC;IAMhB;;;;OAIG;;IAGH;;;;OAIG;;CAEH,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { invoke } from '@tauri-apps/api/core';
2
+ import { listen } from '@tauri-apps/api/event';
3
+
4
+ // Exposes guest-side bindings for the desktop-integration plugin
5
+ //
6
+ // (c) Copyright 2026 Liminal HQ, Scott Morris
7
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
8
+ const PREFIX = 'plugin:desktop-integration|';
9
+ function cmd(name, args) {
10
+ return invoke(`${PREFIX}${name}`, args);
11
+ }
12
+ let activationCallback = null;
13
+ let activationListening = null;
14
+ function ensureActivationListener() {
15
+ if (activationListening)
16
+ return;
17
+ activationListening = listen('shortcut-activated', () => {
18
+ activationCallback?.();
19
+ });
20
+ }
21
+ const desktopIntegration = {
22
+ /**
23
+ * Registers a global shortcut. On X11 it's bound immediately; on Wayland,
24
+ * binding is deferred until the compositor confirms it — see
25
+ * checkShortcutBindingComplete/checkShortcutBindingError.
26
+ *
27
+ * `sessionId` and `sessionDescription` identify the Wayland portal session:
28
+ * `sessionId` should be a stable, app-specific string, and `sessionDescription`
29
+ * is shown to the user in the compositor's shortcut binding dialog.
30
+ *
31
+ * `onActivated` fires each time the shortcut is pressed. Registering a new
32
+ * shortcut replaces both the binding and the callback.
33
+ */
34
+ registerShortcut: (sessionId, sessionDescription, shortcut, onActivated) => {
35
+ activationCallback = onActivated;
36
+ ensureActivationListener();
37
+ return cmd('register_shortcut', { sessionId, sessionDescription, shortcut });
38
+ },
39
+ /**
40
+ * Returns true once the portal BindShortcuts call has completed successfully.
41
+ * On X11 this is always true immediately after startup.
42
+ * Use this as a race guard after registering the shortcut-binding-result listener.
43
+ */
44
+ checkShortcutBindingComplete: () => cmd('check_shortcut_binding_complete'),
45
+ /**
46
+ * Returns the error message if BindShortcuts failed, or null if still pending
47
+ * or successful. Use this as a race guard after registering the
48
+ * shortcut-binding-result listener — complements checkShortcutBindingComplete.
49
+ */
50
+ checkShortcutBindingError: () => cmd('check_shortcut_binding_error'),
51
+ };
52
+
53
+ export { desktopIntegration };
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../guest-js/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAAA;AACA;AACA;AACA;AASA,MAAM,MAAM,GAAG,6BAA6B;AAE5C,SAAS,GAAG,CAAI,IAAY,EAAE,IAA8B,EAAA;IAC3D,OAAO,MAAM,CAAI,CAAA,EAAG,MAAM,CAAA,EAAG,IAAI,CAAA,CAAE,EAAE,IAAI,CAAC;AAC3C;AAEA,IAAI,kBAAkB,GAAwB,IAAI;AAClD,IAAI,mBAAmB,GAA4B,IAAI;AAEvD,SAAS,wBAAwB,GAAA;AAChC,IAAA,IAAI,mBAAmB;QAAE;AACzB,IAAA,mBAAmB,GAAG,MAAM,CAA2B,oBAAoB,EAAE,MAAK;QACjF,kBAAkB,IAAI;AACvB,IAAA,CAAC,CAAC;AACH;AAEO,MAAM,kBAAkB,GAAG;AACjC;;;;;;;;;;;AAWG;IACH,gBAAgB,EAAE,CACjB,SAAiB,EACjB,kBAA0B,EAC1B,QAAgB,EAChB,WAAuB,KACL;QAClB,kBAAkB,GAAG,WAAW;AAChC,QAAA,wBAAwB,EAAE;AAC1B,QAAA,OAAO,GAAG,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC;IAC7E,CAAC;AAED;;;;AAIG;AACH,IAAA,4BAA4B,EAAE,MAAM,GAAG,CAAU,iCAAiC,CAAC;AAEnF;;;;AAIG;AACH,IAAA,yBAAyB,EAAE,MAAM,GAAG,CAAgB,8BAA8B,CAAC;;;;;"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@liminal-hq/plugin-desktop-integration",
3
+ "version": "0.1.0",
4
+ "license": "Apache-2.0 OR MIT",
5
+ "authors": [
6
+ "Liminal HQ Contributors"
7
+ ],
8
+ "repository": "https://github.com/liminal-hq/tauri-plugins-workspace",
9
+ "type": "module",
10
+ "types": "./dist-js/index.d.ts",
11
+ "main": "./dist-js/index.cjs",
12
+ "module": "./dist-js/index.js",
13
+ "exports": {
14
+ "types": "./dist-js/index.d.ts",
15
+ "import": "./dist-js/index.js",
16
+ "require": "./dist-js/index.cjs"
17
+ },
18
+ "scripts": {
19
+ "build": "rollup -c",
20
+ "prepare": "pnpm build"
21
+ },
22
+ "files": [
23
+ "dist-js",
24
+ "README.md",
25
+ "LICENSE-MIT",
26
+ "LICENSE-APACHE",
27
+ "api-iife.js"
28
+ ],
29
+ "dependencies": {
30
+ "@tauri-apps/api": "^2.10.0"
31
+ },
32
+ "devDependencies": {
33
+ "@rollup/plugin-typescript": "^12.1.4",
34
+ "rollup": "^4.59.1",
35
+ "tslib": "^2.8.1",
36
+ "typescript": "^5.9.3"
37
+ }
38
+ }