You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(macos): make moveToBack/moveToFront actually visible (v6.4.4)
`CGSOrderWindow(below)` alone was a no-op when the lowered window's app
was the active app — every active-app window sits above every
inactive-app window on macOS, regardless of within-app z-order. So
pressing Shift+Ctrl+Cmd+Left moved nothing visible.
Fix: after lowering, activate the next app's frontmost window so the
lowered app drops into the inactive layer. Then remember the lowered
PID in a module-local `Mutex<Option<i32>>` so the next moveToFront can
target that PID instead of "currently focused window" (which is now
the app we just activated to push the original behind). The remembered
PID is consumed on first front-call after a back, giving a natural
back/front-pair undo. Falls back to focused window when no memory is
set. Windows/Linux don't need this trick (no active-app grouping).
Default keybinding switched from window-toggle to app-scope so the
visible behavior matches macOS reality:
Shift+Ctrl+Super+Left → command/app/moveToBack
Shift+Ctrl+Super+Right → command/app/moveToFront
Also added `tiling::run_zorder_selftest()` — a debug aid behind
`DISPLAY_DJ_ZORDER_SELFTEST=1` that runs all 6 z-order commands with
state snapshots between steps. Each platform's
`is_focused_window_at_front()` is now `pub(super)` so the shared
self-test can observe live state.
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
**Default keybindings**: `Shift+Ctrl+Super+Left` = window toggle, `Shift+Ctrl+Super+Right` = app toggle (mnemonic: Left = single window, Right = many windows; `Super` = Cmd/Win/Super).
201
+
**Default keybindings**: `Shift+Ctrl+Super+Left` = `command/app/moveToBack`, `Shift+Ctrl+Super+Right` = `command/app/moveToFront` (mnemonic: Left = back/away, Right = front/toward you; `Super` = Cmd/Win/Super). App-scope rather than window-scope so the visible behavior is symmetric on macOS — see "Back" below for why single-window scope can't visibly lower the active app's only window.
202
+
203
+
**Self-test (debug aid)**: Set `DISPLAY_DJ_ZORDER_SELFTEST=1` before launching. Five seconds after startup, `tiling::run_zorder_selftest()` runs all 6 z-order commands on whatever window is currently focused, with state snapshots between steps. Logs everything with a `[zorder-selftest]` prefix. Off by default — running on every launch would manipulate the user's focused window.
202
204
203
205
Parsing centralized in `tiling::parse_zorder_command()`; dispatch in `tiling::execute_zorder()` which forwards to platform impls. Dispatched in-process from `tray.rs::execute_command()` on a background thread (no sidecar HTTP), so `build_command_url()` returns `None`.
204
206
@@ -210,7 +212,7 @@ Parsing centralized in `tiling::parse_zorder_command()`; dispatch in `tiling::ex
210
212
211
213
### Back
212
214
213
-
-**macOS**: no public AX API to lower — uses private `CGSOrderWindow(cid, wid, -1, 0)` (CoreGraphics SkyLight, `kCGSOrderBelow`), the standard approach in yabai/Rectangle/AeroSpace. CGS extern declared next to existing `CGSGetActiveSpace`/`CGSMoveWindowsToManagedSpace` in `tiling/macos.rs`. `move_window_to_back` resolves AX → CGWindowID via `_AXUIElementGetWindow`. `move_app_to_back` iterates AXWindows front-first, lowering each — preserves within-app relative order at the bottom of the stack.
215
+
- **macOS**: no public AX API to lower — uses private `CGSOrderWindow(cid, wid, -1, 0)` (CoreGraphics SkyLight, `kCGSOrderBelow`), the standard approach in yabai/Rectangle/AeroSpace. CGS extern declared next to existing `CGSGetActiveSpace`/`CGSMoveWindowsToManagedSpace` in `tiling/macos.rs`. `move_window_to_back` resolves AX → CGWindowID via `_AXUIElementGetWindow`. `move_app_to_back` iterates AXWindows front-first, lowering each — preserves within-app relative order at the bottom of the stack. **Critical: `CGSOrderWindow` alone is invisible when the lowered window's app is the active app** — every window of the active app sits above every window of every inactive app on macOS, regardless of within-app z-order. After lowering, `activate_next_app_excluding_pid()` activates the frontmost window's PID from `get_all_windows()` whose owner differs from the lowered app, dropping the lowered app into the inactive layer. The lowered PID is then stored in the module-local `LAST_BACKED_PID: Mutex<Option<i32>>` so a subsequent `move_window_to_front` / `move_app_to_front` can bring that PID back even though focus has shifted to the app we activated. The remembered PID is consumed (cleared) on the first front call after a back, giving natural back/front-pair undo semantics; once consumed, front falls back to "currently focused window." Windows and Linux do not need this trick (no active-app grouping at the WM level), so the remembered-PID logic is macOS-only.
214
216
-**Windows**: `SetWindowPos(HWND_BOTTOM, …, SWP_NOACTIVATE)` (Windows transfers focus automatically). App scope iterates HWNDs front-to-back so each `HWND_BOTTOM` drops one window to the absolute bottom.
215
217
-**Linux**: `ConfigureWindow(stack_mode = BELOW)` via `lower_window()` — honored by Mutter/KWin/xfwm4.
0 commit comments