From 5edf5251f672088058383d3f82805e5f32459127 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sat, 25 Apr 2026 17:37:34 +0200 Subject: [PATCH] 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); }