// This file is part of river, a dynamic tiling wayland compositor. // // Copyright 2020 The River Developers // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . const build_options = @import("build_options"); const std = @import("std"); const fs = std.fs; const io = std.io; const os = std.os; const wlr = @import("wlroots"); const flags = @import("flags"); const c = @import("c.zig"); const util = @import("util.zig"); const Server = @import("Server.zig"); const usage: []const u8 = \\usage: river [options] \\ \\ -h Print this help message and exit. \\ -version Print the version number and exit. \\ -c Run `sh -c ` on startup. \\ -log-level Set the log level to error, warning, info, or debug. \\ ; pub var server: Server = undefined; pub fn main() anyerror!void { // This line is here because of https://github.com/ziglang/zig/issues/7807 const argv: [][*:0]const u8 = os.argv; const result = flags.parse(argv[1..], &[_]flags.Flag{ .{ .name = "-h", .kind = .boolean }, .{ .name = "-version", .kind = .boolean }, .{ .name = "-c", .kind = .arg }, .{ .name = "-log-level", .kind = .arg }, }) catch { try io.getStdErr().writeAll(usage); os.exit(1); }; if (result.boolFlag("-h")) { try io.getStdOut().writeAll(usage); os.exit(0); } if (result.args.len != 0) { std.log.err("unknown option '{s}'", .{result.args[0]}); try io.getStdErr().writeAll(usage); os.exit(1); } if (result.boolFlag("-version")) { try io.getStdOut().writeAll(build_options.version ++ "\n"); os.exit(0); } if (result.argFlag("-log-level")) |level_str| { const level = std.meta.stringToEnum(LogLevel, std.mem.span(level_str)) orelse { std.log.err("invalid log level '{s}'", .{level_str}); try io.getStdErr().writeAll(usage); os.exit(1); }; runtime_log_level = switch (level) { .@"error" => .err, .warning => .warn, .info => .info, .debug => .debug, }; } const startup_command = blk: { if (result.argFlag("-c")) |command| { break :blk try util.gpa.dupeZ(u8, std.mem.span(command)); } else { break :blk try defaultInitPath(); } }; river_init_wlroots_log(switch (runtime_log_level) { .debug => .debug, .notice, .info => .info, .warn, .err, .crit, .alert, .emerg => .err, }); std.log.info("initializing server", .{}); try server.init(); defer server.deinit(); try server.start(); // 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 init executable '{s}'", .{cmd}); const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null }; const pid = try os.fork(); if (pid == 0) { if (c.setsid() < 0) unreachable; if (os.system.sigprocmask(os.SIG_SETMASK, &os.empty_sigset, null) < 0) unreachable; os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1); } util.gpa.free(cmd); // 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 |err| { std.log.err("failed to kill init process group: {s}", .{@errorName(err)}); }; std.log.info("running server", .{}); server.wl_server.run(); std.log.info("shutting down", .{}); } 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; } /// Tell std.log to leave all log level filtering to us. pub const log_level: std.log.Level = .debug; /// Set the default log level based on the build mode. var runtime_log_level: std.log.Level = switch (std.builtin.mode) { .Debug => .debug, .ReleaseSafe, .ReleaseFast, .ReleaseSmall => .info, }; /// River only exposes these 4 log levels to the user for simplicity const LogLevel = enum { @"error", warning, info, debug, }; pub fn log( comptime message_level: std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype, ) void { if (@enumToInt(message_level) > @enumToInt(runtime_log_level)) return; const river_level: LogLevel = switch (message_level) { .emerg, .alert, .crit, .err => .@"error", .warn => .warning, .notice, .info => .info, .debug => .debug, }; const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; const stderr = std.io.getStdErr().writer(); stderr.print(@tagName(river_level) ++ scope_prefix ++ format ++ "\n", args) catch {}; } /// See wlroots_log_wrapper.c extern fn river_init_wlroots_log(importance: wlr.log.Importance) void; export fn river_wlroots_log_callback(importance: wlr.log.Importance, ptr: [*:0]const u8, len: usize) void { switch (importance) { .err => log(.err, .wlroots, "{s}", .{ptr[0..len]}), .info => log(.info, .wlroots, "{s}", .{ptr[0..len]}), .debug => log(.debug, .wlroots, "{s}", .{ptr[0..len]}), .silent, .last => unreachable, } }