diff --git a/README.md b/README.md index 37f9b51..1a58374 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ and missing features. If you run into a bug don't hesitate to ## Design goals -- Simplicity and minimalism, river should not overstep the bounds of a -window manager. +- Simple and predictable behavior, river should be easy to use and have a +low cognitive load. - Window management based on a stack of views and tags. -- Dynamic layouts generated by external, user-written executables. (A default -`rivertile` layout generator is provided.) +- Dynamic layouts generated by external, user-written executables. A default +`rivertile` layout generator is provided. - Scriptable configuration and control through a custom wayland protocol and separate `riverctl` binary implementing it. @@ -53,20 +53,18 @@ To enable experimental Xwayland support pass the `-Dxwayland` option as well. ## Usage River can either be run nested in an X11/wayland session or directly -from a tty using KMS/DRM. +from a tty using KMS/DRM. Simply run the `river` command. -On startup river will look for and run an **executable** file at one of the -following locations, checked in the order listed: +On startup river will run an executable file at `$XDG_CONFIG_HOME/river/init` +if such an executable exists. If $XDG_CONFIG_HOME is not set, ~/.config/ will +be used instead. -- `$XDG_CONFIG_HOME/river/init` -- `$HOME/.config/river/init` -- `/etc/river/init` - -Usually this executable init file will be a shell script invoking riverctl -to create mappings and preform other configuration. +Usually this executable is a shell script invoking *riverctl*(1) to create +mappings, start programs such as a layout generator or status bar, and +preform other configuration. An example init script with sane defaults is provided [here](example/init) -in the example directory and installed to `/etc/river/init`. +in the example directory. For complete documentation see the `river(1)`, `riverctl(1)`, and `rivertile(1)` man pages. diff --git a/build.zig b/build.zig index d41e62f..d2f43ee 100644 --- a/build.zig +++ b/build.zig @@ -48,14 +48,6 @@ pub fn build(b: *zbs.Builder) !void { const examples = b.option(bool, "examples", "Set to true to build examples") orelse false; - const rel_config_path = if (mem.eql(u8, try fs.path.resolve(b.allocator, &[_][]const u8{b.install_prefix}), "/usr")) - "../etc/river/init" - else - "etc/river/init"; - b.installFile("example/init", rel_config_path); - const abs_config_path = try fs.path.resolve(b.allocator, &[_][]const u8{ b.install_prefix, rel_config_path }); - assert(fs.path.isAbsolute(abs_config_path)); - const scanner = ScanProtocolsStep.create(b); scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml"); @@ -72,7 +64,6 @@ pub fn build(b: *zbs.Builder) !void { river.setTarget(target); river.setBuildMode(mode); river.addBuildOption(bool, "xwayland", xwayland); - river.addBuildOption([]const u8, "default_config_path", abs_config_path); addServerDeps(river, scanner); diff --git a/doc/river.1.scd b/doc/river.1.scd index 1b3e4d0..0ca73bf 100644 --- a/doc/river.1.scd +++ b/doc/river.1.scd @@ -32,19 +32,17 @@ utility may be used to communicate with river over these protocols. # CONFIGURATION -On startup river will look for and run an executable file at one of the -following locations, checked in the order listed: - -- $XDG_CONFIG_HOME/river/init -- $HOME/.config/river/init -- /etc/river/init +On startup river will run an executable file at `$XDG_CONFIG_HOME/river/init` +if such an executable exists. If $XDG_CONFIG_HOME is not set, ~/.config/ will +be used instead. The executable init file will be run as a process group leader after river's wayland server is initialized but before entering the main loop. On exit, river will send SIGTERM to this process group. -Usually this will be a shell script invoking *riverctl*(1) to create mappings, -start programs such as a status bar, and preform other configuration. +Usually this executable is a shell script invoking *riverctl*(1) to create +mappings, start programs such as a layout generator or status bar, and +preform other configuration. # ENVIRONMENT diff --git a/river/main.zig b/river/main.zig index 874b518..30b2f78 100644 --- a/river/main.zig +++ b/river/main.zig @@ -16,6 +16,7 @@ // along with this program. If not, see . const std = @import("std"); +const fs = std.fs; const os = std.os; const wlr = @import("wlroots"); @@ -86,9 +87,7 @@ pub fn main() anyerror!void { .warn, .err, .crit, .alert, .emerg => .err, }); - if (startup_command == null) { - if (try getStartupCommand()) |path| startup_command = path; - } + if (startup_command == null) startup_command = try defaultInitPath(); std.log.info("initializing server", .{}); try server.init(); @@ -99,7 +98,7 @@ pub fn main() anyerror!void { // Run the child in a new process group so that we can send SIGTERM to all // descendants on exit. const child_pgid = if (startup_command) |cmd| blk: { - std.log.info("running startup command '{s}'", .{cmd}); + std.log.info("running init executable '{s}'", .{cmd}); const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null }; const pid = try os.fork(); if (pid == 0) { @@ -111,8 +110,9 @@ pub fn main() anyerror!void { // Since the child has called setsid, the pid is the pgid break :blk pid; } else null; - defer if (child_pgid) |pgid| - os.kill(-pgid, os.SIGTERM) catch |e| std.log.err("failed to kill startup process: {s}", .{e}); + defer if (child_pgid) |pgid| os.kill(-pgid, os.SIGTERM) catch |err| { + std.log.err("failed to kill init process group: {s}", .{@errorName(err)}); + }; std.log.info("running server", .{}); @@ -127,29 +127,24 @@ fn printErrorExit(comptime format: []const u8, args: anytype) noreturn { os.exit(1); } -fn testConfigPath(comptime fmt: []const u8, args: anytype) std.fmt.AllocPrintError!?[:0]const u8 { - const path = try std.fmt.allocPrintZ(util.gpa, fmt, args); - os.access(path, os.X_OK) catch { +fn defaultInitPath() !?[:0]const u8 { + const path = blk: { + if (os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| { + break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ xdg_config_home, "river/init" }); + } else if (os.getenv("HOME")) |home| { + break :blk try fs.path.joinZ(util.gpa, &[_][]const u8{ home, ".config/river/init" }); + } else { + return null; + } + }; + + os.accessZ(path, os.X_OK) catch |err| { + std.log.err("failed to run init executable {s}: {s}", .{ path, @errorName(err) }); util.gpa.free(path); return null; }; - return path; -} -fn getStartupCommand() std.fmt.AllocPrintError!?[:0]const u8 { - if (os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| { - if (try testConfigPath("{s}/river/init", .{xdg_config_home})) |path| { - return path; - } - } else if (os.getenv("HOME")) |home| { - if (try testConfigPath("{s}/.config/river/init", .{home})) |path| { - return path; - } - } - if (try testConfigPath(build_options.default_config_path, .{})) |path| { - return path; - } - return null; + return path; } pub fn log(