diff --git a/build.zig b/build.zig index d7dd3b4..f913468 100644 --- a/build.zig +++ b/build.zig @@ -23,7 +23,7 @@ comptime { } } -const ly_version = std.SemanticVersion{ .major = 1, .minor = 3, .patch = 0 }; +const ly_version = std.SemanticVersion{ .major = 1, .minor = 3, .patch = 2 }; var dest_directory: []const u8 = undefined; var config_directory: []const u8 = undefined; diff --git a/build.zig.zon b/build.zig.zon index 3c6a247..67d6814 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .ly, - .version = "1.3.0", + .version = "1.3.2", .fingerprint = 0xa148ffcc5dc2cb59, .minimum_zig_version = "0.15.0", .dependencies = .{ @@ -13,8 +13,8 @@ .hash = "zigini-0.3.3-36M0FRJJAADZVq5HPm-hYKMpFFTr0OgjbEYcK2ijKZ5n", }, .termbox2 = .{ - .url = "git+https://github.com/AnErrupTion/termbox2?ref=master#290ac6b8225aacfd16851224682b851b65fcb918", - .hash = "N-V-__8AAGcUBQAa5vov1Yi_9AXEffFQ1e2KsXaK4dgygRKq", + .url = "git+https://github.com/AnErrupTion/termbox2?ref=master#496730697c662893eec43192f48ff616c2539da6", + .hash = "N-V-__8AAOEWBQDt5tNdIzIFY6n8DdZsCP-6MyLoNS20wgpA", }, }, .paths = .{""}, diff --git a/readme.md b/readme.md index e0308e3..b7afade 100644 --- a/readme.md +++ b/readme.md @@ -121,9 +121,15 @@ that Ly will run on, otherwise bad things will happen. For example, to disable ` # systemctl disable getty@tty2.service ``` -You can change the TTY Ly will run on by editing the corresponding -service file for your platform, or on systemd, by enabling the service on -different TTYs, as is done above. +On platforms that use systemd-logind to dynamically start `autovt@.service` instances when the switch to a new tty occurs, any ly instances for ttys *except the default tty* need to be enabled using a different mechanism: To autostart ly on switch to `tty2`, do not enable any `ly` unit directly, instead symlink `autovt@tty2.service` to `ly@tty2.service` within `/usr/lib/systemd/system/` (analogous for every other tty you want to enable ly on). + +The target of the symlink, `ly@ttyN.service`, does not actually exist, but systemd nevertheless recognizes that the instanciation of `autovt@.service` with `%I` equal to `ttyN` now points to an instanciation of `ly@.service` with `%I` set to `ttyN`. + +Compare to `man 5 logind.conf`, especially regarding the `NAutoVTs=` and `ReserveVT=` parameters. + + +On non-systemd systems, you can change the TTY Ly will run on by editing the corresponding +service file for your platform. ### OpenRC diff --git a/res/config.ini b/res/config.ini index 52fb15d..83e4ab5 100644 --- a/res/config.ini +++ b/res/config.ini @@ -62,7 +62,7 @@ auto_login_service = ly-autologin # To find available session names, check the .desktop files in: # - /usr/share/xsessions/ (for X11 sessions) # - /usr/share/wayland-sessions/ (for Wayland sessions) -# Use the filename without .desktop extension, or the value of DesktopNames field +# Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field # Examples: "i3", "sway", "gnome", "plasma", "xfce" # If null, automatic login is disabled auto_login_session = null @@ -200,7 +200,7 @@ fg = 0x00FFFFFF # TB_WHITE 0x0008 # If full color is off, the styling options still work. The colors are # always 32-bit values with the styling in the most significant byte. -# Note: If using the dur_file animation option and the dur file's color range +# Note: If using the dur_file animation option and the dur file's color range # is saved as 256 with this option disabled, the file will not be drawn. full_color = true @@ -354,6 +354,11 @@ waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions # Xorg server command x_cmd = $PREFIX_DIRECTORY/bin/X +# Xorg virtual terminal number +# Mostly useful for FreeBSD where choosing the current TTY causes issues +# If null, the current TTY will be chosen +x_vt = null + # Xorg xauthority edition tool xauth_cmd = $PREFIX_DIRECTORY/bin/xauth diff --git a/res/ly@.service b/res/ly@.service index 21d20df..54d8bf6 100644 --- a/res/ly@.service +++ b/res/ly@.service @@ -1,8 +1,8 @@ [Unit] Description=TUI display manager After=systemd-user-sessions.service plymouth-quit-wait.service -After=getty@%I.service -Conflicts=getty@%I.service +After=getty@%i.service +Conflicts=getty@%i.service [Service] Type=idle diff --git a/src/Environment.zig b/src/Environment.zig index eab875d..8184f92 100644 --- a/src/Environment.zig +++ b/src/Environment.zig @@ -14,9 +14,9 @@ pub const DesktopEntry = struct { pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} }; entry_ini: ?Ini(Entry) = null, +file_name: []const u8 = "", name: []const u8 = "", xdg_session_desktop: ?[]const u8 = null, -xdg_session_desktop_owned: bool = false, xdg_desktop_names: ?[]const u8 = null, cmd: ?[]const u8 = null, specifier: []const u8 = "", diff --git a/src/auth.zig b/src/auth.zig index 8bd7621..cde6eed 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -20,6 +20,7 @@ pub const AuthOptions = struct { setup_cmd: []const u8, login_cmd: ?[]const u8, x_cmd: []const u8, + x_vt: ?u8, session_pid: std.posix.pid_t, }; @@ -169,6 +170,7 @@ fn startSession( // Reset the XDG environment variables try setXdgEnv(allocator, tty_str, current_environment); + try setXdgRuntimeDir(allocator); // Set the PAM variables const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle); @@ -188,7 +190,7 @@ fn startSession( .wayland, .shell, .custom => try executeCmd(log_file, allocator, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd), .xinitrc, .x11 => if (build_options.enable_x11_support) { var vt_buf: [5]u8 = undefined; - const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty}); + const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.x_vt orelse options.tty}); try executeX11Cmd(log_file, allocator, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt); }, } @@ -217,6 +219,15 @@ fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environme .custom => if (environment.is_terminal) "tty" else "unspecified", }, false); + if (environment.xdg_desktop_names) |xdg_desktop_names| try interop.setEnvironmentVariable(allocator, "XDG_CURRENT_DESKTOP", xdg_desktop_names, false); + try interop.setEnvironmentVariable(allocator, "XDG_SESSION_CLASS", "user", false); + try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false); + if (environment.xdg_session_desktop) |desktop_name| try interop.setEnvironmentVariable(allocator, "XDG_SESSION_DESKTOP", desktop_name, false); + try interop.setEnvironmentVariable(allocator, "XDG_SEAT", "seat0", false); + try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false); +} + +fn setXdgRuntimeDir(allocator: std.mem.Allocator) !void { // The "/run/user/%d" directory is not available on FreeBSD. It is much // better to stick to the defaults and let applications using // XDG_RUNTIME_DIR to fall back to directories inside user's home @@ -228,13 +239,6 @@ fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environme try interop.setEnvironmentVariable(allocator, "XDG_RUNTIME_DIR", uid_str, false); } - - if (environment.xdg_desktop_names) |xdg_desktop_names| try interop.setEnvironmentVariable(allocator, "XDG_CURRENT_DESKTOP", xdg_desktop_names, false); - try interop.setEnvironmentVariable(allocator, "XDG_SESSION_CLASS", "user", false); - try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false); - if (environment.xdg_session_desktop) |desktop_name| try interop.setEnvironmentVariable(allocator, "XDG_SESSION_DESKTOP", desktop_name, false); - try interop.setEnvironmentVariable(allocator, "XDG_SEAT", "seat0", false); - try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false); } fn loginConv( @@ -366,7 +370,7 @@ fn createXauthFile(pwd: []const u8, buffer: []u8) ![]const u8 { const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file }); - std.fs.makeDirAbsolute(trimmed_xauth_dir) catch {}; + std.fs.cwd().makePath(trimmed_xauth_dir) catch {}; const file = try std.fs.createFileAbsolute(xauthority, .{}); file.close(); @@ -434,7 +438,7 @@ fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []cons std.process.exit(1); } - var ok: c_int = undefined; + var ok: c_int = -1; var xcb: ?*interop.xcb.xcb_connection_t = null; while (ok != 0) { xcb = interop.xcb.xcb_connect(null, null); @@ -502,6 +506,14 @@ fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: [] } fn redirectStandardStreams(global_log_file: *LogFile, session_log: []const u8, create: bool) !std.fs.File { + create_session_log_dir: { + const session_log_dir = std.fs.path.dirname(session_log) orelse break :create_session_log_dir; + std.fs.cwd().makePath(session_log_dir) catch |err| { + try global_log_file.file_writer.interface.print("failed to create session log file directory: {s}\n", .{@errorName(err)}); + return err; + }; + } + const log_file = if (create) (std.fs.cwd().createFile(session_log, .{ .mode = 0o666 }) catch |err| { try global_log_file.file_writer.interface.print("failed to create new session log file: {s}\n", .{@errorName(err)}); return err; diff --git a/src/config/Config.zig b/src/config/Config.zig index e9169bd..d249be3 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -90,6 +90,7 @@ vi_default_mode: ViMode = .normal, vi_mode: bool = false, waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions", x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X", +x_vt: ?u8 = null, xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth", xinitrc: ?[]const u8 = "~/.xinitrc", xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions", diff --git a/src/interop.zig b/src/interop.zig index 4d52f49..d55e850 100644 --- a/src/interop.zig +++ b/src/interop.zig @@ -81,9 +81,6 @@ fn PlatformStruct() type { pub const vt_activate = vt.VT_ACTIVATE; pub const vt_waitactive = vt.VT_WAITACTIVE; - const SYSTEMD_HOMED_UID_MIN = 60001; - const SYSTEMD_HOMED_UID_MAX = 60513; - pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void { const status = grp.initgroups(username, @intCast(entry.gid)); if (status != 0) return error.GroupInitializationFailed; @@ -187,19 +184,6 @@ fn PlatformStruct() type { if (!nameFound) return error.UidNameNotFound; - // This code assumes the OS has a login.defs file with UID_MIN - // and UID_MAX values defined in it, which should be the case - // for most systemd-based Linux distributions out there. - // This should be a good enough safeguard for now, as there's - // no reliable (and clean) way to check for systemd support - if (uid_range.uid_min > SYSTEMD_HOMED_UID_MIN) { - uid_range.uid_min = SYSTEMD_HOMED_UID_MIN; - } - - if (uid_range.uid_max < SYSTEMD_HOMED_UID_MAX) { - uid_range.uid_max = SYSTEMD_HOMED_UID_MAX; - } - return uid_range; } diff --git a/src/main.zig b/src/main.zig index 8565c9a..f2b9e87 100644 --- a/src/main.zig +++ b/src/main.zig @@ -529,7 +529,7 @@ pub fn main() !void { active_input = .password; - if (user.session_index < session.label.list.items.len) session.label.current = user.session_index; + session.label.current = @min(user.session_index, session.label.list.items.len - 1); } } @@ -633,7 +633,7 @@ pub fn main() !void { while (run) { // If there's no input or there's an animation, a resolution change needs to be checked - if (!update or animate) { + if (!update or animate or config.bigclock != .none or config.clock != null) { if (!update) std.Thread.sleep(std.time.ns_per_ms * 100); _ = termbox.tb_present(); // Required to update tb_width() and tb_height() @@ -1085,6 +1085,7 @@ pub fn main() !void { .setup_cmd = config.setup_cmd, .login_cmd = config.login_cmd, .x_cmd = config.x_cmd, + .x_vt = config.x_vt, .session_pid = session_pid, }; @@ -1251,10 +1252,10 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa }); errdefer entry_ini.deinit(); + const file_name = try session.label.allocator.dupe(u8, std.fs.path.stem(item.name)); const entry = entry_ini.data.@"Desktop Entry"; var maybe_xdg_session_desktop: ?[]const u8 = null; var maybe_xdg_desktop_names: ?[]const u8 = null; - var xdg_session_desktop_owned = false; // Prepare the XDG_SESSION_DESKTOP and XDG_CURRENT_DESKTOP environment // variables here @@ -1268,18 +1269,14 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa } else if (display_server != .custom) { // If DesktopNames is empty, and this isn't a custom session entry, // we'll take the name of the session file - const stem = std.fs.path.stem(item.name); - if (stem.len > 0) { - maybe_xdg_session_desktop = try session.label.allocator.dupe(u8, stem); - xdg_session_desktop_owned = true; - } + if (file_name.len > 0) maybe_xdg_session_desktop = file_name; } try session.addEnvironment(.{ .entry_ini = entry_ini, + .file_name = file_name, .name = entry.Name, .xdg_session_desktop = maybe_xdg_session_desktop, - .xdg_session_desktop_owned = xdg_session_desktop_owned, .xdg_desktop_names = maybe_xdg_desktop_names, .cmd = entry.Exec, .specifier = switch (display_server) { @@ -1303,13 +1300,14 @@ fn isValidUsername(username: []const u8, usernames: StringList) bool { fn findSessionByName(session: *Session, name: []const u8) ?usize { for (session.label.list.items, 0..) |env, i| { + if (std.ascii.eqlIgnoreCase(env.environment.file_name, name)) return i; + if (std.ascii.eqlIgnoreCase(env.environment.name, name)) return i; if (env.environment.xdg_session_desktop) |session_desktop| { if (session_desktop.len > 0 and std.ascii.eqlIgnoreCase(session_desktop, name)) return i; } if (env.environment.xdg_desktop_names) |session_desktop_name| { if (std.ascii.eqlIgnoreCase(session_desktop_name, name)) return i; } - if (std.ascii.eqlIgnoreCase(env.environment.name, name)) return i; } return null; } @@ -1323,13 +1321,33 @@ fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8, ui }; }; + // There's no reliable (and clean) way to check for systemd support, so + // let's just define a range and check if a user is within it + const SYSTEMD_HOMED_UID_MIN = 60001; + const SYSTEMD_HOMED_UID_MAX = 60513; + const homed_uid_range = UidRange{ + .uid_min = SYSTEMD_HOMED_UID_MIN, + .uid_max = SYSTEMD_HOMED_UID_MAX, + }; + var usernames: StringList = .empty; var maybe_entry = interop.getNextUsernameEntry(); while (maybe_entry) |entry| { // We check if the UID is equal to 0 because we always want to add root // as a username (even if you can't log into it) - if (entry.uid >= uid_range.uid_min and entry.uid <= uid_range.uid_max or entry.uid == 0 and entry.username != null) { + const is_within_range = + entry.uid >= uid_range.uid_min and + entry.uid <= uid_range.uid_max; + const is_within_homed_range = + builtin.os.tag == .linux and + entry.uid >= homed_uid_range.uid_min and + entry.uid <= homed_uid_range.uid_max; + const is_root = + entry.uid == 0 and + entry.username != null; + + if (is_within_range or is_within_homed_range or is_root) { const username = try allocator.dupe(u8, entry.username.?); try usernames.append(allocator, username); } diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index 74ab7a2..3283ff0 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -29,9 +29,7 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, user_list: *UserList) pub fn deinit(self: *Session) void { for (self.label.list.items) |*env| { if (env.environment.entry_ini) |*entry_ini| entry_ini.deinit(); - if (env.environment.xdg_session_desktop_owned) { - self.label.allocator.free(env.environment.xdg_session_desktop.?); - } + self.label.allocator.free(env.environment.file_name); } self.label.deinit(); @@ -41,15 +39,19 @@ pub fn addEnvironment(self: *Session, environment: Environment) !void { const env = Env{ .environment = environment, .index = self.label.list.items.len }; try self.label.addItem(env); - sessionChanged(env, self.user_list); + addedSession(env, self.user_list); +} + +fn addedSession(env: Env, user_list: *UserList) void { + const user = user_list.label.list.items[user_list.label.current]; + if (!user.first_run) return; + + user.session_index.* = env.index; } fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void { if (maybe_user_list) |user_list| { - const user = user_list.label.list.items[user_list.label.current]; - if (!user.first_run) return; - - user.session_index.* = env.index; + user_list.label.list.items[user_list.label.current].session_index.* = env.index; } } diff --git a/src/tui/components/UserList.zig b/src/tui/components/UserList.zig index d9ed846..91ddc15 100644 --- a/src/tui/components/UserList.zig +++ b/src/tui/components/UserList.zig @@ -71,8 +71,7 @@ pub fn getCurrentUsername(self: UserList) []const u8 { fn usernameChanged(user: User, maybe_session: ?*Session) void { if (maybe_session) |session| { - if (user.session_index.* >= session.label.list.items.len) return; - session.label.current = user.session_index.*; + session.label.current = @min(user.session_index.*, session.label.list.items.len - 1); } }