git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitce0fcefde6b3bf6b3d2a03035b54eacf2618d8b6
parentd7952cc5c1
authorOrfeas <[email protected]>
date2024-01-03 16:33
attach-mode: implement after <N>

 doc/riverctl.1.scd            | 16 ++++++++++++++--
 river/Config.zig              |  9 +++++----
 river/Output.zig              |  7 +++++++
 river/View.zig                | 21 +++++++++++++++++++--
 river/command.zig             |  4 +++-
 river/command/attach_mode.zig | 36 ++++++++++++++++++++++++++----------
 6 files changed, 74 insertions(+), 19 deletions(-)

diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index b46185d..b3c95b2 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -347,8 +347,20 @@ matches everything while _\*\*_ and the empty string are invalid.
 
 ## CONFIGURATION
 
-*attach-mode* *top*|*bottom*
-	Configure where new views should attach to the view stack.
+*attach-mode* *top*|*bottom*|*after <N>*
+	Set the attach mode to be used by all outputs by default.
+
+	Possible values:
+	- top: Prepends the newly spawned view at the top of the stack.
+	- bottom: Appends the newly spawned view at the bottom of the stack.
+	- after <N>: Inserts the newly spawned view after N views in the stack.
+
+*default-attach-mode* *top*|*bottom*|*after <N>*
+	Alias to attach-mode.
+
+*output-attach-mode* *top*|*bottom*|*after <N>*
+	Set the attach mode of the currently focused output, overriding the value of
+	default-attach-mode if any.
 
 *background-color* _0xRRGGBB_|_0xRRGGBBAA_
 	Set the background color.
diff --git a/river/Config.zig b/river/Config.zig
index 34336a7..75730f4 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -31,9 +31,10 @@ const Mode = @import("Mode.zig");
 const RuleList = @import("rule_list.zig").RuleList;
 const View = @import("View.zig");
 
-pub const AttachMode = enum {
-    top,
-    bottom,
+pub const AttachMode = union(enum) {
+    top: void,
+    bottom: void,
+    after: usize,
 };
 
 pub const FocusFollowsCursorMode = enum {
@@ -112,7 +113,7 @@ default_layout_namespace: []const u8 = &[0]u8{},
 spawn_tagmask: u32 = std.math.maxInt(u32),
 
 /// Determines where new views will be attached to the view stack.
-attach_mode: AttachMode = .top,
+default_attach_mode: AttachMode = .top,
 
 /// Keyboard repeat rate in characters per second
 repeat_rate: u31 = 25,
diff --git a/river/Output.zig b/river/Output.zig
index f9c3b0e..be370d9 100644
--- a/river/Output.zig
+++ b/river/Output.zig
@@ -36,6 +36,7 @@ const LockSurface = @import("LockSurface.zig");
 const OutputStatus = @import("OutputStatus.zig");
 const SceneNodeData = @import("SceneNodeData.zig");
 const View = @import("View.zig");
+const AttachMode = @import("Config.zig").AttachMode;
 
 const log = std.log.scoped(.output);
 
@@ -166,6 +167,8 @@ current: struct {
 /// Remembered version of tags (from last run)
 previous_tags: u32 = 1 << 0,
 
+attach_mode: ?AttachMode = null,
+
 /// List of all layouts
 layouts: std.TailQueue(Layout) = .{},
 
@@ -608,3 +611,7 @@ pub fn handleLayoutNamespaceChange(self: *Self) void {
 pub fn layoutNamespace(self: Self) []const u8 {
     return self.layout_namespace orelse server.config.default_layout_namespace;
 }
+
+pub fn attachMode(self: Self) AttachMode {
+    return self.attach_mode orelse server.config.default_attach_mode;
+}
diff --git a/river/View.zig b/river/View.zig
index 4deea21..7cdc82f 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -33,6 +33,7 @@ const SceneNodeData = @import("SceneNodeData.zig");
 const Seat = @import("Seat.zig");
 const XdgToplevel = @import("XdgToplevel.zig");
 const XwaylandView = @import("XwaylandView.zig");
+const PendingState = @import("Output.zig").PendingState;
 
 const log = std.log.scoped(.view);
 
@@ -418,9 +419,10 @@ pub fn setPendingOutput(view: *Self, output: *Output) void {
     view.pending_wm_stack_link.remove();
     view.pending_focus_stack_link.remove();
 
-    switch (server.config.attach_mode) {
+    switch (output.attachMode()) {
         .top => output.pending.wm_stack.prepend(view),
         .bottom => output.pending.wm_stack.append(view),
+        .after => |n| view.attach_after(&output.pending, n),
     }
     output.pending.focus_stack.prepend(view);
 
@@ -476,6 +478,20 @@ pub fn applyConstraints(self: *Self, box: *wlr.Box) void {
     box.height = math.clamp(box.height, self.constraints.min_height, self.constraints.max_height);
 }
 
+/// Attach current view after n Views on the pending wm_stack
+pub fn attach_after(view: *Self, pending_state: *PendingState, n: usize) void {
+    var nvisible: u32 = 0;
+    var it = pending_state.wm_stack.iterator(.forward);
+
+    while (it.next()) |cur_view| {
+        if (nvisible >= n) break;
+        if (!cur_view.pending.float // ignore floating views
+        and cur_view.pending.tags & pending_state.tags != 0) nvisible += 1;
+    }
+
+    it.current.prev.?.insert(&view.pending_wm_stack_link);
+}
+
 /// Called by the impl when the surface is ready to be displayed
 pub fn map(view: *Self) !void {
     log.debug("view '{?s}' mapped", .{view.getTitle()});
@@ -530,9 +546,10 @@ pub fn map(view: *Self) !void {
         view.pending_wm_stack_link.remove();
         view.pending_focus_stack_link.remove();
 
-        switch (server.config.attach_mode) {
+        switch (server.config.default_attach_mode) {
             .top => server.root.fallback_pending.wm_stack.prepend(view),
             .bottom => server.root.fallback_pending.wm_stack.append(view),
+            .after => |n| view.attach_after(&server.root.fallback_pending, n),
         }
         server.root.fallback_pending.focus_stack.prepend(view);
 
diff --git a/river/command.zig b/river/command.zig
index 4a2a647..ad8d21f 100644
--- a/river/command.zig
+++ b/river/command.zig
@@ -40,7 +40,7 @@ pub const Orientation = enum {
 const command_impls = std.ComptimeStringMap(
     *const fn (*Seat, []const [:0]const u8, *?[]const u8) Error!void,
     .{
-        .{ "attach-mode",               @import("command/attach_mode.zig").attachMode },
+        .{ "attach-mode",               @import("command/attach_mode.zig").defaultAttachMode },
         .{ "background-color",          @import("command/config.zig").backgroundColor },
         .{ "border-color-focused",      @import("command/config.zig").borderColorFocused },
         .{ "border-color-unfocused",    @import("command/config.zig").borderColorUnfocused },
@@ -48,6 +48,7 @@ const command_impls = std.ComptimeStringMap(
         .{ "border-width",              @import("command/config.zig").borderWidth },
         .{ "close",                     @import("command/close.zig").close },
         .{ "declare-mode",              @import("command/declare_mode.zig").declareMode },
+        .{ "default-attach-mode",       @import("command/attach_mode.zig").defaultAttachMode },
         .{ "default-layout",            @import("command/layout.zig").defaultLayout },
         .{ "enter-mode",                @import("command/enter_mode.zig").enterMode },
         .{ "exit",                      @import("command/exit.zig").exit },
@@ -70,6 +71,7 @@ const command_impls = std.ComptimeStringMap(
         .{ "map-pointer",               @import("command/map.zig").mapPointer },
         .{ "map-switch",                @import("command/map.zig").mapSwitch },
         .{ "move",                      @import("command/move.zig").move },
+        .{ "output-attach-mode",        @import("command/attach_mode.zig").outputAttachMode },
         .{ "output-layout",             @import("command/layout.zig").outputLayout },
         .{ "resize",                    @import("command/move.zig").resize },
         .{ "rule-add",                  @import("command/rule.zig").ruleAdd },
diff --git a/river/command/attach_mode.zig b/river/command/attach_mode.zig
index 7ca3663..bac7ad4 100644
--- a/river/command/attach_mode.zig
+++ b/river/command/attach_mode.zig
@@ -21,20 +21,36 @@ const server = &@import("../main.zig").server;
 
 const Error = @import("../command.zig").Error;
 const Seat = @import("../Seat.zig");
+const AttachMode = @import("../Config.zig").AttachMode;
 
-pub fn attachMode(
-    _: *Seat,
-    args: []const [:0]const u8,
-    _: *?[]const u8,
-) Error!void {
+fn parseAttachMode(args: []const [:0]const u8) Error!AttachMode {
     if (args.len < 2) return Error.NotEnoughArguments;
-    if (args.len > 2) return Error.TooManyArguments;
 
     if (mem.eql(u8, "top", args[1])) {
-        server.config.attach_mode = .top;
+        return if (args.len > 2) Error.TooManyArguments else .top;
     } else if (mem.eql(u8, "bottom", args[1])) {
-        server.config.attach_mode = .bottom;
-    } else {
-        return Error.UnknownOption;
+        return if (args.len > 2) Error.TooManyArguments else .bottom;
+    } else if (mem.eql(u8, "after", args[1])) {
+        if (args.len < 3) return Error.NotEnoughArguments;
+        if (args.len > 3) return Error.TooManyArguments;
+        return .{ .after = try std.fmt.parseInt(usize, args[2], 10) };
     }
+    return Error.UnknownOption;
+}
+
+pub fn outputAttachMode(
+    seat: *Seat,
+    args: []const [:0]const u8,
+    _: *?[]const u8,
+) Error!void {
+    const output = seat.focused_output orelse return;
+    output.attach_mode = try parseAttachMode(args);
+}
+
+pub fn defaultAttachMode(
+    _: *Seat,
+    args: []const [:0]const u8,
+    _: *?[]const u8,
+) Error!void {
+    server.config.default_attach_mode = try parseAttachMode(args);
 }