Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions docs/VERIFICATION.md
Original file line number Diff line number Diff line change
@@ -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`).

Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/06-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/color/color_matrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/config_ui/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 10 additions & 4 deletions src/core/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Loading