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

commit69ba8e6504dc9942518dd1bfa3d35de639befd25
parenta86e630649
authorIsaac Freund <[email protected]>
date2025-03-23 11:12
rivercompat: implement more actions

 rivercompat/Seat.zig | 55 ++++++++++++++++++++++++++++++++++++++++++++++++----
 rivercompat/main.zig |  9 +++++++++
 2 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/rivercompat/Seat.zig b/rivercompat/Seat.zig
index a47d37d..8098241 100644
--- a/rivercompat/Seat.zig
+++ b/rivercompat/Seat.zig
@@ -85,9 +85,13 @@ pub fn updateWindowing(seat: *Seat) void {
     if (seat.pending.new) {
         seat.focused_output = wm.outputs.first();
 
-        XkbBinding.create(seat, xkb.Keysym.n, .{ .mod4 = true }, .focus_next);
+        // These are my personal keybindings which probably only make sense on my weird keyboard layout
+        XkbBinding.create(seat, xkb.Keysym.space, .{ .mod4 = true, .mod1 = true }, .{ .spawn = "foot" });
+        XkbBinding.create(seat, xkb.Keysym.u, .{ .mod4 = true, .mod1 = true }, .close_focused);
+        XkbBinding.create(seat, xkb.Keysym.a, .{ .mod4 = true }, .focus_prev);
+        XkbBinding.create(seat, xkb.Keysym.e, .{ .mod4 = true }, .focus_next);
+
         XkbBinding.create(seat, xkb.Keysym.h, .{ .mod4 = true }, .hide_focused);
-        XkbBinding.create(seat, xkb.Keysym.k, .{ .mod4 = true }, .close_focused);
         XkbBinding.create(seat, xkb.Keysym.s, .{ .mod4 = true }, .show_all);
         PointerBinding.create(seat, c.BTN_LEFT, .{ .mod4 = true }, .move_start, .op_end);
         PointerBinding.create(seat, c.BTN_RIGHT, .{ .mod4 = true }, .resize_start, .op_end);
@@ -105,7 +109,8 @@ pub fn updateWindowing(seat: *Seat) void {
     seat.pending = .{};
 }
 
-pub const Action = enum {
+pub const Action = union(enum) {
+    focus_prev,
     focus_next,
     close_focused,
     hide_focused,
@@ -113,11 +118,14 @@ pub const Action = enum {
     move_start,
     resize_start,
     op_end,
+    /// slice must have a static lifetime
+    spawn: [:0]const u8,
 };
 
 pub fn execute(seat: *Seat, action: Action) void {
     switch (action) {
-        .focus_next => {}, // XXX
+        .focus_prev => seat.focus(seat.actionTarget(.prev)),
+        .focus_next => seat.focus(seat.actionTarget(.next)),
         .close_focused => if (seat.focused) |window| window.window_v1.close(),
         .hide_focused => if (seat.focused) |window| window.window_v1.hide(),
         .show_all => {
@@ -144,6 +152,17 @@ pub fn execute(seat: *Seat, action: Action) void {
             }
         },
         .op_end => seat.seat_v1.opEnd(),
+        .spawn => |command| {
+            if (std.posix.fork()) |pid| {
+                if (pid == 0) {
+                    std.posix.execveZ("/bin/sh", &.{ "/bin/sh", "-c", command, null }, std.c.environ) catch {
+                        std.c._exit(1);
+                    };
+                }
+            } else |err| {
+                std.log.err("fork() failed: {s}", .{@errorName(err)});
+            }
+        },
     }
 }
 
@@ -180,3 +199,31 @@ pub fn focus(seat: *Seat, _target: ?*Window) void {
         seat.focused = null;
     }
 }
+
+fn actionTarget(seat: *Seat, comptime dir: enum { next, prev }) ?*Window {
+    const output = seat.focused_output orelse return null;
+    if (seat.focused) |focused| {
+        var it = output.stack_wm.iterator(switch (dir) {
+            .next => .forward,
+            .prev => .reverse,
+        });
+        while (it.next()) |window| {
+            if (window == focused) break;
+        } else {
+            unreachable;
+        }
+        // Return the next window in the stack matching the tags if any.
+        while (it.next()) |window| {
+            if (output.tags & window.tags != 0) return window;
+        }
+        // Wrap and return the first window in the stack matching the tags if
+        // any is found before completing the loop back to the focused window.
+        while (it.next()) |window| {
+            if (window == focused) return null;
+            if (output.tags & window.tags != 0) return window;
+        }
+        unreachable;
+    } else {
+        return output.stack_wm.first();
+    }
+}
diff --git a/rivercompat/main.zig b/rivercompat/main.zig
index 5ecc4ea..3588ebd 100644
--- a/rivercompat/main.zig
+++ b/rivercompat/main.zig
@@ -87,6 +87,15 @@ pub fn main() !void {
         posix.exit(0);
     }
 
+    // Avoid the need to waitpid after fork/exec.
+    // This approach doesn't need any cleanup post-fork/pre-exec as the
+    // handler remains default and the SA_NOCLDWAIT flag is reset.
+    posix.sigaction(posix.SIG.CHLD, &.{
+        .handler = .{ .handler = posix.SIG.DFL },
+        .mask = posix.empty_sigset,
+        .flags = posix.SA.NOCLDWAIT,
+    }, null) catch unreachable;
+
     const display = wl.Display.connect(null) catch {
         std.debug.print("Unable to connect to Wayland server.\n", .{});
         posix.exit(1);