Implement custom session support (fixes #757)

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion 2025-07-08 22:39:14 +02:00
parent 48e5369f56
commit 04920e1b1b
No known key found for this signature in database
27 changed files with 106 additions and 18 deletions

View File

@ -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}); 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" }); 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.fs.cwd().makePath(ly_lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path}); 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 }); 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; var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
defer lang_dir.close(); defer lang_dir.close();

View File

@ -100,6 +100,11 @@ colormix_col2 = 0x000000FF
# Color mixing animation third color id # Color mixing animation third color id
colormix_col3 = 0x20000000 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 # Input box active by default on startup
# Available inputs: info_line, session, login, password # Available inputs: info_line, session, login, password
default_input = login default_input = login
@ -251,7 +256,7 @@ vi_mode = false
# Wayland desktop environments # Wayland desktop environments
# You can specify multiple directories, # 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 waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
# Xorg server command # Xorg server command
@ -266,5 +271,5 @@ xinitrc = ~/.xinitrc
# Xorg desktop environments # Xorg desktop environments
# You can specify multiple directories, # 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 xsessions = $PREFIX_DIRECTORY/share/xsessions

View File

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

View File

@ -2,6 +2,7 @@ authenticating = جاري المصادقة...
brightness_down = خفض السطوع brightness_down = خفض السطوع
brightness_up = رفع السطوع brightness_up = رفع السطوع
capslock = capslock capslock = capslock
err_alloc = فشل في تخصيص الذاكرة err_alloc = فشل في تخصيص الذاكرة
err_bounds = out-of-bounds index err_bounds = out-of-bounds index
err_brightness_change = فشل في تغيير سطوع الشاشة err_brightness_change = فشل في تغيير سطوع الشاشة

View File

@ -2,6 +2,7 @@ authenticating = autenticant...
brightness_down = abaixar brillantor brightness_down = abaixar brillantor
brightness_up = apujar brillantor brightness_up = apujar brillantor
capslock = Bloq Majús capslock = Bloq Majús
err_alloc = assignació de memòria fallida err_alloc = assignació de memòria fallida
err_bounds = índex fora de límits err_bounds = índex fora de límits
err_brightness_change = error en canviar la brillantor err_brightness_change = error en canviar la brillantor

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = alokace paměti selhala err_alloc = alokace paměti selhala
err_bounds = index je mimo hranice pole err_bounds = index je mimo hranice pole

View File

@ -2,6 +2,7 @@ authenticating = authentifizieren...
brightness_down = Helligkeit- brightness_down = Helligkeit-
brightness_up = Helligkeit+ brightness_up = Helligkeit+
capslock = Feststelltaste capslock = Feststelltaste
err_alloc = Speicherzuweisung fehlgeschlagen err_alloc = Speicherzuweisung fehlgeschlagen
err_bounds = Index ausserhalb des Bereichs err_bounds = Index ausserhalb des Bereichs
err_brightness_change = Helligkeitsänderung fehlgeschlagen err_brightness_change = Helligkeitsänderung fehlgeschlagen

View File

@ -2,6 +2,7 @@ authenticating = authenticating...
brightness_down = decrease brightness brightness_down = decrease brightness
brightness_up = increase brightness brightness_up = increase brightness
capslock = capslock capslock = capslock
custom = custom
err_alloc = failed memory allocation err_alloc = failed memory allocation
err_bounds = out-of-bounds index err_bounds = out-of-bounds index
err_brightness_change = failed to change brightness err_brightness_change = failed to change brightness

View File

@ -2,6 +2,7 @@ authenticating = autenticando...
brightness_down = bajar brillo brightness_down = bajar brillo
brightness_up = subir brillo brightness_up = subir brillo
capslock = Bloq Mayús capslock = Bloq Mayús
err_alloc = asignación de memoria fallida err_alloc = asignación de memoria fallida
err_bounds = índice fuera de límites err_bounds = índice fuera de límites

View File

@ -2,6 +2,7 @@ authenticating = authentification...
brightness_down = diminuer la luminosité brightness_down = diminuer la luminosité
brightness_up = augmenter la luminosité brightness_up = augmenter la luminosité
capslock = verr.maj capslock = verr.maj
custom = customisé
err_alloc = échec d'allocation mémoire err_alloc = échec d'allocation mémoire
err_bounds = indice hors-limite err_bounds = indice hors-limite
err_brightness_change = échec du changement de luminosité err_brightness_change = échec du changement de luminosité

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = impossibile allocare memoria err_alloc = impossibile allocare memoria
err_bounds = indice fuori limite err_bounds = indice fuori limite

View File

@ -2,6 +2,7 @@ authenticating = uwierzytelnianie...
brightness_down = zmniejsz jasność brightness_down = zmniejsz jasność
brightness_up = zwiększ jasność brightness_up = zwiększ jasność
capslock = capslock capslock = capslock
err_alloc = nieudana alokacja pamięci err_alloc = nieudana alokacja pamięci
err_bounds = indeks poza zakresem err_bounds = indeks poza zakresem
err_brightness_change = nie udało się zmienić jasności err_brightness_change = nie udało się zmienić jasności

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = erro na atribuição de memória err_alloc = erro na atribuição de memória
err_bounds = índice fora de limites err_bounds = índice fora de limites

View File

@ -2,6 +2,7 @@
capslock = caixa alta capslock = caixa alta
err_alloc = alocação de memória malsucedida err_alloc = alocação de memória malsucedida
err_bounds = índice fora de limites err_bounds = índice fora de limites

View File

@ -17,6 +17,7 @@ capslock = capslock
err_pam_abort = tranzacţie pam anulată err_pam_abort = tranzacţie pam anulată
err_pam_acct_expired = cont expirat err_pam_acct_expired = cont expirat
err_pam_auth = eroare de autentificare err_pam_auth = eroare de autentificare

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = не удалось выделить память err_alloc = не удалось выделить память
err_bounds = за пределами индекса err_bounds = за пределами индекса

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = neuspijesna alokacija memorije err_alloc = neuspijesna alokacija memorije
err_bounds = izvan granica indeksa err_bounds = izvan granica indeksa

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = misslyckad minnesallokering err_alloc = misslyckad minnesallokering
err_bounds = utanför banan index err_bounds = utanför banan index

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = basarisiz bellek ayirma err_alloc = basarisiz bellek ayirma
err_bounds = sinirlarin disinda dizin err_bounds = sinirlarin disinda dizin

View File

@ -2,6 +2,7 @@
capslock = capslock capslock = capslock
err_alloc = невдале виділення пам'яті err_alloc = невдале виділення пам'яті
err_bounds = поза межами індексу err_bounds = поза межами індексу

View File

@ -2,6 +2,7 @@
capslock = 大写锁定 capslock = 大写锁定
err_alloc = 内存分配失败 err_alloc = 内存分配失败
err_bounds = 索引越界 err_bounds = 索引越界

View File

@ -8,6 +8,7 @@ pub const DesktopEntry = struct {
Exec: []const u8 = "", Exec: []const u8 = "",
Name: [:0]const u8 = "", Name: [:0]const u8 = "",
DesktopNames: ?[:0]u8 = null, DesktopNames: ?[:0]u8 = null,
Terminal: ?bool = null,
}; };
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} }; pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
@ -19,3 +20,4 @@ xdg_desktop_names: ?[:0]const u8 = null,
cmd: []const u8 = "", cmd: []const u8 = "",
specifier: []const u8 = "", specifier: []const u8 = "",
display_server: DisplayServer = .wayland, display_server: DisplayServer = .wayland,
is_terminal: bool = false,

View File

@ -180,6 +180,7 @@ fn startSession(
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty}); 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); 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", .wayland => "wayland",
.shell => "tty", .shell => "tty",
.xinitrc, .x11 => "x11", .xinitrc, .x11 => "x11",
.custom => "unspecified",
}, 0); }, 0);
// The "/run/user/%d" directory is not available on FreeBSD. It is much // 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); _ = 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 { 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 })); 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 }));

View File

@ -28,6 +28,7 @@ cmatrix_max_codepoint: u16 = 0x7B,
colormix_col1: u32 = 0x00FF0000, colormix_col1: u32 = 0x00FF0000,
colormix_col2: u32 = 0x000000FF, colormix_col2: u32 = 0x000000FF,
colormix_col3: u32 = 0x20000000, colormix_col3: u32 = 0x20000000,
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
default_input: Input = .login, default_input: Input = .login,
doom_fire_height: u8 = 6, doom_fire_height: u8 = 6,
doom_fire_spread: u8 = 2, doom_fire_spread: u8 = 2,

View File

@ -7,6 +7,7 @@ authenticating: []const u8 = "authenticating...",
brightness_down: []const u8 = "decrease brightness", brightness_down: []const u8 = "decrease brightness",
brightness_up: []const u8 = "increase brightness", brightness_up: []const u8 = "increase brightness",
capslock: []const u8 = "capslock", capslock: []const u8 = "capslock",
custom: []const u8 = "custom",
err_alloc: []const u8 = "failed memory allocation", err_alloc: []const u8 = "failed memory allocation",
err_bounds: []const u8 = "out-of-bounds index", err_bounds: []const u8 = "out-of-bounds index",
err_brightness_change: []const u8 = "failed to change brightness", err_brightness_change: []const u8 = "failed to change brightness",

View File

@ -11,6 +11,7 @@ pub const DisplayServer = enum {
shell, shell,
xinitrc, xinitrc,
x11, x11,
custom,
}; };
pub const Input = enum { pub const Input = enum {

View File

@ -290,10 +290,12 @@ pub fn main() !void {
try info_line.addMessage(hostname, config.bg, config.fg); 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, ':'); var wayland_session_dirs = std.mem.splitScalar(u8, config.waylandsessions, ':');
while (wayland_session_dirs.next()) |dir| { while (wayland_session_dirs.next()) |dir| {
try crawl(&session, lang, dir, .wayland); try crawl(&session, lang, dir, .wayland);
} }
if (build_options.enable_x11_support) { if (build_options.enable_x11_support) {
var x_session_dirs = std.mem.splitScalar(u8, config.xsessions, ':'); var x_session_dirs = std.mem.splitScalar(u8, config.xsessions, ':');
while (x_session_dirs.next()) |dir| { 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); var login = Text.init(allocator, &buffer, false, null);
defer login.deinit(); defer login.deinit();
@ -864,11 +871,7 @@ fn addOtherEnvironment(session: *Session, lang: Lang, display_server: DisplaySer
.xdg_session_desktop = null, .xdg_session_desktop = null,
.xdg_desktop_names = null, .xdg_desktop_names = null,
.cmd = exec orelse "", .cmd = exec orelse "",
.specifier = switch (display_server) { .specifier = lang.other,
.wayland => lang.wayland,
.x11 => lang.x11,
else => lang.other,
},
.display_server = display_server, .display_server = display_server,
}); });
} }
@ -890,40 +893,43 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa
}); });
errdefer entry_ini.deinit(); 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; const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
if (maybe_desktop_names) |desktop_names| { if (maybe_desktop_names) |desktop_names| {
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';'); maybe_xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
} else { } else if (display_server != .custom) {
// if DesktopNames is empty, we'll take the name of the session file // If DesktopNames is empty, and this isn't a custom session entry,
xdg_session_desktop = std.fs.path.stem(item.name); // 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 // Prepare the XDG_CURRENT_DESKTOP environment variable here
const entry = entry_ini.data.@"Desktop Entry"; 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| { if (entry.DesktopNames) |desktop_names| {
for (desktop_names) |*c| { for (desktop_names) |*c| {
if (c.* == ';') 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); const maybe_session_desktop = if (maybe_xdg_session_desktop) |xdg_session_desktop| try session.label.allocator.dupeZ(u8, xdg_session_desktop) else null;
errdefer session.label.allocator.free(session_desktop); errdefer if (maybe_session_desktop) |session_desktop| session.label.allocator.free(session_desktop);
try session.addEnvironment(.{ try session.addEnvironment(.{
.entry_ini = entry_ini, .entry_ini = entry_ini,
.name = entry.Name, .name = entry.Name,
.xdg_session_desktop = session_desktop, .xdg_session_desktop = maybe_session_desktop,
.xdg_desktop_names = xdg_desktop_names, .xdg_desktop_names = maybe_xdg_desktop_names,
.cmd = entry.Exec, .cmd = entry.Exec,
.specifier = switch (display_server) { .specifier = switch (display_server) {
.wayland => lang.wayland, .wayland => lang.wayland,
.x11 => lang.x11, .x11 => lang.x11,
.custom => lang.custom,
else => lang.other, else => lang.other,
}, },
.display_server = display_server, .display_server = display_server,
.is_terminal = entry.Terminal orelse false,
}); });
} }
} }