From c27065c260d08557820688964075b8d471d2ad0f Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 29 Jul 2026 00:38:41 +0200 Subject: [PATCH] feat(client/windows): punktfunk:// opens the Windows client, and cards can write a shortcut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WP4 / D2. Protocol activation of a full-trust packaged app delivers the URI as the command line, so a browser prompt, `start punktfunk://…` and a written `.lnk` all arrive the same way — as a positional argument, the same door the Linux shell uses. What Windows does not give us is single-instancing: unlike GApplication, a second activation is simply a second process. So the first instance claims the named mutex `unom.punktfunk.client` and any later one hands its URL to the winner over `WM_COPYDATA` and exits — one window, and the link opens where the user's hosts already are. The hand-off retries while the primary's window is still coming up (a shortcut double-clicked during startup is the ordinary case), and a hand-off that finds nobody falls through so this process becomes the shell that opens it. A link is never silently dropped, which is also why the inbox is a queue: two shortcuts in quick succession are two links. Routing is the same four lines of translation as on Linux, because the decisions belong to `plan_from_link`: a resolved link becomes the call a tile click makes, with the same wake, trust and error surfaces. Never preempting a live session is checked here, since only this layer knows one is running. The manifest declares the protocol and an app execution alias rather than writing registry keys, so uninstall leaves nothing behind — and Windows' own "allow this app to handle punktfunk links?" prompt is the origin friction the design wants, not duplicated in-app. (Manifest comments are free of double hyphens: XML forbids them and makepri rejects the whole file, which is how the console flag broke the v0.15.0 build. A test parses the substituted manifest and asserts both declarations, so that can't rot silently.) "Create shortcut…" writes a `.lnk` targeting the ALIAS with the URL as an argument, not a `.url` internet shortcut: both work while the scheme is registered, only this one still works if it isn't, and targeting the alias keeps it valid across updates when the package path changes. Verified on the CI VM (192.168.1.133): clippy -D warnings clean and its tests green. Not yet exercised on glass — `start punktfunk://…` cold and with the app running, and a written shortcut, still want a real desktop session. Co-Authored-By: Claude Opus 5 (1M context) --- clients/windows/Cargo.toml | 4 + clients/windows/packaging/AppxManifest.xml | 29 ++- clients/windows/src/app/hosts.rs | 15 ++ clients/windows/src/app/mod.rs | 92 +++++++ clients/windows/src/deeplink.rs | 277 +++++++++++++++++++++ clients/windows/src/main.rs | 27 +- 6 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 clients/windows/src/deeplink.rs diff --git a/clients/windows/Cargo.toml b/clients/windows/Cargo.toml index d5222651..54c55922 100644 --- a/clients/windows/Cargo.toml +++ b/clients/windows/Cargo.toml @@ -60,6 +60,10 @@ windows = { git = "https://github.com/microsoft/windows-rs", rev = "a4f7b2cb7c63 "Win32_Graphics_Direct3D11", "Win32_Graphics_Direct3D_Fxc", "Win32_Graphics_Gdi", + # IShellLinkW + IPersistFile for the "Create shortcut" writer, and COPYDATASTRUCT for the + # link hand-off between instances (deeplink.rs). + "Win32_System_Com", + "Win32_System_DataExchange", "Win32_System_Console", "Win32_System_LibraryLoader", "Win32_System_Threading", diff --git a/clients/windows/packaging/AppxManifest.xml b/clients/windows/packaging/AppxManifest.xml index a713bf56..8c96a691 100644 --- a/clients/windows/packaging/AppxManifest.xml +++ b/clients/windows/packaging/AppxManifest.xml @@ -19,8 +19,9 @@ + IgnorableNamespaces="uap uap5 rescap"> + + + + + Punktfunk stream link + + + + + + + + +