From 04920e1b1ba5c50cbc3f69227b04867dd60ece72 Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Tue, 8 Jul 2025 22:39:14 +0200 Subject: [PATCH] Implement custom session support (fixes #757) Signed-off-by: AnErrupTion --- build.zig | 14 ++++++++++++++ res/config.ini | 9 +++++++-- res/custom-sessions/README | 22 ++++++++++++++++++++++ res/lang/ar.ini | 1 + res/lang/cat.ini | 1 + res/lang/cs.ini | 1 + res/lang/de.ini | 1 + res/lang/en.ini | 1 + res/lang/es.ini | 1 + res/lang/fr.ini | 1 + 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/lang/zh_CN.ini | 1 + src/Environment.zig | 2 ++ src/auth.zig | 18 ++++++++++++++++++ src/config/Config.zig | 1 + src/config/Lang.zig | 1 + src/enums.zig | 1 + src/main.zig | 38 ++++++++++++++++++++++---------------- 27 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 res/custom-sessions/README diff --git a/build.zig b/build.zig index 80de3e8..128357c 100644 --- a/build.zig +++ b/build.zig @@ -135,6 +135,12 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory}); }; + const ly_custom_sessions_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" }); + + std.fs.cwd().makePath(ly_custom_sessions_directory) catch { + std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory}); + }; + const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" }); std.fs.cwd().makePath(ly_lang_path) catch { std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path}); @@ -167,6 +173,14 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 }); } + { + var custom_sessions_dir = std.fs.cwd().openDir(ly_custom_sessions_directory, .{}) catch unreachable; + defer custom_sessions_dir.close(); + + const patched_readme = try patchFile(allocator, "res/custom-sessions/README", patch_map); + try installText(patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{}); + } + { var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable; defer lang_dir.close(); diff --git a/res/config.ini b/res/config.ini index 492319c..8b4a4f9 100644 --- a/res/config.ini +++ b/res/config.ini @@ -100,6 +100,11 @@ colormix_col2 = 0x000000FF # Color mixing animation third color id colormix_col3 = 0x20000000 +# Custom sessions directory +# You can specify multiple directories, +# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions +waylandsessions = $CONFIG_DIRECTORY/share/custom-sessions + # Input box active by default on startup # Available inputs: info_line, session, login, password default_input = login @@ -251,7 +256,7 @@ vi_mode = false # Wayland desktop environments # You can specify multiple directories, -# e.g. /usr/share/wayland-sessions:/usr/local/share/wayland-sessions +# e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions # Xorg server command @@ -266,5 +271,5 @@ xinitrc = ~/.xinitrc # Xorg desktop environments # You can specify multiple directories, -# e.g. /usr/share/xsessions:/usr/local/share/xsessions +# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions xsessions = $PREFIX_DIRECTORY/share/xsessions diff --git a/res/custom-sessions/README b/res/custom-sessions/README new file mode 100644 index 0000000..23c1ed1 --- /dev/null +++ b/res/custom-sessions/README @@ -0,0 +1,22 @@ +A custom session is just a desktop entry file, like for X11 and Wayland +sessions. For example: + +[Desktop Entry] +Name=Fish shell +Exec=$PREFIX_DIRECTORY/bin/fish +DesktopNames=null +Terminal=true + +The DesktopNames value is optional and sets the XDG_SESSION_DESKTOP and +XDG_CURRENT_DESKTOP environment variables. If equal to null or if not present, +XDG_SESSION_DESKTOP and XDG_CURRENT_DESKTOP will not be set. Otherwise, the +syntax is the same as described in the Freedesktop Desktop Entry Specification. + +The Terminal value specifies if standard output and standard error should be +redirected to the session log file found in Ly's configuration file. If set to +true, Ly will consider the program is going to run in a TTY, and thus will not +redirect standard output & error. + +Finally, do note that the XDG_SESSION_TYPE environment variable is set to +"unspecified" (without quotes), which is behavior that at least systemd +recognizes (see pam_systemd's man page) diff --git a/res/lang/ar.ini b/res/lang/ar.ini index ba1871f..9c2f9be 100644 --- a/res/lang/ar.ini +++ b/res/lang/ar.ini @@ -2,6 +2,7 @@ authenticating = جاري المصادقة... brightness_down = خفض السطوع brightness_up = رفع السطوع capslock = capslock + err_alloc = فشل في تخصيص الذاكرة err_bounds = out-of-bounds index err_brightness_change = فشل في تغيير سطوع الشاشة diff --git a/res/lang/cat.ini b/res/lang/cat.ini index 40d877f..7aa4261 100644 --- a/res/lang/cat.ini +++ b/res/lang/cat.ini @@ -2,6 +2,7 @@ authenticating = autenticant... brightness_down = abaixar brillantor brightness_up = apujar brillantor capslock = Bloq Majús + err_alloc = assignació de memòria fallida err_bounds = índex fora de límits err_brightness_change = error en canviar la brillantor diff --git a/res/lang/cs.ini b/res/lang/cs.ini index 9052663..cca457a 100644 --- a/res/lang/cs.ini +++ b/res/lang/cs.ini @@ -2,6 +2,7 @@ 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 3f76cc6..32e00a6 100644 --- a/res/lang/de.ini +++ b/res/lang/de.ini @@ -2,6 +2,7 @@ authenticating = authentifizieren... brightness_down = Helligkeit- brightness_up = Helligkeit+ capslock = Feststelltaste + err_alloc = Speicherzuweisung fehlgeschlagen err_bounds = Index ausserhalb des Bereichs err_brightness_change = Helligkeitsänderung fehlgeschlagen diff --git a/res/lang/en.ini b/res/lang/en.ini index 423020e..d7076db 100644 --- a/res/lang/en.ini +++ b/res/lang/en.ini @@ -2,6 +2,7 @@ authenticating = authenticating... brightness_down = decrease brightness brightness_up = increase brightness capslock = capslock +custom = custom err_alloc = failed memory allocation err_bounds = out-of-bounds index err_brightness_change = failed to change brightness diff --git a/res/lang/es.ini b/res/lang/es.ini index 5ec48af..fe3e6d9 100644 --- a/res/lang/es.ini +++ b/res/lang/es.ini @@ -2,6 +2,7 @@ authenticating = autenticando... brightness_down = bajar brillo brightness_up = subir brillo 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 bda901b..6d46a1d 100644 --- a/res/lang/fr.ini +++ b/res/lang/fr.ini @@ -2,6 +2,7 @@ authenticating = authentification... brightness_down = diminuer la luminosité brightness_up = augmenter la luminosité capslock = verr.maj +custom = customisé err_alloc = échec d'allocation mémoire err_bounds = indice hors-limite err_brightness_change = échec du changement de luminosité diff --git a/res/lang/it.ini b/res/lang/it.ini index e7e0d17..4bff87e 100644 --- a/res/lang/it.ini +++ b/res/lang/it.ini @@ -2,6 +2,7 @@ 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 eeb43e4..cfc5722 100644 --- a/res/lang/pl.ini +++ b/res/lang/pl.ini @@ -2,6 +2,7 @@ authenticating = uwierzytelnianie... brightness_down = zmniejsz jasność brightness_up = zwiększ jasność capslock = capslock + err_alloc = nieudana alokacja pamięci err_bounds = indeks poza zakresem err_brightness_change = nie udało się zmienić jasności diff --git a/res/lang/pt.ini b/res/lang/pt.ini index 97afee3..df1db13 100644 --- a/res/lang/pt.ini +++ b/res/lang/pt.ini @@ -2,6 +2,7 @@ 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 c89b8aa..a7cee7b 100644 --- a/res/lang/pt_BR.ini +++ b/res/lang/pt_BR.ini @@ -2,6 +2,7 @@ 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 fe7ee38..4289b7f 100644 --- a/res/lang/ro.ini +++ b/res/lang/ro.ini @@ -17,6 +17,7 @@ capslock = capslock + err_pam_abort = tranzacţie pam anulată err_pam_acct_expired = cont expirat err_pam_auth = eroare de autentificare diff --git a/res/lang/ru.ini b/res/lang/ru.ini index 25dc553..b9cfb1a 100644 --- a/res/lang/ru.ini +++ b/res/lang/ru.ini @@ -2,6 +2,7 @@ capslock = capslock + err_alloc = не удалось выделить память err_bounds = за пределами индекса diff --git a/res/lang/sr.ini b/res/lang/sr.ini index a24e4b2..61ec4c0 100644 --- a/res/lang/sr.ini +++ b/res/lang/sr.ini @@ -2,6 +2,7 @@ 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 668f431..9ccd0f3 100644 --- a/res/lang/sv.ini +++ b/res/lang/sv.ini @@ -2,6 +2,7 @@ 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 ce69231..d553495 100644 --- a/res/lang/tr.ini +++ b/res/lang/tr.ini @@ -2,6 +2,7 @@ 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 af8f247..ffae75c 100644 --- a/res/lang/uk.ini +++ b/res/lang/uk.ini @@ -2,6 +2,7 @@ capslock = capslock + err_alloc = невдале виділення пам'яті err_bounds = поза межами індексу diff --git a/res/lang/zh_CN.ini b/res/lang/zh_CN.ini index ecc19be..6f7ba41 100644 --- a/res/lang/zh_CN.ini +++ b/res/lang/zh_CN.ini @@ -2,6 +2,7 @@ capslock = 大写锁定 + err_alloc = 内存分配失败 err_bounds = 索引越界 diff --git a/src/Environment.zig b/src/Environment.zig index b21d451..47d4d7f 100644 --- a/src/Environment.zig +++ b/src/Environment.zig @@ -8,6 +8,7 @@ pub const DesktopEntry = struct { Exec: []const u8 = "", Name: [:0]const u8 = "", DesktopNames: ?[:0]u8 = null, + Terminal: ?bool = null, }; pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} }; @@ -19,3 +20,4 @@ xdg_desktop_names: ?[:0]const u8 = null, cmd: []const u8 = "", specifier: []const u8 = "", display_server: DisplayServer = .wayland, +is_terminal: bool = false, diff --git a/src/auth.zig b/src/auth.zig index aa853c9..0b468da 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -180,6 +180,7 @@ fn startSession( const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty}); try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, options, current_environment.cmd, vt); }, + .custom => try executeCustomCmd(pwd.pw_shell.?, options, current_environment.is_terminal, current_environment.cmd), } } @@ -201,6 +202,7 @@ fn setXdgEnv(tty_str: [:0]u8, environment: Environment) !void { .wayland => "wayland", .shell => "tty", .xinitrc, .x11 => "x11", + .custom => "unspecified", }, 0); // The "/run/user/%d" directory is not available on FreeBSD. It is much @@ -462,6 +464,22 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, options: AuthOptio _ = std.c.waitpid(x_pid, &status, 0); } +fn executeCustomCmd(shell: [*:0]const u8, options: AuthOptions, is_terminal: bool, exec_cmd: []const u8) !void { + var maybe_log_file: ?std.fs.File = null; + if (!is_terminal) { + // For custom desktop entries, the "Terminal" value here determines if + // we redirect standard output & error or not. That is, we redirect only + // if it's equal to false (so if it's not running in a TTY). + maybe_log_file = try redirectStandardStreams(options.session_log, true); + } + defer if (maybe_log_file) |log_file| log_file.close(); + + var cmd_buffer: [1024]u8 = undefined; + const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", exec_cmd }); + const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; + return std.posix.execveZ(shell, &args, std.c.environ); +} + fn redirectStandardStreams(session_log: []const u8, create: bool) !std.fs.File { const log_file = if (create) (try std.fs.cwd().createFile(session_log, .{ .mode = 0o666 })) else (try std.fs.cwd().openFile(session_log, .{ .mode = .read_write })); diff --git a/src/config/Config.zig b/src/config/Config.zig index 8b020f0..4a95b40 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -28,6 +28,7 @@ cmatrix_max_codepoint: u16 = 0x7B, colormix_col1: u32 = 0x00FF0000, colormix_col2: u32 = 0x000000FF, colormix_col3: u32 = 0x20000000, +custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions", default_input: Input = .login, doom_fire_height: u8 = 6, doom_fire_spread: u8 = 2, diff --git a/src/config/Lang.zig b/src/config/Lang.zig index f673090..3761fa7 100644 --- a/src/config/Lang.zig +++ b/src/config/Lang.zig @@ -7,6 +7,7 @@ authenticating: []const u8 = "authenticating...", brightness_down: []const u8 = "decrease brightness", brightness_up: []const u8 = "increase brightness", capslock: []const u8 = "capslock", +custom: []const u8 = "custom", err_alloc: []const u8 = "failed memory allocation", err_bounds: []const u8 = "out-of-bounds index", err_brightness_change: []const u8 = "failed to change brightness", diff --git a/src/enums.zig b/src/enums.zig index a70f17c..82c478d 100644 --- a/src/enums.zig +++ b/src/enums.zig @@ -11,6 +11,7 @@ pub const DisplayServer = enum { shell, xinitrc, x11, + custom, }; pub const Input = enum { diff --git a/src/main.zig b/src/main.zig index aed67e5..b52e779 100644 --- a/src/main.zig +++ b/src/main.zig @@ -290,10 +290,12 @@ pub fn main() !void { try info_line.addMessage(hostname, config.bg, config.fg); } + // Crawl session directories (Wayland, X11 and custom respectively) var wayland_session_dirs = std.mem.splitScalar(u8, config.waylandsessions, ':'); while (wayland_session_dirs.next()) |dir| { try crawl(&session, lang, dir, .wayland); } + if (build_options.enable_x11_support) { var x_session_dirs = std.mem.splitScalar(u8, config.xsessions, ':'); while (x_session_dirs.next()) |dir| { @@ -301,6 +303,11 @@ pub fn main() !void { } } + var custom_session_dirs = std.mem.splitScalar(u8, config.custom_sessions, ':'); + while (custom_session_dirs.next()) |dir| { + try crawl(&session, lang, dir, .custom); + } + var login = Text.init(allocator, &buffer, false, null); defer login.deinit(); @@ -864,11 +871,7 @@ fn addOtherEnvironment(session: *Session, lang: Lang, display_server: DisplaySer .xdg_session_desktop = null, .xdg_desktop_names = null, .cmd = exec orelse "", - .specifier = switch (display_server) { - .wayland => lang.wayland, - .x11 => lang.x11, - else => lang.other, - }, + .specifier = lang.other, .display_server = display_server, }); } @@ -890,40 +893,43 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa }); errdefer entry_ini.deinit(); - var xdg_session_desktop: []const u8 = undefined; + var maybe_xdg_session_desktop: ?[]const u8 = null; 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); + maybe_xdg_session_desktop = std.mem.sliceTo(desktop_names, ';'); + } 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 + maybe_xdg_session_desktop = std.fs.path.stem(item.name); } // Prepare the XDG_CURRENT_DESKTOP environment variable here const entry = entry_ini.data.@"Desktop Entry"; - var xdg_desktop_names: ?[:0]const u8 = null; + var maybe_xdg_desktop_names: ?[:0]const u8 = null; if (entry.DesktopNames) |desktop_names| { for (desktop_names) |*c| { if (c.* == ';') c.* = ':'; } - xdg_desktop_names = desktop_names; + maybe_xdg_desktop_names = desktop_names; } - const session_desktop = try session.label.allocator.dupeZ(u8, xdg_session_desktop); - errdefer session.label.allocator.free(session_desktop); + const maybe_session_desktop = if (maybe_xdg_session_desktop) |xdg_session_desktop| try session.label.allocator.dupeZ(u8, xdg_session_desktop) else null; + errdefer if (maybe_session_desktop) |session_desktop| session.label.allocator.free(session_desktop); try session.addEnvironment(.{ .entry_ini = entry_ini, .name = entry.Name, - .xdg_session_desktop = session_desktop, - .xdg_desktop_names = xdg_desktop_names, + .xdg_session_desktop = maybe_session_desktop, + .xdg_desktop_names = maybe_xdg_desktop_names, .cmd = entry.Exec, .specifier = switch (display_server) { .wayland => lang.wayland, .x11 => lang.x11, + .custom => lang.custom, else => lang.other, }, .display_server = display_server, + .is_terminal = entry.Terminal orelse false, }); } }