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.
This commit is contained in:
Ben Buhse 2026-02-12 13:37:42 -06:00
commit 1df6820b1d
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
5 changed files with 75 additions and 7 deletions

View file

@ -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 // Whether new windows should go to the top or bottom of the window stack
attach_mode "top" 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 // Whether mousing over a new window should move focus
focus_follows_pointer #true focus_follows_pointer #true
@ -37,6 +43,8 @@ wallpaper_image_path "~/Pictures/wallpaper.png"
| Setting | Type | Default | Description | | Setting | Type | Default | Description |
|------------------------------|--------|---------|-----------------------------------------------------| |------------------------------|--------|---------|-----------------------------------------------------|
| `attach_mode` | enum | `top` | Where new windows go in the stack (`top` or `bottom`) | | `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.100.90) |
| `focus_follows_pointer` | bool | `#true` | Focus follows the pointer between windows | | `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 | | `pointer_warp_on_focus_change` | bool | `#true` | Warp pointer to center of newly-focused windows |
| `wallpaper_image_path` | string | none | Path to wallpaper image | | `wallpaper_image_path` | string | none | Path to wallpaper image |

View file

@ -2,8 +2,6 @@
These are in rough order of my priority, though no promises I do them in this order. 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 a river-tag-overlay clone
- [ ] Implement an optional clock bar - [ ] Implement an optional clock bar
- [ ] Support window rules (float/tags/SSD by app-id/title) - [ ] 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] 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 `None` modifier for keybinds (needed for media/brightness keys)
- [x] Support per-host config using properties - [x] Support per-host config using properties
- [x] Implement primary count/ratio per tagmask
- [x] Add primary_count and primary_ratio to Config

View file

@ -1,5 +1,9 @@
// Whether new windows should go to the top or bottom of the window stack // Whether new windows should go to the top or bottom of the window stack
attach_mode top 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 // Whether mousing over a new window should move focus
focus_follows_pointer #true focus_follows_pointer #true
// Whether the focus should warp to the center of newly-focused windows // Whether the focus should warp to the center of newly-focused windows

View file

@ -13,6 +13,13 @@ border_color_focused: RiverColor = utils.parseRgbaComptime("0x89b4fa"),
/// Color of unfocused windows' borders in 0xRRGGBBAA or 0xRRGGBB form /// Color of unfocused windows' borders in 0xRRGGBBAA or 0xRRGGBB form
border_color_unfocused: RiverColor = utils.parseRgbaComptime("0x1e1e2e"), 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 /// Where a new window should attach, top or bottom of the stack
attach_mode: AttachMode = .top, attach_mode: AttachMode = .top,
/// Should focus change when the cursor moves onto a new window /// Should focus change when the cursor moves onto a new window
@ -80,6 +87,8 @@ pub const AttachMode = enum {
const NodeName = enum { const NodeName = enum {
attach_mode, attach_mode,
primary_count,
primary_ratio,
focus_follows_pointer, focus_follows_pointer,
pointer_warp_on_focus_change, pointer_warp_on_focus_change,
wallpaper_image_path, 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 // Next, we have to check the specifics for the NodeName
switch (name) { 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 => { .attach_mode => {
const attach_mode_str = utils.stripQuotes(node.arg(&parser, 0) orelse ""); const attach_mode_str = utils.stripQuotes(node.arg(&parser, 0) orelse "");
if (std.meta.stringToEnum(AttachMode, attach_mode_str)) |mode| { if (std.meta.stringToEnum(AttachMode, attach_mode_str)) |mode| {

View file

@ -25,10 +25,15 @@ wl_surface: ?*wl.Surface = null,
layer_surface: ?*zwlr.LayerSurfaceV1 = null, layer_surface: ?*zwlr.LayerSurfaceV1 = null,
/// Proportion of output width taken by the primary stack /// Proportion of output width taken by the primary stack
primary_ratio: f32 = 0.55, primary_ratio: f32,
/// Number of windows in the primary stack /// 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 are 32-bit bitfield. A window can be active on one(?) or more tags.
tags: u32 = 0x0001, tags: u32 = 0x0001,
@ -46,6 +51,12 @@ windows: wl.list.Head(Window, .link),
link: wl.list.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 { pub const PendingManage = struct {
width: ?u31 = null, width: ?u31 = null,
height: ?u31 = null, height: ?u31 = null,
@ -64,6 +75,8 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
output.* = .{ output.* = .{
.context = context, .context = context,
.river_output_v1 = river_output_v1, .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 .windows = undefined, // we will initialize this shortly
.link = undefined, // Handled by the wl.list .link = undefined, // Handled by the wl.list
}; };
@ -82,6 +95,7 @@ pub fn destroy(output: *Output) void {
window.destroy(); window.destroy();
} }
output.tag_layout_overrides.deinit(utils.allocator);
output.deinitWallpaperLayerSurface(); output.deinitWallpaperLayerSurface();
output.river_output_v1.destroy(); output.river_output_v1.destroy();
utils.allocator.destroy(output); utils.allocator.destroy(output);
@ -425,9 +439,6 @@ pub fn manage(output: *Output) void {
output.y = y; output.y = y;
} }
if (output.pending_manage.tags) |tags| {
output.tags = tags;
}
if (output.pending_manage.primary_ratio) |primary_ratio| { if (output.pending_manage.primary_ratio) |primary_ratio| {
// Ratios outside of this range could cause crashes (when doing the layout calculation) // 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); 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 // Don't allow less than 1 primary
output.primary_count = @max(1, primary_count); 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 // Calculate layout before managing windows
output.calculatePrimaryStackLayout(); output.calculatePrimaryStackLayout();