From 0a887c29c4b0f042cbf850465a044310d29d2660 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 22 Oct 2026 20:37:44 -0400 Subject: [PATCH] ci(windows-smoke): system-wide voice hotkeys round trip (#2) Register PTT on F9 through set_global_hotkeys, press and release F9 via keybd_event, expect pressed and released lotus-global-hotkey events, and none after clearing the bindings. Refs #2, #19 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- scripts/windows-smoke.mjs | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/scripts/windows-smoke.mjs b/scripts/windows-smoke.mjs index 3beadc2..d94c0a6 100644 --- a/scripts/windows-smoke.mjs +++ b/scripts/windows-smoke.mjs @@ -228,6 +228,66 @@ for (const [state, put] of [ record(name, 'fail', String(e).slice(0, 200)); } } +// 9. System-wide voice hotkeys (#2): register PTT on F9, press and release F9 +// through Windows (keybd_event, as a game in front would see it), expect a +// pressed and a released `lotus-global-hotkey`; after clearing the bindings +// the same key must produce nothing (the poll stopped). +const pressScript = `${OUT}/press-f9.ps1`; +writeFileSync( + pressScript, + [ + 'Add-Type -Name K -Namespace W -MemberDefinition @"', + '[DllImport("user32.dll")] public static extern void keybd_event(byte v, byte s, uint f, System.UIntPtr e);', + '"@', + '[W.K]::keybd_event(0x78, 0, 0, [System.UIntPtr]::Zero)', + 'Start-Sleep -Milliseconds 300', + '[W.K]::keybd_event(0x78, 0, 2, [System.UIntPtr]::Zero)', + ].join('\r\n'), +); +const pressF9 = () => execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${pressScript}"`); +try { + const supported = await page.evaluate(() => + window.__TAURI_INTERNALS__.invoke('global_hotkeys_supported'), + ); + if (!supported) { + record('global hotkeys reach the page', 'fail', 'global_hotkeys_supported = false on Windows'); + } else { + await page.evaluate(async () => { + window.__hotkeys = []; + window.addEventListener('lotus-global-hotkey', (e) => window.__hotkeys.push(e.detail)); + await window.__TAURI_INTERNALS__.invoke('set_global_hotkeys', { + bindings: [{ id: 'ptt', code: 'F9' }], + }); + }); + await new Promise((r) => setTimeout(r, 500)); + pressF9(); + const seen = await until( + () => + page.evaluate(() => { + const h = window.__hotkeys.map((e) => `${e.id}:${e.state}`); + return h.includes('ptt:pressed') && h.includes('ptt:released') ? h : undefined; + }), + 5_000, + 250, + ); + await page.evaluate(() => + window.__TAURI_INTERNALS__.invoke('set_global_hotkeys', { bindings: [] }), + ); + const before = await page.evaluate(() => window.__hotkeys.length); + pressF9(); + await new Promise((r) => setTimeout(r, 1500)); + const after = await page.evaluate(() => window.__hotkeys.length); + record( + 'global hotkeys reach the page, and stop when cleared', + seen && after === before ? 'pass' : 'fail', + `events: ${JSON.stringify(seen ?? (await page.evaluate(() => window.__hotkeys)))}; after clearing: ${after - before} more`, + ); + } +} catch (e) { + const msg = String(e).slice(0, 200); + record('global hotkeys reach the page', /not found/i.test(msg) ? 'n/a' : 'fail', msg); +} + await page.screenshot({ path: `${OUT}/02-end.png` }).catch(() => undefined); writeFileSync(`${OUT}/results.json`, JSON.stringify(results, null, 2));