From 5edf5251f672088058383d3f82805e5f32459127 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sat, 25 Apr 2026 17:37:34 +0200 Subject: [PATCH 01/10] Update to Zig 0.16.0 (#962) Signed-off-by: AnErrupTion ## What are the changes about? Ports the code base to Zig 0.16.0. ## What existing issue does this resolve? N/A ## Pre-requisites - [x] I have tested & confirmed the changes work locally - [x] I have run `zig fmt` throughout my changes Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/962 --- .gitignore | 1 + build.zig | 271 ++++++++++---------- build.zig.zon | 12 +- create_vendor_tarball.sh | 2 + ly-core/build.zig | 41 +++ ly-core/build.zig.zon | 10 +- ly-core/src/LogFile.zig | 34 +-- ly-core/src/SharedError.zig | 14 +- ly-core/src/interop.zig | 123 ++++----- ly-core/src/root.zig | 3 +- ly-ui/build.zig | 28 ++- ly-ui/build.zig.zon | 10 +- ly-ui/src/TerminalBuffer.zig | 43 ++-- ly-ui/src/components/Text.zig | 11 +- ly-ui/src/components/generic.zig | 9 +- readme.md | 2 +- src/animations/Cascade.zig | 7 +- src/animations/ColorMix.zig | 2 +- src/animations/DurFile.zig | 31 +-- src/auth.zig | 237 ++++++++--------- src/components/InfoLine.zig | 2 + src/components/Session.zig | 2 + src/components/UserList.zig | 2 + src/config/custom.zig | 4 +- src/config/migrator.zig | 18 +- src/main.zig | 419 ++++++++++++++++++------------- 26 files changed, 751 insertions(+), 587 deletions(-) create mode 100755 create_vendor_tarball.sh diff --git a/.gitignore b/.gitignore index de08f4f..fa23ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ zig-cache/ zig-out/ valgrind.log .zig-cache +zig-pkg diff --git a/build.zig b/build.zig index c53b905..ff5ce4a 100644 --- a/build.zig +++ b/build.zig @@ -12,7 +12,7 @@ const InitSystem = enum { freebsd, }; -const min_zig_string = "0.15.0"; +const min_zig_string = "0.16.0"; const current_zig = builtin.zig_version; // Implementing zig version detection through compile time @@ -67,8 +67,8 @@ pub fn build(b: *std.Build) !void { .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), - // Here until the native backend matures in terms of performance .use_llvm = true, }); @@ -80,9 +80,8 @@ pub fn build(b: *std.Build) !void { const clap = b.dependency("clap", .{ .target = target, .optimize = optimize }); exe.root_module.addImport("clap", clap.module("clap")); - exe.linkSystemLibrary("pam"); - if (enable_x11_support) exe.linkSystemLibrary("xcb"); - exe.linkLibC(); + exe.root_module.linkSystemLibrary("pam", .{}); + if (enable_x11_support) exe.root_module.linkSystemLibrary("xcb", .{}); b.installArtifact(exe); @@ -113,6 +112,8 @@ pub fn build(b: *std.Build) !void { pub fn Installer(install_config: bool) type { return struct { pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); const allocator = step.owner.allocator; var patch_map = PatchMap.init(allocator); @@ -127,75 +128,75 @@ pub fn Installer(install_config: bool) type { // instead to shutdown the system. try patch_map.put("$PLATFORM_SHUTDOWN_ARG", if (init_system == .freebsd) "-p" else "-a"); - try install_ly(allocator, patch_map, install_config); - try install_service(allocator, patch_map); + try install_ly(allocator, io, patch_map, install_config); + try install_service(allocator, io, patch_map); } }; } -fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: bool) !void { - const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly" }); +fn install_ly(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap, install_config: bool) !void { + const ly_config_directory = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly" }); - std.fs.cwd().makePath(ly_config_directory) catch { + std.Io.Dir.cwd().createDirPath(io, ly_config_directory) catch { std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory}); }; - const ly_custom_sessions_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" }); + const ly_custom_sessions_directory = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" }); - std.fs.cwd().makePath(ly_custom_sessions_directory) catch { + std.Io.Dir.cwd().createDirPath(io, ly_custom_sessions_directory) catch { std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory}); }; - const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" }); - std.fs.cwd().makePath(ly_lang_path) catch { + const ly_lang_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" }); + std.Io.Dir.cwd().createDirPath(io, ly_lang_path) catch { std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path}); }; { - const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" }); - std.fs.cwd().makePath(exe_path) catch { + const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" }); + std.Io.Dir.cwd().createDirPath(io, exe_path) catch { if (!std.mem.eql(u8, dest_directory, "")) { std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path}); } }; - var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable; - defer executable_dir.close(); + var executable_dir = std.Io.Dir.cwd().openDir(io, exe_path, .{}) catch unreachable; + defer executable_dir.close(io); - try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{}); + try installFile(io, "zig-out/bin/ly", executable_dir, exe_path, executable_name, .{}); } { - var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable; - defer config_dir.close(); + var config_dir = std.Io.Dir.cwd().openDir(io, ly_config_directory, .{}) catch unreachable; + defer config_dir.close(io); if (install_config) { - const patched_config = try patchFile(allocator, "res/config.ini", patch_map); - try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{}); + const patched_config = try patchFile(allocator, io, "res/config.ini", patch_map); + try installText(io, patched_config, config_dir, ly_config_directory, "config.ini", .{}); - try installFile("res/startup.sh", config_dir, ly_config_directory, "startup.sh", .{ .override_mode = 0o755 }); + try installFile(io, "res/startup.sh", config_dir, ly_config_directory, "startup.sh", .{ .permissions = .fromMode(0o755) }); } - const patched_example_config = try patchFile(allocator, "res/config.ini", patch_map); - try installText(patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{}); + const patched_example_config = try patchFile(allocator, io, "res/config.ini", patch_map); + try installText(io, patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{}); - const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map); - try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 }); + const patched_setup = try patchFile(allocator, io, "res/setup.sh", patch_map); + try installText(io, patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .permissions = .fromMode(0o755) }); - try installFile("res/example.dur", config_dir, ly_config_directory, "example.dur", .{ .override_mode = 0o755 }); + try installFile(io, "res/example.dur", config_dir, ly_config_directory, "example.dur", .{ .permissions = .fromMode(0o755) }); } { - var custom_sessions_dir = std.fs.cwd().openDir(ly_custom_sessions_directory, .{}) catch unreachable; - defer custom_sessions_dir.close(); + var custom_sessions_dir = std.Io.Dir.cwd().openDir(io, ly_custom_sessions_directory, .{}) catch unreachable; + defer custom_sessions_dir.close(io); - const patched_readme = try patchFile(allocator, "res/custom-sessions/README", patch_map); - try installText(patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{}); + const patched_readme = try patchFile(allocator, io, "res/custom-sessions/README", patch_map); + try installText(io, patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{}); } { - var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable; - defer lang_dir.close(); + var lang_dir = std.Io.Dir.cwd().openDir(io, ly_lang_path, .{}) catch unreachable; + defer lang_dir.close(io); const languages = [_][]const u8{ "ar.ini", @@ -221,66 +222,66 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: }; inline for (languages) |language| { - try installFile("res/lang/" ++ language, lang_dir, ly_lang_path, language, .{}); + try installFile(io, "res/lang/" ++ language, lang_dir, ly_lang_path, language, .{}); } } { - const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" }); - std.fs.cwd().makePath(pam_path) catch { + const pam_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" }); + std.Io.Dir.cwd().createDirPath(io, pam_path) catch { if (!std.mem.eql(u8, dest_directory, "")) { std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path}); } }; - var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable; - defer pam_dir.close(); + var pam_dir = std.Io.Dir.cwd().openDir(io, pam_path, .{}) catch unreachable; + defer pam_dir.close(io); - try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 }); - try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd-autologin" else "res/pam.d/ly-linux-autologin", pam_dir, pam_path, "ly-autologin", .{ .override_mode = 0o644 }); + try installFile(io, if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .permissions = .fromMode(0o644) }); + try installFile(io, if (init_system == .freebsd) "res/pam.d/ly-freebsd-autologin" else "res/pam.d/ly-linux-autologin", pam_dir, pam_path, "ly-autologin", .{ .permissions = .fromMode(0o644) }); } } -fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void { +fn install_service(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap) !void { switch (init_system) { .systemd => { - const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" }); - std.fs.cwd().makePath(service_path) catch {}; - var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; - defer service_dir.close(); + const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" }); + std.Io.Dir.cwd().createDirPath(io, service_path) catch {}; + var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable; + defer service_dir.close(io); - const patched_service = try patchFile(allocator, "res/ly@.service", patch_map); - try installText(patched_service, service_dir, service_path, "ly@.service", .{ .mode = 0o644 }); + const patched_service = try patchFile(allocator, io, "res/ly@.service", patch_map); + try installText(io, patched_service, service_dir, service_path, "ly@.service", .{ .permissions = .fromMode(0o644) }); - const patched_kmsconvt_service = try patchFile(allocator, "res/ly-kmsconvt@.service", patch_map); - try installText(patched_kmsconvt_service, service_dir, service_path, "ly-kmsconvt@.service", .{ .mode = 0o644 }); + const patched_kmsconvt_service = try patchFile(allocator, io, "res/ly-kmsconvt@.service", patch_map); + try installText(io, patched_kmsconvt_service, service_dir, service_path, "ly-kmsconvt@.service", .{ .permissions = .fromMode(0o644) }); }, .openrc => { - const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" }); - std.fs.cwd().makePath(service_path) catch {}; - var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; - defer service_dir.close(); + const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" }); + std.Io.Dir.cwd().createDirPath(io, service_path) catch {}; + var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable; + defer service_dir.close(io); - const patched_service = try patchFile(allocator, "res/ly-openrc", patch_map); - try installText(patched_service, service_dir, service_path, executable_name, .{ .mode = 0o755 }); + const patched_service = try patchFile(allocator, io, "res/ly-openrc", patch_map); + try installText(io, patched_service, service_dir, service_path, executable_name, .{ .permissions = .fromMode(0o755) }); }, .runit => { - const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" }); - std.fs.cwd().makePath(service_path) catch {}; - var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; - defer service_dir.close(); + const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" }); + std.Io.Dir.cwd().createDirPath(io, service_path) catch {}; + var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable; + defer service_dir.close(io); - const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" }); + const supervise_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ service_path, "supervise" }); - const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map); - try installText(patched_conf, service_dir, service_path, "conf", .{}); + const patched_conf = try patchFile(allocator, io, "res/ly-runit-service/conf", patch_map); + try installText(io, patched_conf, service_dir, service_path, "conf", .{}); - try installFile("res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .override_mode = 0o755 }); + try installFile(io, "res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .permissions = .fromMode(0o755) }); - const patched_run = try patchFile(allocator, "res/ly-runit-service/run", patch_map); - try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 }); + const patched_run = try patchFile(allocator, io, "res/ly-runit-service/run", patch_map); + try installText(io, patched_run, service_dir, service_path, "run", .{ .permissions = .fromMode(0o755) }); - std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{}) catch |err| { + std.Io.Dir.cwd().symLink(io, "/run/runit/supervise.ly", supervise_path, .{}) catch |err| { if (err == error.PathAlreadyExists) { std.debug.print("warn: /run/runit/supervise.ly already exists as a symbolic link.\n", .{}); } else { @@ -290,49 +291,49 @@ fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void { std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{}); }, .s6 => { - const admin_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" }); - std.fs.cwd().makePath(admin_service_path) catch {}; - var admin_service_dir = std.fs.cwd().openDir(admin_service_path, .{}) catch unreachable; - defer admin_service_dir.close(); + const admin_service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" }); + std.Io.Dir.cwd().createDirPath(io, admin_service_path) catch {}; + var admin_service_dir = std.Io.Dir.cwd().openDir(io, admin_service_path, .{}) catch unreachable; + defer admin_service_dir.close(io); - const file = try admin_service_dir.createFile("ly-srv", .{}); - file.close(); + const file = try admin_service_dir.createFile(io, "ly-srv", .{}); + file.close(io); - const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" }); - std.fs.cwd().makePath(service_path) catch {}; - var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; - defer service_dir.close(); + const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" }); + std.Io.Dir.cwd().createDirPath(io, service_path) catch {}; + var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable; + defer service_dir.close(io); - const patched_run = try patchFile(allocator, "res/ly-s6/run", patch_map); - try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 }); + const patched_run = try patchFile(allocator, io, "res/ly-s6/run", patch_map); + try installText(io, patched_run, service_dir, service_path, "run", .{ .permissions = .fromMode(0o755) }); - try installFile("res/ly-s6/type", service_dir, service_path, "type", .{}); + try installFile(io, "res/ly-s6/type", service_dir, service_path, "type", .{}); }, .dinit => { - const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" }); - std.fs.cwd().makePath(service_path) catch {}; - var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; - defer service_dir.close(); + const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" }); + std.Io.Dir.cwd().createDirPath(io, service_path) catch {}; + var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable; + defer service_dir.close(io); - const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map); - try installText(patched_service, service_dir, service_path, "ly", .{}); + const patched_service = try patchFile(allocator, io, "res/ly-dinit", patch_map); + try installText(io, patched_service, service_dir, service_path, "ly", .{}); }, .sysvinit => { - const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" }); - std.fs.cwd().makePath(service_path) catch {}; - var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; - defer service_dir.close(); + const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" }); + std.Io.Dir.cwd().createDirPath(io, service_path) catch {}; + var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable; + defer service_dir.close(io); - const patched_service = try patchFile(allocator, "res/ly-sysvinit", patch_map); - try installText(patched_service, service_dir, service_path, "ly", .{ .mode = 0o755 }); + const patched_service = try patchFile(allocator, io, "res/ly-sysvinit", patch_map); + try installText(io, patched_service, service_dir, service_path, "ly", .{ .permissions = .fromMode(0o755) }); }, .freebsd => { - const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" }); - var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable; - defer executable_dir.close(); + const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" }); + var executable_dir = std.Io.Dir.cwd().openDir(io, exe_path, .{}) catch unreachable; + defer executable_dir.close(io); - const patched_wrapper = try patchFile(allocator, "res/ly-freebsd-wrapper", patch_map); - try installText(patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .mode = 0o755 }); + const patched_wrapper = try patchFile(allocator, io, "res/ly-freebsd-wrapper", patch_map); + try installText(io, patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .permissions = .fromMode(0o755) }); }, } } @@ -340,33 +341,35 @@ fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void { pub fn Uninstaller(uninstall_config: bool) type { return struct { pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { + var threaded: std.Io.Threaded = .init_single_threaded; + const io = threaded.io(); const allocator = step.owner.allocator; if (uninstall_config) { - try deleteTree(allocator, config_directory, "/ly", "ly config directory not found"); + try deleteTree(allocator, io, config_directory, "/ly", "ly config directory not found"); } - const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ prefix_directory, "/bin/", executable_name }); + const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ prefix_directory, "/bin/", executable_name }); var success = true; - std.fs.cwd().deleteFile(exe_path) catch { + std.Io.Dir.cwd().deleteFile(io, exe_path) catch { std.debug.print("warn: ly executable not found\n", .{}); success = false; }; if (success) std.debug.print("info: deleted {s}\n", .{exe_path}); - try deleteFile(allocator, config_directory, "/pam.d/ly", "ly pam file not found"); + try deleteFile(allocator, io, config_directory, "/pam.d/ly", "ly pam file not found"); switch (init_system) { - .systemd => try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly@.service", "systemd service not found"), - .openrc => try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found"), - .runit => try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found"), + .systemd => try deleteFile(allocator, io, prefix_directory, "/lib/systemd/system/ly@.service", "systemd service not found"), + .openrc => try deleteFile(allocator, io, config_directory, "/init.d/ly", "openrc service not found"), + .runit => try deleteTree(allocator, io, config_directory, "/sv/ly", "runit service not found"), .s6 => { - try deleteTree(allocator, config_directory, "/s6/sv/ly-srv", "s6 service not found"); - try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found"); + try deleteTree(allocator, io, config_directory, "/s6/sv/ly-srv", "s6 service not found"); + try deleteFile(allocator, io, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found"); }, - .dinit => try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found"), - .sysvinit => try deleteFile(allocator, config_directory, "/init.d/ly", "sysvinit service not found"), - .freebsd => try deleteFile(allocator, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"), + .dinit => try deleteFile(allocator, io, config_directory, "/dinit.d/ly", "dinit service not found"), + .sysvinit => try deleteFile(allocator, io, config_directory, "/init.d/ly", "sysvinit service not found"), + .freebsd => try deleteFile(allocator, io, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"), } } }; @@ -384,11 +387,11 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) "--match", "*.*.*", "--tags", - }, &status, .Ignore) catch { + }, &status, .ignore) catch { return version_str; }; var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r"); - git_describe = std.mem.trimLeft(u8, git_describe, "v"); + git_describe = std.mem.trimStart(u8, git_describe, "v"); switch (std.mem.count(u8, git_describe, "-")) { 0 => { @@ -401,7 +404,7 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) 2 => { // Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9). var it = std.mem.splitScalar(u8, git_describe, '-'); - const tagged_ancestor = std.mem.trimLeft(u8, it.first(), "v"); + const tagged_ancestor = std.mem.trimStart(u8, it.first(), "v"); const commit_height = it.next().?; const commit_id = it.next().?; @@ -428,24 +431,25 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) } fn installFile( + io: std.Io, source_file: []const u8, - destination_directory: std.fs.Dir, + destination_directory: std.Io.Dir, destination_directory_path: []const u8, destination_file: []const u8, - options: std.fs.Dir.CopyFileOptions, + options: std.Io.Dir.CopyFileOptions, ) !void { - try std.fs.cwd().copyFile(source_file, destination_directory, destination_file, options); + try std.Io.Dir.cwd().copyFile(source_file, destination_directory, destination_file, io, options); std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file }); } -fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: PatchMap) ![]const u8 { - var file = try std.fs.cwd().openFile(source_file, .{}); - defer file.close(); +fn patchFile(allocator: std.mem.Allocator, io: std.Io, source_file: []const u8, patch_map: PatchMap) ![]const u8 { + var file = try std.Io.Dir.cwd().openFile(io, source_file, .{}); + defer file.close(io); - const stat = try file.stat(); + const stat = try file.stat(io); var buffer: [4096]u8 = undefined; - var reader = file.reader(&buffer); + var reader = file.reader(io, &buffer); var text = try reader.interface.readAlloc(allocator, stat.size); var iterator = patch_map.iterator(); @@ -459,17 +463,18 @@ fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: P } fn installText( + io: std.Io, text: []const u8, - destination_directory: std.fs.Dir, + destination_directory: std.Io.Dir, destination_directory_path: []const u8, destination_file: []const u8, - options: std.fs.File.CreateFlags, + options: std.Io.File.CreateFlags, ) !void { - var file = try destination_directory.createFile(destination_file, options); - defer file.close(); + var file = try destination_directory.createFile(io, destination_file, options); + defer file.close(io); var buffer: [1024]u8 = undefined; - var writer = file.writer(&buffer); + var writer = file.writer(io, &buffer); try writer.interface.writeAll(text); try writer.interface.flush(); @@ -478,13 +483,14 @@ fn installText( fn deleteFile( allocator: std.mem.Allocator, + io: std.Io, prefix: []const u8, file: []const u8, warning: []const u8, ) !void { - const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file }); + const path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file }); - std.fs.cwd().deleteFile(path) catch |err| { + std.Io.Dir.cwd().deleteFile(io, path) catch |err| { if (err == error.FileNotFound) { std.debug.print("warn: {s}\n", .{warning}); return; @@ -498,13 +504,14 @@ fn deleteFile( fn deleteTree( allocator: std.mem.Allocator, + io: std.Io, prefix: []const u8, directory: []const u8, warning: []const u8, ) !void { - const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory }); + const path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory }); - var dir = std.fs.cwd().openDir(path, .{}) catch |err| { + var dir = std.Io.Dir.cwd().openDir(io, path, .{}) catch |err| { if (err == error.FileNotFound) { std.debug.print("warn: {s}\n", .{warning}); return; @@ -512,9 +519,9 @@ fn deleteTree( return err; }; - dir.close(); + dir.close(io); - try std.fs.cwd().deleteTree(path); + try std.Io.Dir.cwd().deleteTree(io, path); std.debug.print("info: deleted {s}\n", .{path}); } diff --git a/build.zig.zon b/build.zig.zon index 5471106..63e8c32 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,15 +2,19 @@ .name = .ly, .version = "1.4.0", .fingerprint = 0xa148ffcc5dc2cb59, - .minimum_zig_version = "0.15.0", + .minimum_zig_version = "0.16.0", .dependencies = .{ .ly_ui = .{ .path = "ly-ui", }, .clap = .{ - .url = "git+https://github.com/Hejsil/zig-clap#5289e0753cd274d65344bef1c114284c633536ea", - .hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e", + .url = "git+https://github.com/Hejsil/zig-clap#fc1e5cc3f6d9d3001112385ee6256d694e959d2f", + .hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1", }, }, - .paths = .{""}, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, } diff --git a/create_vendor_tarball.sh b/create_vendor_tarball.sh new file mode 100755 index 0000000..b42f58c --- /dev/null +++ b/create_vendor_tarball.sh @@ -0,0 +1,2 @@ +#!/bin/sh +tar --zstd -cvf vendor.tar.zst zig-pkg ly-ui/zig-pkg ly-core/zig-pkg diff --git a/ly-core/build.zig b/ly-core/build.zig index 0671528..614404e 100644 --- a/ly-core/build.zig +++ b/ly-core/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Translator = @import("translate_c").Translator; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -12,6 +13,29 @@ pub fn build(b: *std.Build) void { const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize }); mod.addImport("zigini", zigini.module("zigini")); + const translate_c = b.dependency("translate_c", .{ + .target = target, + .optimize = optimize, + }); + + 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 (target.result.os.tag == .freebsd) { + addCImport(b, mod, translate_c, target, optimize, "pwd", + \\#include + \\#include + \\#include + ); + } else { + addCImport(b, mod, translate_c, target, optimize, "pwd", "#include "); + } + addCImport(b, mod, translate_c, target, optimize, "stdlib", "#include "); + addCImport(b, mod, translate_c, target, optimize, "unistd", "#include "); + addCImport(b, mod, translate_c, target, optimize, "grp", "#include "); + addCImport(b, mod, translate_c, target, optimize, "system_time", "#include "); + addCImport(b, mod, translate_c, target, optimize, "time", "#include "); + const mod_tests = b.addTest(.{ .root_module = mod, }); @@ -20,3 +44,20 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run tests"); test_step.dependOn(&run_mod_tests.step); } + +fn addCImport( + b: *std.Build, + mod: *std.Build.Module, + translate_c: *std.Build.Dependency, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + comptime name: []const u8, + comptime bytes: []const u8, +) void { + const pam: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add(name ++ ".h", bytes), + .target = target, + .optimize = optimize, + }); + mod.addImport(name, pam.mod); +} diff --git a/ly-core/build.zig.zon b/ly-core/build.zig.zon index 7dc470f..a389405 100644 --- a/ly-core/build.zig.zon +++ b/ly-core/build.zig.zon @@ -2,11 +2,15 @@ .name = .ly_core, .version = "1.0.0", .fingerprint = 0xddda7afda795472, - .minimum_zig_version = "0.15.0", + .minimum_zig_version = "0.16.0", .dependencies = .{ .zigini = .{ - .url = "git+https://github.com/AshAmetrine/zigini?ref=master#831f6aff55703b7fa34b43c972d60eb3d5d6f4a4", - .hash = "zigini-0.3.2-BSkB7UVDAAAErcEC6s7zF9bKTe7CjdBgrV35K3DGHpbr", + .url = "git+https://github.com/AshAmetrine/zigini?ref=master#a665d081dda42664a96da2840ea09c5ccf9d0692", + .hash = "zigini-0.5.0-BSkB7e9WAACfyCBABNZiWL3gFMw18GKn3qBcPs8L1Ec1", + }, + .translate_c = .{ + .url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7", + .hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q", }, }, .paths = .{ diff --git a/ly-core/src/LogFile.zig b/ly-core/src/LogFile.zig index 5a2ef8f..f60b153 100644 --- a/ly-core/src/LogFile.zig +++ b/ly-core/src/LogFile.zig @@ -5,27 +5,27 @@ const LogFile = @This(); path: []const u8, could_open_log_file: bool = undefined, -file: std.fs.File = undefined, +file: std.Io.File = undefined, buffer: []u8, -file_writer: std.fs.File.Writer = undefined, +file_writer: std.Io.File.Writer = undefined, -pub fn init(path: []const u8, buffer: []u8) !LogFile { +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(path, &log_file); + log_file.could_open_log_file = try openLogFile(io, path, &log_file); return log_file; } -pub fn reinit(self: *LogFile) !void { - self.could_open_log_file = try openLogFile(self.path, self); +pub fn reinit(self: *LogFile, io: std.Io) !void { + self.could_open_log_file = try openLogFile(io, self.path, self); } -pub fn deinit(self: *LogFile) void { - self.file.close(); +pub fn deinit(self: *LogFile, io: std.Io) void { + self.file.close(io); } -pub fn info(self: *LogFile, category: []const u8, comptime message: []const u8, args: anytype) !void { +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(&buffer, "%Y-%m-%d %H:%M:%S"); + 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); @@ -33,9 +33,9 @@ pub fn info(self: *LogFile, category: []const u8, comptime message: []const u8, try self.file_writer.interface.flush(); } -pub fn err(self: *LogFile, category: []const u8, comptime message: []const u8, args: anytype) !void { +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(&buffer, "%Y-%m-%d %H:%M:%S"); + 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); @@ -43,10 +43,10 @@ pub fn err(self: *LogFile, category: []const u8, comptime message: []const u8, a try self.file_writer.interface.flush(); } -fn openLogFile(path: []const u8, log_file: *LogFile) !bool { +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.fs.cwd().openFile(path, .{ .mode = .write_only }) catch std.fs.cwd().createFile(path, .{ .mode = 0o666 }) catch { + 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 { // If we could neither open an existing log file nor create a new // one, abort. could_open_log_file = false; @@ -55,14 +55,14 @@ fn openLogFile(path: []const u8, log_file: *LogFile) !bool { } if (!could_open_log_file) { - log_file.file = try std.fs.openFileAbsolute("/dev/null", .{ .mode = .write_only }); + log_file.file = try std.Io.Dir.openFileAbsolute(io, "/dev/null", .{ .mode = .write_only }); } - var log_file_writer = log_file.file.writer(log_file.buffer); + var log_file_writer = log_file.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(); + const stat = try log_file.file.stat(io); try log_file_writer.seekTo(stat.size); } diff --git a/ly-core/src/SharedError.zig b/ly-core/src/SharedError.zig index ba3656e..c3ce088 100644 --- a/ly-core/src/SharedError.zig +++ b/ly-core/src/SharedError.zig @@ -1,10 +1,12 @@ const std = @import("std"); const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror)); +const PaddingInt = std.meta.Int(.unsigned, 8 - (@bitSizeOf(ErrInt) + @bitSizeOf(bool)) % 8); const ErrorHandler = packed struct { has_error: bool = false, err_int: ErrInt = 0, + padding: PaddingInt = 0, }; const SharedError = @This(); @@ -17,7 +19,7 @@ pub fn init( write_error_event_fn: ?*const fn (anyerror, *anyopaque) anyerror!void, ctx: ?*anyopaque, ) !SharedError { - const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), std.posix.PROT.READ | std.posix.PROT.WRITE, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0); + const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), .{ .READ = true, .WRITE = true }, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0); return .{ .data = data, @@ -31,9 +33,8 @@ pub fn deinit(self: *SharedError) void { } pub fn writeError(self: SharedError, err: anyerror) void { - var buf_stream = std.io.fixedBufferStream(self.data); - const writer = buf_stream.writer(); - writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }) catch {}; + var writer: std.Io.Writer = .fixed(self.data); + writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }, .native) catch {}; if (self.write_error_event_fn) |write_error_event_fn| { @call(.auto, write_error_event_fn, .{ err, self.ctx.? }) catch {}; @@ -41,9 +42,8 @@ pub fn writeError(self: SharedError, err: anyerror) void { } pub fn readError(self: SharedError) ?anyerror { - var buf_stream = std.io.fixedBufferStream(self.data); - const reader = buf_stream.reader(); - const err_handler = try reader.readStruct(ErrorHandler); + var reader: std.Io.Reader = .fixed(self.data); + const err_handler = try reader.takeStruct(ErrorHandler, .native); if (err_handler.has_error) return @errorFromInt(err_handler.err_int); diff --git a/ly-core/src/interop.zig b/ly-core/src/interop.zig index ef542f7..c580a49 100644 --- a/ly-core/src/interop.zig +++ b/ly-core/src/interop.zig @@ -1,49 +1,17 @@ const std = @import("std"); const builtin = @import("builtin"); const UidRange = @import("UidRange.zig"); +const pwd = @import("pwd"); +const stdlib = @import("stdlib"); +const unistd = @import("unistd"); +const grp = @import("grp"); +const system_time = @import("system_time"); +const time = @import("time"); -pub const pam = @cImport({ - @cInclude("security/pam_appl.h"); -}); - -pub const utmp = @cImport({ - @cInclude("utmpx.h"); -}); - +pub const pam = @import("pam"); +pub const utmp = @import("utmp"); // Exists for X11 support only -pub const xcb = @cImport({ - @cInclude("xcb/xcb.h"); -}); - -const pwd = @cImport({ - @cInclude("pwd.h"); - // We include a FreeBSD-specific header here since login_cap.h references - // the passwd struct directly, so we can't import it separately - if (builtin.os.tag == .freebsd) { - @cInclude("sys/types.h"); - @cInclude("login_cap.h"); - } -}); - -const stdlib = @cImport({ - @cInclude("stdlib.h"); -}); - -const unistd = @cImport({ - @cInclude("unistd.h"); -}); - -const grp = @cImport({ - @cInclude("grp.h"); -}); - -const system_time = @cImport({ - @cInclude("sys/time.h"); -}); - -const time = @cImport({ - @cInclude("time.h"); -}); +pub const xcb = @import("xcb"); pub const TimeOfDay = struct { seconds: i64, @@ -83,8 +51,8 @@ fn PlatformStruct() type { const status = grp.initgroups(username, @intCast(entry.gid)); if (status != 0) return error.GroupInitializationFailed; - std.posix.setgid(@intCast(entry.gid)) catch return error.SetUserGidFailed; - std.posix.setuid(@intCast(entry.uid)) catch return error.SetUserUidFailed; + if (isError(std.posix.system.setgid(@intCast(entry.gid)))) return error.SetUserGidFailed; + if (isError(std.posix.system.setuid(@intCast(entry.uid)))) return error.SetUserUidFailed; } // Procedure: @@ -96,14 +64,14 @@ fn PlatformStruct() type { // 4. Finally, compare the major and minor device numbers with the // extracted values. If they correspond, parse [dir] to get the // TTY ID - pub fn getActiveTtyImpl(allocator: std.mem.Allocator, use_kmscon_vt: bool) !u8 { + pub fn getActiveTtyImpl(allocator: std.mem.Allocator, io: std.Io, use_kmscon_vt: bool) !u8 { var file_buffer: [256]u8 = undefined; if (use_kmscon_vt) { - var file = try std.fs.openFileAbsolute("/sys/class/tty/tty0/active", .{}); - defer file.close(); + var file = try std.Io.Dir.openFileAbsolute(io, "/sys/class/tty/tty0/active", .{}); + defer file.close(io); - var reader = file.reader(&file_buffer); + var reader = file.reader(io, &file_buffer); var buffer: [16]u8 = undefined; const read = try readBuffer(&reader.interface, &buffer); @@ -115,10 +83,10 @@ fn PlatformStruct() type { var tty_minor: u16 = undefined; { - var file = try std.fs.openFileAbsolute("/proc/self/stat", .{}); - defer file.close(); + var file = try std.Io.Dir.openFileAbsolute(io, "/proc/self/stat", .{}); + defer file.close(io); - var reader = file.reader(&file_buffer); + var reader = file.reader(io, &file_buffer); var buffer: [1024]u8 = undefined; const read = try readBuffer(&reader.interface, &buffer); @@ -136,18 +104,18 @@ fn PlatformStruct() type { tty_minor = tty_nr % 256; } - var directory = try std.fs.openDirAbsolute("/sys/class/tty", .{ .iterate = true }); - defer directory.close(); + var directory = try std.Io.Dir.openDirAbsolute(io, "/sys/class/tty", .{ .iterate = true }); + defer directory.close(io); var iterator = directory.iterate(); - while (try iterator.next()) |entry| { + while (try iterator.next(io)) |entry| { const path = try std.fmt.allocPrint(allocator, "/sys/class/tty/{s}/dev", .{entry.name}); defer allocator.free(path); - var file = try std.fs.openFileAbsolute(path, .{}); - defer file.close(); + var file = try std.Io.Dir.openFileAbsolute(io, path, .{}); + defer file.close(io); - var reader = file.reader(&file_buffer); + var reader = file.reader(io, &file_buffer); var buffer: [16]u8 = undefined; const read = try readBuffer(&reader.interface, &buffer); @@ -170,11 +138,14 @@ fn PlatformStruct() type { // This is very bad parsing, but we only need to get 2 values.. // and the format of the file seems to be standard? So this should // be fine... - pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange { - const login_defs_file = try std.fs.cwd().openFile(file_path, .{}); - defer login_defs_file.close(); + pub fn getUserIdRange(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8) !UidRange { + const login_defs_file = try std.Io.Dir.cwd().openFile(io, file_path, .{}); + defer login_defs_file.close(io); - const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16)); + var buffer: [4096]u8 = undefined; + var reader = login_defs_file.reader(io, &buffer); + + const login_defs_buffer = try reader.interface.allocRemaining(allocator, .unlimited); defer allocator.free(login_defs_buffer); var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n'); @@ -255,11 +226,11 @@ fn PlatformStruct() type { if (result != 0) return error.SetUserUidFailed; } - pub fn getActiveTtyImpl(_: std.mem.Allocator, _: bool) !u8 { + pub fn getActiveTtyImpl(_: std.mem.Allocator, _: std.Io, _: bool) !u8 { return error.FeatureUnimplemented; } - pub fn getUserIdRange(_: std.mem.Allocator, _: []const u8) !UidRange { + pub fn getUserIdRange(_: std.mem.Allocator, _: std.Io, _: []const u8) !UidRange { return .{ // Hardcoded default values chosen from // /usr/src/usr.sbin/pw/pw_conf.c @@ -274,12 +245,28 @@ fn PlatformStruct() type { const platform_struct = PlatformStruct(); +// TODO 0.16.0: Can we get away with this? +pub fn isError(result: anytype) bool { + if (@typeInfo(@TypeOf(result)).int.signedness == .signed) { + return result < 0; + } + + if (@typeInfo(@TypeOf(result)).int.signedness == .unsigned) { + return switch (builtin.os.tag) { + .linux => std.os.linux.errno(result) != .SUCCESS, + else => @compileError("interop.isError() not implemented for current target!"), + }; + } + + unreachable; +} + pub fn supportsUnicode() bool { return builtin.os.tag == .linux or builtin.os.tag == .freebsd; } -pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) []u8 { - const timer = std.time.timestamp(); +pub fn timeAsString(io: std.Io, buf: [:0]u8, format: [:0]const u8) []u8 { + const timer = std.Io.Timestamp.now(io, .real).toSeconds(); const tm_info = time.localtime(&timer); const len = time.strftime(buf, buf.len, format, tm_info); @@ -298,8 +285,8 @@ pub fn getTimeOfDay() !TimeOfDay { }; } -pub fn getActiveTty(allocator: std.mem.Allocator, use_kmscon_vt: bool) !u8 { - return platform_struct.getActiveTtyImpl(allocator, use_kmscon_vt); +pub fn getActiveTty(allocator: std.mem.Allocator, io: std.Io, use_kmscon_vt: bool) !u8 { + return platform_struct.getActiveTtyImpl(allocator, io, use_kmscon_vt); } pub fn switchTty(tty: u8) !void { @@ -402,6 +389,6 @@ pub fn closePasswordDatabase() void { // This is very bad parsing, but we only need to get 2 values... and the format // of the file doesn't seem to be standard? So this should be fine... -pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange { - return platform_struct.getUserIdRange(allocator, file_path); +pub fn getUserIdRange(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8) !UidRange { + return platform_struct.getUserIdRange(allocator, io, file_path); } diff --git a/ly-core/src/root.zig b/ly-core/src/root.zig index 4bcb6a8..42198f5 100644 --- a/ly-core/src/root.zig +++ b/ly-core/src/root.zig @@ -27,6 +27,7 @@ pub fn IniParser(comptime Struct: type) type { pub fn init( allocator: std.mem.Allocator, + io: std.Io, path: []const u8, field_handler: ?fn (allocator: std.mem.Allocator, field: ini.IniField) ?ini.IniField, ) !Self { @@ -35,7 +36,7 @@ pub fn IniParser(comptime Struct: type) type { var maybe_load_error: ?anyerror = null; - const structure = ini_struct.readFileToStruct(path, .{ + const structure = ini_struct.readFileToStruct(io, path, .{ .fieldHandler = field_handler, .errorHandler = errorHandler, .comment_characters = "#", diff --git a/ly-ui/build.zig b/ly-ui/build.zig index 589f7c7..a7a3051 100644 --- a/ly-ui/build.zig +++ b/ly-ui/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Translator = @import("translate_c").Translator; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -17,15 +18,30 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - const translate_c = b.addTranslateC(.{ - .root_source_file = termbox_dep.path("termbox2.h"), + const translate_c_dep = b.dependency("translate_c", .{ .target = target, .optimize = optimize, }); - translate_c.defineCMacroRaw("TB_IMPL"); - translate_c.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit) - const termbox2 = translate_c.addModule("termbox2"); - mod.addImport("termbox2", termbox2); + + const termbox2: Translator = .init(translate_c_dep, .{ + .c_source_file = termbox_dep.path("termbox2.h"), + .target = target, + .optimize = optimize, + }); + termbox2.defineCMacro("TB_IMPL", null); + // TODO 0.16.0: Workaround until Aro gets better... + // https://codeberg.org/ziglang/translate-c/issues/319 + termbox2.defineCMacro("_XOPEN_SOURCE", "700"); + termbox2.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit) + // TODO 0.16.0: Including with -OReleaseSafe causes + // __attribute__(__error__()) to be called. Below + // is the workaround. + termbox2.defineCMacro("_FORTIFY_SOURCE", "0"); + // TODO 0.16.0: Needed for now + if (target.result.os.tag == .freebsd) { + termbox2.defineCMacro("__BSD_VISIBLE", "1"); + } + mod.addImport("termbox2", termbox2.mod); const mod_tests = b.addTest(.{ .root_module = mod, diff --git a/ly-ui/build.zig.zon b/ly-ui/build.zig.zon index a16ce9e..598cba0 100644 --- a/ly-ui/build.zig.zon +++ b/ly-ui/build.zig.zon @@ -2,14 +2,18 @@ .name = .ly_ui, .version = "1.0.0", .fingerprint = 0x8d11bf85a74ec803, - .minimum_zig_version = "0.15.0", + .minimum_zig_version = "0.16.0", .dependencies = .{ .ly_core = .{ .path = "../ly-core", }, .termbox2 = .{ - .url = "git+https://github.com/AnErrupTion/termbox2?ref=master#496730697c662893eec43192f48ff616c2539da6", - .hash = "N-V-__8AAOEWBQDt5tNdIzIFY6n8DdZsCP-6MyLoNS20wgpA", + .url = "git+https://github.com/AnErrupTion/termbox2?ref=master#c7f241e8888ce243e1748b05c26a42fcfaaad936", + .hash = "N-V-__8AAAUXBQD6Fwpi9m0MBqWXFFaqW5l1lVrJC2Ynj7a-", + }, + .translate_c = .{ + .url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7", + .hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q", }, }, .paths = .{ diff --git a/ly-ui/src/TerminalBuffer.zig b/ly-ui/src/TerminalBuffer.zig index 4551d68..565593b 100644 --- a/ly-ui/src/TerminalBuffer.zig +++ b/ly-ui/src/TerminalBuffer.zig @@ -97,6 +97,7 @@ active_widget_index: usize, pub fn init( allocator: Allocator, + io: std.Io, options: InitOptions, log_file: *LogFile, random: Random, @@ -106,9 +107,9 @@ pub fn init( if (options.full_color) { _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR); - try log_file.info("tui", "termbox2 set to 24-bit color output mode", .{}); + try log_file.info(io, "tui", "termbox2 set to 24-bit color output mode", .{}); } else { - try log_file.info("tui", "termbox2 set to eight-color output mode", .{}); + try log_file.info(io, "tui", "termbox2 set to eight-color output mode", .{}); } _ = termbox.tb_clear(); @@ -119,7 +120,7 @@ pub fn init( const width: usize = @intCast(termbox.tb_width()); const height: usize = @intCast(termbox.tb_height()); - try log_file.info("tui", "screen resolution is {d}x{d}", .{ width, height }); + try log_file.info(io, "tui", "screen resolution is {d}x{d}", .{ width, height }); return .{ .log_file = log_file, @@ -168,6 +169,7 @@ pub fn deinit(self: *TerminalBuffer) void { pub fn runEventLoop( self: *TerminalBuffer, allocator: Allocator, + io: std.Io, shared_error: SharedError, layers: [][]*Widget, active_widget: *Widget, @@ -176,14 +178,14 @@ pub fn runEventLoop( inactivity_event_fn: ?*const fn (*anyopaque) anyerror!void, context: *anyopaque, ) !void { - try self.registerGlobalKeybind("Ctrl+K", &moveCursorUp, self); - try self.registerGlobalKeybind("Up", &moveCursorUp, self); + try self.registerGlobalKeybind(io, "Ctrl+K", &moveCursorUp, self); + try self.registerGlobalKeybind(io, "Up", &moveCursorUp, self); - try self.registerGlobalKeybind("Ctrl+J", &moveCursorDown, self); - try self.registerGlobalKeybind("Down", &moveCursorDown, self); + try self.registerGlobalKeybind(io, "Ctrl+J", &moveCursorDown, self); + try self.registerGlobalKeybind(io, "Down", &moveCursorDown, self); - try self.registerGlobalKeybind("Tab", &wrapCursor, self); - try self.registerGlobalKeybind("Shift+Tab", &wrapCursorReverse, self); + try self.registerGlobalKeybind(io, "Tab", &wrapCursor, self); + try self.registerGlobalKeybind(io, "Shift+Tab", &wrapCursorReverse, self); defer self.handlable_widgets.deinit(allocator); @@ -218,6 +220,7 @@ pub fn runEventLoop( current_widget.handle(null) catch |err| { shared_error.writeError(error.SetCursorFailed); try self.log_file.err( + io, "tui", "failed to set cursor in active widget '{s}': {s}", .{ current_widget.display_name, @errorName(err) }, @@ -261,6 +264,7 @@ pub fn runEventLoop( self.height = TerminalBuffer.getHeight(); try self.log_file.info( + io, "tui", "screen resolution updated to {d}x{d}", .{ self.width, self.height }, @@ -271,6 +275,7 @@ pub fn runEventLoop( widget.realloc() catch |err| { shared_error.writeError(error.WidgetReallocationFailed); try self.log_file.err( + io, "tui", "failed to reallocate widget '{s}': {s}", .{ widget.display_name, @errorName(err) }, @@ -294,6 +299,7 @@ pub fn runEventLoop( current_widget.handle(key) catch |err| { shared_error.writeError(error.CurrentWidgetHandlingFailed); try self.log_file.err( + io, "tui", "failed to handle active widget '{s}': {s}", .{ current_widget.display_name, @errorName(err) }, @@ -390,18 +396,20 @@ pub fn reclaim(self: TerminalBuffer) !void { pub fn registerKeybind( self: *TerminalBuffer, + io: std.Io, keybinds: *KeybindMap, keybind: []const u8, callback: KeybindCallbackFn, context: *anyopaque, ) !void { - const key = try self.parseKeybind(keybind); + const key = try self.parseKeybind(io, keybind); keybinds.put(key, .{ .callback = callback, .context = context, }) catch |err| { try self.log_file.err( + io, "tui", "failed to register keybind {s}: {s}", .{ keybind, @errorName(err) }, @@ -411,15 +419,16 @@ pub fn registerKeybind( pub fn registerGlobalKeybind( self: *TerminalBuffer, + io: std.Io, keybind: []const u8, callback: KeybindCallbackFn, context: *anyopaque, ) !void { - try self.registerKeybind(&self.keybinds, keybind, callback, context); + try self.registerKeybind(io, &self.keybinds, keybind, callback, context); } -pub fn simulateKeybind(self: *TerminalBuffer, keybind: []const u8) !bool { - const key = try self.parseKeybind(keybind); +pub fn simulateKeybind(self: *TerminalBuffer, io: std.Io, keybind: []const u8) !bool { + const key = try self.parseKeybind(io, keybind); if (self.keybinds.get(key)) |binding| { return try @call( @@ -509,10 +518,13 @@ fn clearBackBuffer() !void { // Clear the TTY because termbox2 doesn't seem to do it properly const capability = termbox.global.caps[termbox.TB_CAP_CLEAR_SCREEN]; const capability_slice = std.mem.span(capability); - _ = try std.posix.write(termbox.global.ttyfd, capability_slice); + const result = std.posix.system.write(termbox.global.ttyfd, capability_slice.ptr, capability_slice.len); + + if (result != capability_slice.len) return error.PartialClearBackBuffer; + if (result < 0) return error.ClearBackBufferFailed; } -fn parseKeybind(self: *TerminalBuffer, keybind: []const u8) !keyboard.Key { +fn parseKeybind(self: *TerminalBuffer, io: std.Io, keybind: []const u8) !keyboard.Key { var key = std.mem.zeroes(keyboard.Key); var iterator = std.mem.splitScalar(u8, keybind, '+'); @@ -529,6 +541,7 @@ fn parseKeybind(self: *TerminalBuffer, keybind: []const u8) !keyboard.Key { if (!found) { try self.log_file.err( + io, "tui", "failed to parse key {s} of keybind {s}", .{ item, keybind }, diff --git a/ly-ui/src/components/Text.zig b/ly-ui/src/components/Text.zig index fc04fe7..4fc35bf 100644 --- a/ly-ui/src/components/Text.zig +++ b/ly-ui/src/components/Text.zig @@ -29,6 +29,7 @@ keybinds: TerminalBuffer.KeybindMap, pub fn init( allocator: Allocator, + io: std.Io, buffer: *TerminalBuffer, should_insert: bool, masked: bool, @@ -57,11 +58,11 @@ pub fn init( .keybinds = .init(allocator), }; - try buffer.registerKeybind(&self.keybinds, "Left", &goLeft, self); - try buffer.registerKeybind(&self.keybinds, "Right", &goRight, self); - try buffer.registerKeybind(&self.keybinds, "Delete", &delete, self); - try buffer.registerKeybind(&self.keybinds, "Backspace", &backspace, self); - try buffer.registerKeybind(&self.keybinds, "Ctrl+U", &clearTextEntry, self); + try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self); + try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self); + try buffer.registerKeybind(io, &self.keybinds, "Delete", &delete, self); + try buffer.registerKeybind(io, &self.keybinds, "Backspace", &backspace, self); + try buffer.registerKeybind(io, &self.keybinds, "Ctrl+U", &clearTextEntry, self); return self; } diff --git a/ly-ui/src/components/generic.zig b/ly-ui/src/components/generic.zig index d7a609d..0a76c4f 100644 --- a/ly-ui/src/components/generic.zig +++ b/ly-ui/src/components/generic.zig @@ -32,6 +32,7 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ pub fn init( allocator: Allocator, + io: std.Io, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, @@ -60,10 +61,10 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ .keybinds = .init(allocator), }; - try buffer.registerKeybind(&self.keybinds, "Left", &goLeft, self); - try buffer.registerKeybind(&self.keybinds, "Ctrl+H", &goLeft, self); - try buffer.registerKeybind(&self.keybinds, "Right", &goRight, self); - try buffer.registerKeybind(&self.keybinds, "Ctrl+L", &goRight, self); + try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self); + try buffer.registerKeybind(io, &self.keybinds, "Ctrl+H", &goLeft, self); + try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self); + try buffer.registerKeybind(io, &self.keybinds, "Ctrl+L", &goRight, self); return self; } diff --git a/readme.md b/readme.md index 79f5941..aa23db8 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,7 @@ Join us on Matrix over at [#ly-dm:matrix.org](https://matrix.to/#/#ly-dm:matrix. ## Dependencies - Compile-time: - - zig 0.15.x + - zig 0.16.x - libc diff --git a/src/animations/Cascade.zig b/src/animations/Cascade.zig index b11c728..c1e88f1 100644 --- a/src/animations/Cascade.zig +++ b/src/animations/Cascade.zig @@ -8,17 +8,20 @@ const Widget = ly_ui.Widget; const Cascade = @This(); +io: std.Io, instance: ?Widget = null, buffer: *TerminalBuffer, current_auth_fails: *usize, max_auth_fails: usize, pub fn init( + io: std.Io, buffer: *TerminalBuffer, current_auth_fails: *usize, max_auth_fails: usize, ) Cascade { return .{ + .io = io, .instance = null, .buffer = buffer, .current_auth_fails = current_auth_fails, @@ -44,7 +47,7 @@ pub fn widget(self: *Cascade) *Widget { fn draw(self: *Cascade) void { while (self.current_auth_fails.* >= self.max_auth_fails) { - std.Thread.sleep(std.time.ns_per_ms * 10); + self.io.sleep(.fromMilliseconds(10), .real) catch {}; var changed = false; var y = self.buffer.height - 2; @@ -80,7 +83,7 @@ fn draw(self: *Cascade) void { } if (!changed) { - std.Thread.sleep(std.time.ns_per_s * 7); + self.io.sleep(.fromSeconds(7), .real) catch {}; self.current_auth_fails.* = 0; } diff --git a/src/animations/ColorMix.zig b/src/animations/ColorMix.zig index 0715825..49c6238 100644 --- a/src/animations/ColorMix.zig +++ b/src/animations/ColorMix.zig @@ -113,7 +113,7 @@ fn draw(self: *ColorMix) void { uv -= @splat(1.0 * math.cos(uv[0] + uv[1]) - math.sin(uv[0] * 0.7 - uv[1])); } - const cell = self.palette[@as(usize, @intFromFloat(math.floor(length(uv) * 5.0))) % palette_len]; + const cell = self.palette[@as(usize, @trunc(math.floor(length(uv) * 5.0))) % palette_len]; cell.put(x, y); } } diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index b6aceae..18844b8 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -19,16 +19,16 @@ const LogFile = ly_core.LogFile; const enums = @import("../enums.zig"); const DurOffsetAlignment = enums.DurOffsetAlignment; -fn read_decompress_file(allocator: Allocator, file_path: []const u8) ![]u8 { - const file_buffer = std.fs.cwd().openFile(file_path, .{}) catch { +fn read_decompress_file(allocator: Allocator, io: std.Io, file_path: []const u8) ![]u8 { + const file_buffer = std.Io.Dir.cwd().openFile(io, file_path, .{}) catch { return error.FileNotFound; }; - defer file_buffer.close(); + defer file_buffer.close(io); var file_reader_buffer: [4096]u8 = undefined; var decompress_buffer: [flate.max_window_len]u8 = undefined; - var file_reader = file_buffer.reader(&file_reader_buffer); + var file_reader = file_buffer.reader(io, &file_reader_buffer); var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer); const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch { @@ -150,8 +150,8 @@ const DurFormat = struct { } } - pub fn create_from_file(self: *DurFormat, allocator: Allocator, file_path: []const u8) !void { - const file_decompressed = try read_decompress_file(allocator, file_path); + pub fn create_from_file(self: *DurFormat, allocator: Allocator, io: std.Io, file_path: []const u8) !void { + const file_decompressed = try read_decompress_file(allocator, io, file_path); defer allocator.free(file_decompressed); const parsed = try Json.parseFromSlice(Json.Value, allocator, file_decompressed, .{}); @@ -307,6 +307,7 @@ const DurFile = @This(); instance: ?Widget = null, start_time: TimeOfDay, allocator: Allocator, +io: std.Io, terminal_buffer: *TerminalBuffer, dur_movie: DurFormat, frames: u64, @@ -368,6 +369,7 @@ fn calc_frame_size(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat) UVec pub fn init( allocator: Allocator, + io: std.Io, terminal_buffer: *TerminalBuffer, log_file: *LogFile, file_path: []const u8, @@ -381,13 +383,13 @@ pub fn init( ) !DurFile { var dur_movie: DurFormat = .init(allocator); - dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) { + dur_movie.create_from_file(allocator, io, file_path) catch |err| switch (err) { error.FileNotFound => { - try log_file.err("tui", "dur_file was not found at: {s}", .{file_path}); + try log_file.err(io, "tui", "dur_file was not found at: {s}", .{file_path}); return err; }, error.NotValidFile => { - try log_file.err("tui", "dur_file loaded was invalid or not a dur file!", .{}); + try log_file.err(io, "tui", "dur_file loaded was invalid or not a dur file!", .{}); return err; }, else => return err, @@ -395,7 +397,7 @@ pub fn init( // 4 bit mode with 256 color is unsupported if (!full_color and eql(u8, dur_movie.colorFormat.?, "256")) { - try log_file.err("tui", "dur_file can not be 256 color encoded when not using full_color option!", .{}); + try log_file.err(io, "tui", "dur_file can not be 256 color encoded when not using full_color option!", .{}); dur_movie.deinit(); return error.InvalidColorFormat; } @@ -406,15 +408,16 @@ pub fn init( const frame_size = calc_frame_size(terminal_buffer, &dur_movie); // Convert dur fps to frames per ms - const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); + const frame_time: u32 = @trunc(1000 / dur_movie.framerate.?); return .{ .instance = null, .start_time = try interop.getTimeOfDay(), .allocator = allocator, + .io = io, .terminal_buffer = terminal_buffer, .frames = 0, - .time_previous = std.time.milliTimestamp(), + .time_previous = std.Io.Timestamp.now(io, .real).toMilliseconds(), .frame_size = frame_size, .start_pos = start_pos, .full_color = full_color, @@ -499,11 +502,11 @@ fn draw(self: *DurFile) void { } } - const time_current = std.time.milliTimestamp(); + const time_current = std.Io.Timestamp.now(self.io, .real).toMilliseconds(); const delta_time = time_current - self.time_previous; // Convert delay from sec to ms - const delay_time: u32 = @intFromFloat(current_frame.delay * 1000); + const delay_time: u32 = @trunc(current_frame.delay * 1000); if (delta_time > (self.frame_time + delay_time)) { self.time_previous = time_current; diff --git a/src/auth.zig b/src/auth.zig index e74843f..b765c08 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -27,16 +27,16 @@ pub const AuthOptions = struct { }; var xorg_pid: std.posix.pid_t = 0; -pub fn xorgSignalHandler(i: c_int) callconv(.c) void { - if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i); +pub fn xorgSignalHandler(sig: std.posix.SIG) callconv(.c) void { + if (xorg_pid > 0) _ = std.c.kill(xorg_pid, sig); } var child_pid: std.posix.pid_t = 0; -pub fn sessionSignalHandler(i: c_int) callconv(.c) void { - if (child_pid > 0) _ = std.c.kill(child_pid, i); +pub fn sessionSignalHandler(sig: std.posix.SIG) callconv(.c) void { + if (child_pid > 0) _ = std.c.kill(child_pid, sig); } -pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void { +pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void { var tty_buffer: [3]u8 = undefined; const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty}); @@ -44,11 +44,11 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty}); // Set the XDG environment variables - try log_file.info("auth/env", "setting xdg environment variables", .{}); + try log_file.info(io, "auth/env", "setting xdg environment variables", .{}); try setXdgEnv(allocator, tty_str, current_environment); // Open the PAM session - try log_file.info("auth/pam", "encoding credentials", .{}); + try log_file.info(io, "auth/pam", "encoding credentials", .{}); const login_z = try allocator.dupeZ(u8, login); defer allocator.free(login_z); @@ -63,36 +63,36 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A }; var handle: ?*interop.pam.pam_handle = undefined; - try log_file.info("auth/pam", "starting session", .{}); + try log_file.info(io, "auth/pam", "starting session", .{}); var status = interop.pam.pam_start(options.service_name, null, &conv, &handle); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); defer _ = interop.pam.pam_end(handle, status); // Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module - try log_file.info("auth/pam", "setting tty", .{}); + try log_file.info(io, "auth/pam", "setting tty", .{}); status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); // Do the PAM routine - try log_file.info("auth/pam", "authenticating", .{}); + try log_file.info(io, "auth/pam", "authenticating", .{}); status = interop.pam.pam_authenticate(handle, 0); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - try log_file.info("auth/pam", "validating account", .{}); + try log_file.info(io, "auth/pam", "validating account", .{}); status = interop.pam.pam_acct_mgmt(handle, 0); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - try log_file.info("auth/pam", "setting credentials", .{}); + try log_file.info(io, "auth/pam", "setting credentials", .{}); status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); - try log_file.info("auth/pam", "opening session", .{}); + try log_file.info(io, "auth/pam", "opening session", .{}); status = interop.pam.pam_open_session(handle, 0); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); defer status = interop.pam.pam_close_session(handle, 0); - try log_file.info("auth/passwd", "getting struct", .{}); + try log_file.info(io, "auth/passwd", "getting struct", .{}); var user_entry: interop.UsernameEntry = undefined; { defer interop.closePasswordDatabase(); @@ -102,27 +102,27 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A } // Set user shell if it hasn't already been set - try log_file.info("auth/passwd", "setting user shell", .{}); + try log_file.info(io, "auth/passwd", "setting user shell", .{}); if (user_entry.shell == null) interop.setUserShell(&user_entry); var shared_err = try SharedError.init(null, null); defer shared_err.deinit(); - log_file.deinit(); + log_file.deinit(io); - child_pid = try std.posix.fork(); + child_pid = std.posix.system.fork(); if (child_pid == 0) { - try log_file.reinit(); - try log_file.info("auth/sys", "starting session", .{}); + try log_file.reinit(io); + try log_file.info(io, "auth/sys", "starting session", .{}); - startSession(log_file, allocator, options, tty_str, user_entry, handle, current_environment) catch |e| { + startSession(log_file, allocator, io, options, tty_str, user_entry, handle, current_environment) catch |e| { shared_err.writeError(e); - log_file.deinit(); + log_file.deinit(io); std.process.exit(1); }; - log_file.deinit(); + log_file.deinit(io); std.process.exit(0); } @@ -132,7 +132,8 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A // If an error occurs here, we can send SIGTERM to the session errdefer cleanup: { std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup; - _ = std.posix.waitpid(child_pid, 0); + var child_status: c_int = undefined; + _ = std.posix.system.waitpid(child_pid, &child_status, 0); } // If we receive SIGTERM, forward it to child_pid @@ -143,14 +144,15 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A }; std.posix.sigaction(std.posix.SIG.TERM, &act, null); - try addUtmpEntry(&entry, user_entry.username.?, child_pid); + try addUtmpEntry(io, &entry, user_entry.username.?, child_pid); } // Wait for the session to stop - _ = std.posix.waitpid(child_pid, 0); + var child_status: c_int = undefined; + _ = std.posix.system.waitpid(child_pid, &child_status, 0); - try log_file.reinit(); + try log_file.reinit(io); - try log_file.info("auth/utmp", "removing utmp entry", .{}); + try log_file.info(io, "auth/utmp", "removing utmp entry", .{}); removeUtmpEntry(&entry); if (shared_err.readError()) |err| return err; @@ -159,6 +161,7 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A fn startSession( log_file: *LogFile, allocator: std.mem.Allocator, + io: std.Io, options: AuthOptions, tty_str: []u8, user_entry: interop.UsernameEntry, @@ -166,15 +169,15 @@ fn startSession( current_environment: Environment, ) !void { // Set the user's GID & PID - try log_file.info("auth/passwd", "setting user context", .{}); + try log_file.info(io, "auth/passwd", "setting user context", .{}); try interop.setUserContext(allocator, user_entry); // Set up the environment - try log_file.info("auth/env", "setting environment variables", .{}); + try log_file.info(io, "auth/env", "setting environment variables", .{}); try initEnv(allocator, user_entry, options.path); // Reset the XDG environment variables - try log_file.info("auth/env", "resetting xdg environment variables", .{}); + try log_file.info(io, "auth/env", "resetting xdg environment variables", .{}); try setXdgEnv(allocator, tty_str, current_environment); try setXdgRuntimeDir(allocator); @@ -185,27 +188,30 @@ fn startSession( const env_list = std.mem.span(pam_env_vars.?); for (env_list) |env_var| { if (env_var == null) continue; - try log_file.info("auth/env", "setting pam environment variable: {s}", .{std.mem.span(env_var.?)}); + try log_file.info(io, "auth/env", "setting pam environment variable: {s}", .{std.mem.span(env_var.?)}); try interop.putEnvironmentVariable(env_var); } + const home_z = try allocator.dupeZ(u8, user_entry.home.?); + defer allocator.free(home_z); + // Change to the user's home directory - try log_file.info("auth/sys", "changing cwd to user home", .{}); - std.posix.chdir(user_entry.home.?) catch return error.ChangeDirectoryFailed; + try log_file.info(io, "auth/sys", "changing cwd to user home", .{}); + if (std.posix.system.chdir(home_z.ptr) < 0) return error.ChangeDirectoryFailed; // Signal to the session process to give up control on the TTY - try log_file.info("auth/sys", "releasing tty", .{}); + try log_file.info(io, "auth/sys", "releasing tty", .{}); std.posix.kill(options.session_pid, std.posix.SIG.CHLD) catch return error.TtyControlTransferFailed; // Execute what the user requested switch (current_environment.display_server) { - .wayland, .shell, .custom => try executeCmd(log_file, allocator, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd), + .wayland, .shell, .custom => try executeCmd(log_file, allocator, io, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd), .xinitrc, .x11 => if (build_options.enable_x11_support) { var vt_buf: [5]u8 = undefined; const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.x_vt orelse options.tty}); - try log_file.info("auth/x11", "setting vt to {s}", .{vt}); - try executeX11Cmd(log_file, allocator, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt); + try log_file.info(io, "auth/x11", "setting vt to {s}", .{vt}); + try executeX11Cmd(log_file, allocator, io, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt); }, } } @@ -247,7 +253,7 @@ fn setXdgRuntimeDir(allocator: std.mem.Allocator) !void { // XDG_RUNTIME_DIR to fall back to directories inside user's home // directory. if (builtin.os.tag != .freebsd) { - const uid = std.posix.getuid(); + const uid = std.posix.system.getuid(); var uid_buffer: [32]u8 = undefined; // No UID can be larger than this const uid_str = try std.fmt.bufPrint(&uid_buffer, "/run/user/{d}", .{uid}); @@ -317,20 +323,20 @@ fn getFreeDisplay() !u8 { var buf: [15]u8 = undefined; var i: u8 = 0; while (i < 200) : (i += 1) { - const xlock = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{i}); - std.posix.access(xlock, std.posix.F_OK) catch break; + const xlock = try std.fmt.bufPrintZ(&buf, "/tmp/.X{d}-lock", .{i}); + if (interop.isError(std.posix.system.access(xlock.ptr, std.posix.F_OK))) break; } return i; } -fn getXPid(display_num: u8) !i32 { +fn getXPid(io: std.Io, display_num: u8) !i32 { var buf: [15]u8 = undefined; const file_name = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{display_num}); - const file = try std.fs.openFileAbsolute(file_name, .{}); - defer file.close(); + const file = try std.Io.Dir.openFileAbsolute(io, file_name, .{}); + defer file.close(io); var file_buffer: [32]u8 = undefined; - var file_reader = file.reader(&file_buffer); + var file_reader = file.reader(io, &file_buffer); var reader = &file_reader.interface; var buffer: [20]u8 = undefined; @@ -340,41 +346,41 @@ fn getXPid(display_num: u8) !i32 { return std.fmt.parseInt(i32, std.mem.trim(u8, buffer[0..written], " "), 10); } -fn createXauthFile(log_file: *LogFile, pwd: []const u8, buffer: []u8) ![]const u8 { +fn createXauthFile(log_file: *LogFile, io: std.Io, pwd: []const u8, buffer: []u8) ![]const u8 { var xauth_buf: [100]u8 = undefined; var xauth_dir: []const u8 = undefined; - const xdg_rt_dir = std.posix.getenv("XDG_RUNTIME_DIR"); + const xdg_rt_dir = std.posix.system.getenv("XDG_RUNTIME_DIR"); var xauth_file: []const u8 = "lyxauth"; if (xdg_rt_dir == null) no_rt_dir: { - const xdg_cfg_home = std.posix.getenv("XDG_CONFIG_HOME"); + const xdg_cfg_home = std.posix.system.getenv("XDG_CONFIG_HOME"); if (xdg_cfg_home == null) no_cfg_home: { xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config", .{pwd}); - var dir = std.fs.cwd().openDir(xauth_dir, .{}) catch { + var dir = std.Io.Dir.cwd().openDir(io, xauth_dir, .{}) catch { // xauth_dir isn't a directory xauth_dir = pwd; xauth_file = ".lyxauth"; break :no_cfg_home; }; - dir.close(); + dir.close(io); // xauth_dir is a directory, use it to store Xauthority xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config/ly", .{pwd}); } else { - xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?}); + xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{std.mem.span(xdg_cfg_home.?)}); } - const file = std.fs.cwd().openFile(xauth_dir, .{}) catch break :no_rt_dir; - file.close(); + const file = std.Io.Dir.cwd().openFile(io, xauth_dir, .{}) catch break :no_rt_dir; + file.close(io); // xauth_dir is a file, create the parent directory - std.posix.mkdir(xauth_dir, 777) catch { + std.Io.Dir.createDirAbsolute(io, xauth_dir, .fromMode(777)) catch { xauth_dir = pwd; xauth_file = ".lyxauth"; }; } else { - xauth_dir = xdg_rt_dir.?; + xauth_dir = std.mem.span(xdg_rt_dir.?); } // Trim trailing slashes @@ -384,19 +390,19 @@ fn createXauthFile(log_file: *LogFile, pwd: []const u8, buffer: []u8) ![]const u const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file }); - std.fs.cwd().makePath(trimmed_xauth_dir) catch {}; + std.Io.Dir.cwd().createDirPath(io, trimmed_xauth_dir) catch {}; - try log_file.info("auth/x11", "creating xauth file: {s}", .{xauthority}); + try log_file.info(io, "auth/x11", "creating xauth file: {s}", .{xauthority}); - const file = try std.fs.createFileAbsolute(xauthority, .{}); - file.close(); + const file = try std.Io.Dir.createFileAbsolute(io, xauthority, .{}); + file.close(io); return xauthority; } -fn mcookie() [Md5.digest_length * 2]u8 { +fn mcookie(io: std.Io) [Md5.digest_length * 2]u8 { var buf: [4096]u8 = undefined; - std.crypto.random.bytes(&buf); + io.random(&buf); var out: [Md5.digest_length]u8 = undefined; Md5.hash(&buf, &out, .{}); @@ -404,86 +410,87 @@ fn mcookie() [Md5.digest_length * 2]u8 { return std.fmt.bytesToHex(&out, .lower); } -fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, display_name: []u8, shell: [*:0]const u8, home: []const u8, xauth_buffer: []u8, options: AuthOptions) ![]const u8 { - const xauthority = try createXauthFile(log_file, home, xauth_buffer); +fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, display_name: []u8, shell: [*:0]const u8, home: []const u8, xauth_buffer: []u8, options: AuthOptions) ![]const u8 { + const xauthority = try createXauthFile(log_file, io, home, xauth_buffer); try interop.setEnvironmentVariable(allocator, "XAUTHORITY", xauthority, true); try interop.setEnvironmentVariable(allocator, "DISPLAY", display_name, true); - const magic_cookie = mcookie(); + const magic_cookie = mcookie(io); - const pid = try std.posix.fork(); + const pid = std.posix.system.fork(); if (pid == 0) { 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("auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str }); + 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.execveZ(shell, &args, std.c.environ) catch {}; + _ = std.posix.system.execve(shell, &args, std.c.environ); std.process.exit(1); } - const status = std.posix.waitpid(pid, 0); - if (status.status != 0) { - try log_file.file_writer.interface.print("xauth command failed with status {d}\n", .{status.status}); + var status: c_int = undefined; + const result = std.posix.system.waitpid(pid, &status, 0); + if (interop.isError(result) or status != 0) { + try log_file.file_writer.interface.print("xauth command failed with status {d}\n", .{status}); return error.XauthFailed; } return xauthority; } -fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, home: []const u8, options: AuthOptions, desktop_cmd: []const u8, vt: []const u8) !void { +fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, shell: []const u8, home: []const u8, options: AuthOptions, desktop_cmd: []const u8, vt: []const u8) !void { var xauth_buffer: [256]u8 = undefined; - try log_file.info("auth/x11", "getting free display", .{}); + try log_file.info(io, "auth/x11", "getting free display", .{}); const display_num = try getFreeDisplay(); var buf: [4]u8 = undefined; const display_name = try std.fmt.bufPrint(&buf, ":{d}", .{display_num}); - try log_file.info("auth/x11", "got free display: {d}", .{display_num}); + try log_file.info(io, "auth/x11", "got free display: {d}", .{display_num}); const shell_z = try allocator.dupeZ(u8, shell); defer allocator.free(shell_z); - try log_file.info("auth/x11", "creating xauth file", .{}); - const xauthority = try xauth(log_file, allocator, display_name, shell_z, home, &xauth_buffer, options); + try log_file.info(io, "auth/x11", "creating xauth file", .{}); + const xauthority = try xauth(log_file, allocator, io, display_name, shell_z, home, &xauth_buffer, options); - try log_file.info("auth/x11", "starting x server", .{}); - const pid = try std.posix.fork(); + try log_file.info(io, "auth/x11", "starting x server", .{}); + const pid = std.posix.system.fork(); if (pid == 0) { var cmd_buffer: [1024]u8 = undefined; const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} -auth {s}", .{ options.x_cmd, display_name, vt, xauthority }) catch std.process.exit(1); - try log_file.info("auth/x11", "executing: {s} -c {s} -auth {s}", .{ shell, cmd_str, xauthority }); + try log_file.info(io, "auth/x11", "executing: {s} -c {s} -auth {s}", .{ shell, cmd_str, xauthority }); const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str }; - std.posix.execveZ(shell_z, &args, std.c.environ) catch {}; + _ = std.posix.system.execve(shell_z, &args, std.c.environ); std.process.exit(1); } - try log_file.info("auth/x11", "waiting for xcb connection", .{}); + try log_file.info(io, "auth/x11", "waiting for xcb connection", .{}); var ok: c_int = -1; var xcb: ?*interop.xcb.xcb_connection_t = null; while (ok != 0) { xcb = interop.xcb.xcb_connect(null, null); ok = interop.xcb.xcb_connection_has_error(xcb); - std.posix.kill(pid, 0) catch |e| { + std.posix.kill(pid, @enumFromInt(0)) catch |e| { if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed; }; } // X Server detaches from the process. // PID can be fetched from /tmp/X{d}.lock - try log_file.info("auth/x11", "getting x server pid", .{}); - const x_pid = try getXPid(display_num); - try log_file.info("auth/x11", "got x server pid: {d}", .{x_pid}); + try log_file.info(io, "auth/x11", "getting x server pid", .{}); + const x_pid = try getXPid(io, display_num); + try log_file.info(io, "auth/x11", "got x server pid: {d}", .{x_pid}); - try log_file.info("auth/x11", "launching environment", .{}); - xorg_pid = try std.posix.fork(); + try log_file.info(io, "auth/x11", "launching environment", .{}); + xorg_pid = std.posix.system.fork(); if (xorg_pid == 0) { var cmd_buffer: [1024]u8 = undefined; const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} {s}", .{ if (options.use_kmscon_vt) "kmscon-launch-gui" else "", options.setup_cmd, options.login_cmd orelse "", desktop_cmd }) catch std.process.exit(1); - try log_file.info("auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str }); + try log_file.info(io, "auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str }); const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str }; - std.posix.execveZ(shell_z, &args, std.c.environ) catch {}; + _ = std.posix.system.execve(shell_z, &args, std.c.environ); std.process.exit(1); } @@ -495,26 +502,28 @@ fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []cons }; std.posix.sigaction(std.posix.SIG.TERM, &act, null); - _ = std.posix.waitpid(xorg_pid, 0); + var xorg_status: c_int = undefined; + _ = std.posix.system.waitpid(xorg_pid, &xorg_status, 0); - try log_file.info("auth/x11", "disconnecting xcb", .{}); + try log_file.info(io, "auth/x11", "disconnecting xcb", .{}); interop.xcb.xcb_disconnect(xcb); // TODO: Find a more robust way to ensure that X has been terminated (pidfds?) std.posix.kill(x_pid, std.posix.SIG.TERM) catch {}; - std.Thread.sleep(std.time.ns_per_s * 1); // Wait 1 second before sending SIGKILL + io.sleep(.fromSeconds(1), .real) catch {}; // Wait 1 second before sending SIGKILL std.posix.kill(x_pid, std.posix.SIG.KILL) catch return; - _ = std.posix.waitpid(x_pid, 0); + var x_status: c_int = undefined; + _ = std.posix.system.waitpid(x_pid, &x_status, 0); } -fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void { - try global_log_file.info("auth/sys", "launching wayland/shell/custom session", .{}); +fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void { + try global_log_file.info(io, "auth/sys", "launching wayland/shell/custom session", .{}); - var maybe_log_file: ?std.fs.File = null; + var maybe_log_file: ?std.Io.File = null; if (!is_terminal) redirect_streams: { if (options.use_kmscon_vt) { - try global_log_file.err("auth/sys", "cannot redirect stdio & stderr with kmscon", .{}); + try global_log_file.err(io, "auth/sys", "cannot redirect stdio & stderr with kmscon", .{}); break :redirect_streams; } @@ -522,11 +531,11 @@ fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: [] // we redirect standard output & error or not. That is, we redirect only // if it's equal to false (so if it's not running in a TTY). if (options.session_log) |log_path| { - try global_log_file.info("auth/sys", "setting up stdio & stderr redirection", .{}); - maybe_log_file = try redirectStandardStreams(global_log_file, log_path, true); + try global_log_file.info(io, "auth/sys", "setting up stdio & stderr redirection", .{}); + maybe_log_file = try redirectStandardStreams(global_log_file, io, log_path, true); } } - defer if (maybe_log_file) |log_file| log_file.close(); + defer if (maybe_log_file) |log_file| log_file.close(io); const shell_z = try allocator.dupeZ(u8, shell); defer allocator.free(shell_z); @@ -534,40 +543,42 @@ fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: [] var cmd_buffer: [1024]u8 = undefined; const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} {s}", .{ if (!is_terminal and options.use_kmscon_vt) "kmscon-launch-gui" else "", options.setup_cmd, options.login_cmd orelse "", exec_cmd orelse shell }); - try global_log_file.info("auth/sys", "executing: {s} -c {s}", .{ shell, cmd_str }); + try global_log_file.info(io, "auth/sys", "executing: {s} -c {s}", .{ shell, cmd_str }); const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str }; - return std.posix.execveZ(shell_z, &args, std.c.environ); + _ = std.posix.system.execve(shell_z, &args, std.c.environ); + return error.CmdExecveFailed; } -fn redirectStandardStreams(global_log_file: *LogFile, session_log: []const u8, create: bool) !std.fs.File { +fn redirectStandardStreams(global_log_file: *LogFile, io: std.Io, session_log: []const u8, create: bool) !std.Io.File { create_session_log_dir: { - const session_log_dir = std.fs.path.dirname(session_log) orelse break :create_session_log_dir; - std.fs.cwd().makePath(session_log_dir) catch |err| { - try global_log_file.err("auth/sys", "failed to create session log file directory: {s}", .{@errorName(err)}); + const session_log_dir = std.Io.Dir.path.dirname(session_log) orelse break :create_session_log_dir; + std.Io.Dir.cwd().createDirPath(io, session_log_dir) catch |err| { + try global_log_file.err(io, "auth/sys", "failed to create session log file directory: {s}", .{@errorName(err)}); return err; }; } - const log_file = if (create) (std.fs.cwd().createFile(session_log, .{ .mode = 0o666 }) catch |err| { - try global_log_file.err("auth/sys", "failed to create new session log file: {s}", .{@errorName(err)}); + const log_file = if (create) (std.Io.Dir.cwd().createFile(io, session_log, .{ .permissions = .fromMode(0o666) }) catch |err| { + try global_log_file.err(io, "auth/sys", "failed to create new session log file: {s}", .{@errorName(err)}); return err; - }) else (std.fs.cwd().openFile(session_log, .{ .mode = .read_write }) catch |err| { - try global_log_file.err("auth/sys", "failed to open existing session log file: {s}", .{@errorName(err)}); + }) else (std.Io.Dir.cwd().openFile(io, session_log, .{ .mode = .read_write }) catch |err| { + try global_log_file.err(io, "auth/sys", "failed to open existing session log file: {s}", .{@errorName(err)}); return err; }); - try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO); - try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO); + if (interop.isError(std.posix.system.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO))) return error.StdoutDup2Failed; + if (interop.isError(std.posix.system.dup2(log_file.handle, std.posix.STDOUT_FILENO))) return error.LogFileDup2Failed; return log_file; } -fn addUtmpEntry(entry: *Utmp, username: []const u8, pid: c_int) !void { +fn addUtmpEntry(io: std.Io, entry: *Utmp, username: []const u8, pid: c_int) !void { entry.ut_type = utmp.USER_PROCESS; entry.ut_pid = pid; - var buf: [std.fs.max_path_bytes]u8 = undefined; - const tty_path = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf); + var buf: [std.Io.Dir.max_path_bytes]u8 = undefined; + const length = try std.Io.File.stdin().realPath(io, &buf); + const tty_path = buf[0..length]; // Get the TTY name (i.e. without the /dev/ prefix) var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined; diff --git a/src/components/InfoLine.zig b/src/components/InfoLine.zig index 7a1a06b..97a8a73 100644 --- a/src/components/InfoLine.zig +++ b/src/components/InfoLine.zig @@ -23,6 +23,7 @@ label: *MessageLabel, pub fn init( allocator: Allocator, + io: std.Io, buffer: *TerminalBuffer, width: usize, arrow_fg: u32, @@ -32,6 +33,7 @@ pub fn init( .instance = null, .label = try MessageLabel.init( allocator, + io, buffer, drawItem, null, diff --git a/src/components/Session.zig b/src/components/Session.zig index 1abf9b9..1e975c6 100644 --- a/src/components/Session.zig +++ b/src/components/Session.zig @@ -24,6 +24,7 @@ user_list: *UserList, pub fn init( allocator: Allocator, + io: std.Io, buffer: *TerminalBuffer, user_list: *UserList, width: usize, @@ -35,6 +36,7 @@ pub fn init( .instance = null, .label = try EnvironmentLabel.init( allocator, + io, buffer, drawItem, sessionChanged, diff --git a/src/components/UserList.zig b/src/components/UserList.zig index 2131cae..c407173 100644 --- a/src/components/UserList.zig +++ b/src/components/UserList.zig @@ -26,6 +26,7 @@ label: *UserLabel, pub fn init( allocator: Allocator, + io: std.Io, buffer: *TerminalBuffer, usernames: StringList, saved_users: *SavedUsers, @@ -39,6 +40,7 @@ pub fn init( .instance = null, .label = try UserLabel.init( allocator, + io, buffer, drawItem, usernameChanged, diff --git a/src/config/custom.zig b/src/config/custom.zig index 28cded0..464316d 100644 --- a/src/config/custom.zig +++ b/src/config/custom.zig @@ -21,5 +21,5 @@ pub const CustomCommandInfo = struct { counter: u32 = 0, }; -pub var binds: std.StringArrayHashMap(CustomCommandBind) = undefined; -pub var labels: std.StringArrayHashMap(CustomCommandInfo) = undefined; +pub var binds: std.array_hash_map.String(CustomCommandBind) = undefined; +pub var labels: std.array_hash_map.String(CustomCommandInfo) = undefined; diff --git a/src/config/migrator.zig b/src/config/migrator.zig index 53e4011..366bc81 100644 --- a/src/config/migrator.zig +++ b/src/config/migrator.zig @@ -190,7 +190,7 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie const key = field.header["cmd:".len..]; const keyZ = temporary_allocator.dupe(u8, key) catch ""; if (!custom.binds.contains(key)) { - custom.binds.put(keyZ, .{}) catch {}; + custom.binds.put(temporary_allocator, keyZ, .{}) catch {}; } if (custom.binds.getPtr(keyZ)) |command| { if (std.mem.eql(u8, field.key, "name")) { @@ -206,7 +206,7 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie const key = field.header["lbl:".len..]; const keyZ = temporary_allocator.dupe(u8, key) catch ""; if (!custom.labels.contains(keyZ)) { - custom.labels.put(keyZ, .{ .name = keyZ }) catch {}; + custom.labels.put(temporary_allocator, keyZ, .{ .name = keyZ }) catch {}; } if (custom.labels.getPtr(keyZ)) |label| { if (std.mem.eql(u8, field.key, "cmd")) { @@ -254,12 +254,12 @@ pub fn lateConfigFieldHandler(config: *Config) void { } } -pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !?IniParser(OldSave) { - var save_parser = try IniParser(OldSave).init(allocator, path, null); +pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !?IniParser(OldSave) { + var save_parser = try IniParser(OldSave).init(allocator, io, path, null); errdefer save_parser.deinit(); var user_buf: [32]u8 = undefined; - const maybe_save = if (save_parser.maybe_load_error == null) save_parser.structure else tryMigrateFirstSaveFile(&user_buf); + const maybe_save = if (save_parser.maybe_load_error == null) save_parser.structure else tryMigrateFirstSaveFile(io, &user_buf); if (maybe_save) |save| { // Add all other users to the list @@ -282,16 +282,16 @@ pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, path: []const u8, sav return null; } -fn tryMigrateFirstSaveFile(user_buf: *[32]u8) ?OldSave { +fn tryMigrateFirstSaveFile(io: std.Io, user_buf: *[32]u8) ?OldSave { if (maybe_save_file) |path| { defer temporary_allocator.free(path); var save = OldSave{}; - var file = std.fs.openFileAbsolute(path, .{}) catch return null; - defer file.close(); + var file = std.Io.Dir.openFileAbsolute(io, path, .{}) catch return null; + defer file.close(io); var file_buffer: [64]u8 = undefined; - var file_reader = file.reader(&file_buffer); + var file_reader = file.reader(io, &file_buffer); var reader = &file_reader.interface; var user_writer = std.Io.Writer.fixed(user_buf); diff --git a/src/main.zig b/src/main.zig index bd9294c..ac112a2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -46,21 +46,21 @@ const Entry = Environment.Entry; const ly_version_str = "Ly version " ++ build_options.version; var session_pid: std.posix.pid_t = -1; -fn signalHandler(i: c_int) callconv(.c) void { +fn signalHandler(sig: std.posix.SIG) callconv(.c) void { if (session_pid == 0) return; // Forward signal to session to clean up if (session_pid > 0) { - _ = std.c.kill(session_pid, i); + _ = std.c.kill(session_pid, sig); var status: c_int = 0; _ = std.c.waitpid(session_pid, &status, 0); } TerminalBuffer.shutdown(); - std.c.exit(i); + std.c.exit(@intCast(@intFromEnum(sig))); } -fn ttyControlTransferSignalHandler(_: c_int) callconv(.c) void { +fn ttyControlTransferSignalHandler(_: std.posix.SIG) callconv(.c) void { TerminalBuffer.shutdown(); } @@ -68,6 +68,7 @@ const CustomBindLabel = struct { cmd: custom.CustomCommandBind, key: []const u8, lbl: Label, + io: std.Io, }; const CustomInfoLabel = struct { @@ -77,6 +78,7 @@ const CustomInfoLabel = struct { const UiState = struct { allocator: Allocator, + io: std.Io, auth_fails: u64, is_autologin: bool, use_kmscon_vt: bool, @@ -128,24 +130,26 @@ const UiState = struct { var shutdown = false; var restart = false; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var shutdown_cmd: []const u8 = undefined; var restart_cmd: []const u8 = undefined; var commands_allocated = false; var state: UiState = undefined; + state.io = init.io; + var stderr_buffer: [128]u8 = undefined; - var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer); + var stderr_writer = std.Io.File.stderr().writer(state.io, &stderr_buffer); var stderr = &stderr_writer.interface; defer { // 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.execv(temporary_allocator, &[_][]const u8{ "/bin/sh", "-c", shutdown_cmd }); + 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); } else if (restart) { - const restart_error = std.process.execv(temporary_allocator, &[_][]const u8{ "/bin/sh", "-c", restart_cmd }); + 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); } else { @@ -173,7 +177,7 @@ pub fn main() !void { var diag = clap.Diagnostic{}; var arg_parse_error: anyerror = undefined; - var maybe_res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .diagnostic = &diag, .allocator = state.allocator }) catch |err| parse_error: { + var maybe_res = clap.parse(clap.Help, ¶ms, clap.parsers.default, init.minimal.args, .{ .diagnostic = &diag, .allocator = state.allocator }) catch |err| parse_error: { arg_parse_error = err; diag.report(stderr, err) catch {}; try stderr.flush(); @@ -219,12 +223,12 @@ pub fn main() !void { state.allocator.free(state.old_save_path); }; - const config_path = try std.fs.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" }); + const config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" }); defer state.allocator.free(config_path); - custom.binds = .init(state.allocator); - custom.labels = .init(state.allocator); - var config_parser = try IniParser(Config).init(state.allocator, config_path, migrator.configFieldHandler); + custom.binds = .empty; + custom.labels = .empty; + var config_parser = try IniParser(Config).init(state.allocator, state.io, config_path, migrator.configFieldHandler); defer config_parser.deinit(); defer if (!shutdown or !restart) { var iter = custom.binds.iterator(); @@ -233,14 +237,14 @@ pub fn main() !void { temporary_allocator.free(i.value_ptr.*.cmd); temporary_allocator.free(i.value_ptr.*.name); } - custom.binds.deinit(); + custom.binds.deinit(temporary_allocator); var labelIter = custom.labels.iterator(); while (labelIter.next()) |i| { temporary_allocator.free(i.key_ptr.*); if (i.value_ptr.cmd) |cmd| temporary_allocator.free(cmd); } - custom.labels.deinit(); + custom.labels.deinit(temporary_allocator); }; state.config = config_parser.structure; @@ -248,17 +252,17 @@ pub fn main() !void { var lang_buffer: [16]u8 = undefined; const lang_file = try std.fmt.bufPrint(&lang_buffer, "{s}.ini", .{state.config.lang}); - const lang_path = try std.fs.path.join(state.allocator, &[_][]const u8{ config_parent_path, "lang", lang_file }); + const lang_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "lang", lang_file }); defer state.allocator.free(lang_path); - var lang_parser = try IniParser(Lang).init(state.allocator, lang_path, null); + var lang_parser = try IniParser(Lang).init(state.allocator, state.io, lang_path, null); defer lang_parser.deinit(); state.lang = lang_parser.structure; if (state.config.save) { - state.save_path = try std.fs.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.txt" }); - state.old_save_path = try std.fs.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.ini" }); + state.save_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.txt" }); + state.old_save_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.ini" }); save_path_alloc = true; } @@ -267,7 +271,7 @@ pub fn main() !void { } var maybe_uid_range_error: ?anyerror = null; - var usernames = try getAllUsernames(state.allocator, state.config.login_defs_path, &maybe_uid_range_error); + var usernames = try getAllUsernames(state.allocator, state.io, state.config.login_defs_path, &maybe_uid_range_error); defer { for (usernames.items) |username| state.allocator.free(username); usernames.deinit(state.allocator); @@ -276,7 +280,7 @@ pub fn main() !void { state.has_old_save = false; if (state.config.save) read_save_file: { - old_save_parser = migrator.tryMigrateIniSaveFile(state.allocator, state.old_save_path, &state.saved_users, usernames.items) catch break :read_save_file; + old_save_parser = migrator.tryMigrateIniSaveFile(state.allocator, state.io, state.old_save_path, &state.saved_users, usernames.items) catch break :read_save_file; // Don't read the new save file if the old one still exists if (old_save_parser != null) { @@ -284,11 +288,11 @@ pub fn main() !void { break :read_save_file; } - var save_file = std.fs.cwd().openFile(state.save_path, .{}) catch break :read_save_file; - defer save_file.close(); + var save_file = std.Io.Dir.cwd().openFile(state.io, state.save_path, .{}) catch break :read_save_file; + defer save_file.close(state.io); var file_buffer: [256]u8 = undefined; - var file_reader = save_file.reader(&file_buffer); + var file_reader = save_file.reader(state.io, &file_buffer); var reader = &file_reader.interface; const last_username_index_str = reader.takeDelimiterInclusive('\n') catch break :read_save_file; @@ -327,10 +331,10 @@ pub fn main() !void { var log_file_buffer: [1024]u8 = undefined; - state.log_file = try LogFile.init(state.config.ly_log, &log_file_buffer); - defer state.log_file.deinit(); + state.log_file = try LogFile.init(state.io, state.config.ly_log, &log_file_buffer); + defer state.log_file.deinit(state.io); - try state.log_file.info("tui", "using {s} vt", .{if (state.use_kmscon_vt) "kmscon" else "default"}); + try state.log_file.info(state.io, "tui", "using {s} vt", .{if (state.use_kmscon_vt) "kmscon" else "default"}); // These strings only end up getting freed if the user quits Ly using Ctrl+C, which is fine since in the other cases // we end up shutting down or restarting the system @@ -338,25 +342,27 @@ pub fn main() !void { restart_cmd = try temporary_allocator.dupe(u8, state.config.restart_cmd); commands_allocated = true; - if (state.config.start_cmd) |start_cmd| { - var start = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", start_cmd }, state.allocator); - start.stdout_behavior = .Inherit; - start.stderr_behavior = .Ignore; + if (state.config.start_cmd) |start_cmd| handle_start_cmd: { + var process = std.process.spawn(state.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", start_cmd }, + .stdout = .inherit, + .stderr = .ignore, + }) catch { + break :handle_start_cmd; + }; - handle_start_cmd: { - const process_result = start.spawnAndWait() catch { - break :handle_start_cmd; - }; - start_cmd_exit_code = process_result.Exited; - } + const process_result = process.wait(state.io) catch { + break :handle_start_cmd; + }; + start_cmd_exit_code = process_result.exited; } // Initialize terminal buffer - try state.log_file.info("tui", "initializing terminal buffer", .{}); + try state.log_file.info(state.io, "tui", "initializing terminal buffer", .{}); state.labels_max_length = @max(TerminalBuffer.strWidth(state.lang.login), TerminalBuffer.strWidth(state.lang.password)); var seed: u64 = undefined; - std.crypto.random.bytes(std.mem.asBytes(&seed)); // Get a random seed for the PRNG (used by animations) + state.io.random(std.mem.asBytes(&seed)); // Get a random seed for the PRNG (used by animations) var prng = std.Random.DefaultPrng.init(seed); const random = prng.random(); @@ -370,12 +376,13 @@ pub fn main() !void { }; state.buffer = try TerminalBuffer.init( state.allocator, + state.io, buffer_options, &state.log_file, random, ); defer { - state.log_file.info("tui", "shutting down terminal buffer", .{}) catch {}; + state.log_file.info(state.io, "tui", "shutting down terminal buffer", .{}) catch {}; state.buffer.deinit(); } @@ -586,6 +593,7 @@ pub fn main() !void { state.info_line = try InfoLine.init( state.allocator, + state.io, &state.buffer, state.box.width - 2 * state.box.horizontal_margin, state.buffer.fg, @@ -593,8 +601,8 @@ pub fn main() !void { ); defer state.info_line.deinit(); - try state.buffer.registerKeybind(&state.info_line.label.keybinds, "H", &viGoLeft, &state); - try state.buffer.registerKeybind(&state.info_line.label.keybinds, "L", &viGoRight, &state); + try state.buffer.registerKeybind(state.io, &state.info_line.label.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(state.io, &state.info_line.label.keybinds, "L", &viGoRight, &state); if (maybe_res == null) { var longest = diag.name.longest(); @@ -607,6 +615,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "cli", "unable to parse argument '{s}{s}': {s}", .{ longest.kind.prefix(), longest.name, @errorName(arg_parse_error) }, @@ -620,6 +629,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to get uid range: {s}; falling back to default", .{@errorName(err)}, @@ -633,6 +643,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to execute start command: exit code {d}", .{start_cmd_exit_code}, @@ -647,6 +658,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "conf", "unable to parse config file: {s}", .{@errorName(load_error)}, @@ -654,6 +666,7 @@ pub fn main() !void { for (config_parser.errors.items) |err| { try state.log_file.err( + state.io, "conf", "failed to convert value '{s}' of option '{s}' to type '{s}': {s}", .{ err.value, err.key, err.type_name, err.error_name }, @@ -668,6 +681,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to open log file", .{}, @@ -681,6 +695,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to set numlock: {s}", .{@errorName(err)}, @@ -699,6 +714,7 @@ pub fn main() !void { state.session = try Session.init( state.allocator, + state.io, &state.buffer, &state.login, state.box.width - 2 * state.box.horizontal_margin - state.labels_max_length - 1, @@ -708,8 +724,8 @@ pub fn main() !void { ); defer state.session.deinit(); - try state.buffer.registerKeybind(&state.session.label.keybinds, "H", &viGoLeft, &state); - try state.buffer.registerKeybind(&state.session.label.keybinds, "L", &viGoRight, &state); + try state.buffer.registerKeybind(state.io, &state.session.label.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(state.io, &state.session.label.keybinds, "L", &viGoRight, &state); state.login_label = Label.init( state.lang.login, @@ -723,6 +739,7 @@ pub fn main() !void { state.login = try UserList.init( state.allocator, + state.io, &state.buffer, usernames, &state.saved_users, @@ -734,8 +751,8 @@ pub fn main() !void { ); defer state.login.deinit(); - try state.buffer.registerKeybind(&state.login.label.keybinds, "H", &viGoLeft, &state); - try state.buffer.registerKeybind(&state.login.label.keybinds, "L", &viGoRight, &state); + try state.buffer.registerKeybind(state.io, &state.login.label.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(state.io, &state.login.label.keybinds, "L", &viGoRight, &state); if (state.config.shell) { addOtherEnvironment(&state.session, state.lang, .shell, null) catch |err| { @@ -745,6 +762,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to add shell environment: {s}", .{@errorName(err)}, @@ -761,6 +779,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to add xinitrc environment: {s}", .{@errorName(err)}, @@ -774,6 +793,7 @@ pub fn main() !void { state.config.fg, ); try state.log_file.info( + state.io, "comp", "x11 support disabled at compile-time", .{}, @@ -786,9 +806,10 @@ pub fn main() !void { if (state.config.waylandsessions) |waylandsessions| { var wayland_session_dirs = std.mem.splitScalar(u8, waylandsessions, ':'); while (wayland_session_dirs.next()) |dir| { - crawl(&state.session, state.lang, dir, .wayland) catch |err| { + crawl(&state.session, state.io, state.lang, dir, .wayland) catch |err| { has_crawl_error = true; try state.log_file.err( + state.io, "sys", "failed to crawl wayland session directory '{s}': {s}", .{ dir, @errorName(err) }, @@ -801,9 +822,10 @@ pub fn main() !void { if (state.config.xsessions) |xsessions| { var x_session_dirs = std.mem.splitScalar(u8, xsessions, ':'); while (x_session_dirs.next()) |dir| { - crawl(&state.session, state.lang, dir, .x11) catch |err| { + crawl(&state.session, state.io, state.lang, dir, .x11) catch |err| { has_crawl_error = true; try state.log_file.err( + state.io, "sys", "failed to crawl x11 session directory '{s}': {s}", .{ dir, @errorName(err) }, @@ -815,9 +837,10 @@ pub fn main() !void { var custom_session_dirs = std.mem.splitScalar(u8, state.config.custom_sessions, ':'); while (custom_session_dirs.next()) |dir| { - crawl(&state.session, state.lang, dir, .custom) catch |err| { + crawl(&state.session, state.io, state.lang, dir, .custom) catch |err| { has_crawl_error = true; try state.log_file.err( + state.io, "sys", "failed to crawl custom session directory '{s}': {s}", .{ dir, @errorName(err) }, @@ -839,7 +862,7 @@ pub fn main() !void { // accounts *and* no root account...but at this point, if that's the // case, you have bigger problems to deal with in the first place. :D try state.info_line.addMessage(state.lang.err_no_users, state.config.error_bg, state.config.error_fg); - try state.log_file.err("sys", "no users found", .{}); + try state.log_file.err(state.io, "sys", "no users found", .{}); } state.password_label = Label.init( @@ -856,6 +879,7 @@ pub fn main() !void { state.password = try Text.init( state.allocator, + state.io, &state.buffer, state.insert_mode, true, @@ -866,8 +890,8 @@ pub fn main() !void { ); defer state.password.deinit(); - try state.buffer.registerKeybind(&state.password.keybinds, "H", &viGoLeft, &state); - try state.buffer.registerKeybind(&state.password.keybinds, "L", &viGoRight, &state); + try state.buffer.registerKeybind(state.io, &state.password.keybinds, "H", &viGoLeft, &state); + try state.buffer.registerKeybind(state.io, &state.password.keybinds, "L", &viGoRight, &state); state.password_widget = state.password.widget(); @@ -894,6 +918,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "auth", "autologin failed: username '{s}' not found", .{auto_user}, @@ -903,6 +928,7 @@ pub fn main() !void { const session_index = findSessionByName(&state.session, auto_session) orelse { try state.log_file.err( + state.io, "auth", "autologin failed: session '{s}' not found", .{auto_session}, @@ -915,6 +941,7 @@ pub fn main() !void { break :check_autologin; }; try state.log_file.info( + state.io, "auth", "attempting autologin for user '{s}' with session '{s}'", .{ auto_user, auto_session }, @@ -931,13 +958,14 @@ pub fn main() !void { } // Switch to selected TTY - state.active_tty = interop.getActiveTty(state.allocator, state.use_kmscon_vt) catch |err| no_tty_found: { + state.active_tty = interop.getActiveTty(state.allocator, state.io, state.use_kmscon_vt) catch |err| no_tty_found: { try state.info_line.addMessage( state.lang.err_get_active_tty, state.config.error_bg, state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to get active tty: {s}", .{@errorName(err)}, @@ -952,6 +980,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to switch to tty {d}: {s}", .{ state.active_tty, @errorName(err) }, @@ -1025,6 +1054,7 @@ pub fn main() !void { .dur_file => { var dur = try DurFile.init( state.allocator, + state.io, &state.buffer, &state.log_file, state.config.dur_file_path, @@ -1042,6 +1072,7 @@ pub fn main() !void { defer if (animation) |a| a.deinit(); var cascade = Cascade.init( + state.io, &state.buffer, &state.auth_fails, state.config.auth_fails, @@ -1140,6 +1171,7 @@ pub fn main() !void { ), .cmd = i.value_ptr.*, .key = i.key_ptr.*, + .io = state.io, }); state.custom_binds.items[state.custom_binds.items.len - 1].lbl.allocator = state.allocator; } @@ -1208,26 +1240,26 @@ pub fn main() !void { } for (state.custom_binds.items) |*item| { - try state.buffer.registerGlobalKeybind(item.key, &customCommand, item); + try state.buffer.registerGlobalKeybind(state.io, item.key, &customCommand, item); } - try state.buffer.registerGlobalKeybind("Esc", &disableInsertMode, &state); - try state.buffer.registerGlobalKeybind("I", &enableInsertMode, &state); + try state.buffer.registerGlobalKeybind(state.io, "Esc", &disableInsertMode, &state); + try state.buffer.registerGlobalKeybind(state.io, "I", &enableInsertMode, &state); - try state.buffer.registerGlobalKeybind("Ctrl+C", &quit, &state); + try state.buffer.registerGlobalKeybind(state.io, "Ctrl+C", &quit, &state); - try state.buffer.registerGlobalKeybind("K", &viMoveCursorUp, &state); - try state.buffer.registerGlobalKeybind("J", &viMoveCursorDown, &state); + try state.buffer.registerGlobalKeybind(state.io, "K", &viMoveCursorUp, &state); + try state.buffer.registerGlobalKeybind(state.io, "J", &viMoveCursorDown, &state); - try state.buffer.registerGlobalKeybind("Enter", &authenticate, &state); + try state.buffer.registerGlobalKeybind(state.io, "Enter", &authenticate, &state); - try state.buffer.registerGlobalKeybind(state.config.shutdown_key, &shutdownCmd, &state); - try state.buffer.registerGlobalKeybind(state.config.restart_key, &restartCmd, &state); - try state.buffer.registerGlobalKeybind(state.config.show_password_key, &togglePasswordMask, &state); - if (state.config.sleep_cmd != null) try state.buffer.registerGlobalKeybind(state.config.sleep_key, &sleepCmd, &state); - if (state.config.hibernate_cmd != null) try state.buffer.registerGlobalKeybind(state.config.hibernate_key, &hibernateCmd, &state); - if (state.config.brightness_down_key) |key| try state.buffer.registerGlobalKeybind(key, &decreaseBrightnessCmd, &state); - if (state.config.brightness_up_key) |key| try state.buffer.registerGlobalKeybind(key, &increaseBrightnessCmd, &state); + try state.buffer.registerGlobalKeybind(state.io, state.config.shutdown_key, &shutdownCmd, &state); + try state.buffer.registerGlobalKeybind(state.io, state.config.restart_key, &restartCmd, &state); + try state.buffer.registerGlobalKeybind(state.io, state.config.show_password_key, &togglePasswordMask, &state); + if (state.config.sleep_cmd != null) try state.buffer.registerGlobalKeybind(state.io, state.config.sleep_key, &sleepCmd, &state); + if (state.config.hibernate_cmd != null) try state.buffer.registerGlobalKeybind(state.io, state.config.hibernate_key, &hibernateCmd, &state); + if (state.config.brightness_down_key) |key| try state.buffer.registerGlobalKeybind(state.io, key, &decreaseBrightnessCmd, &state); + if (state.config.brightness_up_key) |key| try state.buffer.registerGlobalKeybind(state.io, key, &increaseBrightnessCmd, &state); if (state.config.initial_info_text) |text| { try state.info_line.addMessage(text, state.config.bg, state.config.fg); @@ -1241,6 +1273,7 @@ pub fn main() !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to get hostname: {s}", .{@errorName(err)}, @@ -1268,6 +1301,7 @@ pub fn main() !void { try state.buffer.runEventLoop( state.allocator, + state.io, shared_error, widgets.items, active_widget, @@ -1329,31 +1363,31 @@ fn enableInsertMode(ptr: *anyopaque) !bool { } fn viGoLeft(ptr: *anyopaque) !bool { - var self: *UiState = @ptrCast(@alignCast(ptr)); - if (self.insert_mode) return true; + var state: *UiState = @ptrCast(@alignCast(ptr)); + if (state.insert_mode) return true; - return try self.buffer.simulateKeybind("Left"); + return try state.buffer.simulateKeybind(state.io, "Left"); } fn viGoRight(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.insert_mode) return true; - return try state.buffer.simulateKeybind("Right"); + return try state.buffer.simulateKeybind(state.io, "Right"); } fn viMoveCursorUp(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.insert_mode) return true; - return try state.buffer.simulateKeybind("Up"); + return try state.buffer.simulateKeybind(state.io, "Up"); } fn viMoveCursorDown(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.insert_mode) return true; - return try state.buffer.simulateKeybind("Down"); + return try state.buffer.simulateKeybind(state.io, "Down"); } fn togglePasswordMask(ptr: *anyopaque) !bool { @@ -1373,18 +1407,21 @@ fn quit(ptr: *anyopaque) !bool { fn customCommand(ptr: *anyopaque) !bool { const lbl: *CustomBindLabel = @ptrCast(@alignCast(ptr)); - var proc = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", lbl.cmd.cmd }, lbl.lbl.allocator.?); - proc.stdout_behavior = .Ignore; - proc.stderr_behavior = .Ignore; - const res = proc.spawnAndWait() catch return false; - if (res.Exited != 0) return error.CommandFailed; + var proc = std.process.spawn(lbl.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", lbl.cmd.cmd }, + .stdout = .ignore, + .stderr = .ignore, + }) catch return false; + + const res = proc.wait(lbl.io) catch return false; + if (res.exited != 0) return error.CommandFailed; return false; } fn authenticate(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); - try state.log_file.info("auth", "starting authentication", .{}); + try state.log_file.info(state.io, "auth", "starting authentication", .{}); if (!state.config.allow_empty_password and state.password.text.items.len == 0) { // Let's not log this message for security reasons @@ -1400,6 +1437,7 @@ fn authenticate(ptr: *anyopaque) !bool { state.config.error_fg, ); try state.log_file.err( + state.io, "tui", "failed to clear info line: {s}", .{@errorName(err)}, @@ -1422,6 +1460,7 @@ fn authenticate(ptr: *anyopaque) !bool { state.config.error_fg, ); try state.log_file.err( + state.io, "tui", "failed to clear info line: {s}", .{@errorName(err)}, @@ -1435,23 +1474,25 @@ fn authenticate(ptr: *anyopaque) !bool { // handling, so let's just report a generic error message, // that should be good enough for debugging anyway. errdefer state.log_file.err( + state.io, "conf", "failed to save current user data", .{}, ) catch {}; - var file = std.fs.cwd().createFile(state.save_path, .{}) catch |err| { + var file = std.Io.Dir.cwd().createFile(state.io, state.save_path, .{}) catch |err| { state.log_file.err( + state.io, "sys", "failed to create save file: {s}", .{@errorName(err)}, ) catch break :save_last_settings; break :save_last_settings; }; - defer file.close(); + defer file.close(state.io); var file_buffer: [256]u8 = undefined; - var file_writer = file.writer(&file_buffer); + var file_writer = file.writer(state.io, &file_buffer); var writer = &file_writer.interface; try writer.print("{d}\n", .{state.login.label.current}); @@ -1462,9 +1503,9 @@ fn authenticate(ptr: *anyopaque) !bool { // Delete previous save file if it exists if (migrator.maybe_save_file) |path| { - std.fs.cwd().deleteFile(path) catch {}; + std.Io.Dir.cwd().deleteFile(state.io, path) catch {}; } else if (state.has_old_save) { - std.fs.cwd().deleteFile(state.old_save_path) catch {}; + std.Io.Dir.cwd().deleteFile(state.io, state.old_save_path) catch {}; } } @@ -1472,9 +1513,9 @@ fn authenticate(ptr: *anyopaque) !bool { defer shared_err.deinit(); { - state.log_file.deinit(); + state.log_file.deinit(state.io); - session_pid = try std.posix.fork(); + session_pid = std.posix.system.fork(); if (session_pid == 0) { const current_environment = state.session.label.list.items[state.session.label.current].environment; @@ -1504,10 +1545,11 @@ fn authenticate(ptr: *anyopaque) !bool { }; std.posix.sigaction(std.posix.SIG.CHLD, &tty_control_transfer_act, null); - try state.log_file.reinit(); + try state.log_file.reinit(state.io); auth.authenticate( state.allocator, + state.io, &state.log_file, auth_options, current_environment, @@ -1516,21 +1558,22 @@ fn authenticate(ptr: *anyopaque) !bool { ) catch |err| { shared_err.writeError(err); - state.log_file.deinit(); + state.log_file.deinit(state.io); std.process.exit(1); }; - state.log_file.deinit(); + state.log_file.deinit(state.io); std.process.exit(0); } - _ = std.posix.waitpid(session_pid, 0); + var session_status: c_int = undefined; + _ = std.posix.system.waitpid(session_pid, &session_status, 0); // HACK: It seems like the session process is not exiting immediately after the waitpid call. // This is a workaround to ensure the session process has exited before re-initializing the TTY. - std.Thread.sleep(std.time.ns_per_s * 1); + state.io.sleep(.fromSeconds(1), .real) catch {}; session_pid = -1; - try state.log_file.reinit(); + try state.log_file.reinit(state.io); } try state.buffer.reclaim(); @@ -1546,6 +1589,7 @@ fn authenticate(ptr: *anyopaque) !bool { state.config.error_fg, ); try state.log_file.err( + state.io, "auth", "failed to authenticate: {s}", .{@errorName(err)}, @@ -1553,9 +1597,11 @@ fn authenticate(ptr: *anyopaque) !bool { if (state.config.clear_password or err != error.PamAuthError) state.password.clear(); } else { - if (state.config.logout_cmd) |logout_cmd| { - var logout_process = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", logout_cmd }, state.allocator); - _ = logout_process.spawnAndWait() catch .{}; + if (state.config.logout_cmd) |logout_cmd| execute_cmd: { + var process = std.process.spawn(state.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", logout_cmd }, + }) catch break :execute_cmd; + _ = process.wait(state.io) catch {}; } state.password.clear(); @@ -1565,7 +1611,7 @@ fn authenticate(ptr: *anyopaque) !bool { state.config.bg, state.config.fg, ); - try state.log_file.info("auth", "logged out", .{}); + try state.log_file.info(state.io, "auth", "logged out", .{}); } if (state.config.auth_fails == 0 or state.auth_fails < state.config.auth_fails) { @@ -1599,21 +1645,24 @@ fn sleepCmd(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.config.sleep_cmd) |sleep_cmd| { - var sleep = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, state.allocator); - sleep.stdout_behavior = .Ignore; - sleep.stderr_behavior = .Ignore; + var process = std.process.spawn(state.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, + .stdout = .ignore, + .stderr = .ignore, + }) catch return false; - const process_result = sleep.spawnAndWait() catch return false; - if (process_result.Exited != 0) { + const process_result = process.wait(state.io) catch return false; + if (process_result.exited != 0) { try state.info_line.addMessage( state.lang.err_sleep, state.config.error_bg, state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to execute sleep command: exit code {d}", - .{process_result.Exited}, + .{process_result.exited}, ); } } @@ -1624,21 +1673,24 @@ fn hibernateCmd(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.config.hibernate_cmd) |hibernate_cmd| { - var hibernate = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", hibernate_cmd }, state.allocator); - hibernate.stdout_behavior = .Ignore; - hibernate.stderr_behavior = .Ignore; + var process = std.process.spawn(state.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", hibernate_cmd }, + .stdout = .ignore, + .stderr = .ignore, + }) catch return false; - const process_result = hibernate.spawnAndWait() catch return false; - if (process_result.Exited != 0) { + const process_result = process.wait(state.io) catch return false; + if (process_result.exited != 0) { try state.info_line.addMessage( state.lang.err_hibernate, state.config.error_bg, state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to execute hibernate command: exit code {d}", - .{process_result.Exited}, + .{process_result.exited}, ); } } @@ -1648,13 +1700,14 @@ fn hibernateCmd(ptr: *anyopaque) !bool { fn decreaseBrightnessCmd(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); - adjustBrightness(state.allocator, state.config.brightness_down_cmd) catch |err| { + adjustBrightness(state.io, state.config.brightness_down_cmd) catch |err| { try state.info_line.addMessage( state.lang.err_brightness_change, state.config.error_bg, state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to decrease brightness: {s}", .{@errorName(err)}, @@ -1666,13 +1719,14 @@ fn decreaseBrightnessCmd(ptr: *anyopaque) !bool { fn increaseBrightnessCmd(ptr: *anyopaque) !bool { var state: *UiState = @ptrCast(@alignCast(ptr)); - adjustBrightness(state.allocator, state.config.brightness_up_cmd) catch |err| { + adjustBrightness(state.io, state.config.brightness_up_cmd) catch |err| { try state.info_line.addMessage( state.lang.err_brightness_change, state.config.error_bg, state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to increase brightness: {s}", .{@errorName(err)}, @@ -1692,6 +1746,7 @@ fn updateNumlock(self: *Label, ptr: *anyopaque) !void { state.config.error_fg, ); try state.log_file.err( + state.io, "sys", "failed to get lock state: {s}", .{@errorName(err)}, @@ -1708,7 +1763,7 @@ fn updateCapslock(self: *Label, ptr: *anyopaque) !void { const lock_state = interop.getLockState() catch |err| { self.update_fn = null; try state.info_line.addMessage(state.lang.err_lock_state, state.config.error_bg, state.config.error_fg); - try state.log_file.err("sys", "failed to get lock state: {s}", .{@errorName(err)}); + try state.log_file.err(state.io, "sys", "failed to get lock state: {s}", .{@errorName(err)}); return; }; @@ -1719,9 +1774,10 @@ fn updateBattery(self: *Label, ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.config.battery_id) |id| { - const battery_percentage = getBatteryPercentage(id) catch |err| { + const battery_percentage = getBatteryPercentage(state.io, id) catch |err| { self.update_fn = null; try state.log_file.err( + state.io, "sys", "failed to get battery percentage: {s}", .{@errorName(err)}, @@ -1746,7 +1802,7 @@ fn updateClock(self: *Label, ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); if (state.config.clock) |clock| draw_clock: { - const clock_str = interop.timeAsString(&state.clock_buf, clock); + const clock_str = interop.timeAsString(state.io, &state.clock_buf, clock); if (clock_str.len == 0) { self.update_fn = null; @@ -1756,6 +1812,7 @@ fn updateClock(self: *Label, ptr: *anyopaque) !void { state.config.error_fg, ); try state.log_file.err( + state.io, "tui", "clock string too long", .{}, @@ -1770,11 +1827,7 @@ fn updateClock(self: *Label, ptr: *anyopaque) !void { fn updateCustomInfo(lbl: *Label, ptr: *anyopaque) !void { const state: *UiState = @ptrCast(@alignCast(ptr)); const wid = lbl.widget().id; - var stdout = std.ArrayList(u8).empty; - defer stdout.deinit(state.allocator); - var stderr = std.ArrayList(u8).empty; - defer stderr.deinit(state.allocator); for (state.custom_info.items) |*i| { if (i.info.id != wid) continue; // Here, a counter ticks down every time `updateCustomInfo` runs on that @@ -1782,37 +1835,40 @@ fn updateCustomInfo(lbl: *Label, ptr: *anyopaque) !void { // once it reaches to 1. If a refresh value is defined it's then reset to // that refresh value. if (i.info.counter == 1) { - var c = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", i.info.cmd orelse custom.UNDEFINED_CMD }, state.allocator); - c.stderr_behavior = .Pipe; - c.stdout_behavior = .Pipe; - try c.spawn(); + var c = try std.process.spawn(state.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", i.info.cmd orelse custom.UNDEFINED_CMD }, + .stdout = .pipe, + .stderr = .pipe, + }); - c.collectOutput(state.allocator, &stdout, &stderr, state.buffer.width) catch { - try stdout.print(state.allocator, "{s}: [{s}]", .{ i.info.name, state.lang.custom_info_err_output_long }); + var stdout_buffer: [1024]u8 = undefined; + var stdout_file_reader = c.stdout.?.reader(state.io, &stdout_buffer); + + const stdout = stdout_file_reader.interface.allocRemaining(state.allocator, .limited(state.buffer.width)) catch alloc_error: { + break :alloc_error try std.fmt.allocPrint(state.allocator, "{s}: [{s}]", .{ i.info.name, state.lang.custom_info_err_output_long }); }; + defer state.allocator.free(stdout); - const newlineIdx = std.mem.indexOfAny(u8, stdout.items, "\n"); - if (newlineIdx) |idx| { - stdout.shrinkAndFree(state.allocator, idx); - } + var cur_stdout = stdout; + const newline_index = std.mem.indexOfAny(u8, stdout, "\n"); + if (newline_index) |idx| cur_stdout = stdout[0..idx]; - if (stdout.items.len > state.buffer.width) { - stdout.clearRetainingCapacity(); - try stdout.print(state.allocator, "{s}: [{s}]", .{ i.info.name, state.lang.custom_info_err_output_long }); - } - - _ = try c.wait(); + _ = try c.wait(state.io); // Sometimes, the output of a command would have an unprintable character at // the end of its output, causing '�' (U+FFFD) to appear in its place. Here, we check // if this is the case and remove it. - if (stdout.items.len != 0 and !std.ascii.isPrint(stdout.items[stdout.items.len - 1])) { - _ = stdout.pop(); - } else if (stdout.items.len == 0) { - try stdout.print(state.allocator, "{s}: [{s}{s}]", .{ i.info.name, state.lang.custom_info_err_no_output, if (stderr.items.len > 0) state.lang.custom_info_err_no_output_error else "" }); + if (cur_stdout.len != 0 and !std.ascii.isPrint(cur_stdout[cur_stdout.len - 1])) { + cur_stdout = cur_stdout[0 .. cur_stdout.len - 1]; } + state.allocator.free(lbl.text); - try lbl.setTextAlloc(state.allocator, "{s}", .{stdout.items}); + if (cur_stdout.len == 0) { + const stderr_length = try c.stderr.?.length(state.io); + try lbl.setTextAlloc(state.allocator, "{s}: [{s}{s}]", .{ i.info.name, state.lang.custom_info_err_no_output, if (stderr_length > 0) state.lang.custom_info_err_no_output_error else "" }); + } else { + try lbl.setTextAlloc(state.allocator, "{s}", .{cur_stdout}); + } // Called to re-position the widgets after they receive their output. try positionWidgets(state); @@ -1851,7 +1907,7 @@ fn updateBigClock(self: *BigLabel, ptr: *anyopaque) !void { }, ); - const clock_str = interop.timeAsString(&state.bigclock_buf, format); + const clock_str = interop.timeAsString(state.io, &state.bigclock_buf, format); self.setText(clock_str); } @@ -2015,27 +2071,28 @@ fn positionWidgets(ptr: *anyopaque) !void { fn handleInactivity(ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); - if (state.config.inactivity_cmd) |inactivity_cmd| { - var inactivity = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", inactivity_cmd }, state.allocator); - inactivity.stdout_behavior = .Ignore; - inactivity.stderr_behavior = .Ignore; + if (state.config.inactivity_cmd) |inactivity_cmd| handle_inactivity_cmd: { + var process = std.process.spawn(state.io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", inactivity_cmd }, + .stdout = .ignore, + .stderr = .ignore, + }) catch break :handle_inactivity_cmd; - handle_inactivity_cmd: { - const process_result = inactivity.spawnAndWait() catch { - break :handle_inactivity_cmd; - }; - if (process_result.Exited != 0) { - try state.info_line.addMessage( - state.lang.err_inactivity, - state.config.error_bg, - state.config.error_fg, - ); - try state.log_file.err( - "sys", - "failed to execute inactivity command: exit code {d}", - .{process_result.Exited}, - ); - } + const process_result = process.wait(state.io) catch { + break :handle_inactivity_cmd; + }; + if (process_result.exited != 0) { + try state.info_line.addMessage( + state.lang.err_inactivity, + state.config.error_bg, + state.config.error_fg, + ); + try state.log_file.err( + state.io, + "sys", + "failed to execute inactivity command: exit code {d}", + .{process_result.exited}, + ); } } } @@ -2059,26 +2116,26 @@ fn addOtherEnvironment(session: *Session, lang: Lang, display_server: DisplaySer }); } -fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: DisplayServer) !void { - if (!std.fs.path.isAbsolute(path)) return error.PathNotAbsolute; +fn crawl(session: *Session, io: std.Io, lang: Lang, path: []const u8, display_server: DisplayServer) !void { + if (!std.Io.Dir.path.isAbsolute(path)) return error.PathNotAbsolute; - var iterable_directory = try std.fs.openDirAbsolute(path, .{ .iterate = true }); - defer iterable_directory.close(); + var iterable_directory = try std.Io.Dir.openDirAbsolute(io, path, .{ .iterate = true }); + defer iterable_directory.close(io); var iterator = iterable_directory.iterate(); - while (try iterator.next()) |item| { - if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue; + while (try iterator.next(io)) |item| { + if (!std.mem.eql(u8, std.Io.Dir.path.extension(item.name), ".desktop")) continue; const entry_path = try std.fmt.allocPrint(session.label.allocator, "{s}/{s}", .{ path, item.name }); defer session.label.allocator.free(entry_path); var entry_ini = Ini(Entry).init(session.label.allocator); - const data = try entry_ini.readFileToStruct(entry_path, .{ + const data = try entry_ini.readFileToStruct(io, entry_path, .{ .fieldHandler = null, .comment_characters = "#", }); errdefer entry_ini.deinit(); - const file_name = try session.label.allocator.dupe(u8, std.fs.path.stem(item.name)); + const file_name = try session.label.allocator.dupe(u8, std.Io.Dir.path.stem(item.name)); const entry = data.@"Desktop Entry"; var maybe_xdg_session_desktop: ?[]const u8 = null; var maybe_xdg_desktop_names: ?[]const u8 = null; @@ -2138,8 +2195,8 @@ fn findSessionByName(session: *Session, name: []const u8) ?usize { return null; } -fn getAllUsernames(allocator: Allocator, login_defs_path: []const u8, uid_range_error: *?anyerror) !StringList { - const uid_range = interop.getUserIdRange(allocator, login_defs_path) catch |err| no_uid_range: { +fn getAllUsernames(allocator: Allocator, io: std.Io, login_defs_path: []const u8, uid_range_error: *?anyerror) !StringList { + const uid_range = interop.getUserIdRange(allocator, io, login_defs_path) catch |err| no_uid_range: { uid_range_error.* = err; break :no_uid_range UidRange{ .uid_min = build_options.fallback_uid_min, @@ -2185,29 +2242,31 @@ fn getAllUsernames(allocator: Allocator, login_defs_path: []const u8, uid_range_ return usernames; } -fn adjustBrightness(allocator: Allocator, cmd: []const u8) !void { - var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", cmd }, allocator); - brightness.stdout_behavior = .Ignore; - brightness.stderr_behavior = .Ignore; +fn adjustBrightness(io: std.Io, cmd: []const u8) !void { + var process = std.process.spawn(io, .{ + .argv = &[_][]const u8{ "/bin/sh", "-c", cmd }, + .stdout = .ignore, + .stderr = .ignore, + }) catch return; - const process_result = brightness.spawnAndWait() catch return; - if (process_result.Exited != 0) { + const process_result = process.wait(io) catch return; + if (process_result.exited != 0) { return error.BrightnessChangeFailed; } } -fn getBatteryPercentage(battery_id: []const u8) !u8 { +fn getBatteryPercentage(io: std.Io, battery_id: []const u8) !u8 { const path = try std.fmt.allocPrint(temporary_allocator, "/sys/class/power_supply/{s}/capacity", .{battery_id}); defer temporary_allocator.free(path); - const battery_file = try std.fs.cwd().openFile(path, .{}); - defer battery_file.close(); + const battery_file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer battery_file.close(io); var buffer: [8]u8 = undefined; - const bytes_read = try battery_file.read(&buffer); + const bytes_read = try battery_file.readStreaming(io, &.{&buffer}); const capacity_str = buffer[0..bytes_read]; - const trimmed = std.mem.trimRight(u8, capacity_str, "\n\r"); + const trimmed = std.mem.trimEnd(u8, capacity_str, "\n\r"); return try std.fmt.parseInt(u8, trimmed, 10); } From 4f45d92ea87315a32f1413c17d5b14c6fd1b7e9b Mon Sep 17 00:00:00 2001 From: RadsammyT Date: Sun, 26 Apr 2026 09:37:39 +0200 Subject: [PATCH 02/10] Fix: battery label positioning, custom keybinds not disappearing on `hide_key_hints = false` (#970) ## What are the changes about? Fixes battery label positioning in regards to custom binds. Since the label was on the top-left, it only accounted for the first line of built-in keybinds, and it didn't account for the other lines of custom ones. This also fixes the custom keybinds not disappearing on `hide_key_hints = false`, which is my bad. whoops. Also, with https://codeberg.org/fairyglade/ly/pulls/963 being a thing, we should probably think about deprecating this hardcoded battery label in favor of a custom label command, top-left by default. ## What existing issue does this resolve? N/A ## Pre-requisites - [x] I have tested & confirmed the changes work locally - [x] I have run `zig fmt` throughout my changes Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/970 Reviewed-by: AnErrupTion --- src/main.zig | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/main.zig b/src/main.zig index ac112a2..53400df 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1153,33 +1153,33 @@ pub fn main(init: std.process.Init) !void { }; var iter = custom.binds.iterator(); - while (iter.next()) |i| { - var concat = try std.mem.concat(state.allocator, u8, &[_][]const u8{ i.key_ptr.*, " ", i.value_ptr.name }); - inline for (@typeInfo(Lang).@"struct".fields) |lang_key| { - const new = try std.mem.replaceOwned(u8, state.allocator, concat, "$" ++ lang_key.name, @field(state.lang, lang_key.name)); - state.allocator.free(concat); - concat = new; - } - try state.custom_binds.append(state.allocator, .{ - .lbl = .init( - concat, - null, - state.buffer.fg, - state.buffer.bg, - null, - null, - ), - .cmd = i.value_ptr.*, - .key = i.key_ptr.*, - .io = state.io, - }); - state.custom_binds.items[state.custom_binds.items.len - 1].lbl.allocator = state.allocator; - } defer for (state.custom_binds.items) |*i| { i.lbl.deinit(); }; if (!state.config.hide_key_hints) { + while (iter.next()) |i| { + var concat = try std.mem.concat(state.allocator, u8, &[_][]const u8{ i.key_ptr.*, " ", i.value_ptr.name }); + inline for (@typeInfo(Lang).@"struct".fields) |lang_key| { + const new = try std.mem.replaceOwned(u8, state.allocator, concat, "$" ++ lang_key.name, @field(state.lang, lang_key.name)); + state.allocator.free(concat); + concat = new; + } + try state.custom_binds.append(state.allocator, .{ + .lbl = .init( + concat, + null, + state.buffer.fg, + state.buffer.bg, + null, + null, + ), + .cmd = i.value_ptr.*, + .key = i.key_ptr.*, + .io = state.io, + }); + state.custom_binds.items[state.custom_binds.items.len - 1].lbl.allocator = state.allocator; + } try layer2.append(state.allocator, state.shutdown_label.widget()); try layer2.append(state.allocator, state.restart_label.widget()); if (state.config.sleep_cmd != null) { @@ -1940,6 +1940,11 @@ fn updateSessionSpecifier(self: *Label, ptr: *anyopaque) !void { fn positionWidgets(ptr: *anyopaque) !void { var state: *UiState = @ptrCast(@alignCast(ptr)); + // Offsets for custom bind placement. Declared here instead of the + // below if stmt as we need these for `battery_label` positioning. + var x_offset: usize = 0; + // To account for the first row of built-in key hints + var y_offset: usize = 1; if (!state.config.hide_key_hints) { state.shutdown_label.positionX(state.edge_margin .add(TerminalBuffer.START_POSITION)); @@ -1973,8 +1978,6 @@ fn positionWidgets(ptr: *anyopaque) !void { state.brightness_up_label.positionXY(last_label .childrenPosition() .addX(1)); - var x_offset: usize = 0; - var y_offset: usize = 1; for (state.custom_binds.items) |*item| { item.lbl.positionXY(state.edge_margin .addY(y_offset) @@ -1998,6 +2001,7 @@ fn positionWidgets(ptr: *anyopaque) !void { state.battery_label.positionXY(state.edge_margin .add(TerminalBuffer.START_POSITION) .addYFromIf(state.brightness_up_label.childrenPosition(), !state.config.hide_key_hints) + .addYIf(y_offset, !state.config.hide_key_hints) .removeYFromIf(state.edge_margin, !state.config.hide_key_hints)); const tty_label_width = if (state.config.show_tty) TerminalBuffer.strWidth(state.tty_label.text) else 0; From 80d4b114f35e3966b1cce71b1414a84b917b6c67 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sun, 26 Apr 2026 18:37:33 +0200 Subject: [PATCH 03/10] Fix 32-bit issues on Ly's side Signed-off-by: AnErrupTion --- build.zig | 2 +- ly-core/src/interop.zig | 2 +- src/animations/Cascade.zig | 8 ++++---- src/animations/DurFile.zig | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.zig b/build.zig index ff5ce4a..2aeac26 100644 --- a/build.zig +++ b/build.zig @@ -450,7 +450,7 @@ fn patchFile(allocator: std.mem.Allocator, io: std.Io, source_file: []const u8, var buffer: [4096]u8 = undefined; var reader = file.reader(io, &buffer); - var text = try reader.interface.readAlloc(allocator, stat.size); + var text = try reader.interface.readAlloc(allocator, @intCast(stat.size)); var iterator = patch_map.iterator(); while (iterator.next()) |kv| { diff --git a/ly-core/src/interop.zig b/ly-core/src/interop.zig index c580a49..5b2b954 100644 --- a/ly-core/src/interop.zig +++ b/ly-core/src/interop.zig @@ -266,7 +266,7 @@ pub fn supportsUnicode() bool { } pub fn timeAsString(io: std.Io, buf: [:0]u8, format: [:0]const u8) []u8 { - const timer = std.Io.Timestamp.now(io, .real).toSeconds(); + const timer: isize = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()); const tm_info = time.localtime(&timer); const len = time.strftime(buf, buf.len, format, tm_info); diff --git a/src/animations/Cascade.zig b/src/animations/Cascade.zig index c1e88f1..587cd37 100644 --- a/src/animations/Cascade.zig +++ b/src/animations/Cascade.zig @@ -11,14 +11,14 @@ const Cascade = @This(); io: std.Io, instance: ?Widget = null, buffer: *TerminalBuffer, -current_auth_fails: *usize, -max_auth_fails: usize, +current_auth_fails: *u64, +max_auth_fails: u64, pub fn init( io: std.Io, buffer: *TerminalBuffer, - current_auth_fails: *usize, - max_auth_fails: usize, + current_auth_fails: *u64, + max_auth_fails: u64, ) Cascade { return .{ .io = io, diff --git a/src/animations/DurFile.zig b/src/animations/DurFile.zig index 18844b8..c20c2d2 100644 --- a/src/animations/DurFile.zig +++ b/src/animations/DurFile.zig @@ -310,7 +310,7 @@ allocator: Allocator, io: std.Io, terminal_buffer: *TerminalBuffer, dur_movie: DurFormat, -frames: u64, +frames: usize, frame_size: UVec2, start_pos: IVec2, full_color: bool, From 59c07aa3ba05746e6f1fffa2e9210bca5cde23d6 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Tue, 28 Apr 2026 19:35:21 +0200 Subject: [PATCH 04/10] Fix xauth log not being flushed Signed-off-by: AnErrupTion --- src/auth.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/auth.zig b/src/auth.zig index b765c08..d191d55 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -431,7 +431,12 @@ fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, display_n var status: c_int = undefined; const result = std.posix.system.waitpid(pid, &status, 0); if (interop.isError(result) or status != 0) { - try log_file.file_writer.interface.print("xauth command failed with status {d}\n", .{status}); + try log_file.err( + io, + "auth/x11", + "xauth command failed with status: {d}", + .{status}, + ); return error.XauthFailed; } From 51c5c3ee0bcb4d7e109f480f92f5e34cd5995e16 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Wed, 29 Apr 2026 22:52:58 +0200 Subject: [PATCH 05/10] Fix waitpid() being interrupted by SIGCHLD Signed-off-by: AnErrupTion --- src/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.zig b/src/main.zig index 53400df..382e5f3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1541,7 +1541,7 @@ fn authenticate(ptr: *anyopaque) !bool { const tty_control_transfer_act = std.posix.Sigaction{ .handler = .{ .handler = &ttyControlTransferSignalHandler }, .mask = std.posix.sigemptyset(), - .flags = 0, + .flags = std.posix.SA.RESTART, // For waitpid() }; std.posix.sigaction(std.posix.SIG.CHLD, &tty_control_transfer_act, null); From 15cd0c4779b083b75347b0d5d92956679dd10e45 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Thu, 30 Apr 2026 14:38:56 +0200 Subject: [PATCH 06/10] Use SIGINT instead of SIGCHILD for TTY control transfer Signed-off-by: AnErrupTion --- src/auth.zig | 2 +- src/main.zig | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth.zig b/src/auth.zig index d191d55..87ddc09 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) { diff --git a/src/main.zig b/src/main.zig index 382e5f3..81c44b4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1541,9 +1541,9 @@ fn authenticate(ptr: *anyopaque) !bool { const tty_control_transfer_act = std.posix.Sigaction{ .handler = .{ .handler = &ttyControlTransferSignalHandler }, .mask = std.posix.sigemptyset(), - .flags = std.posix.SA.RESTART, // For waitpid() + .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); From 807f6d249a1494c1c20949d82a9409497d067a54 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Thu, 30 Apr 2026 22:48:29 +0200 Subject: [PATCH 07/10] Remove further & all @cImport() usage in interop Signed-off-by: AnErrupTion --- ly-core/build.zig | 8 ++++++++ ly-core/src/interop.zig | 18 ++++-------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/ly-core/build.zig b/ly-core/build.zig index 614404e..dc722b5 100644 --- a/ly-core/build.zig +++ b/ly-core/build.zig @@ -36,6 +36,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/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; From 5905e054c587df65f9e4559f33595de1bfccaaae Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Fri, 1 May 2026 07:47:48 +0200 Subject: [PATCH 08/10] Start Ly v1.5.0 development cycle Signed-off-by: AnErrupTion --- build.zig | 2 +- build.zig.zon | 2 +- ly-core/build.zig.zon | 2 +- ly-ui/build.zig.zon | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 2aeac26..0e626d7 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; 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.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-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 = .{ From 3869bfd2f95acc4cd7dcc08675155047da1d4676 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Fri, 1 May 2026 17:40:04 +0200 Subject: [PATCH 09/10] Add config validation argument (closes #969) Signed-off-by: AnErrupTion --- readme.md | 6 ++++++ src/main.zig | 27 ++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) 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/src/main.zig b/src/main.zig index 81c44b4..6373751 100644 --- a/src/main.zig +++ b/src/main.zig @@ -172,7 +172,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{}; @@ -211,6 +212,30 @@ pub fn main(init: std.process.Init) !void { } 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 From 79eebd8ee0a1a8e9a958679605f81620c611ecb4 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Fri, 1 May 2026 17:43:28 +0200 Subject: [PATCH 10/10] Prefer std.log instead of stderr directly Signed-off-by: AnErrupTion --- src/main.zig | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main.zig b/src/main.zig index 6373751..ce222db 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) { @@ -201,13 +199,11 @@ 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;