diff --git a/src/Config.zig b/src/Config.zig index 51e5fd6..b89bfe3 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -151,8 +151,8 @@ fn addFallbackKeybinds(config: *Config) void { const fallbacks = [_]Entry{ .{ .{ .modifiers = .{ .ctrl = true, .mod1 = true }, .keysym = .Delete }, @unionInit(XkbBindings.Command, "exit_river", {}) }, .{ .{ .modifiers = .{ .mod4 = true, .shift = true }, .keysym = .R }, @unionInit(XkbBindings.Command, "reload_config", {}) }, - .{ .{ .modifiers = .{ .mod4 = true }, .keysym = .T }, .{ .spawn = utils.gpa.dupe([]const u8, &.{ - utils.gpa.dupe(u8, "foot") catch @panic("Out of memory"), + .{ .{ .modifiers = .{ .mod4 = true }, .keysym = .T }, .{ .spawn = utils.gpa.dupe([:0]const u8, &.{ + utils.gpa.dupeZ(u8, "foot") catch @panic("Out of memory"), }) catch @panic("Out of memory") } }, }; for (&fallbacks) |*entry| { diff --git a/src/XkbBindings.zig b/src/XkbBindings.zig index f5ea952..d18272d 100644 --- a/src/XkbBindings.zig +++ b/src/XkbBindings.zig @@ -5,7 +5,7 @@ const XkbBindings = @This(); pub const Command = union(enum) { - spawn: []const []const u8, + spawn: []const [:0]const u8, focus_next_window, focus_prev_window, focus_next_output, @@ -139,13 +139,7 @@ const XkbBinding = struct { // TODO: Should I log.warn when commands return early? switch (xkb_binding.command) { - .spawn => |cmd| { - var child = std.process.Child.init(cmd, utils.gpa); - child.env_map = &context.env; - _ = child.spawn() catch |err| { - log.err("Failed to spawn \"{s}\": {}", .{ cmd[0], err }); - }; - }, + .spawn => |cmd| spawnProcess(cmd), .focus_next_window => focusWindow(context, .next), .focus_prev_window => focusWindow(context, .prev), .focus_next_output => focusOutput(context, .next), @@ -403,6 +397,44 @@ const XkbBinding = struct { } } + // Borrowed and modified from river-classic + // https://codeberg.org/river/river-classic/src/commit/d72408df18310d5945147d485fe4bb66eef043d3/river/process.zig + fn spawnProcess(cmd: []const [:0]const u8) void { + const pid = posix.fork() catch |err| { + return log.err("Failed to fork \"{s}\": {}", .{ cmd[0], err }); + }; + + if (pid == 0) { + cleanupChild(); + + const c_argv = utils.gpa.allocSentinel(?[*:0]const u8, cmd.len, null) catch posix.exit(1); + for (cmd, 0..) |arg, i| c_argv[i] = arg.ptr; + + const pid2 = posix.fork() catch posix.exit(1); + if (pid2 == 0) { + posix.execvpeZ(c_argv[0].?, c_argv, std.c.environ) catch posix.exit(1); + } + + posix.exit(0); + } + + _ = posix.waitpid(pid, 0); + } + + // Borrowed and modified from river-classic + // https://codeberg.org/river/river-classic/src/commit/d72408df18310d5945147d485fe4bb66eef043d3/river/process.zig + fn cleanupChild() void { + _ = posix.setsid() catch unreachable; + if (posix.system.sigprocmask(posix.SIG.SETMASK, &posix.sigemptyset(), null) < 0) unreachable; + + const sig_dfl = posix.Sigaction{ + .handler = .{ .handler = posix.SIG.DFL }, + .mask = posix.sigemptyset(), + .flags = 0, + }; + posix.sigaction(posix.SIG.PIPE, &sig_dfl, null); + } + fn moveFloatingWindow(context: *Context, dx: i32, dy: i32) void { const seat = context.wm.seats.first() orelse return; const window = seat.focused_window orelse return; @@ -532,6 +564,8 @@ pub fn manage(xkb_bindings: *XkbBindings) void { const std = @import("std"); const assert = std.debug.assert; +const posix = std.posix; +const process = std.process; const wayland = @import("wayland"); const wl = wayland.client.wl; diff --git a/src/config/helpers.zig b/src/config/helpers.zig index ffc5643..7fcdedc 100644 --- a/src/config/helpers.zig +++ b/src/config/helpers.zig @@ -38,12 +38,13 @@ pub fn parseButton(s: []const u8) ?u32 { return fmt.parseInt(u32, s, 0) catch null; } -pub fn expandTilde(path: []const u8) ![]const u8 { +/// Expand a tilde in a string to the user's $HOME directory +pub fn expandTilde(path: []const u8) ![:0]const u8 { if (path.len > 0 and path[0] == '~') { - const home = std.posix.getenv("HOME") orelse return error.HomeNotSet; - return std.fmt.allocPrint(utils.gpa, "{s}{s}", .{ home, path[1..] }); + const home = posix.getenv("HOME") orelse return error.HomeNotSet; + return mem.concatWithSentinel(utils.gpa, u8, &.{ home, path[1..] }, 0); } - return utils.gpa.dupe(u8, path); + return utils.gpa.dupeZ(u8, path); } /// Check whether this machine's hostname matches the hostname property @@ -85,6 +86,7 @@ pub inline fn logWarnInvalidNode(node_name: []const u8) void { const std = @import("std"); const fmt = std.fmt; const mem = std.mem; +const posix = std.posix; const kdl = @import("kdl"); diff --git a/src/utils.zig b/src/utils.zig index 80a60d4..0718ed2 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -162,8 +162,8 @@ pub fn parseModifiers(s: []const u8) !?river.SeatV1.Modifiers { /// Split a string into tokens, respecting single and double quotes. /// Quoted sections have their quotes stripped. Returns an error if /// a quote is opened but never closed. -pub fn tokenizeShell(input: []const u8) ![][]const u8 { - var list: std.ArrayList([]const u8) = .empty; +pub fn tokenizeShell(input: []const u8) ![][:0]const u8 { + var list: std.ArrayList([:0]const u8) = .empty; errdefer { for (list.items) |item| gpa.free(item); list.deinit(gpa); @@ -190,7 +190,7 @@ pub fn tokenizeShell(input: []const u8) ![][]const u8 { i += 1; } } - try list.append(gpa, try token.toOwnedSlice(gpa)); + try list.append(gpa, try token.toOwnedSliceSentinel(gpa, 0)); } return list.toOwnedSlice(gpa); }