diff --git a/package.json b/package.json index 625b07f..851ebac 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "build:windows": "node scripts/build.mjs win32", "postinstall": "node scripts/postinstall.mjs", "test": "node test/test.mjs && node test/open-links.test.mjs && node test/test-status-item.mjs", + "test:keyboard": "node test/cmd-w-close.test.mjs", "test:platform": "node test/platform.mjs", "publish:check": "./scripts/publish.sh --dry-run", "publish:npm": "./scripts/publish.sh" diff --git a/src/glimpse.swift b/src/glimpse.swift index 7944d3c..a36a0a6 100644 --- a/src/glimpse.swift +++ b/src/glimpse.swift @@ -259,6 +259,11 @@ class GlimpsePanel: NSWindow { return false } + if modifiers == cmd && chars == "w" { + close() + return true + } + let action: Selector? switch (chars, modifiers) { case ("c", cmd): action = #selector(NSText.copy(_:)) diff --git a/test/cmd-w-close.test.mjs b/test/cmd-w-close.test.mjs new file mode 100644 index 0000000..c8a1dc8 --- /dev/null +++ b/test/cmd-w-close.test.mjs @@ -0,0 +1,97 @@ +import { spawnSync } from 'node:child_process'; +import { open } from '../src/glimpse.mjs'; + +const TIMEOUT_MS = 10_000; +const TITLE = `Glimpse Cmd-W Test ${process.pid}`; + +const HTML = ` + + +

Cmd-W close test

+

This window should close when the test sends ⌘W.

+ +`; + +function waitFor(emitter, event, timeoutMs = TIMEOUT_MS) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`Timeout waiting for '${event}' after ${timeoutMs}ms`)); + }, timeoutMs); + + emitter.once(event, (...args) => { + clearTimeout(timer); + resolve(args); + }); + + emitter.once('error', (err) => { + clearTimeout(timer); + reject(err); + }); + }); +} + +function sendCommandWToWindow(title) { + const script = ` + tell application "System Events" + repeat with appProcess in processes + if name of appProcess is "glimpse" then + repeat with appWindow in windows of appProcess + if name of appWindow is "${title.replaceAll('"', '\\"')}" then + set frontmost of appProcess to true + perform action "AXRaise" of appWindow + delay 0.2 + keystroke "w" using command down + return + end if + end repeat + end if + end repeat + error "No Glimpse window named ${title.replaceAll('"', '\\"')}" + end tell + `; + + return spawnSync('osascript', ['-e', script], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); +} + +if (process.platform !== 'darwin') { + console.log('Cmd-W close test skipped: macOS-only keyboard shortcut'); + process.exit(0); +} + +console.log('glimpse Cmd-W close test\n'); + +let win; +try { + win = open(HTML, { + title: TITLE, + width: 520, + height: 260, + }); + + await waitFor(win, 'ready'); + console.log(' ✓ window ready'); + + const result = sendCommandWToWindow(TITLE); + if (result.status !== 0) { + throw new Error([ + 'Failed to send ⌘W with osascript.', + 'This test requires macOS Accessibility permission for the terminal running npm.', + result.stderr.trim(), + result.stdout.trim(), + ].filter(Boolean).join('\n')); + } + console.log(' ✓ sent ⌘W'); + + await waitFor(win, 'closed'); + console.log(' ✓ closed event received'); + + console.log('\nCmd-W close test passed'); + process.exit(0); +} catch (err) { + console.error(`\n ✗ ${err.message}`); + win?.close(); + process.exit(1); +}