diff --git a/build.zig b/build.zig index 2aeac26..45a548b 100644 --- a/build.zig +++ b/build.zig @@ -23,7 +23,7 @@ comptime { } } -const ly_version = std.SemanticVersion{ .major = 1, .minor = 4, .patch = 0 }; +const ly_version = std.SemanticVersion{ .major = 1, .minor = 5, .patch = 0 }; var dest_directory: []const u8 = undefined; var config_directory: []const u8 = undefined; @@ -72,7 +72,11 @@ pub fn build(b: *std.Build) !void { .use_llvm = true, }); - const ly_ui = b.dependency("ly_ui", .{ .target = target, .optimize = optimize }); + const ly_ui = b.dependency("ly_ui", .{ + .target = target, + .optimize = optimize, + .enable_x11_support = enable_x11_support, + }); exe.root_module.addImport("ly-ui", ly_ui.module("ly-ui")); exe.root_module.addOptions("build_options", build_options); diff --git a/build.zig.zon b/build.zig.zon index 63e8c32..3c091cb 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .ly, - .version = "1.4.0", + .version = "1.5.0", .fingerprint = 0xa148ffcc5dc2cb59, .minimum_zig_version = "0.16.0", .dependencies = .{ diff --git a/ly-core/build.zig b/ly-core/build.zig index 614404e..f6574de 100644 --- a/ly-core/build.zig +++ b/ly-core/build.zig @@ -4,6 +4,7 @@ const Translator = @import("translate_c").Translator; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support") orelse true; const mod = b.addModule("ly-core", .{ .root_source_file = b.path("src/root.zig"), .target = target, @@ -20,7 +21,9 @@ pub fn build(b: *std.Build) void { addCImport(b, mod, translate_c, target, optimize, "pam", "#include "); addCImport(b, mod, translate_c, target, optimize, "utmp", "#include "); - addCImport(b, mod, translate_c, target, optimize, "xcb", "#include "); + if (enable_x11_support) { + addCImport(b, mod, translate_c, target, optimize, "xcb", "#include "); + } if (target.result.os.tag == .freebsd) { addCImport(b, mod, translate_c, target, optimize, "pwd", \\#include @@ -36,6 +39,14 @@ pub fn build(b: *std.Build) void { addCImport(b, mod, translate_c, target, optimize, "system_time", "#include "); addCImport(b, mod, translate_c, target, optimize, "time", "#include "); + if (target.result.os.tag == .linux) { + addCImport(b, mod, translate_c, target, optimize, "kd", "#include "); + addCImport(b, mod, translate_c, target, optimize, "vt", "#include "); + } else if (target.result.os.tag == .freebsd) { + addCImport(b, mod, translate_c, target, optimize, "kbio", "#include "); + addCImport(b, mod, translate_c, target, optimize, "consio", "#include "); + } + const mod_tests = b.addTest(.{ .root_module = mod, }); diff --git a/ly-core/build.zig.zon b/ly-core/build.zig.zon index a389405..b40c8f8 100644 --- a/ly-core/build.zig.zon +++ b/ly-core/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .ly_core, - .version = "1.0.0", + .version = "1.1.0", .fingerprint = 0xddda7afda795472, .minimum_zig_version = "0.16.0", .dependencies = .{ diff --git a/ly-core/src/LogFile.zig b/ly-core/src/LogFile.zig index f60b153..192f338 100644 --- a/ly-core/src/LogFile.zig +++ b/ly-core/src/LogFile.zig @@ -3,50 +3,85 @@ const interop = @import("interop.zig"); const LogFile = @This(); -path: []const u8, +maybe_path: ?[]const u8, could_open_log_file: bool = undefined, -file: std.Io.File = undefined, +maybe_file: ?std.Io.File = null, buffer: []u8, -file_writer: std.Io.File.Writer = undefined, +maybe_file_writer: ?std.Io.File.Writer = null, + +pub fn init(io: std.Io, path: ?[]const u8, buffer: []u8) !LogFile { + var log_file = LogFile{ + .maybe_path = path, + .buffer = buffer, + }; + + if (path) |p| { + log_file.could_open_log_file = try openLogFile(io, p, &log_file); + } else { + std.posix.system.openlog("ly", 0, 0); + log_file.could_open_log_file = true; + } -pub fn init(io: std.Io, path: []const u8, buffer: []u8) !LogFile { - var log_file = LogFile{ .path = path, .buffer = buffer }; - log_file.could_open_log_file = try openLogFile(io, path, &log_file); return log_file; } pub fn reinit(self: *LogFile, io: std.Io) !void { - self.could_open_log_file = try openLogFile(io, self.path, self); + if (self.maybe_path) |path| { + self.could_open_log_file = try openLogFile(io, path, self); + } else { + std.posix.system.openlog("ly", 0, 0); + self.could_open_log_file = true; + } } pub fn deinit(self: *LogFile, io: std.Io) void { - self.file.close(io); + if (self.maybe_file) |file| { + file.close(io); + } else { + std.posix.system.closelog(); + } } pub fn info(self: *LogFile, io: std.Io, category: []const u8, comptime message: []const u8, args: anytype) !void { - var buffer: [128:0]u8 = undefined; - const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S"); + if (self.maybe_file_writer) |*writer| { + var buffer: [128:0]u8 = undefined; + const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S"); - try self.file_writer.interface.print("{s} [info/{s}] ", .{ time, category }); - try self.file_writer.interface.print(message, args); - try self.file_writer.interface.writeByte('\n'); - try self.file_writer.interface.flush(); + try writer.interface.print("{s} [info/{s}] ", .{ time, category }); + try writer.interface.print(message, args); + try writer.interface.writeByte('\n'); + try writer.interface.flush(); + } else { + var buffer: [1024]u8 = undefined; + const slice = try std.fmt.bufPrint(&buffer, message, args); + const msg = try std.fmt.bufPrintZ(buffer[slice.len..], "[info/{s}] {s}", .{ category, slice }); + + std.posix.system.syslog(std.posix.LOG.INFO, msg.ptr); + } } pub fn err(self: *LogFile, io: std.Io, category: []const u8, comptime message: []const u8, args: anytype) !void { - var buffer: [128:0]u8 = undefined; - const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S"); + if (self.maybe_file_writer) |*writer| { + var buffer: [128:0]u8 = undefined; + const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S"); - try self.file_writer.interface.print("{s} [err/{s}] ", .{ time, category }); - try self.file_writer.interface.print(message, args); - try self.file_writer.interface.writeByte('\n'); - try self.file_writer.interface.flush(); + try writer.interface.print("{s} [err/{s}] ", .{ time, category }); + try writer.interface.print(message, args); + try writer.interface.writeByte('\n'); + try writer.interface.flush(); + } else { + var buffer: [1024]u8 = undefined; + const slice = try std.fmt.bufPrint(&buffer, message, args); + const msg = try std.fmt.bufPrintZ(buffer[slice.len..], "[info/{s}] {s}", .{ category, slice }); + + std.posix.system.syslog(std.posix.LOG.ERR, msg.ptr); + } } fn openLogFile(io: std.Io, path: []const u8, log_file: *LogFile) !bool { var could_open_log_file = true; open_log_file: { - log_file.file = std.Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }) catch std.Io.Dir.cwd().createFile(io, path, .{ .permissions = .fromMode(0o666) }) catch { + log_file.maybe_file = std.Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }) catch std.Io.Dir.cwd().createFile(io, path, .{ .permissions = .fromMode(0o666) }) catch { // If we could neither open an existing log file nor create a new // one, abort. could_open_log_file = false; @@ -55,17 +90,17 @@ fn openLogFile(io: std.Io, path: []const u8, log_file: *LogFile) !bool { } if (!could_open_log_file) { - log_file.file = try std.Io.Dir.openFileAbsolute(io, "/dev/null", .{ .mode = .write_only }); + log_file.maybe_file = try std.Io.Dir.openFileAbsolute(io, "/dev/null", .{ .mode = .write_only }); } - var log_file_writer = log_file.file.writer(io, log_file.buffer); + var log_file_writer = log_file.maybe_file.?.writer(io, log_file.buffer); // Seek to the end of the log file if (could_open_log_file) { - const stat = try log_file.file.stat(io); + const stat = try log_file.maybe_file.?.stat(io); try log_file_writer.seekTo(stat.size); } - log_file.file_writer = log_file_writer; + log_file.maybe_file_writer = log_file_writer; return could_open_log_file; } diff --git a/ly-core/src/interop.zig b/ly-core/src/interop.zig index 5b2b954..f23583c 100644 --- a/ly-core/src/interop.zig +++ b/ly-core/src/interop.zig @@ -31,13 +31,8 @@ pub const UsernameEntry = struct { fn PlatformStruct() type { return switch (builtin.os.tag) { .linux => struct { - pub const kd = @cImport({ - @cInclude("sys/kd.h"); - }); - - pub const vt = @cImport({ - @cInclude("sys/vt.h"); - }); + pub const kd = @import("kd"); + pub const vt = @import("vt"); pub const LedState = c_char; pub const get_led_state = kd.KDGKBLED; @@ -197,13 +192,8 @@ fn PlatformStruct() type { } }, .freebsd => struct { - pub const kbio = @cImport({ - @cInclude("sys/kbio.h"); - }); - - pub const consio = @cImport({ - @cInclude("sys/consio.h"); - }); + pub const kbio = @import("kbio"); + pub const consio = @import("consio"); pub const LedState = c_int; pub const get_led_state = kbio.KDGETLED; diff --git a/ly-ui/build.zig b/ly-ui/build.zig index a7a3051..7397873 100644 --- a/ly-ui/build.zig +++ b/ly-ui/build.zig @@ -4,13 +4,18 @@ const Translator = @import("translate_c").Translator; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support") orelse true; const mod = b.addModule("ly-ui", .{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); - const ly_core = b.dependency("ly_core", .{ .target = target, .optimize = optimize }); + const ly_core = b.dependency("ly_core", .{ + .target = target, + .optimize = optimize, + .enable_x11_support = enable_x11_support, + }); mod.addImport("ly-core", ly_core.module("ly-core")); const termbox_dep = b.dependency("termbox2", .{ diff --git a/ly-ui/build.zig.zon b/ly-ui/build.zig.zon index 598cba0..2a7e175 100644 --- a/ly-ui/build.zig.zon +++ b/ly-ui/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .ly_ui, - .version = "1.0.0", + .version = "1.1.0", .fingerprint = 0x8d11bf85a74ec803, .minimum_zig_version = "0.16.0", .dependencies = .{ diff --git a/readme.md b/readme.md index aa23db8..78ebffc 100644 --- a/readme.md +++ b/readme.md @@ -226,6 +226,12 @@ You can, of course, still select the init system of your choice when using this You can find all the configuration in `/etc/ly/config.ini`. The file is fully commented, and includes the default values. +You may also check the validity of your configuration file (i.e. if there are any errors in it) with the following command: + +``` +$ ly --validate-config /etc/ly/config.ini +``` + ## Controls Use the Up/Down arrow keys to change the current field, and the Left/Right arrow keys to scroll through the different fields (whether it be the info line, the desktop environment, or the username). The info line is where messages and errors are displayed. diff --git a/res/config.ini b/res/config.ini index 86f44b4..a290773 100644 --- a/res/config.ini +++ b/res/config.ini @@ -97,6 +97,14 @@ blank_box = true # Border foreground color id border_fg = 0x00FFFFFF +# Relative horizontal position from the end of the screen +# default: 0.5 +box_position_h = 0.5 + +# Relative vertical position from the bottom of the screen +# default: 0.4 +box_position_v = 0.4 + # Title to show at the top of the main box # If set to null, none will be shown box_title = null @@ -291,6 +299,7 @@ login_defs_path = /etc/login.defs logout_cmd = null # General log file path +# If null, syslog will be used instead ly_log = /var/log/ly.log # Main box horizontal margin diff --git a/src/auth.zig b/src/auth.zig index d191d55..6a97fff 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -201,7 +201,7 @@ fn startSession( // Signal to the session process to give up control on the TTY try log_file.info(io, "auth/sys", "releasing tty", .{}); - std.posix.kill(options.session_pid, std.posix.SIG.CHLD) catch return error.TtyControlTransferFailed; + std.posix.kill(options.session_pid, std.posix.SIG.INT) catch return error.TtyControlTransferFailed; // Execute what the user requested switch (current_environment.display_server) { @@ -417,19 +417,27 @@ fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, display_n const magic_cookie = mcookie(io); + log_file.deinit(io); + const pid = std.posix.system.fork(); if (pid == 0) { + try log_file.reinit(io); + var cmd_buffer: [1024]u8 = undefined; const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ options.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1); try log_file.info(io, "auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str }); const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; _ = std.posix.system.execve(shell, &args, std.c.environ); + + log_file.deinit(io); std.process.exit(1); } var status: c_int = undefined; const result = std.posix.system.waitpid(pid, &status, 0); + + try log_file.reinit(io); if (interop.isError(result) or status != 0) { try log_file.err( io, diff --git a/src/config/Config.zig b/src/config/Config.zig index 2233cb4..a592faa 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -23,6 +23,8 @@ bigclock_12hr: bool = false, bigclock_seconds: bool = false, blank_box: bool = true, border_fg: u32 = 0x00FFFFFF, +box_position_h: f32 = 0.5, +box_position_v: f32 = 0.4, box_title: ?[]const u8 = null, brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s 10%-", brightness_down_key: ?[]const u8 = "F5", @@ -72,7 +74,7 @@ lang: []const u8 = "en", login_cmd: ?[]const u8 = null, login_defs_path: []const u8 = "/etc/login.defs", logout_cmd: ?[]const u8 = null, -ly_log: []const u8 = "/var/log/ly.log", +ly_log: ?[]const u8 = "/var/log/ly.log", margin_box_h: u8 = 2, margin_box_v: u8 = 1, numlock: bool = false, diff --git a/src/main.zig b/src/main.zig index 53400df..35239d7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -146,12 +146,10 @@ pub fn main(init: std.process.Init) !void { // If we can't shutdown or restart due to an error, we print it to standard error. If that fails, just bail out if (shutdown) { const shutdown_error = std.process.replace(state.io, .{ .argv = &[_][]const u8{ "/bin/sh", "-c", shutdown_cmd } }); - stderr.print("error: couldn't shutdown: {s}\n", .{@errorName(shutdown_error)}) catch std.process.exit(1); - stderr.flush() catch std.process.exit(1); + std.log.err("couldn't shutdown: {s}", .{@errorName(shutdown_error)}); } else if (restart) { const restart_error = std.process.replace(state.io, .{ .argv = &[_][]const u8{ "/bin/sh", "-c", restart_cmd } }); - stderr.print("error: couldn't restart: {s}\n", .{@errorName(restart_error)}) catch std.process.exit(1); - stderr.flush() catch std.process.exit(1); + std.log.err("couldn't restart: {s}", .{@errorName(restart_error)}); } else { // The user has quit Ly using Ctrl+C if (commands_allocated) { @@ -172,7 +170,8 @@ pub fn main(init: std.process.Init) !void { \\-h, --help Shows all commands. \\-v, --version Shows the version of Ly. \\-c, --config Overrides the default configuration path. Example: --config /usr/share/ly - \\--use-kmscon-vt Use KMSCON instead of kernel VT + \\--use-kmscon-vt Uses KMSCON instead of the kernel VT. + \\--validate-config Validates the given configuration file. ); var diag = clap.Diagnostic{}; @@ -200,17 +199,39 @@ pub fn main(init: std.process.Init) !void { if (res.args.help != 0) { try clap.help(stderr, clap.Help, ¶ms, .{}); - _ = try stderr.write("Note: if you want to configure Ly, please check the config file, which is located at " ++ build_options.config_directory ++ "/ly/config.ini.\n"); - try stderr.flush(); + std.log.info("note: if you want to configure Ly, please check the config file, which is located at " ++ build_options.config_directory ++ "/ly/config.ini.", .{}); std.process.exit(0); } if (res.args.version != 0) { - _ = try stderr.write("Ly version " ++ build_options.version ++ "\n"); - try stderr.flush(); + std.log.info("ly version " ++ build_options.version, .{}); std.process.exit(0); } if (res.args.config) |path| config_parent_path = path; if (res.args.@"use-kmscon-vt" != 0) state.use_kmscon_vt = true; + if (res.args.@"validate-config") |path| { + var parser = try IniParser(Config).init( + state.allocator, + state.io, + path, + migrator.configFieldHandler, + ); + defer parser.deinit(); + + for (parser.errors.items) |err| { + std.log.err( + "failed to convert value '{s}' of option '{s}' to type '{s}': {s}", + .{ err.value, err.key, err.type_name, err.error_name }, + ); + } + + if (parser.maybe_load_error) |err| { + std.log.err("failed to load config file: {s}", .{@errorName(err)}); + std.process.exit(1); + } + + std.log.info("no errors detected!", .{}); + std.process.exit(0); + } } // Load configuration file @@ -1543,7 +1564,7 @@ fn authenticate(ptr: *anyopaque) !bool { .mask = std.posix.sigemptyset(), .flags = 0, }; - std.posix.sigaction(std.posix.SIG.CHLD, &tty_control_transfer_act, null); + std.posix.sigaction(std.posix.SIG.INT, &tty_control_transfer_act, null); try state.log_file.reinit(state.io); @@ -2025,22 +2046,36 @@ fn positionWidgets(ptr: *anyopaque) !void { .childrenPosition() .removeX(TerminalBuffer.strWidth(state.lang.numlock) + TerminalBuffer.strWidth(state.lang.capslock) + 1)); - state.box.positionXY(TerminalBuffer.START_POSITION - .addX((state.buffer.width - @min(state.buffer.width - 2, state.box.width)) / 2) - .addY((state.buffer.height - @min(state.buffer.height - 2, state.box.height)) / 2)); + var bb_height = state.box.height; + var bb_width = state.box.width; + const clock_text_len = TerminalBuffer.strWidth(state.bigclock_label.text) * (BigLabel.CHAR_WIDTH + 1); if (state.config.bigclock != .none) { - const half_width = state.buffer.width / 2; - const half_label_width = (TerminalBuffer.strWidth(state.bigclock_label.text) * (BigLabel.CHAR_WIDTH + 1)) / 2; - const half_height = (if (state.buffer.height > state.box.height) state.buffer.height - state.box.height else state.buffer.height) / 2; - - state.bigclock_label.positionXY(TerminalBuffer.START_POSITION - .addX(half_width) - .removeXIf(half_label_width, half_width > half_label_width) - .addY(half_height) - .removeYIf(BigLabel.CHAR_HEIGHT + 2, half_height > BigLabel.CHAR_HEIGHT + 2)); + bb_height += BigLabel.CHAR_HEIGHT + 2; + bb_width = @max(bb_width, clock_text_len); } + const max_v_position: f32 = @floatFromInt(state.buffer.height - bb_height - 1); + const max_h_position: f32 = @floatFromInt(state.buffer.width - bb_width - 1); + + bb_height = @min(bb_height, state.buffer.height - 2); + bb_width = @min(bb_width, state.buffer.width - 2); + + const v_space: f32 = @floatFromInt(state.buffer.height - bb_height); + const v_position: usize = @intFromFloat(std.math.clamp(v_space * state.config.box_position_v, 1.0, max_v_position)); + const h_space: f32 = @floatFromInt(state.buffer.width - bb_width); + const h_position: usize = @intFromFloat(std.math.clamp(h_space * state.config.box_position_h, 1.0, max_h_position)); + + if (state.config.bigclock != .none) { + state.bigclock_label.positionXY(TerminalBuffer.START_POSITION + .addX(h_position + (bb_width - clock_text_len) / 2) + .addY(v_position)); + } + + state.box.positionXY(TerminalBuffer.START_POSITION + .addX(h_position + (bb_width - state.box.width) / 2) + .addY(v_position + (bb_height - state.box.height))); + state.info_line.label.positionY(state.box .childrenPosition());