diff --git a/build.zig b/build.zig index b8e1b8d..bf23051 100644 --- a/build.zig +++ b/build.zig @@ -244,6 +244,7 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: defer pam_dir.close(); try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 }); + try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd-autologin" else "res/pam.d/ly-linux-autologin", pam_dir, pam_path, "ly-autologin", .{ .override_mode = 0o644 }); } } diff --git a/res/config.ini b/res/config.ini index 9b67388..c431280 100644 --- a/res/config.ini +++ b/res/config.ini @@ -46,6 +46,30 @@ auth_fails = 10 # If set to null, battery status won't be shown battery_id = null +# Automatic login configuration +# This feature allows Ly to automatically log in a user without password prompt. +# IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work. +# Autologin only happens once at startup - it won't re-trigger after logout. + +# PAM service name to use for automatic login +# The default service (ly-autologin) uses pam_permit to allow login without password +# The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically +auto_login_service = ly-autologin + +# Session name to launch automatically +# 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 +# Examples: "i3", "sway", "gnome", "plasma", "xfce" +# If null, automatic login is disabled +auto_login_session = null + +# Username to automatically log in +# Must be a valid user on the system +# If null, automatic login is disabled +auto_login_user = null + # Background color id bg = 0x00000000 diff --git a/res/lang/ar.ini b/res/lang/ar.ini index 78d9e93..9b371f8 100644 --- a/res/lang/ar.ini +++ b/res/lang/ar.ini @@ -4,6 +4,7 @@ brightness_up = رفع السطوع capslock = capslock err_alloc = فشل في تخصيص الذاكرة + err_bounds = out-of-bounds index err_brightness_change = فشل في تغيير سطوع الشاشة err_chdir = فشل في فتح مجلد المنزل diff --git a/res/lang/cat.ini b/res/lang/cat.ini index fb829a6..284b14a 100644 --- a/res/lang/cat.ini +++ b/res/lang/cat.ini @@ -4,6 +4,7 @@ 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 err_chdir = error en obrir la carpeta home diff --git a/res/lang/cs.ini b/res/lang/cs.ini index c1206fe..cac7884 100644 --- a/res/lang/cs.ini +++ b/res/lang/cs.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = alokace paměti selhala + err_bounds = index je mimo hranice pole err_chdir = nelze otevřít domovský adresář diff --git a/res/lang/de.ini b/res/lang/de.ini index 5512d5e..12cce5f 100644 --- a/res/lang/de.ini +++ b/res/lang/de.ini @@ -4,6 +4,7 @@ brightness_up = Helligkeit+ capslock = Feststelltaste err_alloc = Speicherzuweisung fehlgeschlagen + err_bounds = Index ausserhalb des Bereichs err_brightness_change = Helligkeitsänderung fehlgeschlagen err_chdir = Fehler beim Oeffnen des Home-Ordners diff --git a/res/lang/en.ini b/res/lang/en.ini index 1a8c16c..7c95a09 100644 --- a/res/lang/en.ini +++ b/res/lang/en.ini @@ -4,6 +4,7 @@ brightness_up = increase brightness capslock = capslock custom = custom err_alloc = failed memory allocation +err_autologin_session = autologin session not found err_bounds = out-of-bounds index err_brightness_change = failed to change brightness err_chdir = failed to open home folder diff --git a/res/lang/es.ini b/res/lang/es.ini index 776c998..0878bf2 100644 --- a/res/lang/es.ini +++ b/res/lang/es.ini @@ -4,6 +4,7 @@ brightness_up = subir brillo capslock = Bloq Mayús err_alloc = asignación de memoria fallida + err_bounds = índice fuera de límites err_chdir = error al abrir la carpeta home diff --git a/res/lang/fr.ini b/res/lang/fr.ini index 31c0971..7774a16 100644 --- a/res/lang/fr.ini +++ b/res/lang/fr.ini @@ -4,6 +4,7 @@ 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é err_chdir = échec de l'ouverture du répertoire home diff --git a/res/lang/it.ini b/res/lang/it.ini index 4ba807c..84d0d39 100644 --- a/res/lang/it.ini +++ b/res/lang/it.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = impossibile allocare memoria + err_bounds = indice fuori limite err_chdir = impossibile aprire home directory diff --git a/res/lang/ja_JP.ini b/res/lang/ja_JP.ini index 200422a..0f4c34a 100644 --- a/res/lang/ja_JP.ini +++ b/res/lang/ja_JP.ini @@ -4,6 +4,7 @@ brightness_up = 明るさを上げる capslock = CapsLock err_alloc = メモリ割り当て失敗 + err_bounds = 境界外インデックス err_brightness_change = 明るさの変更に失敗しました err_chdir = ホームフォルダを開けませんでした diff --git a/res/lang/lv.ini b/res/lang/lv.ini index dd15b05..024b130 100644 --- a/res/lang/lv.ini +++ b/res/lang/lv.ini @@ -4,6 +4,7 @@ brightness_up = palielināt spilgtumu capslock = caps lock custom = pielāgots err_alloc = neizdevās atmiņas piešķiršana + err_bounds = indekss ārpus robežām err_brightness_change = neizdevās mainīt spilgtumu err_chdir = neizdevās atvērt mājas mapi @@ -68,4 +69,3 @@ sleep = snauda wayland = wayland x11 = x11 xinitrc = xinitrc - diff --git a/res/lang/pl.ini b/res/lang/pl.ini index 065fb22..b159a1d 100644 --- a/res/lang/pl.ini +++ b/res/lang/pl.ini @@ -4,6 +4,7 @@ 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 err_chdir = nie udało się otworzyć folderu domowego diff --git a/res/lang/pt.ini b/res/lang/pt.ini index a1951fd..d47112c 100644 --- a/res/lang/pt.ini +++ b/res/lang/pt.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = erro na atribuição de memória + err_bounds = índice fora de limites err_chdir = erro ao abrir a pasta home diff --git a/res/lang/pt_BR.ini b/res/lang/pt_BR.ini index f886816..94312fd 100644 --- a/res/lang/pt_BR.ini +++ b/res/lang/pt_BR.ini @@ -4,6 +4,7 @@ capslock = caixa alta err_alloc = alocação de memória malsucedida + err_bounds = índice fora de limites err_chdir = não foi possível abrir o diretório home diff --git a/res/lang/ro.ini b/res/lang/ro.ini index 8503245..78ffcc0 100644 --- a/res/lang/ro.ini +++ b/res/lang/ro.ini @@ -21,6 +21,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 dab497c..3715250 100644 --- a/res/lang/ru.ini +++ b/res/lang/ru.ini @@ -4,6 +4,7 @@ brightness_up = увеличить яркость capslock = capslock err_alloc = не удалось выделить память + err_bounds = за пределами индекса err_brightness_change = не удалось изменить яркость err_chdir = не удалось открыть домашнюю папку diff --git a/res/lang/sr.ini b/res/lang/sr.ini index 1e3c6f7..6f8be10 100644 --- a/res/lang/sr.ini +++ b/res/lang/sr.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = neuspijesna alokacija memorije + err_bounds = izvan granica indeksa err_chdir = neuspijesno otvaranje home foldera diff --git a/res/lang/sv.ini b/res/lang/sv.ini index 3fc2bfd..030a66a 100644 --- a/res/lang/sv.ini +++ b/res/lang/sv.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = misslyckad minnesallokering + err_bounds = utanför banan index err_chdir = misslyckades att öppna hemkatalog diff --git a/res/lang/tr.ini b/res/lang/tr.ini index 78de8c3..0722574 100644 --- a/res/lang/tr.ini +++ b/res/lang/tr.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = basarisiz bellek ayirma + err_bounds = sinirlarin disinda dizin err_chdir = ev klasoru acilamadi diff --git a/res/lang/uk.ini b/res/lang/uk.ini index de41076..bbc6993 100644 --- a/res/lang/uk.ini +++ b/res/lang/uk.ini @@ -4,6 +4,7 @@ capslock = capslock err_alloc = невдале виділення пам'яті + err_bounds = поза межами індексу err_chdir = не вдалося відкрити домашній каталог diff --git a/res/lang/zh_CN.ini b/res/lang/zh_CN.ini index 5e77f8e..0c4e79e 100644 --- a/res/lang/zh_CN.ini +++ b/res/lang/zh_CN.ini @@ -4,6 +4,7 @@ capslock = 大写锁定 err_alloc = 内存分配失败 + err_bounds = 索引越界 err_chdir = 无法打开home文件夹 diff --git a/res/pam.d/ly-freebsd-autologin b/res/pam.d/ly-freebsd-autologin new file mode 100644 index 0000000..e2448ad --- /dev/null +++ b/res/pam.d/ly-freebsd-autologin @@ -0,0 +1,9 @@ +#%PAM-1.0 + +# OpenPAM (used in FreeBSD) doesn't support prepending "-" for ignoring missing +# modules. +auth required pam_permit.so +auth include login +account include login +password include login +session include login diff --git a/res/pam.d/ly-linux-autologin b/res/pam.d/ly-linux-autologin new file mode 100644 index 0000000..31a51a1 --- /dev/null +++ b/res/pam.d/ly-linux-autologin @@ -0,0 +1,16 @@ +#%PAM-1.0 + +auth required pam_permit.so +-auth optional pam_gnome_keyring.so +-auth optional pam_kwallet5.so + +account include login + +password include login +-password optional pam_gnome_keyring.so use_authtok + +-session optional pam_systemd.so class=greeter +-session optional pam_elogind.so +session include login +-session optional pam_gnome_keyring.so auto_start +-session optional pam_kwallet5.so auto_start diff --git a/src/Environment.zig b/src/Environment.zig index 849cb08..eab875d 100644 --- a/src/Environment.zig +++ b/src/Environment.zig @@ -16,6 +16,7 @@ pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} }; entry_ini: ?Ini(Entry) = null, 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/config/Config.zig b/src/config/Config.zig index 8f95627..371f256 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -12,6 +12,9 @@ animation_timeout_sec: u12 = 0, asterisk: ?u32 = '*', auth_fails: u64 = 10, battery_id: ?[]const u8 = null, +auto_login_service: [:0]const u8 = "ly-autologin", +auto_login_session: ?[]const u8 = null, +auto_login_user: ?[]const u8 = null, bg: u32 = 0x00000000, bigclock: Bigclock = .none, bigclock_12hr: bool = false, diff --git a/src/config/Lang.zig b/src/config/Lang.zig index 886644d..359feec 100644 --- a/src/config/Lang.zig +++ b/src/config/Lang.zig @@ -9,6 +9,7 @@ brightness_up: []const u8 = "increase brightness", capslock: []const u8 = "capslock", custom: []const u8 = "custom", err_alloc: []const u8 = "failed memory allocation", +err_autologin_session: []const u8 = "autologin session not found", err_bounds: []const u8 = "out-of-bounds index", err_brightness_change: []const u8 = "failed to change brightness", err_chdir: []const u8 = "failed to open home folder", diff --git a/src/main.zig b/src/main.zig index 80c1ab2..8f6d434 100644 --- a/src/main.zig +++ b/src/main.zig @@ -432,9 +432,38 @@ pub fn main() !void { var active_input = config.default_input; var insert_mode = !config.vi_mode or config.vi_default_mode == .insert; + var is_autologin = false; + + check_autologin: { + const auto_user = config.auto_login_user orelse break :check_autologin; + const auto_session = config.auto_login_session orelse break :check_autologin; + + if (!isValidUsername(auto_user, usernames)) { + try info_line.addMessage(lang.err_pam_user_unknown, config.error_bg, config.error_fg); + try log_writer.print("autologin failed: username '{s}' not found\n", .{auto_user}); + break :check_autologin; + } + + const session_index = findSessionByName(&session, auto_session) orelse { + try log_writer.print("autologin failed: session '{s}' not found\n", .{auto_session}); + try info_line.addMessage(lang.err_autologin_session, config.error_bg, config.error_fg); + break :check_autologin; + }; + try log_writer.print("attempting autologin for user '{s}' with session '{s}'\n", .{ auto_user, auto_session }); + + session.label.current = session_index; + for (login.label.list.items, 0..) |username, i| { + if (std.mem.eql(u8, username.name, auto_user)) { + login.label.current = i; + break; + } + } + is_autologin = true; + } // Load last saved username and desktop selection, if any - if (config.save) { + // Skip if autologin is active to prevent overriding autologin session + if (config.save and !is_autologin) { if (saved_users.last_username_index) |index| load_last_user: { // If the saved index isn't valid, bail out if (index >= saved_users.user_list.items.len) break :load_last_user; @@ -777,11 +806,25 @@ pub fn main() !void { timeout = @intCast(1000 - @divTrunc(time.microseconds, 1000) + 1); } - const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); + // Skip event polling if autologin is set, use simulated Enter key press instead + if (is_autologin) { + event = termbox.tb_event{ + .type = termbox.TB_EVENT_KEY, + .key = termbox.TB_KEY_ENTER, + .ch = 0, + .w = 0, + .h = 0, + .x = 0, + .y = 0, + .mod = 0, + }; + } else { + const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout); - update = timeout != -1; + update = timeout != -1; - if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; + if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue; + } switch (event.key) { termbox.TB_KEY_ESC => { @@ -927,9 +970,14 @@ pub fn main() !void { session_pid = try std.posix.fork(); if (session_pid == 0) { const current_environment = session.label.list.items[session.label.current].environment; + + // Use auto_login_service for autologin, otherwise use configured service + const service_name = if (is_autologin) config.auto_login_service else config.service_name; + const password_text = if (is_autologin) "" else password.text.items; + const auth_options = auth.AuthOptions{ .tty = active_tty, - .service_name = config.service_name, + .service_name = service_name, .path = config.path, .session_log = config.session_log, .xauth_cmd = config.xauth_cmd, @@ -949,7 +997,7 @@ pub fn main() !void { try log_file.reinit(); - auth.authenticate(allocator, &log_file, auth_options, current_environment, login.getCurrentUsername(), password.text.items) catch |err| { + auth.authenticate(allocator, &log_file, auth_options, current_environment, login.getCurrentUsername(), password_text) catch |err| { shared_err.writeError(err); log_file.deinit(); @@ -992,6 +1040,7 @@ pub fn main() !void { } password.clear(); + is_autologin = false; try info_line.addMessage(lang.logout, config.bg, config.fg); try log_writer.writeAll("logged out\n"); } @@ -1110,6 +1159,7 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa 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 @@ -1123,13 +1173,18 @@ 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 - maybe_xdg_session_desktop = std.fs.path.stem(item.name); + 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; + } } try session.addEnvironment(.{ .entry_ini = entry_ini, .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) { @@ -1144,6 +1199,26 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa } } +fn isValidUsername(username: []const u8, usernames: StringList) bool { + for (usernames.items) |valid_username| { + if (std.mem.eql(u8, username, valid_username)) return true; + } + return false; +} + +fn findSessionByName(session: *Session, name: []const u8) ?usize { + for (session.label.list.items, 0..) |env, 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; +} + fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8) !StringList { const uid_range = try interop.getUserIdRange(allocator, login_defs_path); diff --git a/src/tui/components/Session.zig b/src/tui/components/Session.zig index 53a6c90..4417e06 100644 --- a/src/tui/components/Session.zig +++ b/src/tui/components/Session.zig @@ -27,6 +27,9 @@ 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.deinit();