15 Commits

Author SHA1 Message Date
AnErrupTion
1b9286166e [Backport] Fix buffer not resizing with no animation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:10:10 +01:00
AnErrupTion
ac91704c56 [Backport] Add config.x_vt option
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:09:22 +01:00
AnErrupTion
48f8b306db [Backport] Better systemd-homed user detection
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:07:52 +01:00
AnErrupTion
320fa0d8df [Backport] Fix wrong session being chosen in autologin
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:06:19 +01:00
AnErrupTion
bcf025437a [Backport] Fix undefined value in XCB connection check
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:04:38 +01:00
AnErrupTion
735868c51c [Backport] Fix building on musl
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:04:05 +01:00
AnErrupTion
9c1a64692c Start Ly v1.3.2 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 20:03:09 +01:00
AnErrupTion
edbb982c91 [Backport] Recursively create xauth file directory if non-existent
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 20:55:41 +01:00
AnErrupTion
80fb07daa8 [Backport] Fix invalid XDG_RUNTIME_DIR if D-Bus isn't used
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 20:54:47 +01:00
AnErrupTion
93388159bc [Backport] Clamp user session index if invalid
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 18:12:28 +01:00
AnErrupTion
08b4a49729 [Backport] Check for session file name in autologin
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:45:29 +01:00
AnErrupTion
066acf91e6 [Backport] Create session log directory if non-existent
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:29:31 +01:00
AnErrupTion
2660486fde [Backport] Escape TTY in systemd service
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:28:50 +01:00
AnErrupTion
a3b249ded8 [Backport] Fix session not being saved correctly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:27:04 +01:00
AnErrupTion
3c22597054 Start Ly v1.3.1 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:26:53 +01:00
12 changed files with 86 additions and 59 deletions

View File

@@ -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;

View File

@@ -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 = .{""},

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = "",

View File

@@ -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;

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}