// @effect-diagnostics nodeBuiltinImport:off -- Event-driven fake of the portal transport. import * as NodeEvents from "node:events"; import { DBusError, Message, MessageType, Variant, type MessageBus } from "dbus-next"; import { afterEach, expect, it, vi } from "vite-plus/test"; import { PortalCaptureShortcut, portalShortcutTrigger } from "./PortalCaptureShortcut.ts"; const chord = { key: "2", modKey: true, ctrlKey: false, altKey: false, shiftKey: true, metaKey: false, }; const root = "/org/freedesktop/portal/desktop"; const portal = "org.freedesktop.portal.GlobalShortcuts"; const clients: PortalCaptureShortcut[] = []; type Shortcut = [string, { description?: Variant; trigger_description?: Variant }]; class FakeBus extends NodeEvents.EventEmitter { calls: Message[] = []; sends: Message[] = []; disconnect = vi.fn(); registryError: Error | undefined; version = 2; autoBind = true; actualLabel = "Ctrl+Shift+2"; bindStatus = 0; session = ""; requestPath = ""; boundId = ""; foreignHandle = false; send(message: Message) { this.sends.push(message); } signal( iface: string, member: string, path: string, body: unknown[], sender = ":1.2", signature = "", ) { this.emit( "message", new Message({ type: MessageType.SIGNAL, sender, interface: iface, member, path, body, signature, }), ); } response(path: string, status: number, results: Record>) { this.signal("org.freedesktop.portal.Request", "Response", path, [status, results]); } respondBind(status = this.bindStatus) { this.response(this.requestPath, status, { shortcuts: new Variant("a(sa{sv})", [ [ this.boundId, { description: new Variant("s", "Capture a window"), trigger_description: new Variant("s", this.actualLabel), }, ], ]), }); } activate(id = this.boundId, session = this.session, sender = ":1.2") { this.signal(portal, "Activated", root, [session, id, 1, {}], sender, "osta{sv}"); } async call(message: Message) { this.calls.push(message); const reply = (body: unknown[] = []) => new Message({ type: MessageType.METHOD_RETURN, replySerial: "1", destination: ":1.23", body, }); if (message.member === "Register") { if (this.registryError) throw this.registryError; return reply(); } if (message.member === "GetNameOwner") return reply([":1.2"]); if (message.member === "Get") return reply([new Variant("u", this.version)]); if (message.member === "AddMatch" || message.member === "ConfigureShortcuts") return reply(); const options = message.body.at(-1) as Record>; const path = `${root}/request/1_23/${options.handle_token!.value}`; if (message.member === "CreateSession") { this.session = `${root}/session/1_23/${options.session_handle_token!.value}`; this.response(path, 0, { session_handle: new Variant( "s", this.foreignHandle ? `${root}/session/9_9/other` : this.session, ), }); } else if (message.member === "BindShortcuts") { this.boundId = (message.body[1] as Shortcut[])[0]![0]; this.requestPath = path; this.emit("bind"); if (this.autoBind) this.respondBind(); } else throw new Error(`Unexpected call: ${message.member}`); return reply([path]); } } function start(bus = new FakeBus(), shortcut = chord, managedByHyprland = false) { const capture = vi.fn(); const changed = vi.fn(); const client = new PortalCaptureShortcut( "com.t3tools.T3Code", shortcut, capture, changed, bus as unknown as MessageBus, managedByHyprland, ); clients.push(client); return { bus, client, capture, changed }; } afterEach(() => { for (const client of clients.splice(0)) client.close(); vi.useRealTimers(); vi.unstubAllEnvs(); }); it("accepts Hyprland's action-only binding without claiming the keys are reserved", async () => { const bus = new FakeBus(); bus.actualLabel = ""; const { client, capture } = start(bus, chord, true); await client.ready; expect(client.state).toMatchObject({ shortcutRegistered: false, shortcutActionRegistered: true, shortcutPending: false, shortcutCanRetry: false, }); expect(bus.boundId).toBe("capture-window"); expect(bus.calls.find((call) => call.member === "BindShortcuts")?.body[1]).toEqual([ ["capture-window", { description: new Variant("s", "Capture a window") }], ]); bus.activate("wrong"); bus.activate(bus.boundId, bus.session, ":1.999"); expect(capture).not.toHaveBeenCalled(); bus.activate(); expect(capture).toHaveBeenCalledOnce(); await expect(client.configure()).rejects.toThrow("Hyprland config"); bus.signal(portal, "ShortcutsChanged", root, [bus.session, []]); bus.activate(); expect(capture).toHaveBeenCalledOnce(); expect(client.state.shortcutActionRegistered).toBe(false); }); it("keeps the Hyprland action ID stable regardless of a saved key chord", async () => { const first = start(new FakeBus(), chord, true); const second = start(new FakeBus(), { ...chord, key: "7" }, true); await Promise.all([first.client.ready, second.client.ready]); expect(first.bus.boundId).toBe(second.bus.boundId); }); it("does not accept an empty trigger as approval on other desktops", async () => { const bus = new FakeBus(); bus.actualLabel = ""; const { client, capture } = start(bus); await client.ready; expect(client.state.shortcutRegistered).toBe(false); expect(client.state.shortcutActionRegistered).not.toBe(true); bus.activate(); expect(capture).not.toHaveBeenCalled(); }); it.each([ ["2", "CTRL+SHIFT+2"], ["f12", "CTRL+SHIFT+F12"], ["pageup", "CTRL+SHIFT+Page_Up"], ["+", "CTRL+SHIFT+plus"], ["arrowleft", "CTRL+SHIFT+Left"], [" ", "CTRL+SHIFT+space"], ])("encodes %s as an XDG shortcut", (key, expected) => { expect(portalShortcutTrigger({ ...chord, key })).toBe(expected); }); it("normalizes Ctrl/Mod and rejects unsupported keys before requesting access", () => { expect(portalShortcutTrigger({ ...chord, ctrlKey: true, metaKey: true })).toBe( "CTRL+SHIFT+LOGO+2", ); expect(() => portalShortcutTrigger({ ...chord, key: "unknown-key" })).toThrow("isn't supported"); }); it("does not call submission approval, then reads the actual assigned shortcut", async () => { const bus = new FakeBus(); bus.autoBind = false; bus.actualLabel = "Ctrl+Shift+7"; const binding = NodeEvents.EventEmitter.once(bus, "bind"); const { client } = start(bus); await binding; expect(client.state.shortcutRegistered).toBe(false); expect(client.state.shortcutPending).toBe(true); bus.respondBind(); await client.ready; expect(client.state).toMatchObject({ shortcutRegistered: true, shortcutPending: false, shortcutLabel: "Ctrl+Shift+7", }); }); it("binds only the requested shortcut with a stable ID across sessions", async () => { const first = start(); await first.client.ready; first.client.close(); const { bus, client, capture } = start(); await client.ready; expect(bus.boundId).toBe(first.bus.boundId); const binds = bus.calls.filter((call) => call.member === "BindShortcuts"); expect(binds).toHaveLength(1); expect(binds[0]!.body[1]).toEqual([ [ bus.boundId, { description: new Variant("s", "Capture a window"), preferred_trigger: new Variant("s", "CTRL+SHIFT+2"), }, ], ]); bus.activate(); expect(capture).toHaveBeenCalledOnce(); expect(bus.calls[0]?.member).toBe("Register"); }); it("ignores other shortcuts, sessions, and forged activations", async () => { const { bus, client, capture } = start(); await client.ready; bus.activate("stale-shortcut"); bus.activate(bus.boundId, `${root}/session/9_9/other`); bus.activate(bus.boundId, bus.session, ":1.999"); expect(capture).not.toHaveBeenCalled(); bus.activate(); expect(capture).toHaveBeenCalledOnce(); }); it("replaces a binding without restarting the application and stops the old callback", async () => { const first = start(); await first.client.ready; first.client.close(); const next = start(new FakeBus(), { ...chord, key: "8" }); await next.client.ready; expect(next.bus.boundId).not.toBe(first.bus.boundId); expect(next.bus.calls.find((call) => call.member === "BindShortcuts")?.body[1]).toEqual([ [ next.bus.boundId, { description: new Variant("s", "Capture a window"), preferred_trigger: new Variant("s", "CTRL+SHIFT+8"), }, ], ]); first.bus.activate(); next.bus.activate(); expect(first.capture).not.toHaveBeenCalled(); expect(next.capture).toHaveBeenCalledOnce(); expect( first.bus.sends.some( (message) => message.interface === "org.freedesktop.portal.Session" && message.member === "Close", ), ).toBe(true); }); it.each([1, 2])( "reports denied/cancelled response %s and offers the desktop permissions UI", async (status) => { const bus = new FakeBus(); bus.bindStatus = status; const { client, capture } = start(bus); await client.ready; expect(client.state.shortcutRegistered).toBe(false); expect(client.state.shortcutPending).toBe(false); expect(client.state.shortcutCanRetry).toBe(true); expect(client.state.shortcutMessage).toContain("wasn't granted"); bus.activate(); expect(capture).not.toHaveBeenCalled(); await client.configure(); expect(bus.calls.at(-1)?.member).toBe("ConfigureShortcuts"); }, ); it("treats a remembered empty binding as unassigned, then observes desktop-side edits", async () => { const bus = new FakeBus(); bus.actualLabel = ""; const { client, capture } = start(bus); await client.ready; expect(client.state.shortcutRegistered).toBe(false); bus.activate(); expect(capture).not.toHaveBeenCalled(); bus.signal(portal, "ShortcutsChanged", root, [ bus.session, [[bus.boundId, { trigger_description: new Variant("s", "Meta+F12") }]], ]); expect(client.state.shortcutLabel).toBe("Meta+F12"); expect(client.state.shortcutRegistered).toBe(true); bus.activate(); expect(capture).toHaveBeenCalledOnce(); }); it("guides users to manual desktop settings when the portal cannot open them", async () => { const bus = new FakeBus(); bus.version = 1; bus.bindStatus = 1; const { client } = start(bus); await client.ready; expect(client.hasSession).toBe(true); expect(client.state.shortcutCanRetry).toBe(false); expect(client.state.shortcutMessage).toBe( "Shortcut permission wasn't granted. Allow T3 Code in your desktop's shortcut settings.", ); await expect(client.configure()).rejects.toThrow("Open your desktop's shortcut settings"); expect(bus.calls.some((message) => message.member === "ConfigureShortcuts")).toBe(false); client.close(); expect(client.hasSession).toBe(false); }); it("captures on older portals without offering an unsupported permission dialog", async () => { const bus = new FakeBus(); bus.version = 1; const { client, capture } = start(bus); await client.ready; expect(client.state).toMatchObject({ shortcutRegistered: true, shortcutCanRetry: false }); bus.activate(); expect(capture).toHaveBeenCalledOnce(); bus.signal(portal, "ShortcutsChanged", root, [bus.session, []]); expect(client.state).toMatchObject({ shortcutRegistered: false, shortcutCanRetry: false, shortcutMessage: "No shortcut is assigned. Choose one in your desktop's shortcut settings.", }); bus.signal("org.freedesktop.portal.Session", "Closed", bus.session, []); expect(client.hasSession).toBe(false); expect(client.state.shortcutCanRetry).toBe(true); }); it("closes an outstanding consent request and ignores its late response", async () => { const bus = new FakeBus(); bus.autoBind = false; const binding = NodeEvents.EventEmitter.once(bus, "bind"); const { client, capture, changed } = start(bus); await binding; client.close(); bus.respondBind(); await client.ready; bus.activate(); expect(capture).not.toHaveBeenCalled(); expect(changed).not.toHaveBeenCalled(); expect(bus.sends.filter((message) => message.member === "Close")).toHaveLength(2); expect(bus.disconnect).toHaveBeenCalledOnce(); }); it("bounds unanswered consent and cleans up the pending request", async () => { vi.useFakeTimers(); const bus = new FakeBus(); bus.autoBind = false; const binding = NodeEvents.EventEmitter.once(bus, "bind"); const { client } = start(bus); await binding; await vi.advanceTimersByTimeAsync(120_000); await client.ready; expect(client.state.shortcutPending).toBe(false); expect(client.state.shortcutMessage).toContain("timed out"); expect(bus.disconnect).toHaveBeenCalledOnce(); expect(vi.getTimerCount()).toBe(0); }); it("handles a disappearing portal without keeping a false registered state", async () => { const { bus, client } = start(); await client.ready; bus.signal( "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus", ["org.freedesktop.portal.Desktop", ":1.2", ""], "org.freedesktop.DBus", ); expect(client.state.shortcutRegistered).toBe(false); expect(client.state.shortcutMessage).toContain("restarted"); expect(bus.disconnect).toHaveBeenCalledOnce(); }); it("accepts old portals without Registry but does not ignore permission denial", async () => { const bus = new FakeBus(); bus.registryError = new DBusError("org.freedesktop.DBus.Error.UnknownMethod", "Old portal"); const first = start(bus); await first.client.ready; expect(first.client.state.shortcutRegistered).toBe(true); const denied = new FakeBus(); denied.registryError = new DBusError( "org.freedesktop.portal.Error.NotAllowed", "App is not allowed", ); const second = start(denied); await second.client.ready; expect(second.client.state.shortcutRegistered).toBe(false); expect(denied.calls.some((call) => call.member === "CreateSession")).toBe(false); }); it("rejects a foreign session without trying to close someone else's session", async () => { const bus = new FakeBus(); bus.foreignHandle = true; const { client } = start(bus); await client.ready; expect(client.state.shortcutRegistered).toBe(false); expect(bus.sends.some((message) => message.path.includes("9_9"))).toBe(false); });