diff --git a/src/auth.zig b/src/auth.zig index 9f295af..c05b9b3 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -4,7 +4,7 @@ const builtin = @import("builtin"); const enums = @import("enums.zig"); const interop = @import("interop.zig"); const TerminalBuffer = @import("tui/TerminalBuffer.zig"); -const Desktop = @import("tui/components/Desktop.zig"); +const Session = @import("tui/components/Session.zig"); const Text = @import("tui/components/Text.zig"); const Config = @import("config/Config.zig"); const Allocator = std.mem.Allocator; @@ -22,7 +22,7 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void { if (child_pid > 0) _ = std.c.kill(child_pid, i); } -pub fn authenticate(config: Config, current_environment: Desktop.Environment, login: [:0]const u8, password: [:0]const u8) !void { +pub fn authenticate(config: Config, current_environment: Session.Environment, login: [:0]const u8, password: [:0]const u8) !void { var tty_buffer: [3]u8 = undefined; const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty}); @@ -125,7 +125,7 @@ fn startSession( config: Config, pwd: *std.c.passwd, handle: ?*interop.pam.pam_handle, - current_environment: Desktop.Environment, + current_environment: Session.Environment, ) !void { const status = interop.initgroups(pwd.pw_name.?, pwd.pw_gid); if (status != 0) return error.GroupInitializationFailed; diff --git a/src/main.zig b/src/main.zig index 7f49dfc..e5ea380 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,7 +9,7 @@ const interop = @import("interop.zig"); const Doom = @import("animations/Doom.zig"); const Matrix = @import("animations/Matrix.zig"); const TerminalBuffer = @import("tui/TerminalBuffer.zig"); -const Desktop = @import("tui/components/Desktop.zig"); +const Session = @import("tui/components/Session.zig"); const Text = @import("tui/components/Text.zig"); const InfoLine = @import("tui/components/InfoLine.zig"); const Config = @import("config/Config.zig"); @@ -235,23 +235,23 @@ pub fn main() !void { var buffer = TerminalBuffer.init(config, labels_max_length, random); // Initialize components - var desktop = try Desktop.init(allocator, &buffer, config.max_desktop_len, lang); - defer desktop.deinit(); + var session = try Session.init(allocator, &buffer, config.max_desktop_len, lang); + defer session.deinit(); - desktop.addEnvironment(.{ .Name = lang.shell }, "", .shell) catch { + session.addEnvironment(.{ .Name = lang.shell }, "", .shell) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }; if (build_options.enable_x11_support) { if (config.xinitrc) |xinitrc| { - desktop.addEnvironment(.{ .Name = lang.xinitrc, .Exec = xinitrc }, "", .xinitrc) catch { + session.addEnvironment(.{ .Name = lang.xinitrc, .Exec = xinitrc }, "", .xinitrc) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }; } } - try desktop.crawl(config.waylandsessions, .wayland); - if (build_options.enable_x11_support) try desktop.crawl(config.xsessions, .x11); + try session.crawl(config.waylandsessions, .wayland); + if (build_options.enable_x11_support) try session.crawl(config.xsessions, .x11); var login = try Text.init(allocator, &buffer, config.max_login_len, false, null); defer login.deinit(); @@ -272,7 +272,7 @@ pub fn main() !void { } if (save.session_index) |session_index| { - if (session_index < desktop.environments.items.len) desktop.current = session_index; + if (session_index < session.label.list.items.len) session.label.current = session_index; } } @@ -281,12 +281,12 @@ pub fn main() !void { buffer.drawBoxCenter(!config.hide_borders, config.blank_box); const coordinates = buffer.calculateComponentCoordinates(); - desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); + session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); switch (active_input) { - .session => desktop.handle(null, insert_mode), + .session => session.label.handle(null, insert_mode), .login => login.handle(null, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }, @@ -404,7 +404,7 @@ pub fn main() !void { if (resolution_changed) { const coordinates = buffer.calculateComponentCoordinates(); - desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); + session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length); login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); @@ -412,7 +412,7 @@ pub fn main() !void { } switch (active_input) { - .session => desktop.handle(null, insert_mode), + .session => session.label.handle(null, insert_mode), .login => login.handle(null, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }, @@ -506,7 +506,7 @@ pub fn main() !void { } } - desktop.draw(); + session.label.draw(); login.draw(); password.draw(); } else { @@ -631,7 +631,7 @@ pub fn main() !void { const save_data = Save{ .user = login.text.items, - .session_index = desktop.current, + .session_index = session.label.current, }; ini.writeFromStruct(save_data, file.writer(), null, true, .{}) catch break :save_last_settings; } @@ -652,7 +652,7 @@ pub fn main() !void { session_pid = try std.posix.fork(); if (session_pid == 0) { - const current_environment = desktop.environments.items[desktop.current]; + const current_environment = session.label.list.items[session.label.current]; auth.authenticate(config, current_environment, login_text, password_text) catch |err| { shared_err.writeError(err); std.process.exit(1); @@ -715,7 +715,7 @@ pub fn main() !void { } switch (active_input) { - .session => desktop.handle(&event, insert_mode), + .session => session.label.handle(&event, insert_mode), .login => login.handle(&event, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }, diff --git a/src/tui/components/Desktop.zig b/src/tui/components/Desktop.zig deleted file mode 100644 index 9c0fdf1..0000000 --- a/src/tui/components/Desktop.zig +++ /dev/null @@ -1,223 +0,0 @@ -const std = @import("std"); -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 termbox = interop.termbox; - -const Desktop = @This(); - -pub const Environment = struct { - entry_ini: ?Ini(Entry) = null, - name: [:0]const u8 = "", - xdg_session_desktop: [:0]const u8 = "", - xdg_desktop_names: ?[:0]const u8 = "", - cmd: []const u8 = "", - specifier: []const u8 = "", - display_server: DisplayServer = .wayland, -}; - -const DesktopEntry = struct { - Exec: []const u8 = "", - Name: [:0]const u8 = "", - DesktopNames: ?[]const u8 = null, -}; - -pub const Entry = struct { @"Desktop Entry": DesktopEntry = DesktopEntry{} }; - -allocator: Allocator, -buffer: *TerminalBuffer, -environments: EnvironmentList, -current: usize, -visible_length: usize, -x: usize, -y: usize, -lang: Lang, - -pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: usize, lang: Lang) !Desktop { - return .{ - .allocator = allocator, - .buffer = buffer, - .environments = try EnvironmentList.initCapacity(allocator, max_length), - .current = 0, - .visible_length = 0, - .x = 0, - .y = 0, - .lang = lang, - }; -} - -pub fn deinit(self: Desktop) void { - for (self.environments.items) |*environment| { - if (environment.entry_ini) |*entry_ini| entry_ini.deinit(); - if (environment.xdg_desktop_names) |desktop_name| self.allocator.free(desktop_name); - self.allocator.free(environment.xdg_session_desktop); - } - - self.environments.deinit(); -} - -pub fn position(self: *Desktop, x: usize, y: usize, visible_length: usize) void { - self.x = x; - self.y = y; - self.visible_length = visible_length; -} - -pub fn addEnvironment(self: *Desktop, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void { - var xdg_desktop_names: ?[:0]const u8 = null; - if (entry.DesktopNames) |desktop_names| { - const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names); - for (desktop_names_z) |*c| { - if (c.* == ';') c.* = ':'; - } - xdg_desktop_names = desktop_names_z; - } - - errdefer { - if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names); - } - - const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop); - errdefer self.allocator.free(session_desktop); - - try self.environments.append(.{ - .entry_ini = null, - .name = entry.Name, - .xdg_session_desktop = session_desktop, - .xdg_desktop_names = xdg_desktop_names, - .cmd = entry.Exec, - .specifier = switch (display_server) { - .wayland => self.lang.wayland, - .x11 => self.lang.x11, - else => self.lang.other, - }, - .display_server = display_server, - }); - - self.current = self.environments.items.len - 1; -} - -pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void { - const entry = entry_ini.data.@"Desktop Entry"; - var xdg_desktop_names: ?[:0]const u8 = null; - if (entry.DesktopNames) |desktop_names| { - const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names); - for (desktop_names_z) |*c| { - if (c.* == ';') c.* = ':'; - } - xdg_desktop_names = desktop_names_z; - } - - errdefer { - if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names); - } - - const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop); - errdefer self.allocator.free(session_desktop); - - try self.environments.append(.{ - .entry_ini = entry_ini, - .name = entry.Name, - .xdg_session_desktop = session_desktop, - .xdg_desktop_names = xdg_desktop_names, - .cmd = entry.Exec, - .specifier = switch (display_server) { - .wayland => self.lang.wayland, - .x11 => self.lang.x11, - else => self.lang.other, - }, - .display_server = display_server, - }); - - self.current = self.environments.items.len - 1; -} - -pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void { - var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return; - defer iterable_directory.close(); - - var iterator = iterable_directory.iterate(); - while (try iterator.next()) |item| { - if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue; - - 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); - _ = try entry_ini.readFileToStruct(entry_path, "#", null); - errdefer entry_ini.deinit(); - - var xdg_session_desktop: []const u8 = undefined; - const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames; - if (maybe_desktop_names) |desktop_names| { - xdg_session_desktop = std.mem.sliceTo(desktop_names, ';'); - } else { - // if DesktopNames is empty, we'll take the name of the session file - xdg_session_desktop = std.fs.path.stem(item.name); - } - - try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server); - } -} - -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 => { - if (!insert_mode) { - switch (event.ch) { - 'h' => self.goLeft(), - 'l' => self.goRight(), - else => {}, - } - } - }, - } - } - - _ = termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y)); -} - -pub fn draw(self: Desktop) void { - const environment = self.environments.items[self.current]; - - const length = @min(environment.name.len, self.visible_length - 3); - if (length == 0) return; - - const x = self.buffer.box_x + self.buffer.margin_box_h; - const y = self.buffer.box_y + self.buffer.margin_box_v + 2; - self.buffer.drawLabel(environment.specifier, x, y); - - _ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg); - _ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg); - - self.buffer.drawLabel(environment.name, self.x + 2, self.y); -} - -fn goLeft(self: *Desktop) void { - if (self.current == 0) { - self.current = self.environments.items.len - 1; - return; - } - - self.current -= 1; -} - -fn goRight(self: *Desktop) void { - if (self.current == self.environments.items.len - 1) { - self.current = 0; - return; - } - - self.current += 1; -} diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig new file mode 100644 index 0000000..f994ca2 --- /dev/null +++ b/src/tui/components/Session.zig @@ -0,0 +1,153 @@ +const std = @import("std"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); +const enums = @import("../../enums.zig"); +const generic = @import("generic.zig"); +const Ini = @import("zigini").Ini; +const Lang = @import("../../config/Lang.zig"); + +const Allocator = std.mem.Allocator; + +const DisplayServer = enums.DisplayServer; + +const EnvironmentLabel = generic.CyclableLabel(Environment); + +const Session = @This(); + +pub const Environment = struct { + entry_ini: ?Ini(Entry) = null, + name: [:0]const u8 = "", + xdg_session_desktop: [:0]const u8 = "", + xdg_desktop_names: ?[:0]const u8 = "", + cmd: []const u8 = "", + specifier: []const u8 = "", + display_server: DisplayServer = .wayland, +}; + +const DesktopEntry = struct { + Exec: []const u8 = "", + Name: [:0]const u8 = "", + DesktopNames: ?[]const u8 = null, +}; + +pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} }; + +label: EnvironmentLabel, +lang: Lang, + +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: usize, lang: Lang) !Session { + return .{ + .label = try EnvironmentLabel.init(allocator, buffer, max_length, drawItem), + .lang = lang, + }; +} + +pub fn deinit(self: Session) void { + for (self.label.list.items) |*environment| { + if (environment.entry_ini) |*entry_ini| entry_ini.deinit(); + if (environment.xdg_desktop_names) |desktop_name| self.label.allocator.free(desktop_name); + self.label.allocator.free(environment.xdg_session_desktop); + } + + self.label.deinit(); +} + +pub fn addEnvironment(self: *Session, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void { + var xdg_desktop_names: ?[:0]const u8 = null; + if (entry.DesktopNames) |desktop_names| { + const desktop_names_z = try self.label.allocator.dupeZ(u8, desktop_names); + for (desktop_names_z) |*c| { + if (c.* == ';') c.* = ':'; + } + xdg_desktop_names = desktop_names_z; + } + + errdefer { + if (xdg_desktop_names) |desktop_names| self.label.allocator.free(desktop_names); + } + + const session_desktop = try self.label.allocator.dupeZ(u8, xdg_session_desktop); + errdefer self.label.allocator.free(session_desktop); + + try self.label.addItem(.{ + .entry_ini = null, + .name = entry.Name, + .xdg_session_desktop = session_desktop, + .xdg_desktop_names = xdg_desktop_names, + .cmd = entry.Exec, + .specifier = switch (display_server) { + .wayland => self.lang.wayland, + .x11 => self.lang.x11, + else => self.lang.other, + }, + .display_server = display_server, + }); +} + +pub fn addEnvironmentWithIni(self: *Session, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void { + const entry = entry_ini.data.@"Desktop Entry"; + var xdg_desktop_names: ?[:0]const u8 = null; + if (entry.DesktopNames) |desktop_names| { + const desktop_names_z = try self.label.allocator.dupeZ(u8, desktop_names); + for (desktop_names_z) |*c| { + if (c.* == ';') c.* = ':'; + } + xdg_desktop_names = desktop_names_z; + } + + errdefer { + if (xdg_desktop_names) |desktop_names| self.label.allocator.free(desktop_names); + } + + const session_desktop = try self.label.allocator.dupeZ(u8, xdg_session_desktop); + errdefer self.label.allocator.free(session_desktop); + + try self.label.addItem(.{ + .entry_ini = entry_ini, + .name = entry.Name, + .xdg_session_desktop = session_desktop, + .xdg_desktop_names = xdg_desktop_names, + .cmd = entry.Exec, + .specifier = switch (display_server) { + .wayland => self.lang.wayland, + .x11 => self.lang.x11, + else => self.lang.other, + }, + .display_server = display_server, + }); +} + +pub fn crawl(self: *Session, path: []const u8, display_server: DisplayServer) !void { + var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return; + defer iterable_directory.close(); + + var iterator = iterable_directory.iterate(); + while (try iterator.next()) |item| { + if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue; + + const entry_path = try std.fmt.allocPrint(self.label.allocator, "{s}/{s}", .{ path, item.name }); + defer self.label.allocator.free(entry_path); + var entry_ini = Ini(Entry).init(self.label.allocator); + _ = try entry_ini.readFileToStruct(entry_path, "#", null); + errdefer entry_ini.deinit(); + + var xdg_session_desktop: []const u8 = undefined; + const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames; + if (maybe_desktop_names) |desktop_names| { + xdg_session_desktop = std.mem.sliceTo(desktop_names, ';'); + } else { + // if DesktopNames is empty, we'll take the name of the session file + xdg_session_desktop = std.fs.path.stem(item.name); + } + + try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server); + } +} + +fn drawItem(label: EnvironmentLabel, environment: Environment, x: usize, y: usize) bool { + const length = @min(environment.name.len, label.visible_length - 3); + if (length == 0) return false; + + label.buffer.drawLabel(environment.specifier, x, y); + label.buffer.drawLabel(environment.name, label.x + 2, label.y); + return true; +} diff --git a/src/tui/components/generic.zig b/src/tui/components/generic.zig new file mode 100644 index 0000000..99d4a27 --- /dev/null +++ b/src/tui/components/generic.zig @@ -0,0 +1,105 @@ +const std = @import("std"); +const enums = @import("../../enums.zig"); +const interop = @import("../../interop.zig"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); + +pub fn CyclableLabel(comptime ItemType: type) type { + return struct { + const Allocator = std.mem.Allocator; + const ItemList = std.ArrayList(ItemType); + const DrawItemFn = *const fn (Self, ItemType, usize, usize) bool; + + const termbox = interop.termbox; + + const Self = @This(); + + allocator: Allocator, + buffer: *TerminalBuffer, + list: ItemList, + current: usize, + visible_length: usize, + x: usize, + y: usize, + draw_item_fn: DrawItemFn, + + pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: usize, draw_item_fn: DrawItemFn) !Self { + return .{ + .allocator = allocator, + .buffer = buffer, + .list = try ItemList.initCapacity(allocator, max_length), + .current = 0, + .visible_length = 0, + .x = 0, + .y = 0, + .draw_item_fn = draw_item_fn, + }; + } + + pub fn deinit(self: Self) void { + self.list.deinit(); + } + + pub fn position(self: *Self, x: usize, y: usize, visible_length: usize) void { + self.x = x; + self.y = y; + self.visible_length = visible_length; + } + + pub fn addItem(self: *Self, item: ItemType) !void { + try self.list.append(item); + self.current = self.list.items.len - 1; + } + + pub fn handle(self: *Self, 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 => { + if (!insert_mode) { + switch (event.ch) { + 'h' => self.goLeft(), + 'l' => self.goRight(), + else => {}, + } + } + }, + } + } + + _ = termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y)); + } + + pub fn draw(self: Self) void { + const current_item = self.list.items[self.current]; + const x = self.buffer.box_x + self.buffer.margin_box_h; + const y = self.buffer.box_y + self.buffer.margin_box_v + 2; + + const continue_drawing = @call(.auto, self.draw_item_fn, .{ self, current_item, x, y }); + if (!continue_drawing) return; + + _ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg); + _ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg); + } + + fn goLeft(self: *Self) void { + if (self.current == 0) { + self.current = self.list.items.len - 1; + return; + } + + self.current -= 1; + } + + fn goRight(self: *Self) void { + if (self.current == self.list.items.len - 1) { + self.current = 0; + return; + } + + self.current += 1; + } + }; +}