~novakane/public-inbox

rivercarro: Implement per-tag-cfg v1 SUPERSEDED

Iskren Chernev: 1
 Implement per-tag-cfg

 1 files changed, 73 insertions(+), 47 deletions(-)
#1182928 .build.yml success
March 30, 2024 at 8:56 PM, "Iskren Chernev" <me@iskren.info> wrote:
Next
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~novakane/public-inbox/patches/50615/mbox | git am -3
Learn more about email & git

[PATCH rivercarro] Implement per-tag-cfg Export this patch

It is sometimes useful to remember the configuration for every tag,
instead of having a single configuration for all tags. Add a command
line option to pick per-tags-config, Then keep the available
configuration in a HashMap and pick the right one for user_command and
layout.
---
 src/main.zig | 120 +++++++++++++++++++++++++++++++--------------------
 1 file changed, 73 insertions(+), 47 deletions(-)

diff --git a/src/main.zig b/src/main.zig
index f0298c7..f13c49d 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -40,6 +40,7 @@ const usage =
    \\  -h              Print this help message and exit.
    \\  -version        Print the version number and exit.
    \\  -no-smart-gaps  Disable smart gaps
    \\  -per-tag-cfg    Remember configuration per tag
    \\
    \\  The following commands may also be sent to rivercarro at runtime:
    \\
@@ -86,6 +87,7 @@ const Config = struct {
    main_count: u31 = 1,
    main_ratio: f64 = 0.6,
    width_ratio: f64 = 1.0,
    per_tag_config: bool = false,
};

const Context = struct {
@@ -101,10 +103,25 @@ const Output = struct {
    wl_output: *wl.Output,
    name: u32,

    cfg: Config,
    cfgs: std.AutoHashMap(u32, Config),
    user_command_tags: u32 = 0,

    layout: *river.LayoutV3 = undefined,

    fn get_cfg(output: *Output, tags: u32) *Config {
        var default_cfg = output.cfgs.getPtr(0) orelse unreachable;
        if (!cfg.per_tag_config) {
            return default_cfg;
        }
        // default to global config
        var entry = output.cfgs.getOrPutValue(tags, default_cfg.*) catch {
            // 0 always has a value
            log.err("out of memory, reverting to default cfg", .{});
            return default_cfg;
        };
        return entry.value_ptr;
    }

    fn get_layout(output: *Output) !void {
        output.layout = try ctx.layout_manager.?.getLayout(output.wl_output, "rivercarro");
        output.layout.setListener(*Output, layout_listener, output);
@@ -116,6 +133,7 @@ const Output = struct {

            .user_command => |ev| {
                var it = mem.tokenize(u8, mem.span(ev.command), " ");
                const active_cfg = output.get_cfg(output.user_command_tags);
                const raw_cmd = it.next() orelse {
                    log.err("not enough arguments", .{});
                    return;
@@ -139,12 +157,12 @@ const Output = struct {
                            return;
                        };
                        switch (raw_arg[0]) {
                            '+' => output.cfg.inner_gaps +|= @intCast(arg),
                            '+' => active_cfg.inner_gaps +|= @intCast(arg),
                            '-' => {
                                const res = output.cfg.inner_gaps +| arg;
                                if (res >= 0) output.cfg.inner_gaps = @intCast(res);
                                const res = active_cfg.inner_gaps +| arg;
                                if (res >= 0) active_cfg.*.inner_gaps = @intCast(res);
                            },
                            else => output.cfg.inner_gaps = @intCast(arg),
                            else => active_cfg.*.inner_gaps = @intCast(arg),
                        }
                    },
                    .@"outer-gaps" => {
@@ -153,12 +171,12 @@ const Output = struct {
                            return;
                        };
                        switch (raw_arg[0]) {
                            '+' => output.cfg.outer_gaps +|= @intCast(arg),
                            '+' => active_cfg.*.outer_gaps +|= @intCast(arg),
                            '-' => {
                                const res = output.cfg.outer_gaps +| arg;
                                if (res >= 0) output.cfg.outer_gaps = @intCast(res);
                                const res = active_cfg.outer_gaps +| arg;
                                if (res >= 0) active_cfg.*.outer_gaps = @intCast(res);
                            },
                            else => output.cfg.outer_gaps = @intCast(arg),
                            else => active_cfg.*.outer_gaps = @intCast(arg),
                        }
                    },
                    .gaps => {
@@ -168,23 +186,23 @@ const Output = struct {
                        };
                        switch (raw_arg[0]) {
                            '+' => {
                                output.cfg.inner_gaps +|= @intCast(arg);
                                output.cfg.outer_gaps +|= @intCast(arg);
                                active_cfg.*.inner_gaps +|= @intCast(arg);
                                active_cfg.*.outer_gaps +|= @intCast(arg);
                            },
                            '-' => {
                                const o = output.cfg.outer_gaps +| arg;
                                const i = output.cfg.inner_gaps +| arg;
                                if (i >= 0) output.cfg.inner_gaps = @intCast(i);
                                if (o >= 0) output.cfg.outer_gaps = @intCast(o);
                                const o = active_cfg.outer_gaps +| arg;
                                const i = active_cfg.inner_gaps +| arg;
                                if (i >= 0) active_cfg.*.inner_gaps = @intCast(i);
                                if (o >= 0) active_cfg.*.outer_gaps = @intCast(o);
                            },
                            else => {
                                output.cfg.inner_gaps = @intCast(arg);
                                output.cfg.outer_gaps = @intCast(arg);
                                active_cfg.*.inner_gaps = @intCast(arg);
                                active_cfg.*.outer_gaps = @intCast(arg);
                            },
                        }
                    },
                    .@"main-location" => {
                        output.cfg.main_location = std.meta.stringToEnum(Location, raw_arg) orelse {
                        active_cfg.*.main_location = std.meta.stringToEnum(Location, raw_arg) orelse {
                            log.err("unknown location: {s}", .{raw_arg});
                            return;
                        };
@@ -203,11 +221,11 @@ const Output = struct {
                                picked = current;
                                if (pick_next) break;
                            }
                            if (current == output.cfg.main_location) {
                            if (current == active_cfg.main_location) {
                                pick_next = true;
                            }
                        }
                        output.cfg.main_location = picked.?;
                        active_cfg.*.main_location = picked.?;
                    },
                    .@"main-count" => {
                        const arg = fmt.parseInt(i32, raw_arg, 10) catch |err| {
@@ -215,13 +233,13 @@ const Output = struct {
                            return;
                        };
                        switch (raw_arg[0]) {
                            '+' => output.cfg.main_count +|= @intCast(arg),
                            '+' => active_cfg.*.main_count +|= @intCast(arg),
                            '-' => {
                                const res = output.cfg.main_count +| arg;
                                if (res >= 1) output.cfg.main_count = @intCast(res);
                                const res = active_cfg.main_count +| arg;
                                if (res >= 1) active_cfg.*.main_count = @intCast(res);
                            },
                            else => {
                                if (arg >= 1) output.cfg.main_count = @intCast(arg);
                                if (arg >= 1) active_cfg.*.main_count = @intCast(arg);
                            },
                        }
                    },
@@ -232,9 +250,9 @@ const Output = struct {
                        };
                        switch (raw_arg[0]) {
                            '+', '-' => {
                                output.cfg.main_ratio = math.clamp(output.cfg.main_ratio + arg, 0.1, 0.9);
                                active_cfg.*.main_ratio = math.clamp(active_cfg.main_ratio + arg, 0.1, 0.9);
                            },
                            else => output.cfg.main_ratio = math.clamp(arg, 0.1, 0.9),
                            else => active_cfg.*.main_ratio = math.clamp(arg, 0.1, 0.9),
                        }
                    },
                    .@"width-ratio" => {
@@ -244,44 +262,47 @@ const Output = struct {
                        };
                        switch (raw_arg[0]) {
                            '+', '-' => {
                                output.cfg.width_ratio = math.clamp(output.cfg.width_ratio + arg, 0.1, 1.0);
                                active_cfg.*.width_ratio = math.clamp(active_cfg.width_ratio + arg, 0.1, 1.0);
                            },
                            else => output.cfg.width_ratio = math.clamp(arg, 0.1, 1.0),
                            else => active_cfg.*.width_ratio = math.clamp(arg, 0.1, 1.0),
                        }
                    },
                }
            },
            .user_command_tags => {},
            .user_command_tags => |ev| {
                output.user_command_tags = ev.tags;
            },

            .layout_demand => |ev| {
                assert(ev.view_count > 0);

                const main_count = @min(output.cfg.main_count, @as(u31, @truncate(ev.view_count)));
                const active_cfg = output.get_cfg(ev.tags);
                const main_count = @min(active_cfg.main_count, @as(u31, @truncate(ev.view_count)));
                const sec_count = @as(u31, @truncate(ev.view_count)) -| main_count;

                const only_one_view = ev.view_count == 1 or output.cfg.main_location == .monocle;
                const only_one_view = ev.view_count == 1 or active_cfg.main_location == .monocle;

                // Don't add gaps if there is only one view.
                if (only_one_view and cfg.smart_gaps) {
                    cfg.outer_gaps = 0;
                    cfg.inner_gaps = 0;
                } else {
                    cfg.outer_gaps = output.cfg.outer_gaps;
                    cfg.inner_gaps = output.cfg.inner_gaps;
                    cfg.outer_gaps = active_cfg.outer_gaps;
                    cfg.inner_gaps = active_cfg.inner_gaps;
                }

                const usable_w = switch (output.cfg.main_location) {
                const usable_w = switch (active_cfg.main_location) {
                    .left, .right, .monocle => @as(
                        u31,
                        @intFromFloat(@as(f64, @floatFromInt(ev.usable_width)) * output.cfg.width_ratio),
                        @intFromFloat(@as(f64, @floatFromInt(ev.usable_width)) * active_cfg.width_ratio),
                    ) -| (2 *| cfg.outer_gaps),
                    .top, .bottom => @as(u31, @truncate(ev.usable_height)) -| (2 *| cfg.outer_gaps),
                };
                const usable_h = switch (output.cfg.main_location) {
                const usable_h = switch (active_cfg.main_location) {
                    .left, .right, .monocle => @as(u31, @truncate(ev.usable_height)) -| (2 *| cfg.outer_gaps),
                    .top, .bottom => @as(
                        u31,
                        @intFromFloat(@as(f64, @floatFromInt(ev.usable_width)) * output.cfg.width_ratio),
                        @intFromFloat(@as(f64, @floatFromInt(ev.usable_width)) * active_cfg.width_ratio),
                    ) -| (2 *| cfg.outer_gaps),
                };

@@ -295,7 +316,7 @@ const Output = struct {
                var sec_h: u31 = undefined;
                var sec_h_rem: u31 = undefined;

                if (output.cfg.main_location == .monocle) {
                if (active_cfg.main_location == .monocle) {
                    main_w = usable_w;
                    main_h = usable_h;

@@ -303,7 +324,7 @@ const Output = struct {
                    sec_h = usable_h;
                } else {
                    if (sec_count > 0) {
                        main_w = @as(u31, @intFromFloat(output.cfg.main_ratio * @as(f64, @floatFromInt(usable_w))));
                        main_w = @as(u31, @intFromFloat(active_cfg.main_ratio * @as(f64, @floatFromInt(usable_w))));
                        main_h = usable_h / main_count;
                        main_h_rem = usable_h % main_count;

@@ -324,7 +345,7 @@ const Output = struct {
                    var width: u31 = undefined;
                    var height: u31 = undefined;

                    if (output.cfg.main_location == .monocle) {
                    if (active_cfg.main_location == .monocle) {
                        x = 0;
                        y = 0;
                        width = main_w;
@@ -346,7 +367,7 @@ const Output = struct {
                        }
                    }

                    switch (output.cfg.main_location) {
                    switch (active_cfg.main_location) {
                        .left => layout.pushViewDimensions(
                            x +| cfg.outer_gaps,
                            y +| cfg.outer_gaps,
@@ -385,7 +406,7 @@ const Output = struct {
                    }
                }

                switch (output.cfg.main_location) {
                switch (active_cfg.main_location) {
                    .left => layout.commit("left", ev.serial),
                    .right => layout.commit("right", ev.serial),
                    .top => layout.commit("top", ev.serial),
@@ -408,6 +429,7 @@ pub fn main() !void {
        .{ .name = "main-count", .kind = .arg },
        .{ .name = "main-ratio", .kind = .arg },
        .{ .name = "width-ratio", .kind = .arg },
        .{ .name = "per-tag-cfg", .kind = .boolean },
    }).parse(os.argv[1..]) catch {
        try std.io.getStdErr().writeAll(usage);
        os.exit(1);
@@ -457,6 +479,9 @@ pub fn main() !void {
            fatal_usage("Invalid value '{s}' provided to -width-ratio", .{raw});
        }
    }
    if (res.flags.@"per-tag-cfg") {
        cfg.per_tag_config = true;
    }

    const display = wl.Display.connect(null) catch {
        fatal("unable to connect to wayland compositor", .{});
@@ -513,11 +538,11 @@ fn registry_event(context: *Context, registry: *wl.Registry, event: wl.Registry.
                const node = try gpa.create(std.SinglyLinkedList(Output).Node);
                errdefer gpa.destroy(node);

                node.data = .{
                    .wl_output = wl_output,
                    .name = ev.name,
                    .cfg = cfg,
                };
                node.data = .{ .wl_output = wl_output, .name = ev.name, .cfgs = blk: {
                    var cfgs = std.AutoHashMap(u32, Config).init(gpa);
                    try cfgs.put(0, cfg);
                    break :blk cfgs;
                } };

                if (ctx.initialized) try node.data.get_layout();
                context.outputs.prepend(node);
@@ -529,6 +554,7 @@ fn registry_event(context: *Context, registry: *wl.Registry, event: wl.Registry.
                if (node.data.name == ev.name) {
                    node.data.wl_output.release();
                    node.data.layout.destroy();
                    node.data.cfgs.deinit();
                    context.outputs.remove(node);
                    gpa.destroy(node);
                    break;
-- 
2.44.0
A few nitpicks but other than that it seems to works well, I'll test it
a bit more in the next few days, and see if I can think of improvements
for the implementation, but that seems fine for now.
rivercarro/patches/.build.yml: SUCCESS in 1m3s

[Implement per-tag-cfg][0] from [Iskren Chernev][1]

[0]: https://lists.sr.ht/~novakane/public-inbox/patches/50615
[1]: mailto:me@iskren.info

✓ #1182928 SUCCESS rivercarro/patches/.build.yml https://builds.sr.ht/~novakane/job/1182928
Sorry for posting the patch again, I though the old one got lost.

This one adds deinit, and cleans up the init (in a labeled block). 
Ideally there should be an Output init/deinit which captures the logic, 
I can do that as well, but it's a bit offtopic, so decided against it.