From 164ae9a7ab38d297b4247b3c9d0d385bb3be8e48 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Wed, 25 Feb 2026 15:16:48 -0600 Subject: [PATCH] Implement left-side primary This adds a new config `primary_side` that can be either `left` or `right` and determines whether the primary stack is on the left or the right side of the screen. --- docs/CONFIGURATION.md | 1 + docs/TODO.md | 17 +++++++++-------- examples/config.kdl | 2 ++ src/Config.zig | 21 +++++++++++++++++---- src/Output.zig | 19 +++++++++++++------ 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index a06f0b7..8bd9adf 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -45,6 +45,7 @@ wallpaper_image_path "~/Pictures/wallpaper.png" | `attach_mode` | enum | `top` | Where new windows go in the stack (`top` or `bottom`) | | `primary_count` | u8 | `1` | Number of windows in the primary stack (0+) | | `primary_ratio` | float | `0.55` | Proportion of output width for the primary stack (0.10-0.90) | +| `primary_side` | enum | `left` | Whether the primary stack should be on the `left` or `right` | | `single_window_ratio` | float | `1.00` | Proportion of output width taken when a single tiled window is visible (0.10-1.00) | | `focus_follows_pointer` | bool | `#true` | Focus follows the pointer between windows | | `pointer_warp_on_focus_change` | bool | `#true` | Warp pointer to center of newly-focused windows | diff --git a/docs/TODO.md b/docs/TODO.md index 5136745..b93bd2d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -2,16 +2,19 @@ These are in rough order of my priority, though no promises I do them in this order. -- [ ] Add config for single-window width ratio (mostly because my ultrawide makes a single window massive) +- [ ] Support switch handling (e.g. lid close) - [ ] Check pointer position and only warp if not on focused window already - [ ] Change focus direction when closing window - [ ] Use set_xcursor_theme request - [ ] Add focused window title to bar - [ ] Support overriding config location -- [ ] Support configuring primary vs secondary stack side -- [ ] Support switch handling (e.g. lid close) -- [ ] Support keybind modes (e.g. passthrough) +- [ ] Add support for center-primary layout +- [ ] Add bar padding to config +- [ ] Support 12-hour clock format (maybe take any time format string?) +- [ ] Support per-output bar visibility +- [ ] Support more window rule options (e.g. ssd/csd) - [ ] Support solid `background-color` fallback (no wallpaper) +- [ ] Support keybind modes - [ ] Support per-output wallpapers - [ ] Support `focus-follows-cursor` granularity (`normal` vs `always`) - [ ] Save window positions between restarts @@ -21,10 +24,6 @@ These are in rough order of my priority, though no promises I do them in this or - [ ] Support configurable focus-follows-window on send-to-output - [ ] Support configurable prepend/append on send-to-output - [ ] Support taking new output's tags on send-to-output -- [ ] Add bar padding to config -- [ ] Support 12-hour clock format (maybe take any time format string?) -- [ ] Support per-output bar visibility -- [ ] Support more window rule options (e.g. ssd/csd) - [ ] Add `spawn_tagmask`, `focus_previous_tags`, `send_to_previous_tags` commands - [x] Support changeable primary ratio - [x] Support changeable primary count @@ -46,3 +45,5 @@ These are in rough order of my priority, though no promises I do them in this or - [x] Support window rules (float/tags by app-id/title) - [x] Fix resizing size when you pop a window out, basically, it start with its current size but then when you try resize it goes to 75% - [x] Move orphan handling out of .output and .seat events; into manage() +- [x] Add config for single-window width ratio (mostly because my ultrawide makes a single window massive) +- [x] Support configuring primary vs secondary stack side diff --git a/examples/config.kdl b/examples/config.kdl index c9857d5..8c43c34 100644 --- a/examples/config.kdl +++ b/examples/config.kdl @@ -9,6 +9,8 @@ primary_ratio 0.55 // When this is < 1.0 and only one window is being tiled, the window will have width // output_width * single_window_ratio and be centered on the output single_window_ratio 0.70 +/// Side the primary should be on +primary_side left // Whether mousing over a new window should move focus focus_follows_pointer #true // Whether the focus should warp to the center of newly-focused windows diff --git a/src/Config.zig b/src/Config.zig index 507b5d7..6475028 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -28,6 +28,9 @@ primary_ratio: f32 = 0.55, /// output_width * single_window_ratio and be centered on the output single_window_ratio: f32 = 1.0, +/// Side the primary should be on +primary_side: PrimarySide = .left, + /// Where a new window should attach, top or bottom of the stack attach_mode: AttachMode = .top, /// Should focus change when the cursor moves onto a new window @@ -57,16 +60,16 @@ pub const PointerBind = pointer_bind.PointerBind; pub const WindowRule = window_rule.Rule; pub const WindowRuleAction = window_rule.Action; -pub const AttachMode = enum { - top, - bottom, -}; +pub const PrimarySide = enum { left, right }; + +pub const AttachMode = enum { top, bottom }; const NodeName = enum { attach_mode, primary_count, primary_ratio, single_window_ratio, + primary_side, focus_follows_pointer, pointer_warp_on_focus_change, wallpaper_image_path, @@ -208,6 +211,16 @@ fn load(config: *Config, reader: *Io.Reader) !void { } logDebugSettingNode(name, ratio_str); }, + .primary_side => { + const primary_side_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); + if (std.meta.stringToEnum(PrimarySide, primary_side_str)) |mode| { + config.primary_side = mode; + logDebugSettingNode(name, primary_side_str); + } else { + logWarnInvalidNodeArg(name, primary_side_str); + continue; + } + }, .single_window_ratio => { const ratio_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); const ratio = fmt.parseFloat(f32, ratio_str) catch { diff --git a/src/Output.zig b/src/Output.zig index cb09719..d282d85 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -639,8 +639,9 @@ pub fn render(output: *Output) void { } /// Calculate primary/stack layout positions for all windows. -/// - Single window: maximized -/// - Multiple windows: stack (45% left, vertically tiled), primary (55% right) +/// - Single window: window is told it's maximized and takes up usable_width * single_window_ratio width +/// - Multiple windows: two stacks, primary and secondary. By default, the stack is on the right and takes +/// up 55% of the output width, but this can be configured. Each tagmask has its own primary ratio and count. fn calculateLayout(output: *Output) void { // Shouldn't be called if height/width are not positive assert(output.geometry.width > 0 and output.geometry.height > 0); @@ -711,6 +712,12 @@ fn calculateLayout(output: *Output) void { else 0; + // Determine the stack x coordinates based on whether primary is set to the left or right + const primary_x, const stack_x = switch (output.context.config.primary_side) { + .right => .{ output_x + @as(i32, stack_width), output_x }, + .left => .{ output_x, output_x + @as(i32, primary_width) }, + }; + // Iterate through the active windows and apply positions var i: u31 = 0; while (active_list.popFirst()) |node| : (i += 1) { @@ -718,9 +725,9 @@ fn calculateLayout(output: *Output) void { window.pending_manage.maximized = false; if (i < primary_count) { - // Primary window(s) - right side + // Primary window(s) window.pending_render.position = .{ - .x = output_x + @as(i32, stack_width), + .x = primary_x, .y = output_y + @as(i32, i) * @as(i32, primary_height), }; const pending_width = primary_width; @@ -734,10 +741,10 @@ fn calculateLayout(output: *Output) void { .height = pending_height, }; } else { - // Stack window(s) - left side + // Stack window(s) const stack_index = i - primary_count; window.pending_render.position = .{ - .x = output_x, + .x = stack_x, .y = output_y + @as(i32, stack_index) * @as(i32, stack_height), }; const pending_width = stack_width;