From 1df6820b1de69e238696e1f5459e507ce93392e5 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Thu, 12 Feb 2026 13:37:42 -0600 Subject: [PATCH] Implement per-tag primary_count and primary_ratio By default, each tag mask will use the default count and ratio. If the mask gets modified by any of the commands, it gets added to a hash map. When changing tag masks, the current count and ratio are stored, and they're used again later if you switch back to that mask. This commit also adds primary_count and primary_ratio to the general settings for the config, so users can set a default count/ratio to use. --- docs/CONFIGURATION.md | 8 ++++++++ docs/TODO.md | 4 ++-- examples/config.kdl | 4 ++++ src/Config.zig | 27 +++++++++++++++++++++++++++ src/Output.zig | 39 ++++++++++++++++++++++++++++++++++----- 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 920c8b2..1eb329a 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -22,6 +22,12 @@ All configuration is applied top down, so later options will overwrite earlier o // Whether new windows should go to the top or bottom of the window stack attach_mode "top" +// Number of windows in the primary stack +primary_count 1 + +// Proportion of output width taken by the primary stack +primary_ratio 0.55 + // Whether mousing over a new window should move focus focus_follows_pointer #true @@ -37,6 +43,8 @@ wallpaper_image_path "~/Pictures/wallpaper.png" | Setting | Type | Default | Description | |------------------------------|--------|---------|-----------------------------------------------------| | `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) | | `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 | | `wallpaper_image_path` | string | none | Path to wallpaper image | diff --git a/docs/TODO.md b/docs/TODO.md index d849542..a044508 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -2,8 +2,6 @@ These are in rough order of my priority, though no promises I do them in this order. -- [ ] Implement primary count/ratio per tagmask -- [ ] Add primary_count and primary_ratio to Config - [ ] Implement a river-tag-overlay clone - [ ] Implement an optional clock bar - [ ] Support window rules (float/tags/SSD by app-id/title) @@ -26,3 +24,5 @@ These are in rough order of my priority, though no promises I do them in this or - [x] Add input configuration, i.e. pointer acceleration and that type of thing - [x] Support `None` modifier for keybinds (needed for media/brightness keys) - [x] Support per-host config using properties +- [x] Implement primary count/ratio per tagmask +- [x] Add primary_count and primary_ratio to Config diff --git a/examples/config.kdl b/examples/config.kdl index af79e0c..e558233 100644 --- a/examples/config.kdl +++ b/examples/config.kdl @@ -1,5 +1,9 @@ // Whether new windows should go to the top or bottom of the window stack attach_mode top +// Number of windows in the primary stack +primary_count 1 +// Proportion of output width taken by the primary stack +primary_ratio 0.55 // 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 cf6aadd..149ed7c 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -13,6 +13,13 @@ border_color_focused: RiverColor = utils.parseRgbaComptime("0x89b4fa"), /// Color of unfocused windows' borders in 0xRRGGBBAA or 0xRRGGBB form border_color_unfocused: RiverColor = utils.parseRgbaComptime("0x1e1e2e"), +/// Number of windows in the primary stack +/// This is a global default, but each tagmask can have its own value +primary_count: u8 = 1, +/// Proportion of output width taken by the primary stack +/// This is a global default, but each tagmask can have its own value +primary_ratio: f32 = 0.55, + /// 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 @@ -80,6 +87,8 @@ pub const AttachMode = enum { const NodeName = enum { attach_mode, + primary_count, + primary_ratio, focus_follows_pointer, pointer_warp_on_focus_change, wallpaper_image_path, @@ -229,6 +238,24 @@ fn load(config: *Config, reader: *Io.Reader) !void { } // Next, we have to check the specifics for the NodeName switch (name) { + .primary_count => { + const count_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); + // Use @max to ensure a minimum of 1 + config.primary_count = @max(1, fmt.parseInt(u8, count_str, 10) catch { + logWarnInvalidNodeArg(name, count_str); + continue; + }); + logDebugSettingNode(name, count_str); + }, + .primary_ratio => { + const ratio_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); + const ratio = fmt.parseFloat(f32, ratio_str) catch { + logWarnInvalidNodeArg(name, ratio_str); + continue; + }; + config.primary_ratio = std.math.clamp(ratio, 0.10, 0.90); + logDebugSettingNode(name, ratio_str); + }, .attach_mode => { const attach_mode_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); if (std.meta.stringToEnum(AttachMode, attach_mode_str)) |mode| { diff --git a/src/Output.zig b/src/Output.zig index f1cf3ba..c961665 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -25,10 +25,15 @@ wl_surface: ?*wl.Surface = null, layer_surface: ?*zwlr.LayerSurfaceV1 = null, /// Proportion of output width taken by the primary stack -primary_ratio: f32 = 0.55, +primary_ratio: f32, /// Number of windows in the primary stack -primary_count: u8 = 1, +primary_count: u8, + +/// Per-tagmask layout overrides +/// These only get added when the user modifies primary count or ratio +/// Any tagmask NOT in this map keeps using the defaults from Config +tag_layout_overrides: std.AutoHashMapUnmanaged(u32, TagLayoutOverride) = .{}, /// Tags are 32-bit bitfield. A window can be active on one(?) or more tags. tags: u32 = 0x0001, @@ -46,6 +51,12 @@ windows: wl.list.Head(Window, .link), link: wl.list.Link, +/// Struct used for tagmask-specific count/ratio overrides +pub const TagLayoutOverride = struct { + primary_count: u8, + primary_ratio: f32, +}; + pub const PendingManage = struct { width: ?u31 = null, height: ?u31 = null, @@ -64,6 +75,8 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output { output.* = .{ .context = context, .river_output_v1 = river_output_v1, + .primary_count = context.config.primary_count, + .primary_ratio = context.config.primary_ratio, .windows = undefined, // we will initialize this shortly .link = undefined, // Handled by the wl.list }; @@ -82,6 +95,7 @@ pub fn destroy(output: *Output) void { window.destroy(); } + output.tag_layout_overrides.deinit(utils.allocator); output.deinitWallpaperLayerSurface(); output.river_output_v1.destroy(); utils.allocator.destroy(output); @@ -425,9 +439,6 @@ pub fn manage(output: *Output) void { output.y = y; } - if (output.pending_manage.tags) |tags| { - output.tags = tags; - } if (output.pending_manage.primary_ratio) |primary_ratio| { // Ratios outside of this range could cause crashes (when doing the layout calculation) output.primary_ratio = std.math.clamp(primary_ratio, 0.10, 0.90); @@ -436,6 +447,24 @@ pub fn manage(output: *Output) void { // Don't allow less than 1 primary output.primary_count = @max(1, primary_count); } + if (output.pending_manage.tags) |new_tags| { + // Save current layout for the old tagmask + output.tag_layout_overrides.put(utils.allocator, output.tags, .{ + .primary_count = output.primary_count, + .primary_ratio = output.primary_ratio, + }) catch @panic("Out of memory"); + + // Restore layout for the new tagmask, or fall back to config defaults + if (output.tag_layout_overrides.get(new_tags)) |tag_layout_override| { + output.primary_count = tag_layout_override.primary_count; + output.primary_ratio = tag_layout_override.primary_ratio; + } else { + output.primary_count = output.context.config.primary_count; + output.primary_ratio = output.context.config.primary_ratio; + } + + output.tags = new_tags; + } // Calculate layout before managing windows output.calculatePrimaryStackLayout();