Files
galaxy/specs/CODE-1787/TECH.md
T

4.1 KiB
Raw Blame History

Windows Quake Mode: Focus and Sizing Fix — Tech Spec

Product spec: specs/CODE-1787/PRODUCT.md

Context

Two independent bugs prevent quake mode from working correctly on Windows when triggered while a non-Warp application has foreground focus.

Bug 1: focus not transferred

WinitWindow::focus() in crates/warpui/src/windowing/winit/window.rs:1080 had two branches: if the window was already visible it called focus_window(), otherwise it called set_visible(true) and relied on visibility implying focus. On Windows, set_visible(true) does not steal foreground focus from another application — an explicit SetForegroundWindow (via winit's focus_window()) is required. The quake window was hidden via set_visible(false), so re-showing it always took the set_visible(true) branch and never called focus_window().

Bug 2: incorrect window size

All Windows monitor queries in crates/warpui/src/windowing/winit/window/windows_wm.rs routed through get_active_window_handle(), which requires a focused + visible Warp window. When no Warp window has focus, this fails and active_display_bounds() falls back to a hardcoded DEFAULT_WINDOW_SIZE (1280×800). The quake window is then sized as a percentage of that default instead of the actual display dimensions.

Relevant code

  • crates/warpui/src/windowing/winit/window.rs:1080-1092WinitWindow::focus()
  • crates/warpui/src/windowing/winit/window/windows_wm.rs — all Windows monitor query methods
  • crates/warpui/src/windowing/winit/window.rs:222-227WindowManager::show_window_and_focus_app (calls focus())
  • app/src/root_view.rs:1481-1507 — quake mode toggle, hidden→visible branch

Proposed changes

1. Always call focus_window() in WinitWindow::focus()

Restructure focus() so focus_window() is called unconditionally after the window is made visible or un-minimized. The previous code only called it in the already-visible branch.

Before:

if visible → set_minimized(false); focus_window()
else       → set_visible(true)    // hoped this would also focus

After:

if visible → set_minimized(false)
else       → set_visible(true)
focus_window()                    // always, regardless of prior visibility

This fixes Behavior 1 and 2.

2. Decouple monitor queries from active-window requirement

Split the Windows monitor methods into two categories:

Global queries (don't care which monitor): get_primary_monitor_handle, get_available_monitors, get_available_monitor_count. These only need any winit window handle to access platform APIs. Add get_any_window_handle() which returns the first available window regardless of focus, and call it directly from these methods.

Active-monitor queries (need to know which monitor the user is on): get_active_monitor, get_current_monitor_id, get_active_monitor_logical_bounds. When a Warp window has focus, use its current_monitor(). When no Warp window has focus, fall back to get_foreground_monitor(), which uses Win32 GetForegroundWindow + MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) to find the monitor of the app that has keyboard focus. This is the window that triggered the global hotkey.

This fixes Behavior 3 and 4. The foreground-window approach is preferred over cursor position because the cursor may be on a different monitor than the window handling keypress events.

3. Add Win32 feature dependencies

Enable Win32_Graphics_Gdi (for MonitorFromWindow, MONITOR_DEFAULTTONEAREST) and Win32_UI_WindowsAndMessaging (for GetForegroundWindow) in crates/warpui/Cargo.toml.

Testing and validation

  • Manual: configure quake mode with 100% width, focus a non-Warp app, press the hotkey. Verify the quake window receives focus and spans the full display width. (Behavior 1, 3)
  • Manual: with a Warp window focused, press the hotkey. Verify existing behavior is preserved. (Behavior 2)
  • Manual (multi-monitor): focus an app on monitor B, press the hotkey. Verify the quake window appears on monitor B at the correct size. (Behavior 4)
  • Manual: verify macOS quake mode is unaffected — changes are behind #[cfg(windows)] and winit-only code paths. (Behavior 6)