diff --git a/README.md b/README.md index 903a7ec2..6b78b63a 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ The first launch walks you through choosing zoom keys: mouse side buttons, keybo - **Keyboard panning:** optional arrow-key binds move the view while zoomed. - **Quick zoom:** Ctrl plus a zoom key toggles between 1x and your last level. - **Inspect mode:** an optional key freezes the pointer and lets you look around with a crosshair. -- **Ctrl+Alt+Q** quits Wind from anywhere. +- **Win+Esc** quits Wind from anywhere (Ctrl+Alt+Q if another program holds Win+Esc). Bound keys are not passed on to the app you are using. Wind refuses binds that would break normal typing or Windows shortcuts and tells you why. diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index d5b753d9..94a83588 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -1,7 +1,7 @@ # Release smoke checklist Install the build under test, launch Wind from a normal (non-elevated) shell, and work through the -list. Ctrl+Alt+Q quits cleanly at any time. Settings live in `%LOCALAPPDATA%\Wind\magnifier.ini`. +list. Win+Esc quits cleanly at any time. Settings live in `%LOCALAPPDATA%\Wind\magnifier.ini`. `build.bat installer` already checks the installer's files, generated layout and a silent install/uninstall round trip (`tools\installer_check.ps1`). @@ -22,7 +22,7 @@ install/uninstall round trip (`tools\installer_check.ps1`). - [ ] Wheel zoom with the chosen modifiers; plain scrolling is unaffected. - [ ] Keyboard panning while zoomed; at 1x the keys reach the app. - [ ] Clicks, hover, drag and text selection land under the cursor while zoomed. -- [ ] Ctrl+Alt+Q and tray Quit both leave the screen at 1x with a visible cursor. +- [ ] Win+Esc and tray Quit both leave the screen at 1x with a visible cursor. ## Game (borderless) - [ ] Zoom in a game that hides or centre-locks the cursor: the view pans with the mouse. diff --git a/docs/architecture/06-input.md b/docs/architecture/06-input.md index 88c604fe..5d1f9549 100644 --- a/docs/architecture/06-input.md +++ b/docs/architecture/06-input.md @@ -13,7 +13,7 @@ hooks. The code is `src/input/input_router.*`, the `WM_INPUT` handling in `src/c | `WH_MOUSE_LL` (`MouseProc`) | Mouse button and click binds, wheel binds, Inspect click interception | The only user-mode way to swallow a mouse event | | `WH_KEYBOARD_LL` (`KbProc`) | Keyboard binds: down-state and swallowing | Same for keys; also the authority for bound-key state, since a swallowed key never reaches `GetAsyncKeyState` | | Raw Input (`RIDEV_INPUTSINK`, `WM_INPUT` in `WndProc`) | HID mouse deltas for locked and Inspect panning; UP-event safety nets | HID level: unaffected by `ClipCursor`, `SetCursorPos` and `ShowCursor`, and not subject to the hook timeout | -| `RegisterHotKey` | Hide pointer, hotkey-mode quick zoom, Ctrl+Alt+Q quit | The OS suppresses a registered hotkey from other apps; these are toggles, not held keys | +| `RegisterHotKey` | Hide pointer, hotkey-mode quick zoom, Win+Esc quit (Ctrl+Alt+Q when Win+Esc is taken) | The OS suppresses a registered hotkey from other apps; these are toggles, not held keys | **The view must keep moving when a game locks the cursor.** A mouselook game clips or recentres the pointer every frame, so `GetCursorPos` deltas read zero. HID mickeys keep arriving, so diff --git a/src/color/color_matrix.h b/src/color/color_matrix.h index 0e81279e..27e32283 100644 --- a/src/color/color_matrix.h +++ b/src/color/color_matrix.h @@ -35,7 +35,7 @@ inline bool SameMatrix(const ColorMatrix& a, const ColorMatrix& b) { // Lowest brightness the dim control reaches: 1% (owner, 2026-09-30: 0 made the screen completely // black, so the floor stays just above it; under HDR 1% decodes to ~0.08% light, still readable on an -// OLED in the dark). Quitting Wind (Ctrl+Alt+Q) always clears the effect; so does a crash. +// OLED in the dark). Quitting Wind (Win+Esc) always clears the effect; so does a crash. inline constexpr double kMinDim01 = 0.01; // Channel gains for a blackbody colour temperature, in LINEAR light, normalised so 6500 K is white diff --git a/src/config_ui/main.cpp b/src/config_ui/main.cpp index adc6220a..cef44c2d 100644 --- a/src/config_ui/main.cpp +++ b/src/config_ui/main.cpp @@ -508,7 +508,7 @@ static void HandleWebMessage(ICoreWebView2* wv, const std::wstring& jsonW) { ShellExecuteW(nullptr, L"open", L"https://github.com/Maxaubert/Wind", nullptr, nullptr, SW_SHOWNORMAL); } } -// Poll for the magnifier's exit (Ctrl+Alt+Q, tray Quit, or a crash) and take this window down with +// Poll for the magnifier's exit (Win+Esc, tray Quit, or a crash) and take this window down with // it: "the config window should not exist if the magnifier is offline". A poll rather than an event // because a crash never signals an event, and rather than a process handle wait because that needs // OpenProcess against a higher-integrity UIAccess process. <= 1s latency is imperceptible here. diff --git a/src/core/main.cpp b/src/core/main.cpp index 0cdcd8e2..f329b172 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -2999,7 +2999,7 @@ static void RunTick(TickState& t) { } // Message-handler: decodes raw mouse movement (survives cursor lock) and routes tray msgs. -// Global panic/quit hotkey id (Ctrl+Alt+Q). Quits cleanly from anywhere - even while the +// Global panic/quit hotkey id (Win+Esc, #499; Ctrl+Alt+Q only as the fallback). Quits cleanly from anywhere - even while the // render overlay covers the screen and the OS cursor is hidden - so there's always a // keyboard-only escape. The clean exit path restores the cursor and resets zoom to 1x. static const int kQuitHotkeyId = 0xB001; @@ -3366,9 +3366,15 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR, int) { rid[1].dwFlags = RIDEV_INPUTSINK; rid[1].hwndTarget = hwnd; RegisterRawInputDevices(rid, 2, sizeof(rid[0])); - // Safety: global Ctrl+Alt+Q quits cleanly from anywhere (works even with the overlay up - // and the cursor hidden). If the combo is already taken, the tray Quit still works. - RegisterHotKey(hwnd, kQuitHotkeyId, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 'Q'); + // Safety: global Win+Esc quits cleanly from anywhere (works even with the overlay up and the + // cursor hidden); it is Windows Magnifier's own exit shortcut (#499). If another program holds + // it (Magnifier running), Ctrl+Alt+Q takes its place; failing both, the tray Quit still works. + if (!RegisterHotKey(hwnd, kQuitHotkeyId, MOD_WIN | MOD_NOREPEAT, VK_ESCAPE)) { + const DWORD err = GetLastError(); + const bool fallback = RegisterHotKey(hwnd, kQuitHotkeyId, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 'Q') != FALSE; + wind::Log(wind::LogLevel::Warn, "core", "quit hotkey Win+Esc taken (err %lu); %s", err, + fallback ? "using Ctrl+Alt+Q" : "Ctrl+Alt+Q taken too, tray Quit only"); + } g_hdrOn.store(PrimaryHdrOn()); // colour filter space (#288), refreshed on WM_DISPLAYCHANGE + 1 Hz while on RegisterHideCursorHotkey(hwnd, cfg.hideCursorVk, cfg.hideCursorMods); RegisterQuickZoomHotkey(hwnd, (cfg.quickZoomHotkeyMode && cfg.quickZoomVk) ? cfg.quickZoomVk : 0,