diff --git a/README.md b/README.md index dca0a0d..6195287 100644 --- a/README.md +++ b/README.md @@ -182,8 +182,11 @@ separate setting, `repeat`, under [Input](#input). column is divided, so the bottom edge always lands exactly on the output edge. **monocle** — every window gets the full area; only the most recently focused -one is rendered. A window spawned into monocle or tabbed takes focus as it -maps, so it comes up in front instead of hidden behind the current one. +one is rendered. + +In every layout a newly mapped window goes to the front of the arrangement +order — the first master slot — and takes focus, as in dwm. A window spawned +onto a tag nobody is viewing leaves both the order and focus alone. **tabbed** — same geometry as monocle, minus a strip at the top where att_wm draws one solid colour block per window, the focused one highlighted. The blocks diff --git a/src/Wm.zig b/src/Wm.zig index f758bb8..03943c9 100644 --- a/src/Wm.zig +++ b/src/Wm.zig @@ -602,23 +602,27 @@ fn assignOutputs(self: *Wm) void { } } -/// Hand a freshly mapped window the newest focus serial, and focus it, when it -/// arrives into a stacking layout. +/// Put a freshly mapped window at the front of its output's arrangement order +/// and focus it. /// -/// Those layouts render only the most recently focused window, so a new window -/// that keeps its initial serial of zero is hidden the instant it appears and -/// cannot even be reached with the pointer. Making it the top of the stack -/// without also moving focus would be worse still — the keyboard would go on -/// talking to a window that is no longer on screen — so the two move together, -/// which is what dwm does for every new client anyway. -/// -/// In master a new window is visible wherever it lands in the order, so focus -/// is left where the user put it. +/// This is dwm's attach() plus focus(): a new client lands in master rather +/// than at the bottom of the stack, and the keyboard follows it there. In a +/// stacking layout the move is not optional — those layouts render only the +/// most recently focused window, so a window that kept its initial serial of +/// zero would be hidden the instant it appeared and unreachable even with the +/// pointer, and promoting it without moving focus would leave the keyboard +/// talking to a window that is no longer on screen. /// /// Runs before `arrangeAll` so the serial set here is the one the layout picks -/// its top window with. +/// its top window with, and the order set here is the one it arranges. fn focusNewWindows(self: *Wm) void { - for (self.windows.items) |win| { + // `attachToMaster` reorders `windows`, so re-read the list each time round + // rather than holding a slice across the move. It only ever moves a window + // to a lower index, so nothing after `i` shifts and no new window is + // skipped. + var i: usize = 0; + while (i < self.windows.items.len) : (i += 1) { + const win = self.windows.items[i]; if (!win.wants_initial_focus) continue; if (win.closed or !win.mapped) continue; win.wants_initial_focus = false; @@ -627,11 +631,12 @@ fn focusNewWindows(self: *Wm) void { if (win.floating or win.fullscreen) continue; const out = win.output orelse continue; - if (!layout.stacks(out.state().layout)) continue; // Spawned onto a tag nobody is looking at: nothing to come to the // front of, and stealing focus would yank the user off their tag. if ((win.tags & out.tags) == 0) continue; + self.attachToMaster(win, out); + self.focus_serial += 1; win.focus_serial = self.focus_serial; for (self.seats.items) |seat| { @@ -640,6 +645,20 @@ fn focusNewWindows(self: *Wm) void { } } +/// Move `win` ahead of every other tiled window on `out`, so it takes the +/// first master slot. Windows on other outputs and on other tags keep their +/// relative order. +fn attachToMaster(self: *Wm, win: *Window, out: *Output) void { + for (self.windows.items) |other| { + if (other == win) return; // already the first tiled window here + if (other.output != out) continue; + if ((other.tags & out.tags) == 0) continue; + if (other.floating or other.fullscreen) continue; + self.moveToFront(win, other); + return; + } +} + /// If a seat's focused window is no longer visible, move focus the way dwm /// does: to the most recently focused visible window on the same output. fn validateFocus(self: *Wm, seat: *Seat) void { diff --git a/src/layout.zig b/src/layout.zig index f06b535..8278317 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -56,16 +56,6 @@ pub const Result = struct { stacked: bool = false, }; -/// Whether a layout puts every window in the same place, so only the top one -/// is rendered. `arrange` reports the same thing after the fact; this answers -/// it for callers that need to know before the geometry is computed. -pub fn stacks(layout: Layout) bool { - return switch (layout) { - .master => false, - .monocle, .tabbed => true, - }; -} - /// Fill `cells` with one rectangle per window, in arrangement order. /// /// Each cell is the *outer* rectangle including space for the border; the diff --git a/src/test.zig b/src/test.zig index aa8ce83..da486e6 100644 --- a/src/test.zig +++ b/src/test.zig @@ -102,13 +102,12 @@ test "monocle gives every window the full area and reports stacked" { for (cells) |cell| try testing.expectEqual(area, cell); } -test "stacks agrees with what arrange reports" { +test "master does not report stacked" { var cells: [2]Box = undefined; var p = params(1, 0.55); p.tabbar_height = 22; - for ([_]layout.Layout{ .master, .monocle, .tabbed }) |mode| { - try testing.expectEqual(layout.arrange(mode, p, &cells).stacked, layout.stacks(mode)); - } + + try testing.expect(!layout.arrange(.master, p, &cells).stacked); } test "tabbed reserves the bar strip above the windows" {