refactor(windows): minimize, close and raise go through backend()

process_command (close and minimize of the foreground window, four raise
calls) and monitor_reconciliator (four minimize calls) now call
backend().close/minimize/raise. WindowsBackend runs Window::close,
minimize and raise unchanged (minimize still leaves komorebi-bar alone).
docs/dev/backend-switch.md now holds the full table of what is switched
and what is left, including why the queries (window_rect,
foreground_window, cursor_pos, monitors) were NOT switched: each would drop
an error that callers use or report, or fabricate a default.

BEHAVIOUR: a dead window is skipped by an IsWindow check first; minimize
results are ignored as before. e2e gate needed: commands, monitor
reconciliation (monitor_reconciliator), lifecycle. Verified: Windows clippy
-D warnings via tools/fast-check PASS; e2e not run.
This commit is contained in:
LGUG2Z committed 2026-10-31 10:22:41 -07:00
1 parent fad90dbd3e
commit f5a299aa9e
3 files changed
+30 -20

No files matched your search

+14 -10
View File
@@ -2,16 +2,20 @@
Step 8 of the unified-binaries plan. `komorebi::windows_backend::backend()` returns the shared
`komorebi_backend::Backend` (a stateless `WindowsBackend` over `WindowsApi` and `Window`). The daemon
switches one cluster at a time; each switch is its own revision marked BEHAVIOUR.
is switched one cluster at a time; each switch is its own revision marked BEHAVIOUR.
What every switch changes, for the Windows daemon: `WindowsBackend` first asks `IsWindow` and fails
with `Error::WindowGone` for a dead window, and a failure of a window that died meanwhile is reported
as `WindowGone` (the message text changes: it was the Win32 error). Call sites use `?` or ignore the
result, none inspects the error.
What every switched call changes in the Windows daemon: `WindowsBackend` first asks `IsWindow` and
fails with `Error::WindowGone` for a dead window, and a failure of a window that died meanwhile is
reported as `WindowGone` (the error text changes from the Win32 one). Call sites use `?` or ignore the
result; none inspects the error. The operations return `Result` where `Window::hide`, `restore` and
`minimize` returned `()`: those sites ignore it (`let _ =`), as the old code could not fail.
| Cluster | Operation | Status | Sites | Left on `Window`/`WindowsApi`, and why | e2e needed |
| Cluster | Operation | Status | Sites | Left on `Window` / `WindowsApi`, and why | e2e needed |
|---|---|---|---|---|---|
| focus | `focus(id, mouse_follows)` | switched | process_event (4), process_command (3), workspace (7), window_manager (24), focus_manager, stackbar | `animation/transition.rs:534` calls `Window::focus` from inside the slide-in code that `Window::focus` itself defers to (it would recurse through the backend for no gain) | commands, focus, focus_follows_mouse, stacking, workspaces, lifecycle |
| frame / position | `set_rect`, `window_rect` | not switched | | `Window::set_position` has the `top` flag, animation/slide-in/live-resize guards that the backend `set_rect` (always `top = false`) cannot carry | |
| hide / show | `hide`, `restore`, `minimize`, `unminimize`, `raise`, `close` | not switched | | | |
| queries | `window`, `foreground_window`, `cursor_pos`, `monitors`, `monitor_from_point` | not switched | | | |
| focus | `focus(id, mouse_follows)` | switched | process_event 4, process_command 3, workspace 7, window_manager 24, focus_manager 1, stackbar 1 | `animation/transition.rs:534` calls `Window::focus` from inside the slide-in code that `Window::focus` itself defers to | commands, focus, focus_follows_mouse, stacking, workspaces, lifecycle |
| frame | `set_rect(id, rect)` | switched for `top = false` | workspace.rs (layout update), stackbar tab click | `set_position(.., true)` callers (window.rs `move_to_area`/`center`, workspace maximize area): the shared `set_rect` has no `top` flag | commands, resize, layouts, stacking, scrolling, workspaces |
| frame | `window_rect(id)` | NOT switched | 0 of about 60 | the shared `window_rect` is built on `window(id)`, which asks title, class, exe and path of the window, gives `None` for a failure and the call sites rely on `Err` or `unwrap_or_default`. Needs a cheap frame query on the trait (a `frame(id) -> Result<Rect>`) or an override of the provided method with an error | |
| hide / show | `hide`, `restore` | switched for the plain forms | container, workspace, monitor, process_command, process_event, window_manager (23) | `hide_with_border` and `restore_with_border` (no border flag in the trait); `animation/transition.rs` and `ghost_dispatcher.rs` (inside the slide-in machinery); the `Container`/`Workspace` methods stay, they call these | commands, workspaces, stacking, lifecycle_hidden |
| hide / show | `minimize`, `close`, `raise` | switched | process_command (close 1, minimize 1, raise 4), monitor_reconciliator (minimize 4) | | commands, monitor reconciliation, lifecycle |
| hide / show | `unminimize`, `maximize` | not switched | | `unminimize` has no daemon caller (the daemon uses `restore`); `maximize`/`unmaximize` need the `Capability::Maximize` check and keep their own bookkeeping in workspace.rs | |
| queries | `foreground_window`, `cursor_pos`, `monitors`, `monitor_from_point`, `window` | NOT switched | 0 | `WindowsApi::foreground_window` is `Result<isize>` (null handle is an `Err` with a message the callers pass on), the trait gives `Option<WindowId>`; `cursor_pos` is `Result<POINT>` vs `Option<(i32, i32)>`; `monitors` rebuilds `MonitorInfo` (drops the `Monitor` fields the daemon needs); `monitor_from_point` is never `None` on Windows. Each switch would drop an error the caller reports or fabricate a default | |
+10 -4
View File
@@ -15,9 +15,11 @@ use crate::monitor::Monitor;
use crate::monitor_reconciliator::hidden::Hidden;
use crate::notify_subscribers;
use crate::state::State;
use crate::windows_backend::backend;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::atomic::AtomicConsume;
use komorebi_backend::WindowId;
use parking_lot::Mutex;
use serde::Deserialize;
use serde::Serialize;
@@ -593,7 +595,8 @@ where
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if window.is_focused() {
window.minimize();
let _ = backend()
.minimize(WindowId::from_hwnd(window.hwnd));
}
}
}
@@ -604,7 +607,8 @@ where
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if maximized.is_focused() {
maximized.minimize();
let _ =
backend().minimize(WindowId::from_hwnd(maximized.hwnd));
}
}
@@ -616,7 +620,8 @@ where
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if window.is_focused() {
window.minimize();
let _ = backend()
.minimize(WindowId::from_hwnd(window.hwnd));
}
}
}
@@ -626,7 +631,8 @@ where
// Minimize the focused window since Windows might try
// to move it to another monitor if it was focused.
if window.is_focused() {
window.minimize();
let _ =
backend().minimize(WindowId::from_hwnd(window.hwnd));
}
}
}
+6 -6
View File
@@ -406,10 +406,10 @@ impl WindowManager {
WindowsApi::left_click();
}
SocketMessage::Close => {
Window::from(WindowsApi::foreground_window()?).close()?;
backend().close(WindowId::from_hwnd(WindowsApi::foreground_window()?))?;
}
SocketMessage::Minimize => {
Window::from(WindowsApi::foreground_window()?).minimize();
let _ = backend().minimize(WindowId::from_hwnd(WindowsApi::foreground_window()?));
}
SocketMessage::LockMonitorWorkspaceContainer(
monitor_idx,
@@ -1309,7 +1309,7 @@ impl WindowManager {
to_focus = Some(*window);
} else {
let _ = backend().restore(WindowId::from_hwnd(window.hwnd));
window.raise()?;
backend().raise(WindowId::from_hwnd(window.hwnd))?;
}
}
@@ -1317,7 +1317,7 @@ impl WindowManager {
// The focused window should be the last one raised to make sure it is
// on top
let _ = backend().restore(WindowId::from_hwnd(focused_window.hwnd));
focused_window.raise()?;
backend().raise(WindowId::from_hwnd(focused_window.hwnd))?;
}
for container in workspace.containers() {
@@ -1338,7 +1338,7 @@ impl WindowManager {
if let Some(monocle) = &workspace.monocle_container {
if let Some(window) = monocle.focused_window() {
to_focus = Some(*window);
window.raise()?;
backend().raise(WindowId::from_hwnd(window.hwnd))?;
}
for window in workspace.floating_windows() {
let _ = backend().hide(WindowId::from_hwnd(window.hwnd));
@@ -1351,7 +1351,7 @@ impl WindowManager {
if i == focused_container_idx {
to_focus = Some(*window);
}
window.raise()?;
backend().raise(WindowId::from_hwnd(window.hwnd))?;
}
}