From 002630995dcc308e759dcd45530e09b75551eafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A2=E3=82=B7=E3=83=A5?= <120780645+Kawaii-Ash@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:45:30 +0000 Subject: [PATCH] Changes in the Zig rewrite (#596) * Everything * make matrix.zig a bit cleaner * make long lines shorter and add changelog * vi mode * update changelog * get errors from child process and (hopefully) fix some other things * fix utmp entry * run authentication in a child process * update changelog * small code improvements * change that * clear terminal on SIGTERM * Remove LogFile * moved ini to a lib, fixed alternative langs * fix logging out * oops * code improvements * consistency * clearing the env isn't needed anymore (afaik) * replace vi_mode with a bool * type aliases, avoiding zeroes(), breaking a long line * lowercase insert/normal, merge conditionals, code improvements --- build.zig | 11 +- build.zig.zon | 10 +- changelog.md | 45 ++++ res/config.ini | 33 +-- res/lang/cat.ini | 1 - res/lang/cs.ini | 1 - res/lang/de.ini | 1 - res/lang/en.ini | 5 +- res/lang/es.ini | 1 - res/lang/fr.ini | 3 +- res/lang/it.ini | 1 - res/lang/pl.ini | 1 - res/lang/pt.ini | 1 - res/lang/pt_BR.ini | 1 - res/lang/ro.ini | 1 - res/lang/ru.ini | 1 - res/lang/sr.ini | 1 - res/lang/sv.ini | 1 - res/lang/tr.ini | 1 - res/lang/uk.ini | 1 - res/ly.service | 1 + src/SharedError.zig | 39 +++ src/animations/Matrix.zig | 106 ++++---- src/auth.zig | 369 +++++++++++++++++++++------- src/bigclock.zig | 4 +- src/config/Config.zig | 133 ++++------- src/config/ConfigReader.zig | 47 ---- src/config/Lang.zig | 147 ++++-------- src/config/Save.zig | 4 + src/interop.zig | 28 ++- src/main.zig | 412 ++++++++++++++++++++------------ src/tui/TerminalBuffer.zig | 37 +-- src/tui/components/Desktop.zig | 83 ++++--- src/tui/components/InfoLine.zig | 11 + src/tui/components/Text.zig | 25 +- src/tui/utils.zig | 20 +- 36 files changed, 949 insertions(+), 638 deletions(-) create mode 100644 changelog.md mode change 100755 => 100644 res/lang/cat.ini create mode 100644 src/SharedError.zig delete mode 100644 src/config/ConfigReader.zig create mode 100644 src/config/Save.zig create mode 100644 src/tui/components/InfoLine.zig diff --git a/build.zig b/build.zig index f5e8b1c..8542261 100644 --- a/build.zig +++ b/build.zig @@ -1,10 +1,15 @@ const std = @import("std"); +const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 0 }; + pub fn build(b: *std.Build) void { const data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly)"); const build_options = b.addOptions(); build_options.addOption([]const u8, "data_directory", data_directory orelse "/etc/ly"); + const version_str = b.fmt("{d}.{d}.{d}", .{ ly_version.major, ly_version.minor, ly_version.patch }); + + build_options.addOption([]const u8, "version", version_str); const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -29,10 +34,10 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - exe.addOptions("build_options", build_options); + const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize }); + exe.addModule("zigini", zigini.module("zigini")); - const ini = b.dependency("ini", .{}); - exe.addModule("ini", ini.module("ini")); + exe.addOptions("build_options", build_options); const clap = b.dependency("clap", .{ .target = target, .optimize = optimize }); exe.addModule("clap", clap.module("clap")); diff --git a/build.zig.zon b/build.zig.zon index 842afef..d1e55cf 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,13 +2,13 @@ .name = "ly", .version = "1.0.0", .dependencies = .{ - .ini = .{ - .url = "https://github.com/AnErrupTion/zig-ini/archive/7ab77196a4dc63d1ede50e0a1af1a8325c152f2f.tar.gz", - .hash = "12204b1d133060dd0c4304d5e0fbdb33a70038118e7c112b14b5f8d176ea15cd5939", - }, .clap = .{ .url = "https://github.com/Hejsil/zig-clap/archive/f49b94700e0761b7514abdca0e4f0e7f3f938a93.tar.gz", .hash = "1220f48518ce22882e102255ed3bcdb7aeeb4891f50b2cdd3bd74b5b2e24d3149ba2", }, - } + .zigini = .{ + .url = "https://github.com/Kawaii-Ash/zigini/archive/91f47e46591982fc559afa3248749c1d29a0fa2a.tar.gz", + .hash = "12209908f2773f730fbca024c80dc7f48dce15a6527b2387f3768968f5bae0d3931e", + }, + }, } diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..fa1b90d --- /dev/null +++ b/changelog.md @@ -0,0 +1,45 @@ +# Zig Rewrite (Version 1.0.0) + +## Config Options + +res/config.ini contains all of the available config options and their default values. + +### Additions + ++ `border_fg` has been introduced to change the color of the borders. ++ `term_restore_cursor_cmd` should restore the cursor to it's usual state. ++ `vi_mode` to enable vi keybindings. ++ `sleep_key` and `sleep_cmd`. + +Note: `sleep_cmd` is unset by default, meaning it's hidden and has no effect. + +### Changes + ++ xinitrc can be set to null to hide it. ++ `blank_password` has been renamed to `clear_password`. + +### Removals + ++ `wayland_specifier` has been removed. + +## Save File + +The save file is now in .ini format. + +Example: + +```ini +user = ash +session_index = 0 +``` + +## Misc + ++ Display server name added next to selected session. ++ getty@tty2 has been added as a conflict in res/ly.service, so if it is running, ly should still be able to start. ++ `XDG_CURRENT_DESKTOP` is now set by ly. ++ LANG is no longer set by ly. ++ X Server PID is fetched from /tmp/X{d}.lock to be able to kill the process since it detaches. ++ Non .desktop files are now ignored in sessions directory. ++ PAM auth is now done in a child process. (Fixes some issues with logging out and back in). ++ When ly receives SIGTERM, the terminal is now cleared. diff --git a/res/config.ini b/res/config.ini index dc81fc1..9d8fcd6 100644 --- a/res/config.ini +++ b/res/config.ini @@ -1,22 +1,23 @@ -[ly] - # The active animation # none -> Nothing (default) # doom -> PSX DOOM fire # matrix -> CMatrix -animation = doom +animation = none -# Format string for clock in top right corner (see strftime specification) -clock = %c +# Format string for clock in top right corner (see strftime specification). Example: %c +clock = null # Enable/disable big clock -bigclock = true +bigclock = false # The character used to mask the password asterisk = * # Erase password input on failure -blank_password = false +clear_password = false + +# Enable vi keybindings +vi_mode = false # The `fg` and `bg` color settings take a digit 0-8 corresponding to: #define TB_DEFAULT 0x00 @@ -43,6 +44,9 @@ bg = 0 # Foreground color id fg = 8 +# Border color +border_fg = 8 + # Blank main box background # Setting to false will make it transparent blank_box = true @@ -62,7 +66,6 @@ max_desktop_len = 100 max_login_len = 255 max_password_len = 255 - # Input box active by default on startup # Available inputs: session, login, password default_input = login @@ -76,7 +79,6 @@ save = true # File in which to save and load the default desktop and login save_file = /etc/ly/save - # Remove power management command hints hide_key_hints = false @@ -86,28 +88,31 @@ shutdown_key = F1 # Specifies the key used for restart (F1-F12) restart_key = F2 +# Specifies the key used for sleep (F1-F12) +sleep_key = F3 + # Command executed when pressing shutdown_key shutdown_cmd = /sbin/shutdown -a now # Command executed when pressing restart_key restart_cmd = /sbin/shutdown -r now +# Command executed when pressing sleep key (can be null) +sleep_cmd = null # Active language # Available languages are found in /etc/ly/lang/ lang = en - # TTY in use tty = 2 # Console path console_dev = /dev/console -# Default path +# Default path. If null, ly doesn't set a path. path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin - # Event timeout in milliseconds min_refresh_delta = 5 @@ -123,15 +128,13 @@ term_restore_cursor_cmd = /usr/bin/tput cnorm # Cookie generator mcookie_cmd = /usr/bin/mcookie - # Wayland setup command wayland_cmd = /etc/ly/wsetup.sh # Wayland desktop environments waylandsessions = /usr/share/wayland-sessions - -# xinitrc +# xinitrc (hidden if null) xinitrc = ~/.xinitrc # Xorg server command diff --git a/res/lang/cat.ini b/res/lang/cat.ini old mode 100755 new mode 100644 index 56bd49a..380d8d1 --- a/res/lang/cat.ini +++ b/res/lang/cat.ini @@ -1,4 +1,3 @@ -[ly] capslock = Bloq Majús err_alloc = falla d'assignació de memòria err_bounds = índex fora de límit diff --git a/res/lang/cs.ini b/res/lang/cs.ini index 701e0b0..6ab669e 100644 --- a/res/lang/cs.ini +++ b/res/lang/cs.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = alokace paměti selhala err_bounds = index je mimo hranice pole diff --git a/res/lang/de.ini b/res/lang/de.ini index f062273..e18b9ba 100644 --- a/res/lang/de.ini +++ b/res/lang/de.ini @@ -1,4 +1,3 @@ -[ly] capslock = Feststelltaste err_alloc = Speicherzuweisung fehlgeschlagen err_bounds = Listenindex ist außerhalb des Bereichs diff --git a/res/lang/en.ini b/res/lang/en.ini index 0d7d73d..1dc98d4 100644 --- a/res/lang/en.ini +++ b/res/lang/en.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = failed memory allocation err_bounds = out-of-bounds index @@ -35,12 +34,16 @@ err_user_init = failed to initialize user err_user_uid = failed to set user UID err_xsessions_dir = failed to find sessions folder err_xsessions_open = failed to open sessions folder +insert = insert login = login logout = logged out +normal = normal numlock = numlock password = password restart = reboot shell = shell shutdown = shutdown +sleep = sleep wayland = wayland xinitrc = xinitrc +x11 = x11 diff --git a/res/lang/es.ini b/res/lang/es.ini index 1f963d1..fabba15 100644 --- a/res/lang/es.ini +++ b/res/lang/es.ini @@ -1,4 +1,3 @@ -[ly] capslock = Bloq Mayús err_alloc = asignación de memoria fallida err_bounds = índice fuera de límites diff --git a/res/lang/fr.ini b/res/lang/fr.ini index daaa27c..ab1cbc2 100644 --- a/res/lang/fr.ini +++ b/res/lang/fr.ini @@ -1,5 +1,4 @@ -[ly] -capslock = verr.maj +capslock = verr.maj err_alloc = échec d'allocation mémoire err_bounds = indice hors-limite err_chdir = échec de l'ouverture du répertoire home diff --git a/res/lang/it.ini b/res/lang/it.ini index 84a3dea..13eb147 100644 --- a/res/lang/it.ini +++ b/res/lang/it.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = impossibile allocare memoria err_bounds = indice fuori limite diff --git a/res/lang/pl.ini b/res/lang/pl.ini index 460ddef..f37c4a8 100644 --- a/res/lang/pl.ini +++ b/res/lang/pl.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = nieudana alokacja pamięci err_bounds = indeks poza granicami diff --git a/res/lang/pt.ini b/res/lang/pt.ini index a982eb9..ad2c397 100644 --- a/res/lang/pt.ini +++ b/res/lang/pt.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = erro na atribuição de memória err_bounds = índice fora de limites diff --git a/res/lang/pt_BR.ini b/res/lang/pt_BR.ini index 68424a7..017129a 100644 --- a/res/lang/pt_BR.ini +++ b/res/lang/pt_BR.ini @@ -1,4 +1,3 @@ -[ly] capslock = caixa alta err_alloc = alocação de memória malsucedida err_bounds = índice fora de limites diff --git a/res/lang/ro.ini b/res/lang/ro.ini index 8c754d6..884e9da 100644 --- a/res/lang/ro.ini +++ b/res/lang/ro.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock diff --git a/res/lang/ru.ini b/res/lang/ru.ini index 9f0b6fc..356ce5f 100644 --- a/res/lang/ru.ini +++ b/res/lang/ru.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = не удалось выделить память err_bounds = за пределами индекса diff --git a/res/lang/sr.ini b/res/lang/sr.ini index 59fae5a..2f685e3 100644 --- a/res/lang/sr.ini +++ b/res/lang/sr.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = neuspijesna alokacija memorije err_bounds = izvan granica indeksa diff --git a/res/lang/sv.ini b/res/lang/sv.ini index 2f4505a..2e40a05 100644 --- a/res/lang/sv.ini +++ b/res/lang/sv.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = misslyckad minnesallokering err_bounds = utanför banan index diff --git a/res/lang/tr.ini b/res/lang/tr.ini index 885a6db..9bef617 100644 --- a/res/lang/tr.ini +++ b/res/lang/tr.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = basarisiz bellek ayirma err_bounds = sinirlarin disinda dizin diff --git a/res/lang/uk.ini b/res/lang/uk.ini index bf52fb6..5f5b113 100644 --- a/res/lang/uk.ini +++ b/res/lang/uk.ini @@ -1,4 +1,3 @@ -[ly] capslock = capslock err_alloc = невдале виділення пам'яті err_bounds = поза межами індексу diff --git a/res/ly.service b/res/ly.service index 135b987..2fd120a 100644 --- a/res/ly.service +++ b/res/ly.service @@ -2,6 +2,7 @@ Description=TUI display manager After=systemd-user-sessions.service plymouth-quit-wait.service After=getty@tty2.service +Conflicts=getty@tty2.service [Service] Type=idle diff --git a/src/SharedError.zig b/src/SharedError.zig new file mode 100644 index 0000000..fa02100 --- /dev/null +++ b/src/SharedError.zig @@ -0,0 +1,39 @@ +const std = @import("std"); + +const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror)); + +const ErrorHandler = packed struct { + has_error: bool = false, + err_int: ErrInt = 0, +}; + +const SharedError = @This(); + +data: []align(std.mem.page_size) u8, + +pub fn init() !SharedError { + const data = try std.os.mmap(null, @sizeOf(ErrorHandler), std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.SHARED | std.os.MAP.ANONYMOUS, -1, 0); + + return .{ .data = data }; +} + +pub fn deinit(self: *SharedError) void { + defer std.os.munmap(self.data); +} + +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 {}; +} + +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); + + if (err_handler.has_error) + return @errorFromInt(err_handler.err_int); + + return null; +} diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index 7975153..fbacc41 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -66,10 +66,9 @@ pub fn realloc(self: *Matrix) !void { self.lines = lines; } -// TODO: Fix!! pub fn draw(self: *Matrix) void { - var first_column = false; - + const buf_height = self.terminal_buffer.height; + const buf_width = self.terminal_buffer.width; self.count += 1; if (self.count > FRAME_DELAY) { self.frame += 1; @@ -78,81 +77,74 @@ pub fn draw(self: *Matrix) void { var x: u64 = 0; while (x < self.terminal_buffer.width) : (x += 2) { - var line = self.lines[x]; + var tail: u64 = 0; + var line = &self.lines[x]; if (self.frame <= line.update) continue; - var tail: u64 = 0; if (self.dots[x].value == -1 and self.dots[self.terminal_buffer.width + x].value == ' ') { - if (line.space <= 0) { - const random = self.terminal_buffer.random.int(i16); - const h: isize = @intCast(self.terminal_buffer.height); - line.length = @mod(random, h - 3) + 3; - line.space = @mod(random, h) + 1; - self.dots[x].value = @mod(random, MAX_CODEPOINT) + MIN_CODEPOINT; - } else { + if (line.space > 0) { line.space -= 1; + } else { + const randint = self.terminal_buffer.random.int(i16); + const h: isize = @intCast(self.terminal_buffer.height); + line.length = @mod(randint, h - 3) + 3; + self.dots[x].value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT; + line.space = @mod(randint, h + 1); } + } - self.lines[x] = line; - first_column = true; - - var y: u64 = 0; - var seg_length: u64 = 0; - - while (y <= self.terminal_buffer.height) : (y += 1) { - // TODO: Are all these y/height checks required? - var dot = self.dots[y * self.terminal_buffer.width + x]; - - // Skip over spaces - while (dot.value == ' ' or dot.value == -1) { - y += 1; - if (y > self.terminal_buffer.height) break; + var y: u64 = 0; + var first_col = true; + var seg_len: u64 = 0; + height_it: while (y <= buf_height) : (y += 1) { + var dot = &self.dots[buf_width * y + x]; + // Skip over spaces + while (y <= buf_height and (dot.value == ' ' or dot.value == -1)) { + y += 1; + if (y > buf_height) break :height_it; + dot = &self.dots[buf_width * y + x]; + } - dot = self.dots[y * self.terminal_buffer.width + x]; - } - if (y > self.terminal_buffer.height) break; - - // Find the head of this column - tail = y; - seg_length = 0; - while (y <= self.terminal_buffer.height and dot.value != ' ' and dot.value != -1) { - dot.is_head = false; - if (MID_SCROLL_CHANGE) { - const random = self.terminal_buffer.random.int(i16); - if (@mod(random, 8) == 0) dot.value = @mod(random, MAX_CODEPOINT) + MIN_CODEPOINT; + // Find the head of this column + tail = y; + seg_len = 0; + while (y <= buf_height and dot.value != ' ' and dot.value != -1) { + dot.is_head = false; + if (MID_SCROLL_CHANGE) { + const randint = self.terminal_buffer.random.int(i16); + if (@mod(randint, 8) == 0) { + dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT; } - self.dots[y * self.terminal_buffer.width + x] = dot; - - y += 1; - seg_length += 1; - dot = self.dots[y * self.terminal_buffer.width + x]; } - // The head is down offscreen - if (y > self.terminal_buffer.height) { - self.dots[tail * self.terminal_buffer.width + x].value = ' '; - continue; // TODO: Shouldn't this be break? + y += 1; + seg_len += 1; + // Head's down offscreen + if (y > buf_height) { + self.dots[buf_width * tail + x].value = ' '; + break :height_it; } + dot = &self.dots[buf_width * y + x]; + } - const random = self.terminal_buffer.random.int(i16); - self.dots[y * self.terminal_buffer.width + x].value = @mod(random, MAX_CODEPOINT) + MIN_CODEPOINT; - self.dots[y * self.terminal_buffer.width + x].is_head = true; + const randint = self.terminal_buffer.random.int(i16); + dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT; + dot.is_head = true; - if (seg_length > line.length or !first_column) { - self.dots[tail * self.terminal_buffer.width + x].value = ' '; - self.dots[x].value = -1; - } - first_column = false; + if (seg_len > line.length or !first_col) { + self.dots[buf_width * tail + x].value = ' '; + self.dots[x].value = -1; } + first_col = false; } } } var x: u64 = 0; - while (x < self.terminal_buffer.width) : (x += 2) { + while (x < buf_width) : (x += 2) { var y: u64 = 1; while (y <= self.terminal_buffer.height) : (y += 1) { - const dot = self.dots[y * self.terminal_buffer.width + x]; + const dot = self.dots[buf_width * y + x]; var fg: u32 = @intCast(termbox.TB_GREEN); if (dot.value == -1 or dot.value == ' ') { diff --git a/src/auth.zig b/src/auth.zig index c3322cc..27bd145 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -4,35 +4,20 @@ const interop = @import("interop.zig"); const TerminalBuffer = @import("tui/TerminalBuffer.zig"); const Desktop = @import("tui/components/Desktop.zig"); const Text = @import("tui/components/Text.zig"); +const Config = @import("config/Config.zig"); const Allocator = std.mem.Allocator; +const utmp = interop.utmp; +const Utmp = utmp.utmp; +const SharedError = @import("SharedError.zig"); -var login_conv_allocator: Allocator = undefined; - -pub fn authenticate( - allocator: Allocator, - tty: u8, - desktop: Desktop, - login: Text, - password: *Text, - service_name: []const u8, - path: []const u8, - term_reset_cmd: []const u8, - wayland_cmd: []const u8, -) !void { - login_conv_allocator = allocator; - - const uid = interop.getuid(); - - var tty_buffer = std.mem.zeroes([@sizeOf(u8) + 1]u8); - var uid_buffer = std.mem.zeroes([10 + @sizeOf(u32) + 1]u8); - - const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{tty}); - const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid}); +pub fn authenticate(allocator: Allocator, config: Config, desktop: Desktop, login: Text, password: *Text) !void { + var tty_buffer: [2]u8 = undefined; + const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); const current_environment = desktop.environments.items[desktop.current]; // Set the XDG environment variables setXdgSessionEnv(current_environment.display_server); - try setXdgEnv(allocator, tty_str, uid_str, current_environment.xdg_name); + try setXdgEnv(allocator, tty_str, current_environment.xdg_name); // Open the PAM session const login_text_z = try allocator.dupeZ(u8, login.text.items); @@ -53,12 +38,10 @@ pub fn authenticate( }; var handle: ?*interop.pam.pam_handle = undefined; - const service_name_z = try allocator.dupeZ(u8, service_name); + const service_name_z = try allocator.dupeZ(u8, config.service_name); defer allocator.free(service_name_z); var status = interop.pam.pam_start(service_name_z.ptr, null, &conv, &handle); - defer status = interop.pam.pam_end(handle, status); - if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); // Do the PAM routine @@ -74,9 +57,6 @@ pub fn authenticate( status = interop.pam.pam_open_session(handle, 0); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); - // Clear the password - password.clear(); - // Get password structure from username const maybe_pwd = interop.getpwnam(login_text_z.ptr); interop.endpwent(); @@ -103,30 +83,35 @@ pub fn authenticate( } } - // Restore the previous terminal mode - interop.termbox.tb_clear(); - interop.termbox.tb_present(); - interop.termbox.tb_shutdown(); - - const pid = std.c.fork(); + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + const pid = try std.os.fork(); if (pid == 0) { // Set the user information status = interop.initgroups(pwd.pw_name, pwd.pw_gid); - if (status != 0) return error.GroupInitializationFailed; + if (status != 0) { + shared_err.writeError(error.GroupInitializationFailed); + std.os.exit(1); + } status = std.c.setgid(pwd.pw_gid); - if (status != 0) return error.SetUserGidFailed; + if (status != 0) { + shared_err.writeError(error.SetUserGidFailed); + std.os.exit(1); + } status = std.c.setuid(pwd.pw_uid); - if (status != 0) return error.SetUserUidFailed; - - // Set up the environment (this clears the currently set one) - try initEnv(allocator, pwd, path); + if (status != 0) { + shared_err.writeError(error.SetUserUidFailed); + std.os.exit(1); + } - // Reset the XDG environment variables from before - setXdgSessionEnv(current_environment.display_server); - try setXdgEnv(allocator, tty_str, uid_str, current_environment.xdg_name); + // Set up the environment + initEnv(allocator, pwd, config.path) catch |e| { + shared_err.writeError(e); + std.os.exit(1); + }; // Set the PAM variables const pam_env_vars = interop.pam.pam_getenvlist(handle); @@ -141,34 +126,47 @@ pub fn authenticate( // Execute what the user requested status = interop.chdir(pwd.pw_dir); - if (status != 0) return error.ChangeDirectoryFailed; + if (status != 0) { + shared_err.writeError(error.ChangeDirectoryFailed); + std.os.exit(1); + } - try resetTerminal(allocator, pwd.pw_shell, term_reset_cmd); + resetTerminal(allocator, pwd.pw_shell, config.term_reset_cmd) catch |e| { + shared_err.writeError(e); + std.os.exit(1); + }; switch (current_environment.display_server) { - .wayland => try executeWaylandCmd(pwd.pw_shell, wayland_cmd, current_environment.cmd), + .wayland => executeWaylandCmd(pwd.pw_shell, config.wayland_cmd, current_environment.cmd) catch |e| { + shared_err.writeError(e); + std.os.exit(1); + }, .shell => executeShellCmd(pwd.pw_shell), .xinitrc, .x11 => { - // TODO + var vt_buf: [5]u8 = undefined; + const vt = std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty}) catch |e| { + shared_err.writeError(e); + std.os.exit(1); + }; + executeX11Cmd(pwd.pw_shell, pwd.pw_dir, config, current_environment.cmd, vt) catch |e| { + shared_err.writeError(e); + std.os.exit(1); + }; }, } std.os.exit(0); } - // TODO: Add UTMP entry + var entry: Utmp = std.mem.zeroes(Utmp); + addUtmpEntry(&entry, pwd.pw_name, pid) catch {}; // Wait for the session to stop - _ = std.c.waitpid(pid, &status, 0); - // TODO: Remove UTMP entry - - try resetTerminal(allocator, pwd.pw_shell, term_reset_cmd); + _ = std.os.waitpid(pid, 0); - // Re-initialize termbox - _ = interop.termbox.tb_init(); - _ = interop.termbox.tb_select_output_mode(interop.termbox.TB_OUTPUT_NORMAL); + removeUtmpEntry(&entry); - // TODO: Reload the DE list on log out + try resetTerminal(allocator, pwd.pw_shell, config.term_reset_cmd); // Close the PAM session status = interop.pam.pam_close_session(handle, 0); @@ -176,25 +174,30 @@ pub fn authenticate( status = interop.pam.pam_setcred(handle, 0); if (status != 0) return pamDiagnose(status); + + status = interop.pam.pam_end(handle, status); + if (status != 0) return pamDiagnose(status); + + if (shared_err.readError()) |err| return err; } -fn initEnv(allocator: Allocator, pwd: *interop.passwd, path: []const u8) !void { - const term = interop.getenv("TERM"); - const lang = interop.getenv("LANG"); +fn initEnv(allocator: Allocator, pwd: *interop.passwd, path: ?[]const u8) !void { + const term_env = std.os.getenv("TERM"); - if (term[0] == 0) _ = interop.setenv("TERM", "linux", 1); - if (lang[0] == 0) _ = interop.setenv("LANG", "C", 1); + if (term_env) |term| _ = interop.setenv("TERM", term, 1); _ = interop.setenv("HOME", pwd.pw_dir, 1); _ = interop.setenv("PWD", pwd.pw_dir, 1); _ = interop.setenv("SHELL", pwd.pw_shell, 1); _ = interop.setenv("USER", pwd.pw_name, 1); _ = interop.setenv("LOGNAME", pwd.pw_name, 1); - const path_z = try allocator.dupeZ(u8, path); - defer allocator.free(path_z); + if (path != null) { + const path_z = try allocator.dupeZ(u8, path.?); + defer allocator.free(path_z); - const status = interop.setenv("PATH", path_z, 1); - if (status != 0) return error.SetPathFailed; + const status = interop.setenv("PATH", path_z, 1); + if (status != 0) return error.SetPathFailed; + } } fn setXdgSessionEnv(display_server: enums.DisplayServer) void { @@ -205,10 +208,15 @@ fn setXdgSessionEnv(display_server: enums.DisplayServer) void { }, 0); } -fn setXdgEnv(allocator: Allocator, tty_str: [:0]u8, uid_str: [:0]u8, desktop_name: []const u8) !void { +fn setXdgEnv(allocator: Allocator, tty_str: [:0]u8, desktop_name: []const u8) !void { const desktop_name_z = try allocator.dupeZ(u8, desktop_name); defer allocator.free(desktop_name_z); + const uid = interop.getuid(); + var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined; + const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid}); + + _ = interop.setenv("XDG_CURRENT_DESKTOP", desktop_name_z.ptr, 0); _ = interop.setenv("XDG_RUNTIME_DIR", uid_str.ptr, 0); _ = interop.setenv("XDG_SESSION_CLASS", "user", 0); _ = interop.setenv("XDG_SESSION_ID", "1", 0); @@ -226,28 +234,24 @@ fn loginConv( const message_count: u32 = @intCast(num_msg); const messages = msg.?; - const response = login_conv_allocator.alloc(interop.pam.pam_response, message_count) catch return interop.pam.PAM_BUF_ERR; - defer login_conv_allocator.free(response); + const allocator = std.heap.c_allocator; + const response = allocator.alloc(interop.pam.pam_response, message_count) catch return interop.pam.PAM_BUF_ERR; - var status: c_int = undefined; + var username: ?[:0]u8 = null; + var password: ?[:0]u8 = null; + var status: c_int = interop.pam.PAM_SUCCESS; for (0..message_count) |i| set_credentials: { switch (messages[i].?.msg_style) { - // TODO: Potentially cast appdata pointer before so we only do it once - // TODO: Verify if we need to do string duplication here interop.pam.PAM_PROMPT_ECHO_ON => { - const appdata: ?*align(8) anyopaque = @alignCast(appdata_ptr); - const data: [*][:0]u8 = @ptrCast(appdata.?); - const username = data[0]; - - response[i].resp = username; + const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr)); + username = allocator.dupeZ(u8, std.mem.span(data[0])) catch return interop.pam.PAM_BUF_ERR; + response[i].resp = username.?.ptr; }, interop.pam.PAM_PROMPT_ECHO_OFF => { - const appdata: ?*align(8) anyopaque = @alignCast(appdata_ptr); - const data: [*][:0]u8 = @ptrCast(appdata.?); - const password = data[1]; - - response[i].resp = password; + const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr)); + password = allocator.dupeZ(u8, std.mem.span(data[1])) catch return interop.pam.PAM_BUF_ERR; + response[i].resp = password.?.ptr; }, interop.pam.PAM_ERROR_MSG => { status = interop.pam.PAM_CONV_ERR; @@ -257,7 +261,14 @@ fn loginConv( } } - if (status == interop.pam.PAM_SUCCESS) resp.?.* = response.ptr; + if (status != interop.pam.PAM_SUCCESS) { + // Memory is freed by pam otherwise + allocator.free(response); + if (username != null) allocator.free(username.?); + if (password != null) allocator.free(password.?); + } else { + resp.?.* = response.ptr; + } return status; } @@ -266,28 +277,210 @@ fn resetTerminal(allocator: Allocator, shell: [*:0]const u8, term_reset_cmd: []c const term_reset_cmd_z = try allocator.dupeZ(u8, term_reset_cmd); defer allocator.free(term_reset_cmd_z); - const pid = std.c.fork(); + const pid = try std.os.fork(); + + if (pid == 0) { + _ = interop.execl(shell, shell, "-c", term_reset_cmd_z.ptr, @as([*c]const u8, 0)); + std.os.exit(0); + } + + _ = std.os.waitpid(pid, 0); +} + +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.os.access(xlock, std.os.F_OK) catch break; + } + return i; +} + +fn getXPid(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(); + + var file_buf: [20]u8 = undefined; + var fbs = std.io.fixedBufferStream(&file_buf); + + _ = try file.reader().streamUntilDelimiter(fbs.writer(), '\n', 20); + const line = fbs.getWritten(); + + return std.fmt.parseInt(i32, std.mem.trim(u8, line, " "), 10); +} + +fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 { + var xauth_buf: [100]u8 = undefined; + var xauth_dir: [:0]const u8 = undefined; + var xdg_rt_dir = std.os.getenv("XDG_RUNTIME_DIR"); + var xauth_file: []const u8 = "lyxauth"; + + if (xdg_rt_dir == null) { + const xdg_cfg_home = std.os.getenv("XDG_CONFIG_HOME"); + var sb: std.c.Stat = undefined; + if (xdg_cfg_home == null) { + xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/.config", .{pwd}); + _ = std.c.stat(xauth_dir, &sb); + const mode = sb.mode & std.os.S.IFMT; + if (mode == std.os.S.IFDIR) { + xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xauth_dir}); + } else { + xauth_dir = pwd; + xauth_file = ".lyxauth"; + } + } else { + xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?}); + } + + _ = std.c.stat(xauth_dir, &sb); + const mode = sb.mode & std.os.S.IFMT; + if (mode != std.os.S.IFDIR) { + std.os.mkdir(xauth_dir, 777) catch { + xauth_dir = pwd; + xauth_file = ".lyxauth"; + }; + } + } else { + xauth_dir = xdg_rt_dir.?; + } + + // Trim trailing slashes + var i = xauth_dir.len - 1; + while (xauth_dir[i] == '/') i -= 1; + const trimmed_xauth_dir = xauth_dir[0 .. i + 1]; + + var buf: [256]u8 = undefined; + const xauthority: [:0]u8 = try std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file }); + const file = try std.fs.createFileAbsolute(xauthority, .{}); + file.close(); + + return xauthority; +} + +fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xauth_cmd: []const u8, mcookie_cmd: []const u8) !void { + var pwd_buf: [100]u8 = undefined; + var pwd: [:0]u8 = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir}); + const xauthority = try createXauthFile(pwd); + _ = interop.setenv("XAUTHORITY", xauthority, 1); + _ = interop.setenv("DISPLAY", display_name, 1); + + const pid = try std.os.fork(); if (pid == 0) { - _ = interop.execl(shell, shell, "-c\x00".ptr, term_reset_cmd_z.ptr, @as([*c]const u8, 0)); + var cmd_buffer: [1024]u8 = undefined; + const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . $({s})", .{ xauth_cmd, display_name, mcookie_cmd }) catch std.os.exit(1); + _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); std.os.exit(0); } - var status: c_int = undefined; - _ = std.c.waitpid(pid, &status, 0); + _ = std.os.waitpid(pid, 0); } fn executeWaylandCmd(shell: [*:0]const u8, wayland_cmd: []const u8, desktop_cmd: []const u8) !void { - var cmd_buffer = std.mem.zeroes([1024]u8); + var cmd_buffer: [1024]u8 = undefined; const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd }); _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); } +fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, desktop_cmd: []const u8, vt: []const u8) !void { + const display_num = try getFreeDisplay(); + var buf: [5]u8 = undefined; + var display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num}); + try xauth(display_name, shell, pw_dir, config.xauth_cmd, config.mcookie_cmd); + + const pid = try std.os.fork(); + if (pid == 0) { + var cmd_buffer: [1024]u8 = undefined; + const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.x_cmd, display_name, vt }) catch std.os.exit(1); + _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); + std.os.exit(0); + } + + var ok: c_int = undefined; + 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.c.kill(pid, 0); + if (std.c._errno().* == interop.ESRCH and ok != 0) return; + } + + // X Server detaches from the process. + // PID can be fetched from /tmp/X{d}.lock + const x_pid = try getXPid(display_num); + + const xorg_pid = try std.os.fork(); + if (xorg_pid == 0) { + var cmd_buffer: [1024]u8 = undefined; + const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ config.x_cmd_setup, desktop_cmd }) catch std.os.exit(1); + _ = interop.execl(shell, shell, "-c", cmd_str.ptr, @as([*c]const u8, 0)); + std.os.exit(0); + } + + var status: c_int = 0; + _ = std.os.waitpid(xorg_pid, 0); + interop.xcb.xcb_disconnect(xcb); + + _ = std.c.kill(x_pid, 0); + if (std.c._errno().* != interop.ESRCH) { + _ = std.c.kill(x_pid, interop.SIGTERM); + _ = std.c.waitpid(x_pid, &status, 0); + } +} + fn executeShellCmd(shell: [*:0]const u8) void { _ = interop.execl(shell, shell, @as([*c]const u8, 0)); } +fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void { + entry.ut_type = utmp.USER_PROCESS; + entry.ut_pid = pid; + + var buf: [4096]u8 = undefined; + const ttyname = try std.os.getFdPath(0, &buf); + + var ttyname_buf: [32]u8 = undefined; + _ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]}); + + entry.ut_line = ttyname_buf; + entry.ut_id = ttyname_buf["tty".len..7].*; + + var username_buf: [32]u8 = undefined; + _ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username}); + + entry.ut_user = username_buf; + + var host: [256]u8 = undefined; + host[0] = 0; + entry.ut_host = host; + + var tv: std.c.timeval = undefined; + _ = std.c.gettimeofday(&tv, null); + + entry.ut_tv = .{ + .tv_sec = @intCast(tv.tv_sec), + .tv_usec = @intCast(tv.tv_usec), + }; + entry.ut_addr_v6[0] = 0; + + utmp.setutent(); + _ = utmp.pututline(entry); + utmp.endutent(); +} + +fn removeUtmpEntry(entry: *Utmp) void { + entry.ut_type = utmp.DEAD_PROCESS; + entry.ut_line[0] = 0; + entry.ut_user[0] = 0; + utmp.setutent(); + _ = utmp.pututline(entry); + utmp.endutent(); +} + fn pamDiagnose(status: c_int) anyerror { return switch (status) { interop.pam.PAM_ACCT_EXPIRED => return error.PamAccountExpired, diff --git a/src/bigclock.zig b/src/bigclock.zig index 0150dc2..5c3007d 100644 --- a/src/bigclock.zig +++ b/src/bigclock.zig @@ -100,9 +100,9 @@ const E = [_]u32{ // zig fmt: on pub fn clockCell(animate: bool, char: u8, fg: u8, bg: u8) [SIZE]termbox.tb_cell { - var cells = std.mem.zeroes([SIZE]termbox.tb_cell); + var cells: [SIZE]termbox.tb_cell = undefined; - var tv = std.mem.zeroes(std.c.timeval); + var tv: std.c.timeval = undefined; _ = std.c.gettimeofday(&tv, null); const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char); diff --git a/src/config/Config.zig b/src/config/Config.zig index 663e598..84230d8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -6,92 +6,47 @@ const Input = enums.Input; const Config = @This(); -ly: struct { - animation: Animation, - asterisk: u8, - bg: u8, - bigclock: bool, - blank_box: bool, - blank_password: bool, - clock: []const u8, - console_dev: []const u8, - default_input: Input, - fg: u8, - hide_borders: bool, - hide_key_hints: bool, - input_len: u8, - lang: []const u8, - load: bool, - margin_box_h: u8, - margin_box_v: u8, - max_desktop_len: u8, - max_login_len: u8, - max_password_len: u8, - mcookie_cmd: []const u8, - min_refresh_delta: u16, - path: []const u8, - restart_cmd: []const u8, - restart_key: []const u8, - save: bool, - save_file: []const u8, - service_name: []const u8, - shutdown_cmd: []const u8, - shutdown_key: []const u8, - term_reset_cmd: []const u8, - term_restore_cursor_cmd: []const u8, - tty: u8, - wayland_cmd: []const u8, - wayland_specifier: bool, - waylandsessions: []const u8, - x_cmd: []const u8, - xinitrc: []const u8, - x_cmd_setup: []const u8, - xauth_cmd: []const u8, - xsessions: []const u8, -}, - -pub fn init() Config { - return .{ .ly = .{ - .animation = .none, - .asterisk = '*', - .bg = 0, - .bigclock = false, - .blank_box = true, - .blank_password = false, - .clock = "", - .console_dev = "/dev/console", - .default_input = .login, - .fg = 9, - .hide_borders = false, - .hide_key_hints = false, - .input_len = 34, - .lang = "en", - .load = true, - .margin_box_h = 2, - .margin_box_v = 1, - .max_desktop_len = 100, - .max_login_len = 255, - .max_password_len = 255, - .mcookie_cmd = "/usr/bin/mcookie", - .min_refresh_delta = 5, - .path = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin", - .restart_cmd = "/sbin/shutdown -r now", - .restart_key = "F2", - .save = true, - .save_file = "/etc/ly/save", - .service_name = "ly", - .shutdown_cmd = "/sbin/shutdown -a now", - .shutdown_key = "F1", - .term_reset_cmd = "/usr/bin/tput reset", - .term_restore_cursor_cmd = "/usr/bin/tput cnorm", - .tty = 2, - .wayland_cmd = build_options.data_directory ++ "/wsetup.sh", - .wayland_specifier = false, - .waylandsessions = "/usr/share/wayland-sessions", - .x_cmd = "/usr/bin/X", - .xinitrc = "~/.xinitrc", - .x_cmd_setup = build_options.data_directory ++ "/xsetup.sh", - .xauth_cmd = "/usr/bin/xauth", - .xsessions = "/usr/share/xsessions", - } }; -} +animation: Animation = .none, +asterisk: u8 = '*', +bg: u8 = 0, +bigclock: bool = false, +blank_box: bool = true, +border_fg: u8 = 8, +clear_password: bool = false, +clock: ?[:0]const u8 = null, +console_dev: [:0]const u8 = "/dev/console", +default_input: Input = .login, +fg: u8 = 8, +hide_borders: bool = false, +hide_key_hints: bool = false, +input_len: u8 = 34, +lang: []const u8 = "en", +load: bool = true, +margin_box_h: u8 = 2, +margin_box_v: u8 = 1, +max_desktop_len: u8 = 100, +max_login_len: u8 = 255, +max_password_len: u8 = 255, +mcookie_cmd: []const u8 = "/usr/bin/mcookie", +min_refresh_delta: u16 = 5, +path: ?[]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin", +restart_cmd: []const u8 = "/sbin/shutdown -r now", +restart_key: []const u8 = "F2", +save: bool = true, +save_file: []const u8 = "/etc/ly/save", +service_name: []const u8 = "ly", +shutdown_cmd: []const u8 = "/sbin/shutdown -a now", +shutdown_key: []const u8 = "F1", +sleep_cmd: ?[]const u8 = null, +sleep_key: []const u8 = "F3", +term_reset_cmd: []const u8 = "/usr/bin/tput reset", +term_restore_cursor_cmd: []const u8 = "/usr/bin/tput cnorm", +tty: u8 = 2, +vi_mode: bool = false, +wayland_cmd: []const u8 = build_options.data_directory ++ "/wsetup.sh", +waylandsessions: []const u8 = "/usr/share/wayland-sessions", +x_cmd: []const u8 = "/usr/bin/X", +xinitrc: ?[]const u8 = "~/.xinitrc", +x_cmd_setup: []const u8 = build_options.data_directory ++ "/xsetup.sh", +xauth_cmd: []const u8 = "/usr/bin/xauth", +xsessions: []const u8 = "/usr/share/xsessions", diff --git a/src/config/ConfigReader.zig b/src/config/ConfigReader.zig deleted file mode 100644 index d24d7b8..0000000 --- a/src/config/ConfigReader.zig +++ /dev/null @@ -1,47 +0,0 @@ -const std = @import("std"); -const ini = @import("ini"); -const Config = @import("Config.zig"); -const Lang = @import("Lang.zig"); - -const Allocator = std.mem.Allocator; - -pub const CONFIG_MAX_SIZE: u64 = 8 * 1024; - -const ConfigReader = @This(); - -allocator: Allocator, -config_allocated: bool = false, -lang_allocated: bool = false, -config: []u8 = undefined, -lang: []u8 = undefined, - -pub fn init(config_allocator: Allocator) ConfigReader { - return .{ - .allocator = config_allocator, - }; -} - -pub fn deinit(self: ConfigReader) void { - if (self.config_allocated) self.allocator.free(self.config); - if (self.lang_allocated) self.allocator.free(self.lang); -} - -pub fn readConfig(self: *ConfigReader, path: []const u8) !Config { - var file = std.fs.cwd().openFile(path, .{}) catch return Config.init(); - defer file.close(); - - self.config = try file.readToEndAlloc(self.allocator, CONFIG_MAX_SIZE); - self.config_allocated = true; - - return try ini.readToStruct(Config, self.config); -} - -pub fn readLang(self: *ConfigReader, path: []const u8) !Lang { - var file = std.fs.cwd().openFile(path, .{}) catch return Lang.init(); - defer file.close(); - - self.lang = try file.readToEndAlloc(self.allocator, CONFIG_MAX_SIZE); - self.lang_allocated = true; - - return try ini.readToStruct(Lang, self.lang); -} diff --git a/src/config/Lang.zig b/src/config/Lang.zig index 9c7ca6b..a677e54 100644 --- a/src/config/Lang.zig +++ b/src/config/Lang.zig @@ -1,99 +1,52 @@ const Lang = @This(); -ly: struct { - capslock: []const u8, - err_alloc: []const u8, - err_bounds: []const u8, - err_chdir: []const u8, - err_console_dev: []const u8, - err_dgn_oob: []const u8, - err_domain: []const u8, - err_hostname: []const u8, - err_mlock: []const u8, - err_null: []const u8, - err_pam: []const u8, - err_pam_abort: []const u8, - err_pam_acct_expired: []const u8, - err_pam_auth: []const u8, - err_pam_authinfo_unavail: []const u8, - err_pam_authok_reqd: []const u8, - err_pam_buf: []const u8, - err_pam_cred_err: []const u8, - err_pam_cred_expired: []const u8, - err_pam_cred_insufficient: []const u8, - err_pam_cred_unavail: []const u8, - err_pam_maxtries: []const u8, - err_pam_perm_denied: []const u8, - err_pam_session: []const u8, - err_pam_sys: []const u8, - err_pam_user_unknown: []const u8, - err_path: []const u8, - err_perm_dir: []const u8, - err_perm_group: []const u8, - err_perm_user: []const u8, - err_pwnam: []const u8, - err_user_gid: []const u8, - err_user_init: []const u8, - err_user_uid: []const u8, - err_xsessions_dir: []const u8, - err_xsessions_open: []const u8, - login: []const u8, - logout: []const u8, - numlock: []const u8, - password: []const u8, - restart: []const u8, - shell: []const u8, - shutdown: []const u8, - wayland: []const u8, - xinitrc: []const u8, -}, - -pub fn init() Lang { - return .{ .ly = .{ - .capslock = "capslock", - .err_alloc = "failed memory allocation", - .err_bounds = "out-of-bounds index", - .err_chdir = "failed to open home folder", - .err_console_dev = "failed to access console", - .err_dgn_oob = "log message", - .err_domain = "invalid domain", - .err_hostname = "failed to get hostname", - .err_mlock = "failed to lock password memory", - .err_null = "null pointer", - .err_pam = "pam transaction failed", - .err_pam_abort = "pam transaction aborted", - .err_pam_acct_expired = "account expired", - .err_pam_auth = "authentication error", - .err_pam_authinfo_unavail = "failed to get user info", - .err_pam_authok_reqd = "token expired", - .err_pam_buf = "memory buffer error", - .err_pam_cred_err = "failed to set credentials", - .err_pam_cred_expired = "credentials expired", - .err_pam_cred_insufficient = "insufficient credentials", - .err_pam_cred_unavail = "failed to get credentials", - .err_pam_maxtries = "reached maximum tries limit", - .err_pam_perm_denied = "permission denied", - .err_pam_session = "session error", - .err_pam_sys = "system error", - .err_pam_user_unknown = "unknown user", - .err_path = "failed to set path", - .err_perm_dir = "failed to change current directory", - .err_perm_group = "failed to downgrade group permissions", - .err_perm_user = "failed to downgrade user permissions", - .err_pwnam = "failed to get user info", - .err_user_gid = "failed to set user GID", - .err_user_init = "failed to initialize user", - .err_user_uid = "failed to set user UID", - .err_xsessions_dir = "failed to find sessions folder", - .err_xsessions_open = "failed to open sessions folder", - .login = "login:", - .logout = "logged out", - .numlock = "numlock", - .password = "password:", - .restart = "reboot", - .shell = "shell", - .shutdown = "shutdown", - .wayland = "wayland", - .xinitrc = "xinitrc", - } }; -} +capslock: []const u8 = "capslock", +err_alloc: []const u8 = "failed memory allocation", +err_bounds: []const u8 = "out-of-bounds index", +err_chdir: []const u8 = "failed to open home folder", +err_console_dev: []const u8 = "failed to access console", +err_dgn_oob: []const u8 = "log message", +err_domain: []const u8 = "invalid domain", +err_hostname: []const u8 = "failed to get hostname", +err_mlock: []const u8 = "failed to lock password memory", +err_null: []const u8 = "null pointer", +err_pam: []const u8 = "pam transaction failed", +err_pam_abort: []const u8 = "pam transaction aborted", +err_pam_acct_expired: []const u8 = "account expired", +err_pam_auth: []const u8 = "authentication error", +err_pam_authinfo_unavail: []const u8 = "failed to get user info", +err_pam_authok_reqd: []const u8 = "token expired", +err_pam_buf: []const u8 = "memory buffer error", +err_pam_cred_err: []const u8 = "failed to set credentials", +err_pam_cred_expired: []const u8 = "credentials expired", +err_pam_cred_insufficient: []const u8 = "insufficient credentials", +err_pam_cred_unavail: []const u8 = "failed to get credentials", +err_pam_maxtries: []const u8 = "reached maximum tries limit", +err_pam_perm_denied: []const u8 = "permission denied", +err_pam_session: []const u8 = "session error", +err_pam_sys: []const u8 = "system error", +err_pam_user_unknown: []const u8 = "unknown user", +err_path: []const u8 = "failed to set path", +err_perm_dir: []const u8 = "failed to change current directory", +err_perm_group: []const u8 = "failed to downgrade group permissions", +err_perm_user: []const u8 = "failed to downgrade user permissions", +err_pwnam: []const u8 = "failed to get user info", +err_user_gid: []const u8 = "failed to set user GID", +err_user_init: []const u8 = "failed to initialize user", +err_user_uid: []const u8 = "failed to set user UID", +err_xsessions_dir: []const u8 = "failed to find sessions folder", +err_xsessions_open: []const u8 = "failed to open sessions folder", +insert: []const u8 = "insert", +login: []const u8 = "login:", +logout: []const u8 = "logged out", +normal: []const u8 = "normal", +numlock: []const u8 = "numlock", +other: []const u8 = "other", +password: []const u8 = "password:", +restart: []const u8 = "reboot", +shell: []const u8 = "shell", +shutdown: []const u8 = "shutdown", +sleep: []const u8 = "sleep", +wayland: []const u8 = "wayland", +xinitrc: []const u8 = "xinitrc", +x11: []const u8 = "x11", diff --git a/src/config/Save.zig b/src/config/Save.zig new file mode 100644 index 0000000..b9c7ae0 --- /dev/null +++ b/src/config/Save.zig @@ -0,0 +1,4 @@ +const Save = @This(); + +user: ?[]const u8 = null, +session_index: ?u64 = null, diff --git a/src/interop.zig b/src/interop.zig index bfc621a..d2c0a8e 100644 --- a/src/interop.zig +++ b/src/interop.zig @@ -10,6 +10,14 @@ pub const pam = @cImport({ @cInclude("security/pam_appl.h"); }); +pub const utmp = @cImport({ + @cInclude("utmp.h"); +}); + +pub const xcb = @cImport({ + @cInclude("xcb/xcb.h"); +}); + pub const c_size = u64; pub const c_uid = u32; pub const c_gid = u32; @@ -36,6 +44,9 @@ pub const passwd = extern struct { pw_shell: [*:0]u8, }; +pub const SIGTERM: c_int = 15; +pub const ESRCH: c_int = 3; + pub const _POSIX_HOST_NAME_MAX: c_int = 0xFF; pub const _SC_HOST_NAME_MAX: c_int = 0xB4; @@ -63,6 +74,7 @@ pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int; pub extern "c" fn getenv(name: [*:0]const u8) [*:0]u8; pub extern "c" fn putenv(name: [*:0]u8) c_int; +pub extern "c" fn clearenv() c_int; pub extern "c" fn getuid() c_uid; pub extern "c" fn getpwnam(name: [*:0]const u8) ?*passwd; pub extern "c" fn endpwent() void; @@ -81,6 +93,7 @@ pub fn getHostName(allocator: Allocator) !struct { const hostname_max_length: u64 = if (hostname_sysconf < 0) @intCast(_POSIX_HOST_NAME_MAX) else @intCast(hostname_sysconf); const buffer = try allocator.alloc(u8, hostname_max_length); + errdefer allocator.free(buffer); const error_code = std.c.gethostname(buffer.ptr, hostname_max_length); if (error_code < 0) return error.CannotGetHostName; @@ -99,27 +112,22 @@ pub fn getHostName(allocator: Allocator) !struct { }; } -pub fn timeAsString(allocator: Allocator, format: []const u8, max_length: u64) ![:0]u8 { +pub fn timeAsString(allocator: Allocator, format: [:0]const u8, max_length: u64) ![:0]u8 { const timer = time(null); const tm_info = localtime(&timer); const buffer = try allocator.allocSentinel(u8, max_length, 0); + errdefer allocator.free(buffer); - const format_z = try allocator.dupeZ(u8, format); - defer allocator.free(format_z); - - if (strftime(buffer, max_length, format_z, tm_info) < 0) return error.CannotGetFormattedTime; + if (strftime(buffer, max_length, format, tm_info) < 0) return error.CannotGetFormattedTime; return buffer; } -pub fn getLockState(allocator: Allocator, console_dev: []const u8) !struct { +pub fn getLockState(console_dev: [:0]const u8) !struct { numlock: bool, capslock: bool, } { - const console_dev_z = try allocator.dupeZ(u8, console_dev); - defer allocator.free(console_dev_z); - - const fd = std.c.open(console_dev_z, O_RDONLY); + const fd = std.c.open(console_dev, O_RDONLY); if (fd < 0) return error.CannotOpenConsoleDev; var numlock = false; diff --git a/src/main.zig b/src/main.zig index b649449..6d08563 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,13 +10,22 @@ const Matrix = @import("animations/Matrix.zig"); const TerminalBuffer = @import("tui/TerminalBuffer.zig"); const Desktop = @import("tui/components/Desktop.zig"); const Text = @import("tui/components/Text.zig"); +const InfoLine = @import("tui/components/InfoLine.zig"); const Config = @import("config/Config.zig"); -const ConfigReader = @import("config/ConfigReader.zig"); +const ini = @import("zigini"); const Lang = @import("config/Lang.zig"); +const Save = @import("config/Save.zig"); +const ViMode = @import("enums.zig").ViMode; +const SharedError = @import("SharedError.zig"); +const utils = @import("tui/utils.zig"); +const Ini = ini.Ini; const termbox = interop.termbox; -const LY_VERSION = "1.0.0"; +pub fn signalHandler(i: c_int) callconv(.C) void { + termbox.tb_shutdown(); + std.c.exit(i); +} pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -41,7 +50,7 @@ pub fn main() !void { var config: Config = undefined; var lang: Lang = undefined; - var info_line: []const u8 = undefined; + var info_line = InfoLine{}; if (res.args.help != 0) { try clap.help(stderr, clap.Help, ¶ms, .{}); @@ -50,13 +59,15 @@ pub fn main() !void { std.os.exit(0); } if (res.args.version != 0) { - _ = try stderr.write("Ly version " ++ LY_VERSION ++ "\n"); + _ = try stderr.write("Ly version " ++ build_options.version ++ "\n"); std.os.exit(0); } // Load configuration file - var config_reader = ConfigReader.init(allocator); - defer config_reader.deinit(); + var config_ini = Ini(Config).init(allocator); + defer config_ini.deinit(); + var lang_ini = Ini(Lang).init(allocator); + defer lang_ini.deinit(); if (res.args.config) |s| { const trailing_slash = if (s[s.len - 1] != '/') "/" else ""; @@ -64,19 +75,19 @@ pub fn main() !void { const config_path = try std.fmt.allocPrint(allocator, "{s}{s}config.ini", .{ s, trailing_slash }); defer allocator.free(config_path); - config = try config_reader.readConfig(config_path); + config = config_ini.readToStruct(config_path) catch Config{}; - const lang_path = try std.fmt.allocPrint(allocator, "{s}{s}lang/{s}.ini", .{ s, trailing_slash, config.ly.lang }); + const lang_path = try std.fmt.allocPrint(allocator, "{s}{s}lang/{s}.ini", .{ s, trailing_slash, config.lang }); defer allocator.free(lang_path); - lang = try config_reader.readLang(lang_path); + lang = lang_ini.readToStruct(lang_path) catch Lang{}; } else { - config = try config_reader.readConfig(build_options.data_directory ++ "/config.ini"); + config = config_ini.readToStruct(build_options.data_directory ++ "/config.ini") catch Config{}; - const lang_path = try std.fmt.allocPrint(allocator, "{s}/lang/{s}.ini", .{ build_options.data_directory, config.ly.lang }); + const lang_path = try std.fmt.allocPrint(allocator, "{s}/lang/{s}.ini", .{ build_options.data_directory, config.lang }); defer allocator.free(lang_path); - lang = try config_reader.readLang(lang_path); + lang = lang_ini.readToStruct(lang_path) catch Lang{}; } // Initialize information line with host name @@ -86,78 +97,89 @@ pub fn main() !void { get_host_name: { const host_name_struct = interop.getHostName(allocator) catch |err| { if (err == error.CannotGetHostName) { - info_line = lang.ly.err_hostname; + try info_line.setText(lang.err_hostname); } else { - info_line = lang.ly.err_alloc; + try info_line.setText(lang.err_alloc); } break :get_host_name; }; got_host_name = true; host_name_buffer = host_name_struct.buffer; - info_line = host_name_struct.slice; + try info_line.setText(host_name_struct.slice); + } + + defer { + if (got_host_name) allocator.free(host_name_buffer); } // Initialize termbox _ = termbox.tb_init(); defer termbox.tb_shutdown(); + const act = std.os.Sigaction{ + .handler = .{ .handler = &signalHandler }, + .mask = std.os.empty_sigset, + .flags = 0, + }; + try std.os.sigaction(std.os.SIG.TERM, &act, null); + _ = termbox.tb_select_output_mode(termbox.TB_OUTPUT_NORMAL); termbox.tb_clear(); + // we need this to reset it after auth. + const tb_termios = try std.os.tcgetattr(std.os.STDIN_FILENO); + // Initialize terminal buffer - const labels_max_length = @max(lang.ly.login.len, lang.ly.password.len); + const labels_max_length = @max(lang.login.len, lang.password.len); - var buffer = TerminalBuffer.init(config.ly.margin_box_v, config.ly.margin_box_h, config.ly.input_len, labels_max_length, config.ly.fg, config.ly.bg); + var buffer = TerminalBuffer.init(config, labels_max_length); // Initialize components - var desktop = try Desktop.init(allocator, &buffer, config.ly.max_desktop_len); + var desktop = try Desktop.init(allocator, &buffer, config.max_desktop_len, lang); defer desktop.deinit(); - desktop.addEnvironment(lang.ly.shell, "", .shell) catch { - info_line = lang.ly.err_alloc; - }; - desktop.addEnvironment(lang.ly.xinitrc, config.ly.xinitrc, .xinitrc) catch { - info_line = lang.ly.err_alloc; + desktop.addEnvironment(lang.shell, "", .shell) catch { + try info_line.setText(lang.err_alloc); }; + if (config.xinitrc) |xinitrc| { + desktop.addEnvironment(lang.xinitrc, xinitrc, .xinitrc) catch { + try info_line.setText(lang.err_alloc); + }; + } - try desktop.crawl(config.ly.waylandsessions, .wayland); - try desktop.crawl(config.ly.xsessions, .x11); + try desktop.crawl(config.waylandsessions, .wayland); + try desktop.crawl(config.xsessions, .x11); - var login = try Text.init(allocator, &buffer, config.ly.max_login_len); + var login = try Text.init(allocator, &buffer, config.max_login_len); defer login.deinit(); - var password = try Text.init(allocator, &buffer, config.ly.max_password_len); + var password = try Text.init(allocator, &buffer, config.max_password_len); defer password.deinit(); - // Load last saved username and desktop selection, if any - if (config.ly.load) load_last_saved: { - var file = std.fs.openFileAbsolute(config.ly.save_file, .{}) catch break :load_last_saved; - defer file.close(); - - const reader = file.reader(); - const username_length = try reader.readIntLittle(u64); + var active_input = config.default_input; + var insert_mode = !config.vi_mode; - const username_buffer = try allocator.alloc(u8, username_length); - defer allocator.free(username_buffer); - - _ = try reader.read(username_buffer); - - const current_desktop = try reader.readIntLittle(u64); - - if (username_buffer.len > 0) { - try login.text.appendSlice(username_buffer); - login.end = username_buffer.len; + // Load last saved username and desktop selection, if any + if (config.load) { + var save_ini = Ini(Save).init(allocator); + defer save_ini.deinit(); + const save = save_ini.readToStruct(config.save_file) catch Save{}; + + if (save.user) |user| { + try login.text.appendSlice(user); + login.end = user.len; + active_input = .password; } - if (current_desktop < desktop.environments.items.len) desktop.current = current_desktop; + if (save.session_index) |session_index| { + if (session_index < desktop.environments.items.len) desktop.current = session_index; + } } - var active_input = if (config.ly.default_input == .login and login.text.items.len != login.end) .password else config.ly.default_input; - // Place components on the screen { - buffer.drawBoxCenter(!config.ly.hide_borders, config.ly.blank_box); + buffer.drawBoxCenter(!config.hide_borders, config.blank_box); const coordinates = buffer.calculateComponentCoordinates(); desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); @@ -165,12 +187,12 @@ pub fn main() !void { password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); switch (active_input) { - .session => desktop.handle(null), - .login => login.handle(null) catch { - info_line = lang.ly.err_alloc; + .session => desktop.handle(null, insert_mode), + .login => login.handle(null, insert_mode) catch { + try info_line.setText(lang.err_alloc); }, - .password => password.handle(null) catch { - info_line = lang.ly.err_alloc; + .password => password.handle(null, insert_mode) catch { + try info_line.setText(lang.err_alloc); }, } } @@ -179,25 +201,27 @@ pub fn main() !void { var doom: Doom = undefined; var matrix: Matrix = undefined; - switch (config.ly.animation) { + switch (config.animation) { .none => {}, .doom => doom = try Doom.init(allocator, &buffer), .matrix => matrix = try Matrix.init(allocator, &buffer), } defer { - switch (config.ly.animation) { + switch (config.animation) { .none => {}, .doom => doom.deinit(), .matrix => matrix.deinit(), } } - const animate = config.ly.animation != .none; - const has_clock = config.ly.clock.len > 0; - const shutdown_key = try std.fmt.parseInt(u8, config.ly.shutdown_key[1..], 10); - const restart_key = try std.fmt.parseInt(u8, config.ly.restart_key[1..], 10); + const animate = config.animation != .none; + const shutdown_key = try std.fmt.parseInt(u8, config.shutdown_key[1..], 10); + const shutdown_len = try utils.strWidth(lang.shutdown); + const restart_key = try std.fmt.parseInt(u8, config.restart_key[1..], 10); + const restart_len = try utils.strWidth(lang.restart); + const sleep_key = try std.fmt.parseInt(u8, config.sleep_key[1..], 10); - var event = std.mem.zeroes(termbox.tb_event); + var event: termbox.tb_event = undefined; var run = true; var update = true; var resolution_changed = false; @@ -207,8 +231,8 @@ pub fn main() !void { // Switch to selected TTY if possible open_console_dev: { - const console_dev_z = allocator.dupeZ(u8, config.ly.console_dev) catch { - info_line = lang.ly.err_alloc; + const console_dev_z = allocator.dupeZ(u8, config.console_dev) catch { + try info_line.setText(lang.err_alloc); break :open_console_dev; }; defer allocator.free(console_dev_z); @@ -217,17 +241,17 @@ pub fn main() !void { defer _ = std.c.close(fd); if (fd < 0) { - info_line = lang.ly.err_console_dev; + try info_line.setText(lang.err_console_dev); break :open_console_dev; } - _ = std.c.ioctl(fd, interop.VT_ACTIVATE, config.ly.tty); - _ = std.c.ioctl(fd, interop.VT_WAITACTIVE, config.ly.tty); + _ = std.c.ioctl(fd, interop.VT_ACTIVATE, config.tty); + _ = std.c.ioctl(fd, interop.VT_WAITACTIVE, config.tty); } while (run) { // If there's no input or there's an animation, a resolution change needs to be checked - if (!update or config.ly.animation != .none) { + if (!update or config.animation != .none) { if (!update) std.time.sleep(100_000_000); termbox.tb_present(); // Required to update tb_width(), tb_height() and tb_cell_buffer() @@ -249,13 +273,13 @@ pub fn main() !void { if (resolution_changed) { buffer.buffer = termbox.tb_cell_buffer(); - switch (config.ly.animation) { + switch (config.animation) { .none => {}, .doom => doom.realloc() catch { - info_line = lang.ly.err_alloc; + try info_line.setText(lang.err_alloc); }, .matrix => matrix.realloc() catch { - info_line = lang.ly.err_alloc; + try info_line.setText(lang.err_alloc); }, } @@ -267,30 +291,30 @@ pub fn main() !void { // If the user entered a wrong password 10 times in a row, play a cascade animation, else update normally if (auth_fails < 10) { switch (active_input) { - .session => desktop.handle(null), - .login => login.handle(null) catch { - info_line = lang.ly.err_alloc; + .session => desktop.handle(null, insert_mode), + .login => login.handle(null, insert_mode) catch { + try info_line.setText(lang.err_alloc); }, - .password => password.handle(null) catch { - info_line = lang.ly.err_alloc; + .password => password.handle(null, insert_mode) catch { + try info_line.setText(lang.err_alloc); }, } termbox.tb_clear(); - switch (config.ly.animation) { + switch (config.animation) { .none => {}, .doom => doom.draw(), .matrix => matrix.draw(), } - if (config.ly.bigclock and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: { + if (config.bigclock and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: { const format = "%H:%M"; const xo = buffer.width / 2 - (format.len * (bigclock.WIDTH + 1)) / 2; const yo = (buffer.height - buffer.box_height) / 2 - bigclock.HEIGHT - 2; const clock_str = interop.timeAsString(allocator, format, format.len + 1) catch { - info_line = lang.ly.err_alloc; + try info_line.setText(lang.err_alloc); break :draw_big_clock; }; defer allocator.free(clock_str); @@ -301,11 +325,11 @@ pub fn main() !void { } } - buffer.drawBoxCenter(!config.ly.hide_borders, config.ly.blank_box); + buffer.drawBoxCenter(!config.hide_borders, config.blank_box); - if (has_clock) draw_clock: { - const clock_buffer = interop.timeAsString(allocator, config.ly.clock, 32) catch { - info_line = lang.ly.err_alloc; + if (config.clock) |clock| draw_clock: { + const clock_buffer = interop.timeAsString(allocator, clock, 32) catch { + try info_line.setText(lang.err_alloc); break :draw_clock; }; defer allocator.free(clock_buffer); @@ -326,46 +350,61 @@ pub fn main() !void { const label_x = buffer.box_x + buffer.margin_box_h; const label_y = buffer.box_y + buffer.margin_box_v; - buffer.drawLabel(lang.ly.login, label_x, label_y + 4); - buffer.drawLabel(lang.ly.password, label_x, label_y + 6); + buffer.drawLabel(lang.login, label_x, label_y + 4); + buffer.drawLabel(lang.password, label_x, label_y + 6); - if (info_line.len > 0) { - const x = buffer.box_x + ((buffer.box_width - info_line.len) / 2); - buffer.drawLabel(info_line, x, label_y); + if (info_line.width > 0 and buffer.box_width > info_line.width) { + const x = buffer.box_x + ((buffer.box_width - info_line.width) / 2); + buffer.drawLabel(info_line.text, x, label_y); } - if (!config.ly.hide_key_hints) { + if (!config.hide_key_hints) { var length: u64 = 0; - buffer.drawLabel(config.ly.shutdown_key, length, 0); - length += config.ly.shutdown_key.len + 1; + buffer.drawLabel(config.shutdown_key, length, 0); + length += config.shutdown_key.len + 1; + buffer.drawLabel(" ", length - 1, 0); - buffer.drawLabel(lang.ly.shutdown, length, 0); - length += lang.ly.shutdown.len + 1; + buffer.drawLabel(lang.shutdown, length, 0); + length += shutdown_len + 1; - buffer.drawLabel(config.ly.restart_key, length, 0); - length += config.ly.restart_key.len + 1; + buffer.drawLabel(config.restart_key, length, 0); + length += config.restart_key.len + 1; + buffer.drawLabel(" ", length - 1, 0); + + buffer.drawLabel(lang.restart, length, 0); + length += restart_len + 1; + + if (config.sleep_cmd != null) { + buffer.drawLabel(config.sleep_key, length, 0); + length += config.sleep_key.len + 1; + buffer.drawLabel(" ", length - 1, 0); + + buffer.drawLabel(lang.sleep, length, 0); + } + } - buffer.drawLabel(lang.ly.restart, length, 0); - length += lang.ly.restart.len + 1; + if (config.vi_mode) { + const label_txt = if (insert_mode) lang.insert else lang.normal; + buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y - 1); } draw_lock_state: { - const lock_state = interop.getLockState(allocator, config.ly.console_dev) catch |err| { + const lock_state = interop.getLockState(config.console_dev) catch |err| { if (err == error.CannotOpenConsoleDev) { - info_line = lang.ly.err_console_dev; + try info_line.setText(lang.err_console_dev); } else { - info_line = lang.ly.err_alloc; + try info_line.setText(lang.err_alloc); } break :draw_lock_state; }; - var lock_state_x = buffer.width - lang.ly.numlock.len; - const lock_state_y: u64 = if (has_clock) 1 else 0; + var lock_state_x = buffer.width - lang.numlock.len; + const lock_state_y: u64 = if (config.clock != null) 1 else 0; - if (lock_state.numlock) buffer.drawLabel(lang.ly.numlock, lock_state_x, lock_state_y); - lock_state_x -= lang.ly.capslock.len + 1; - if (lock_state.capslock) buffer.drawLabel(lang.ly.capslock, lock_state_x, lock_state_y); + if (lock_state.numlock) buffer.drawLabel(lang.numlock, lock_state_x, lock_state_y); + lock_state_x -= lang.capslock.len + 1; + if (lock_state.capslock) buffer.drawLabel(lang.capslock, lock_state_x, lock_state_y); } if (resolution_changed) { @@ -379,7 +418,7 @@ pub fn main() !void { desktop.draw(); login.draw(); - password.drawMasked(config.ly.asterisk); + password.drawMasked(config.asterisk); update = animate; } else { @@ -399,14 +438,14 @@ pub fn main() !void { // Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead if (animate) { - timeout = config.ly.min_refresh_delta; - } else if (config.ly.bigclock and config.ly.clock.len == 0) { - var tv = std.mem.zeroes(std.c.timeval); + timeout = config.min_refresh_delta; + } else if (config.bigclock and config.clock == null) { + var tv: std.c.timeval = undefined; _ = std.c.gettimeofday(&tv, null); timeout = @intCast((60 - @rem(tv.tv_sec, 60)) * 1000 - @divTrunc(tv.tv_usec, 1000) + 1); - } else if (config.ly.clock.len > 0 or auth_fails >= 10) { - var tv = std.mem.zeroes(std.c.timeval); + } else if (config.clock != null or auth_fails >= 10) { + var tv: std.c.timeval = undefined; _ = std.c.gettimeofday(&tv, null); timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1); @@ -417,13 +456,28 @@ pub fn main() !void { if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; switch (event.key) { - termbox.TB_KEY_F1, termbox.TB_KEY_F2, termbox.TB_KEY_F3, termbox.TB_KEY_F4, termbox.TB_KEY_F5, termbox.TB_KEY_F6, termbox.TB_KEY_F7, termbox.TB_KEY_F8, termbox.TB_KEY_F9, termbox.TB_KEY_F10, termbox.TB_KEY_F11, termbox.TB_KEY_F12 => { - if (0xFFFF - event.key + 1 == shutdown_key) { + termbox.TB_KEY_ESC => { + if (config.vi_mode and insert_mode) { + insert_mode = false; + update = true; + } + }, + termbox.TB_KEY_F12...termbox.TB_KEY_F1 => { + const pressed_key = 0xFFFF - event.key + 1; + if (pressed_key == shutdown_key) { shutdown = true; run = false; - } else if (0xFFFF - event.key + 1 == restart_key) { + } else if (pressed_key == restart_key) { restart = true; run = false; + } else if (pressed_key == sleep_key) { + if (config.sleep_cmd) |sleep_cmd| { + const pid = try std.os.fork(); + if (pid == 0) { + std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", sleep_cmd }) catch std.os.exit(1); + std.os.exit(0); + } + } } }, termbox.TB_KEY_CTRL_C => run = false, @@ -458,51 +512,90 @@ pub fn main() !void { }; update = true; }, - termbox.TB_KEY_ENTER => authenticate: { - if (config.ly.save) save_last_settings: { - var file = std.fs.createFileAbsolute(config.ly.save_file, .{}) catch break :save_last_settings; + termbox.TB_KEY_ENTER => { + if (config.save) save_last_settings: { + var file = std.fs.createFileAbsolute(config.save_file, .{}) catch break :save_last_settings; defer file.close(); - const writer = file.writer(); - try writer.writeIntLittle(u64, login.end); - _ = try writer.write(login.text.items); - try writer.writeIntLittle(u64, desktop.current); + const save_data = Save{ + .user = login.text.items, + .session_index = desktop.current, + }; + ini.writeFromStruct(save_data, file.writer(), null) catch break :save_last_settings; + } + + var shared_err = try SharedError.init(); + defer shared_err.deinit(); + + const session_pid = try std.os.fork(); + if (session_pid == 0) { + auth.authenticate(allocator, config, desktop, login, &password) catch |err| { + shared_err.writeError(err); + std.os.exit(1); + }; + std.os.exit(0); } - var has_error = false; - - auth.authenticate( - allocator, - config.ly.tty, - desktop, - login, - &password, - config.ly.service_name, - config.ly.path, - config.ly.term_reset_cmd, - config.ly.wayland_cmd, - ) catch { - has_error = true; + _ = std.os.waitpid(session_pid, 0); + + var auth_err = shared_err.readError(); + if (auth_err) |err| { auth_fails += 1; active_input = .password; + try info_line.setText(getAuthErrorMsg(err, lang)); + if (config.clear_password or err != error.PamAuthError) password.clear(); + } else { + password.clear(); + try info_line.setText(lang.logout); + } - // TODO: Errors in info_line + try std.os.tcsetattr(std.os.STDIN_FILENO, .FLUSH, tb_termios); + termbox.tb_clear(); + termbox.tb_present(); - if (config.ly.blank_password) password.clear(); - }; update = true; - if (!has_error) info_line = lang.ly.logout; - std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.ly.term_restore_cursor_cmd }) catch break :authenticate; + const pid = try std.os.fork(); + if (pid == 0) { + std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.term_restore_cursor_cmd }) catch std.os.exit(1); + std.os.exit(0); + } }, else => { + if (!insert_mode) { + switch (event.ch) { + 'k' => { + active_input = switch (active_input) { + .session, .login => .session, + .password => .login, + }; + update = true; + continue; + }, + 'j' => { + active_input = switch (active_input) { + .session => .login, + .login, .password => .password, + }; + update = true; + continue; + }, + 'i' => { + insert_mode = true; + update = true; + continue; + }, + else => {}, + } + } + switch (active_input) { - .session => desktop.handle(&event), - .login => login.handle(&event) catch { - info_line = lang.ly.err_alloc; + .session => desktop.handle(&event, insert_mode), + .login => login.handle(&event, insert_mode) catch { + try info_line.setText(lang.err_alloc); }, - .password => password.handle(&event) catch { - info_line = lang.ly.err_alloc; + .password => password.handle(&event, insert_mode) catch { + try info_line.setText(lang.err_alloc); }, } update = true; @@ -510,11 +603,36 @@ pub fn main() !void { } } - if (got_host_name) allocator.free(host_name_buffer); - if (shutdown) { - return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.ly.shutdown_cmd }); + return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.shutdown_cmd }); } else if (restart) { - return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.ly.restart_cmd }); + return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.restart_cmd }); } } + +fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 { + return switch (err) { + error.GetPasswordNameFailed => lang.err_pwnam, + error.GroupInitializationFailed => lang.err_user_init, + error.SetUserGidFailed => lang.err_user_gid, + error.SetUserUidFailed => lang.err_user_uid, + error.ChangeDirectoryFailed => lang.err_perm_dir, + error.SetPathFailed => lang.err_path, + error.PamAccountExpired => lang.err_pam_acct_expired, + error.PamAuthError => lang.err_pam_auth, + error.PamAuthInfoUnavailable => lang.err_pam_authinfo_unavail, + error.PamBufferError => lang.err_pam_buf, + error.PamCredentialsError => lang.err_pam_cred_err, + error.PamCredentialsExpired => lang.err_pam_cred_expired, + error.PamCredentialsInsufficient => lang.err_pam_cred_insufficient, + error.PamCredentialsUnavailable => lang.err_pam_cred_unavail, + error.PamMaximumTries => lang.err_pam_maxtries, + error.PamNewAuthTokenRequired => lang.err_pam_authok_reqd, + error.PamPermissionDenied => lang.err_pam_perm_denied, + error.PamSessionError => lang.err_pam_session, + error.PamSystemError => lang.err_pam_sys, + error.PamUserUnknown => lang.err_pam_user_unknown, + error.PamAbort => lang.err_pam_abort, + else => "An unknown error occurred", + }; +} diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index e586a23..fd3d11f 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -2,6 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const interop = @import("../interop.zig"); const utils = @import("utils.zig"); +const Config = @import("../config/Config.zig"); const Allocator = std.mem.Allocator; const Random = std.rand.Random; @@ -16,6 +17,7 @@ height: u64, buffer: [*]termbox.tb_cell, fg: u8, bg: u8, +border_fg: u8, box_chars: struct { left_up: u32, left_down: u32, @@ -34,7 +36,7 @@ box_height: u64, margin_box_v: u8, margin_box_h: u8, -pub fn init(margin_box_v: u8, margin_box_h: u8, input_length: u8, labels_max_length: u64, fg: u8, bg: u8) TerminalBuffer { +pub fn init(config: Config, labels_max_length: u64) TerminalBuffer { var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp())); return .{ @@ -42,8 +44,9 @@ pub fn init(margin_box_v: u8, margin_box_h: u8, input_length: u8, labels_max_len .width = @intCast(termbox.tb_width()), .height = @intCast(termbox.tb_height()), .buffer = termbox.tb_cell_buffer(), - .fg = fg, - .bg = bg, + .fg = config.fg, + .bg = config.bg, + .border_fg = config.border_fg, .box_chars = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) .{ .left_up = 0x250C, .left_down = 0x2514, @@ -66,10 +69,10 @@ pub fn init(margin_box_v: u8, margin_box_h: u8, input_length: u8, labels_max_len .labels_max_length = labels_max_length, .box_x = 0, .box_y = 0, - .box_width = (2 * margin_box_h) + input_length + 1 + labels_max_length, - .box_height = 7 + (2 * margin_box_v), - .margin_box_v = margin_box_v, - .margin_box_h = margin_box_h, + .box_width = (2 * config.margin_box_h) + config.input_len + 1 + labels_max_length, + .box_height = 7 + (2 * config.margin_box_v), + .margin_box_v = config.margin_box_v, + .margin_box_h = config.margin_box_h, }; } @@ -107,13 +110,13 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) self.box_y = y1; if (show_borders) { - termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.fg, self.bg); - termbox.tb_change_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.fg, self.bg); - termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.fg, self.bg); - termbox.tb_change_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.fg, self.bg); + termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg); + termbox.tb_change_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg); + termbox.tb_change_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg); + termbox.tb_change_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg); - var c1 = utils.initCell(self.box_chars.top, self.fg, self.bg); - var c2 = utils.initCell(self.box_chars.bottom, self.fg, self.bg); + var c1 = utils.initCell(self.box_chars.top, self.border_fg, self.bg); + var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg); for (0..self.box_width) |i| { termbox.tb_put_cell(@intCast(x1 + i), @intCast(y1 - 1), &c1); @@ -158,7 +161,13 @@ pub fn calculateComponentCoordinates(self: TerminalBuffer) struct { pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void { const yc: c_int = @intCast(y); - for (0..text.len) |xx| termbox.tb_change_cell(@intCast(x + xx), yc, text[xx], self.fg, self.bg); + const utf8view = std.unicode.Utf8View.init(text) catch return; + var utf8 = utf8view.iterator(); + + var i = x; + while (utf8.nextCodepoint()) |codepoint| : (i += 1) { + termbox.tb_change_cell(@intCast(i), yc, codepoint, self.fg, self.bg); + } } pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void { diff --git a/src/tui/components/Desktop.zig b/src/tui/components/Desktop.zig index ba70f72..c6808b9 100644 --- a/src/tui/components/Desktop.zig +++ b/src/tui/components/Desktop.zig @@ -1,37 +1,36 @@ const std = @import("std"); -const ini = @import("ini"); const enums = @import("../../enums.zig"); const interop = @import("../../interop.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig"); +const Ini = @import("zigini").Ini; +const Lang = @import("../../config/Lang.zig"); const Allocator = std.mem.Allocator; const EnvironmentList = std.ArrayList(Environment); const DisplayServer = enums.DisplayServer; +const ViMode = enums.ViMode; const termbox = interop.termbox; -pub const DESKTOP_ENTRY_MAX_SIZE = 8 * 1024; - const Desktop = @This(); pub const Environment = struct { - has_entry_buffer: bool, - entry_buffer: []u8, - name: []const u8, - xdg_name: []const u8, - cmd: []const u8, - specifier: []const u8, - display_server: DisplayServer, + entry_ini: ?Ini(Entry) = null, + name: []const u8 = "", + xdg_name: []const u8 = "", + cmd: []const u8 = "", + specifier: []const u8 = "", + display_server: DisplayServer = .wayland, }; -pub const Entry = struct { - Desktop_Entry: struct { - Exec: []const u8, - Name: []const u8, - }, +const DesktopEntry = struct { + Exec: []const u8 = "", + Name: []const u8 = "", }; +pub const Entry = struct { Desktop_Entry: DesktopEntry = DesktopEntry{} }; + allocator: Allocator, buffer: *TerminalBuffer, environments: EnvironmentList, @@ -39,8 +38,9 @@ current: u64, visible_length: u64, x: u64, y: u64, +lang: Lang, -pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Desktop { +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64, lang: Lang) !Desktop { return .{ .allocator = allocator, .buffer = buffer, @@ -49,12 +49,13 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Des .visible_length = 0, .x = 0, .y = 0, + .lang = lang, }; } pub fn deinit(self: Desktop) void { - for (self.environments.items) |environment| { - if (environment.has_entry_buffer) self.allocator.free(environment.entry_buffer); + for (self.environments.items) |*environment| { + if (environment.entry_ini) |*entry_ini| entry_ini.deinit(); } self.environments.deinit(); @@ -68,15 +69,14 @@ pub fn position(self: *Desktop, x: u64, y: u64, visible_length: u64) void { pub fn addEnvironment(self: *Desktop, name: []const u8, cmd: []const u8, display_server: DisplayServer) !void { try self.environments.append(.{ - .has_entry_buffer = false, - .entry_buffer = undefined, + .entry_ini = null, .name = name, .xdg_name = name, // TODO .cmd = cmd, .specifier = switch (display_server) { - .wayland => "wayland", - .x11 => "x11", - else => "other", + .wayland => self.lang.wayland, + .x11 => self.lang.x11, + else => self.lang.other, }, .display_server = display_server, }); @@ -84,17 +84,16 @@ pub fn addEnvironment(self: *Desktop, name: []const u8, cmd: []const u8, display self.current = self.environments.items.len - 1; } -pub fn addEnvironmentWithBuffer(self: *Desktop, entry_buffer: []u8, name: []const u8, cmd: []const u8, display_server: DisplayServer) !void { +pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), name: []const u8, cmd: []const u8, display_server: DisplayServer) !void { try self.environments.append(.{ - .has_entry_buffer = true, - .entry_buffer = entry_buffer, + .entry_ini = entry_ini, .name = name, .xdg_name = name, // TODO .cmd = cmd, .specifier = switch (display_server) { - .wayland => "wayland", - .x11 => "x11", - else => "other", + .wayland => self.lang.wayland, + .x11 => self.lang.x11, + else => self.lang.other, }, .display_server = display_server, }); @@ -103,32 +102,38 @@ pub fn addEnvironmentWithBuffer(self: *Desktop, entry_buffer: []u8, name: []cons } pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void { - var directory = std.fs.openDirAbsolute(path, .{}) catch return; - defer directory.close(); - var iterable_directory = try std.fs.openIterableDirAbsolute(path, .{}); defer iterable_directory.close(); var iterator = iterable_directory.iterate(); while (try iterator.next()) |item| { - var file = try directory.openFile(item.name, .{}); - defer file.close(); + if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue; - const buffer = try file.readToEndAlloc(self.allocator, DESKTOP_ENTRY_MAX_SIZE); - const entry = try ini.readToStruct(Entry, buffer); + const entry_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ path, item.name }); + defer self.allocator.free(entry_path); + var entry_ini = Ini(Entry).init(self.allocator); + var entry = try entry_ini.readToStruct(entry_path); - try self.addEnvironmentWithBuffer(buffer, entry.Desktop_Entry.Name, entry.Desktop_Entry.Exec, display_server); + try self.addEnvironmentWithIni(entry_ini, entry.Desktop_Entry.Name, entry.Desktop_Entry.Exec, display_server); } } -pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event) void { +pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, insert_mode: bool) void { if (maybe_event) |event| blk: { if (event.type != termbox.TB_EVENT_KEY) break :blk; switch (event.key) { termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(), termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(), - else => {}, + else => { + if (!insert_mode) { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + }, } } diff --git a/src/tui/components/InfoLine.zig b/src/tui/components/InfoLine.zig new file mode 100644 index 0000000..0a216a9 --- /dev/null +++ b/src/tui/components/InfoLine.zig @@ -0,0 +1,11 @@ +const utils = @import("../utils.zig"); + +const InfoLine = @This(); + +text: []const u8 = "", +width: u8 = 0, + +pub fn setText(self: *InfoLine, text: []const u8) !void { + self.width = if (text.len > 0) try utils.strWidth(text) else 0; + self.text = text; +} diff --git a/src/tui/components/Text.zig b/src/tui/components/Text.zig index 304f37b..934e97e 100644 --- a/src/tui/components/Text.zig +++ b/src/tui/components/Text.zig @@ -2,6 +2,7 @@ const std = @import("std"); const interop = @import("../../interop.zig"); const TerminalBuffer = @import("../TerminalBuffer.zig"); const utils = @import("../utils.zig"); +const ViMode = @import("../../enums.zig").ViMode; const Allocator = std.mem.Allocator; const DynamicString = std.ArrayList(u8); @@ -46,7 +47,7 @@ pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void { self.visible_length = visible_length; } -pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event) !void { +pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void { if (maybe_event) |event| blk: { if (event.type != termbox.TB_EVENT_KEY) break :blk; @@ -54,9 +55,27 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event) !void { termbox.TB_KEY_ARROW_LEFT => self.goLeft(), termbox.TB_KEY_ARROW_RIGHT => self.goRight(), termbox.TB_KEY_DELETE => self.delete(), - termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => self.backspace(), + termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => { + if (insert_mode) { + self.backspace(); + } else { + self.goLeft(); + } + }, termbox.TB_KEY_SPACE => try self.write(' '), - else => if (event.ch > 31 and event.ch < 127) try self.write(@intCast(event.ch)), + else => { + if (event.ch > 31 and event.ch < 127) { + if (insert_mode) { + try self.write(@intCast(event.ch)); + } else { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + } + }, } } diff --git a/src/tui/utils.zig b/src/tui/utils.zig index bf9304e..37fe2d0 100644 --- a/src/tui/utils.zig +++ b/src/tui/utils.zig @@ -4,9 +4,19 @@ const interop = @import("../interop.zig"); const termbox = interop.termbox; pub fn initCell(ch: u32, fg: u32, bg: u32) termbox.tb_cell { - var cell = std.mem.zeroes(termbox.tb_cell); - cell.ch = ch; - cell.fg = fg; - cell.bg = bg; - return cell; + return .{ + .ch = ch, + .fg = fg, + .bg = bg, + }; +} + +// Every codepoint is assumed to have a width of 1. +// Since ly should be running in a tty, this should be fine. +pub fn strWidth(str: []const u8) !u8 { + const utf8view = try std.unicode.Utf8View.init(str); + var utf8 = utf8view.iterator(); + var i: u8 = 0; + while (utf8.nextCodepoint()) |_| i += 1; + return i; }