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

View File

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

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_up = رفع السطوع
capslock = capslock
err_alloc = فشل في تخصيص الذاكرة
err_bounds = out-of-bounds index
err_brightness_change = فشل في تغيير سطوع الشاشة

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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