Skip to content

fix: add Cmd+Q menu key equivalent as reliable fallback for quit interception#4179

Open
devin-ai-integration[bot] wants to merge 3 commits intomainfrom
devin/1771820515-fix-cmdq-global-monitor
Open

fix: add Cmd+Q menu key equivalent as reliable fallback for quit interception#4179
devin-ai-integration[bot] wants to merge 3 commits intomainfrom
devin/1771820515-fix-cmdq-global-monitor

Conversation

@devin-ai-integration
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot commented Feb 23, 2026

fix: add Cmd+Q menu key equivalent as reliable fallback for quit interception

Summary

Adds a native Cmd+Q menu item ("Close") to the app menu that triggers QuitInterceptor.onMenuCmdQ(), serving as a reliable fallback when NSEvent.addLocalMonitorForEvents intermittently fails to receive keyboard events.

Symptom: Cmd+Q often doesn't show the quit overlay when the app window is visible and focused. It starts working again after screen recording or reopening the app multiple times.

Root cause: In macOS's event dispatch pipeline, performKeyEquivalent: on the responder chain runs before local event monitors. When WKWebView's performKeyEquivalent: intermittently returns YES for Cmd+Q (depending on internal DOM focus state), it consumes the event and the local monitor never sees it. Focus-disrupting actions (screen recording, reopening) reset WKWebView's internal state, temporarily fixing the problem.

Fix: A Cmd+Q menu key equivalent is processed by NSApplication before performKeyEquivalent: reaches the WKWebView responder chain. This is the standard macOS pattern for app-level keyboard shortcuts with complex view hierarchies. The menu handler calls onMenuCmdQ(), which simulates a quick press-and-release (onCmdQPressed() + onKeyReleased()) to correctly transition to .awaiting state — since a menu action has no physical key release event.

Changes:

  • New AppClose menu item (Cmd+Q) added to the app menu, before "Quit Completely" (Cmd+Shift+Q)
  • New onMenuCmdQ() method in QuitInterceptor that simulates press-and-release
  • New Swift entry point _trigger_cmd_q_pressed → calls QuitInterceptor.shared.onMenuCmdQ() on main thread
  • New Rust FFI wrapper hypr_intercept::trigger_cmd_q_pressed()

Review & Testing Checklist for Human

  • Core bug fix: With the app window visible and focused, press Cmd+Q repeatedly across different sessions — the overlay should appear consistently, even in scenarios where it previously failed (e.g. after using the app for a while without focus changes)
  • Double-tap close flow: Press Cmd+Q (overlay appears), release, press Cmd+Q again within 1.5s — app should close/hide as before
  • Hold-to-quit flow: Press and hold Cmd+Q — progress bar should appear and eventually force-quit. ⚠️ Risk: If both the local monitor AND menu item fire for the same keypress, onMenuCmdQ() would call onKeyReleased() prematurely, breaking hold detection. Verify that holding Cmd+Q still works correctly.
  • Menu click behavior: Click "Close" in the app menu with the mouse — should show overlay and transition to .awaiting (not force-quit after 1.2s). This was a bug in an earlier revision that is now fixed via onMenuCmdQ().
  • Verify Cmd+Shift+Q still force-quits immediately

Recommended test plan: Build and run on macOS. Use the app normally for a few minutes (interact with the webview, type in text fields, etc. to establish various DOM focus states). Then try Cmd+Q — this is the scenario most likely to reproduce the original bug. Repeat 10+ times across different focus states. Also test hold-to-quit and mouse-clicking "Close" from the app menu.

Notes

  • This is a macOS-only change that cannot be verified in Linux CI — manual macOS testing is required
  • The menu item approach is the idiomatic macOS solution for app-level keyboard shortcuts when you have complex view hierarchies (like WKWebView) that might consume events via performKeyEquivalent:
  • Requested by @ComputelessComputer
  • Devin session

The local event monitor (NSEvent.addLocalMonitorForEvents) only receives
events dispatched to the app's event stream. When the app is in Accessory
activation mode with all windows hidden (after a close via Cmd+Q), there
is no key window in the responder chain, so macOS doesn't route keyboard
events to the app and the local monitor never fires.

Add a global event monitor (NSEvent.addGlobalMonitorForEvents) alongside
the existing local one. The global monitor receives events even when the
app has no key window, ensuring Cmd+Q works reliably regardless of
activation policy state.

Co-Authored-By: unknown <>
@devin-ai-integration
Copy link
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Feb 23, 2026

Deploy Preview for hyprnote-storybook canceled.

Name Link
🔨 Latest commit a90ff14
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/699bf32a0ff4650008eb5333

@netlify
Copy link

netlify bot commented Feb 23, 2026

Deploy Preview for hyprnote canceled.

Name Link
🔨 Latest commit a90ff14
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/699bf32aac9b2c000874d241

Reverts the global event monitor approach (which only fires for events
going to other apps, not our own) and instead adds a Cmd+Q menu key
equivalent that reliably triggers the quit overlay.

When WKWebView has focus, it can intermittently consume keyboard events
via performKeyEquivalent before NSEvent.addLocalMonitorForEvents sees
them. Menu key equivalents are processed by NSApplication at the same
level and serve as a reliable fallback when the local monitor misses
the event. The state machine handles double-calls gracefully.

Co-Authored-By: unknown <>
@devin-ai-integration devin-ai-integration bot changed the title fix: add global event monitor for Cmd+Q to work in Accessory mode fix: add Cmd+Q menu key equivalent as reliable fallback for quit interception Feb 23, 2026
Copy link
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

@ComputelessComputer
Copy link
Collaborator

/staging

@github-actions
Copy link

Branch Commit Status
devin/1771820515-fix-cmdq-global-monitor 891a9f27 View

When the Cmd+Q menu item fires, there's no physical key release event,
so the state machine would progress firstPress → holding → performQuit
after 1.2s (force-quitting the app). Fix by adding onMenuCmdQ() that
calls onCmdQPressed() followed immediately by onKeyReleased(), which
correctly transitions to .awaiting state (showing overlay, waiting for
a second press to close).

Co-Authored-By: unknown <>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant