diff --git a/build.zig b/build.zig index 36a78c6..a227e07 100644 --- a/build.zig +++ b/build.zig @@ -139,6 +139,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}); @@ -171,6 +177,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/readme.md b/readme.md index 57ca447..c81d6ef 100644 --- a/readme.md +++ b/readme.md @@ -4,6 +4,8 @@ Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD. +Join us on Matrix over at [#ly:envs.net](https://matrix.to/#/#ly:envs.net)! + **Note**: Development happens on [Codeberg](https://codeberg.org/AnErrupTion/ly) with a mirror on [GitHub](https://github.com/fairyglade/ly). ## Dependencies diff --git a/res/config.ini b/res/config.ini index a74cd5c..866b6b2 100644 --- a/res/config.ini +++ b/res/config.ini @@ -100,20 +100,28 @@ colormix_col2 = 0x000000FF # Color mixing animation third color id colormix_col3 = 0x20000000 -# Console path -console_dev = /dev/console +# 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 -# DOOM animation top color (low intensity flames) -doom_top_color = 0x00FF0000 +# DOOM animation fire height (1 thru 9) +doom_fire_height = 6 -# DOOM animation middle color (medium intensity flames) -doom_middle_color = 0x00FFFF00 +# DOOM animation fire spread (0 thru 4) +doom_fire_spread = 2 -# DOOM animation bottom color (high intensity flames) +# DOOM animation custom top color (low intensity flames) +doom_top_color = 0x009F2707 + +# DOOM animation custom middle color (medium intensity flames) +doom_middle_color = 0x00C78F17 + +# DOOM animation custom bottom color (high intensity flames) doom_bottom_color = 0x00FFFFFF # Error background color id @@ -176,6 +184,9 @@ load = true # You can also set environment variables in there, they'll persist until logout login_cmd = null +# Path for login.defs file (used for listing all local users on the system) +login_defs_path = /etc/login.defs + # Command executed when logging out # If null, no command will be executed # Important: the session will already be terminated when this command is executed, so @@ -248,7 +259,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 @@ -263,5 +274,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 835fd66..b06f253 100644 --- a/res/lang/ar.ini +++ b/res/lang/ar.ini @@ -2,17 +2,18 @@ authenticating = جاري المصادقة... brightness_down = خفض السطوع brightness_up = رفع السطوع capslock = capslock + err_alloc = فشل في تخصيص الذاكرة err_bounds = out-of-bounds index err_brightness_change = فشل في تغيير سطوع الشاشة err_chdir = فشل في فتح مجلد المنزل err_config = فشل في تفسير ملف الإعدادات -err_console_dev = فشل في الوصول إلى جهاز وحدة التحكم err_dgn_oob = رسالة سجل (Log) err_domain = اسم نطاق غير صالح err_empty_password = لا يُسمح بكلمة مرور فارغة err_envlist = فشل في جلب قائمة المتغيرات البيئية err_hostname = فشل في جلب اسم المضيف (Hostname) + err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock) err_null = مؤشر فارغ (Null pointer) err_numlock = فشل في ضبط Num Lock @@ -38,7 +39,9 @@ err_perm_group = فشل في تخفيض صلاحيات المجموعة (Group p err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions) err_pwnam = فشل في جلب معلومات المستخدم err_sleep = فشل في تنفيذ أمر sleep + err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY) + err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم err_user_init = فشل في تهيئة بيانات المستخدم err_user_uid = فشل في تعيين معرّف المستخدم (UID) diff --git a/res/lang/cat.ini b/res/lang/cat.ini index 01d9232..d0ce33e 100644 --- a/res/lang/cat.ini +++ b/res/lang/cat.ini @@ -2,17 +2,18 @@ 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 err_chdir = error en obrir la carpeta home -err_console_dev = error en accedir a la consola err_dgn_oob = missatge de registre err_domain = domini invàlid err_envlist = error en obtenir l'envlist err_hostname = error en obtenir el nom de l'amfitrió + err_mlock = error en bloquejar la memòria de clau err_null = punter nul err_numlock = error en establir el Bloq num @@ -39,6 +40,8 @@ err_perm_user = error en degradar els permisos de l'usuari err_pwnam = error en obtenir la informació de l'usuari + + err_user_gid = error en establir el GID de l'usuari err_user_init = error en inicialitzar usuari err_user_uid = error en establir l'UID de l'usuari diff --git a/res/lang/cs.ini b/res/lang/cs.ini index 2adc297..7c87ea6 100644 --- a/res/lang/cs.ini +++ b/res/lang/cs.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = alokace paměti selhala err_bounds = index je mimo hranice pole err_chdir = nelze otevřít domovský adresář -err_console_dev = chyba při přístupu do konzole err_dgn_oob = zpráva protokolu err_domain = neplatná doména err_hostname = nelze získat název hostitele + err_mlock = uzamčení paměti hesel selhalo err_null = nulový ukazatel @@ -39,6 +40,8 @@ err_perm_user = nepodařilo se snížit uživatelská oprávnění err_pwnam = nelze získat informace o uživateli + + err_user_gid = nastavení GID uživatele selhalo err_user_init = inicializace uživatele selhala err_user_uid = nastavení UID uživateli selhalo diff --git a/res/lang/de.ini b/res/lang/de.ini index 47c44aa..44bbc53 100644 --- a/res/lang/de.ini +++ b/res/lang/de.ini @@ -2,17 +2,18 @@ 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 err_chdir = Fehler beim Oeffnen des Home-Ordners err_config = Fehler beim Verarbeiten der Konfigurationsdatei -err_console_dev = Zugriff auf die Konsole fehlgeschlagen err_dgn_oob = Diagnose-Nachricht err_domain = Ungueltige Domain err_empty_password = Leeres Passwort nicht zugelassen err_envlist = Fehler beim Abrufen der Umgebungs-Variablen err_hostname = Abrufen des Hostnames fehlgeschlagen + err_mlock = Sperren des Passwortspeichers fehlgeschlagen err_null = Null Pointer err_numlock = Numlock konnte nicht aktiviert werden @@ -38,7 +39,9 @@ err_perm_group = Fehler beim Heruntersetzen der Gruppenberechtigungen err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen err_sleep = Sleep-Befehl fehlgeschlagen + err_tty_ctrl = Fehler bei der TTY-Uebergabe + err_user_gid = Fehler beim Setzen der Gruppen-ID err_user_init = Nutzer-Initialisierung fehlgeschlagen err_user_uid = Setzen der Benutzer-ID fehlgeschlagen diff --git a/res/lang/en.ini b/res/lang/en.ini index a0eae31..9554658 100644 --- a/res/lang/en.ini +++ b/res/lang/en.ini @@ -2,17 +2,18 @@ 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 err_chdir = failed to open home folder err_config = unable to parse config file -err_console_dev = failed to access console err_dgn_oob = log message err_domain = invalid domain err_empty_password = empty password not allowed err_envlist = failed to get envlist err_hostname = failed to get hostname +err_lock_state = failed to get lock state err_mlock = failed to lock password memory err_null = null pointer err_numlock = failed to set numlock @@ -38,7 +39,9 @@ err_perm_group = failed to downgrade group permissions err_perm_user = failed to downgrade user permissions err_pwnam = failed to get user info err_sleep = failed to execute sleep command +err_switch_tty = failed to switch tty err_tty_ctrl = tty control transfer failed +err_no_users = no users found err_user_gid = failed to set user GID err_user_init = failed to initialize user err_user_uid = failed to set user UID diff --git a/res/lang/es.ini b/res/lang/es.ini index 5004d12..2bd51a7 100644 --- a/res/lang/es.ini +++ b/res/lang/es.ini @@ -2,17 +2,18 @@ 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 err_chdir = error al abrir la carpeta home -err_console_dev = error al acceder a la consola err_dgn_oob = mensaje de registro err_domain = dominio inválido err_hostname = error al obtener el nombre de host + err_mlock = error al bloquear la contraseña de memoria err_null = puntero nulo @@ -39,6 +40,8 @@ err_perm_user = error al degradar los permisos del usuario err_pwnam = error al obtener la información del usuario + + err_user_gid = error al establecer el GID del usuario err_user_init = error al inicializar usuario err_user_uid = error al establecer el UID del usuario diff --git a/res/lang/fr.ini b/res/lang/fr.ini index 75c7c6f..e024820 100644 --- a/res/lang/fr.ini +++ b/res/lang/fr.ini @@ -2,17 +2,18 @@ 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é err_chdir = échec de l'ouverture du répertoire home err_config = échec de lecture du fichier de configuration -err_console_dev = échec d'accès à la console err_dgn_oob = message err_domain = domaine invalide err_empty_password = mot de passe vide non autorisé err_envlist = échec de lecture de la liste d'environnement err_hostname = échec de lecture du nom d'hôte +err_lock_state = échec de lecture de l'état de verrouillage err_mlock = échec du verrouillage mémoire err_null = pointeur null err_numlock = échec de modification du verr.num @@ -38,7 +39,9 @@ err_perm_group = échec du déclassement des permissions de groupe err_perm_user = échec du déclassement des permissions utilisateur err_pwnam = échec de lecture des infos utilisateur err_sleep = échec de l'exécution de la commande de veille +err_switch_tty = échec du changement de terminal err_tty_ctrl = échec du transfert de contrôle du terminal +err_no_users = aucun utilisateur trouvé err_user_gid = échec de modification du GID err_user_init = échec d'initialisation de l'utilisateur err_user_uid = échec de modification du UID diff --git a/res/lang/it.ini b/res/lang/it.ini index 8d749c6..3a7f5a8 100644 --- a/res/lang/it.ini +++ b/res/lang/it.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = impossibile allocare memoria err_bounds = indice fuori limite err_chdir = impossibile aprire home directory -err_console_dev = impossibile aprire console err_dgn_oob = messaggio log err_domain = dominio non valido err_hostname = impossibile ottenere hostname + err_mlock = impossibile ottenere lock per la password in memoria err_null = puntatore nullo @@ -39,6 +40,8 @@ err_perm_user = impossibile ridurre permessi utente err_pwnam = impossibile ottenere dati utente + + err_user_gid = impossibile impostare GID utente err_user_init = impossibile inizializzare utente err_user_uid = impossible impostare UID utente diff --git a/res/lang/ja_JP.ini b/res/lang/ja_JP.ini new file mode 100644 index 0000000..c5f5596 --- /dev/null +++ b/res/lang/ja_JP.ini @@ -0,0 +1,126 @@ +authenticating=認証中... +brightness_down=明るさを下げる +brightness_up=明るさを上げる +capslock=CapsLock +err_alloc=メモリ割り当て失敗 +err_bounds=境界外インデックス +err_brightness_change=明るさの変更に失敗しました +err_chdir=ホームフォルダを開けませんでした +err_config=設定ファイルを解析できません +err_console_dev=コンソールへのアクセスに失敗しました +err_dgn_oob=ログメッセージ +err_domain=無効なドメイン +err_empty_password=空のパスワードは許可されていません +err_envlist=環境変数リストの取得に失敗しました +err_hostname=ホスト名の取得に失敗しました +err_mlock=パスワードメモリのロックに失敗しました +err_null=ヌルポインタ +err_numlock=NumLockの設定に失敗しました +err_pam=PAMトランザクション失敗 +err_pam_abort=PAMトランザクションが中断されました +err_pam_acct_expired=アカウントの有効期限が切れています +err_pam_auth=認証エラー +err_pam_authinfo_unavail=ユーザー情報の取得に失敗しました +err_pam_authok_reqd=トークンの有効期限が切れています +err_pam_buf=メモリバッファエラー +err_pam_cred_err=認証情報の設定に失敗しました +err_pam_cred_expired=認証情報の有効期限が切れています +err_pam_cred_insufficient=認証情報が不十分です +err_pam_cred_unavail=認証情報の取得に失敗しました +err_pam_maxtries=最大試行回数に到達しました +err_pam_perm_denied=アクセスが拒否されました +err_pam_session=セッションエラー +err_pam_sys=システムエラー +err_pam_user_unknown=不明なユーザー +err_path=パスの設定に失敗しました +err_perm_dir=カレントディレクトリの変更に失敗しました +err_perm_group=グループ権限のダウングレードに失敗しました +err_perm_user=ユーザー権限のダウングレードに失敗しました +err_pwnam=ユーザー情報の取得に失敗しました +err_sleep=スリープコマンドの実行に失敗しました +err_tty_ctrl=TTY制御の転送に失敗しました +err_user_gid=ユーザーGIDの設定に失敗しました +err_user_init=ユーザーの初期化に失敗しました +err_user_uid=ユーザーUIDの設定に失敗しました +err_xauth=xauthコマンドの実行に失敗しました +err_xcb_conn=XCB接続に失敗しました +err_xsessions_dir=セッションフォルダが見つかりませんでした +err_xsessions_open=セッションフォルダを開けませんでした +insert=挿入 +login=ログイン +logout=ログアウト済み +no_x11_support=X11サポートはコンパイル時に無効化されています +normal=通常 +numlock=NumLock +other=その他 +password=パスワード +restart=再起動 +shell=シェル +shutdown=シャットダウン +sleep=スリープ +wayland=Wayland +x11=X11 +xinitrc=xinitrc +authenticating=認証中... +brightness_down=明るさを下げる +brightness_up=明るさを上げる +capslock=CapsLock +err_alloc=メモリ割り当て失敗 +err_bounds=境界外インデックス +err_brightness_change=明るさの変更に失敗しました +err_chdir=ホームフォルダを開けませんでした +err_config=設定ファイルを解析できません +err_console_dev=コンソールへのアクセスに失敗しました +err_dgn_oob=ログメッセージ +err_domain=無効なドメイン +err_empty_password=空のパスワードは許可されていません +err_envlist=環境変数リストの取得に失敗しました +err_hostname=ホスト名の取得に失敗しました +err_mlock=パスワードメモリのロックに失敗しました +err_null=ヌルポインタ +err_numlock=NumLockの設定に失敗しました +err_pam=PAMトランザクション失敗 +err_pam_abort=PAMトランザクションが中断されました +err_pam_acct_expired=アカウントの有効期限が切れています +err_pam_auth=認証エラー +err_pam_authinfo_unavail=ユーザー情報の取得に失敗しました +err_pam_authok_reqd=トークンの有効期限が切れています +err_pam_buf=メモリバッファエラー +err_pam_cred_err=認証情報の設定に失敗しました +err_pam_cred_expired=認証情報の有効期限が切れています +err_pam_cred_insufficient=認証情報が不十分です +err_pam_cred_unavail=認証情報の取得に失敗しました +err_pam_maxtries=最大試行回数に到達しました +err_pam_perm_denied=アクセスが拒否されました +err_pam_session=セッションエラー +err_pam_sys=システムエラー +err_pam_user_unknown=不明なユーザー +err_path=パスの設定に失敗しました +err_perm_dir=カレントディレクトリの変更に失敗しました +err_perm_group=グループ権限のダウングレードに失敗しました +err_perm_user=ユーザー権限のダウングレードに失敗しました +err_pwnam=ユーザー情報の取得に失敗しました +err_sleep=スリープコマンドの実行に失敗しました +err_tty_ctrl=TTY制御の転送に失敗しました +err_user_gid=ユーザーGIDの設定に失敗しました +err_user_init=ユーザーの初期化に失敗しました +err_user_uid=ユーザーUIDの設定に失敗しました +err_xauth=xauthコマンドの実行に失敗しました +err_xcb_conn=XCB接続に失敗しました +err_xsessions_dir=セッションフォルダが見つかりませんでした +err_xsessions_open=セッションフォルダを開けませんでした +insert=挿入 +login=ログイン +logout=ログアウト済み +no_x11_support=X11サポートはコンパイル時に無効化されています +normal=通常 +numlock=NumLock +other=その他 +password=パスワード +restart=再起動 +shell=シェル +shutdown=シャットダウン +sleep=スリープ +wayland=Wayland +x11=X11 +xinitrc=xinitrc diff --git a/res/lang/pl.ini b/res/lang/pl.ini index cdd3f29..b3eee3d 100644 --- a/res/lang/pl.ini +++ b/res/lang/pl.ini @@ -2,17 +2,18 @@ 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 err_chdir = nie udało się otworzyć folderu domowego err_config = nie można przetworzyć pliku konfiguracyjnego -err_console_dev = nie udało się uzyskać dostępu do konsoli err_dgn_oob = wiadomość loga err_domain = niepoprawna domena err_empty_password = puste hasło jest niedozwolone err_envlist = nie udało się pobrać listy zmiennych środowiskowych err_hostname = nie udało się uzyskać nazwy hosta + err_mlock = nie udało się zablokować pamięci haseł err_null = pusty wskaźnik err_numlock = nie udało się ustawić numlock @@ -38,7 +39,9 @@ err_perm_group = nie udało się obniżyć uprawnień grupy err_perm_user = nie udało się obniżyć uprawnień użytkownika err_pwnam = nie udało się uzyskać informacji o użytkowniku err_sleep = nie udało się wykonać polecenia sleep + err_tty_ctrl = nie udało się przekazać kontroli tty + err_user_gid = nie udało się ustawić GID użytkownika err_user_init = nie udało się zainicjalizować użytkownika err_user_uid = nie udało się ustawić UID użytkownika diff --git a/res/lang/pt.ini b/res/lang/pt.ini index 25361b6..587d3b4 100644 --- a/res/lang/pt.ini +++ b/res/lang/pt.ini @@ -2,17 +2,18 @@ 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 -err_console_dev = erro ao aceder à consola err_dgn_oob = mensagem de registo err_domain = domínio inválido err_hostname = erro ao obter o nome do host + err_mlock = erro de bloqueio de memória err_null = ponteiro nulo @@ -39,6 +40,8 @@ err_perm_user = erro ao reduzir as permissões do utilizador err_pwnam = erro ao obter informação do utilizador + + err_user_gid = erro ao definir o GID do utilizador err_user_init = erro ao iniciar o utilizador err_user_uid = erro ao definir o UID do utilizador diff --git a/res/lang/pt_BR.ini b/res/lang/pt_BR.ini index 8148e27..2485522 100644 --- a/res/lang/pt_BR.ini +++ b/res/lang/pt_BR.ini @@ -2,17 +2,18 @@ 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 -err_console_dev = não foi possível acessar o console err_dgn_oob = mensagem de log err_domain = domínio inválido err_hostname = não foi possível obter o nome do host + err_mlock = bloqueio da memória de senha malsucedido err_null = ponteiro nulo @@ -39,6 +40,8 @@ err_perm_user = não foi possível reduzir as permissões de usuário err_pwnam = não foi possível obter informações do usuário + + err_user_gid = não foi possível definir o GID do usuário err_user_init = não foi possível iniciar o usuário err_user_uid = não foi possível definir o UID do usuário diff --git a/res/lang/ro.ini b/res/lang/ro.ini index f4928cf..74c0461 100644 --- a/res/lang/ro.ini +++ b/res/lang/ro.ini @@ -7,7 +7,8 @@ capslock = capslock -err_console_dev = nu s-a putut accesa consola + + @@ -47,6 +48,8 @@ err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator + + login = utilizator logout = opreşte sesiunea diff --git a/res/lang/ru.ini b/res/lang/ru.ini index 8e9b1a0..103f135 100644 --- a/res/lang/ru.ini +++ b/res/lang/ru.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = не удалось выделить память err_bounds = за пределами индекса err_chdir = не удалось открыть домашнюю папку -err_console_dev = не удалось получить доступ к консоли err_dgn_oob = отладочное сообщение (log) err_domain = неверный домен err_hostname = не удалось получить имя хоста + err_mlock = сбой блокировки памяти err_null = нулевой указатель @@ -39,6 +40,8 @@ err_perm_user = не удалось понизить права доступа err_pwnam = не удалось получить информацию о пользователе + + err_user_gid = не удалось установить GID пользователя err_user_init = не удалось инициализировать пользователя err_user_uid = не удалось установить UID пользователя diff --git a/res/lang/sr.ini b/res/lang/sr.ini index bedc690..c63fe28 100644 --- a/res/lang/sr.ini +++ b/res/lang/sr.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = neuspijesna alokacija memorije err_bounds = izvan granica indeksa err_chdir = neuspijesno otvaranje home foldera -err_console_dev = neuspijesno pristupanje konzoli err_dgn_oob = log poruka err_domain = nevazeci domen err_hostname = neuspijesno trazenje hostname-a + err_mlock = neuspijesno zakljucavanje memorije lozinke err_null = null pokazivac @@ -39,6 +40,8 @@ err_perm_user = neuspijesno snizavanje dozvola korisnika err_pwnam = neuspijesno skupljanje informacija o korisniku + + err_user_gid = neuspijesno postavljanje korisničkog GID-a err_user_init = neuspijensa inicijalizacija korisnika err_user_uid = neuspijesno postavljanje UID-a korisnika diff --git a/res/lang/sv.ini b/res/lang/sv.ini index 40b8cbd..17359cc 100644 --- a/res/lang/sv.ini +++ b/res/lang/sv.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = misslyckad minnesallokering err_bounds = utanför banan index err_chdir = misslyckades att öppna hemkatalog -err_console_dev = misslyckades att komma åt konsol err_dgn_oob = loggmeddelande err_domain = okänd domän err_hostname = misslyckades att hämta värdnamn + err_mlock = misslyckades att låsa lösenordsminne err_null = nullpekare @@ -39,6 +40,8 @@ err_perm_user = misslyckades att nergradera användarbehörigheter err_pwnam = misslyckades att hämta användarinfo + + err_user_gid = misslyckades att ställa in användar-GID err_user_init = misslyckades att initialisera användaren err_user_uid = misslyckades att ställa in användar-UID diff --git a/res/lang/tr.ini b/res/lang/tr.ini index 77a22ae..535015c 100644 --- a/res/lang/tr.ini +++ b/res/lang/tr.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = basarisiz bellek ayirma err_bounds = sinirlarin disinda dizin err_chdir = ev klasoru acilamadi -err_console_dev = konsola erisilemedi err_dgn_oob = log mesaji err_domain = gecersiz etki alani err_hostname = ana bilgisayar adi alinamadi + err_mlock = parola bellegi kilitlenemedi err_null = bos isaretci hatasi @@ -39,6 +40,8 @@ err_perm_user = kullanici izinleri dusurulemedi err_pwnam = kullanici bilgileri alinamadi + + err_user_gid = kullanici icin GID ayarlanamadi err_user_init = kullanici oturumu baslatilamadi err_user_uid = kullanici icin UID ayarlanamadi diff --git a/res/lang/uk.ini b/res/lang/uk.ini index a24731e..9e6de2d 100644 --- a/res/lang/uk.ini +++ b/res/lang/uk.ini @@ -2,17 +2,18 @@ capslock = capslock + err_alloc = невдале виділення пам'яті err_bounds = поза межами індексу err_chdir = не вдалося відкрити домашній каталог -err_console_dev = невдалий доступ до консолі err_dgn_oob = повідомлення журналу (log) err_domain = недійсний домен err_hostname = не вдалося отримати ім'я хосту + err_mlock = збій блокування пам'яті err_null = нульовий вказівник @@ -39,6 +40,8 @@ err_perm_user = не вдалося понизити права доступу err_pwnam = не вдалося отримати дані користувача + + err_user_gid = не вдалося змінити GID користувача err_user_init = не вдалося ініціалізувати користувача err_user_uid = не вдалося змінити UID користувача diff --git a/res/lang/zh_CN.ini b/res/lang/zh_CN.ini index 9960d07..9afdc66 100644 --- a/res/lang/zh_CN.ini +++ b/res/lang/zh_CN.ini @@ -2,17 +2,18 @@ capslock = 大写锁定 + err_alloc = 内存分配失败 err_bounds = 索引越界 err_chdir = 无法打开home文件夹 -err_console_dev = 无法访问控制台 err_dgn_oob = 日志消息 err_domain = 无效的域 err_hostname = 获取主机名失败 + err_mlock = 锁定密码存储器失败 err_null = 空指针 @@ -39,6 +40,8 @@ err_perm_user = 用户权限降级失败 err_pwnam = 获取用户信息失败 + + err_user_gid = 设置用户GID失败 err_user_init = 初始化用户失败 err_user_uid = 设置用户UID失败 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/UidRange.zig b/src/UidRange.zig new file mode 100644 index 0000000..13eb979 --- /dev/null +++ b/src/UidRange.zig @@ -0,0 +1,6 @@ +const std = @import("std"); + +// We set both values to 0 by default so that, in case they aren't present in +// the login.defs for some reason, then only the root username will be shown +uid_min: std.c.uid_t = 0, +uid_max: std.c.uid_t = 0, diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig index 41de5ee..76850fa 100644 --- a/src/animations/Doom.zig +++ b/src/animations/Doom.zig @@ -7,21 +7,22 @@ const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const Doom = @This(); pub const STEPS = 12; +pub const HEIGHT_MAX = 9; +pub const SPREAD_MAX = 4; allocator: Allocator, terminal_buffer: *TerminalBuffer, buffer: []u8, +height: u8, +spread: u8, fire: [STEPS + 1]Cell, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32) !Doom { +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32, fire_height: u8, fire_spread: u8) !Doom { const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height); initBuffer(buffer, terminal_buffer.width); - return .{ - .allocator = allocator, - .terminal_buffer = terminal_buffer, - .buffer = buffer, - .fire = [_]Cell{ + const levels = + [_]Cell{ Cell.init(' ', TerminalBuffer.Color.DEFAULT, TerminalBuffer.Color.DEFAULT), Cell.init(0x2591, top_color, TerminalBuffer.Color.DEFAULT), Cell.init(0x2592, top_color, TerminalBuffer.Color.DEFAULT), @@ -35,7 +36,15 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u Cell.init(0x2592, bottom_color, middle_color), Cell.init(0x2593, bottom_color, middle_color), Cell.init(0x2588, bottom_color, middle_color), - }, + }; + + return .{ + .allocator = allocator, + .terminal_buffer = terminal_buffer, + .buffer = buffer, + .height = @min(HEIGHT_MAX, fire_height), + .spread = @min(SPREAD_MAX, fire_spread), + .fire = levels, }; } @@ -57,20 +66,35 @@ fn draw(self: *Doom) void { for (0..self.terminal_buffer.width) |x| { // We start from 1 so that we always have the topmost line when spreading fire for (1..self.terminal_buffer.height) |y| { - // Get current cell + // Get index of current cell in fire level buffer const from = y * self.terminal_buffer.width + x; - const cell_index = self.buffer[from]; - // Spread fire - const propagate = self.terminal_buffer.random.int(u1); - const to = from - self.terminal_buffer.width; // Get the line above + // Generate random data for fire propagation + const rand_loss = self.terminal_buffer.random.intRangeAtMost(u8, 0, HEIGHT_MAX); + const rand_spread = self.terminal_buffer.random.intRangeAtMost(u8, 0, self.spread * 2); - self.buffer[to] = if (cell_index > 0) cell_index - propagate else cell_index; + // Select semi-random target cell + const to = from -| self.terminal_buffer.width + self.spread -| rand_spread; + const to_x = to % self.terminal_buffer.width; + const to_y = to / self.terminal_buffer.width; - // Put the cell - const cell = self.fire[cell_index]; - cell.put(x, y); + // Get fire level of current cell + const level_buf_from = self.buffer[from]; + + // Choose new fire level and store in level buffer + const level_buf_to = level_buf_from -| @intFromBool(rand_loss >= self.height); + self.buffer[to] = level_buf_to; + + // Send known fire levels to terminal buffer + const from_cell = self.fire[level_buf_from]; + const to_cell = self.fire[level_buf_to]; + from_cell.put(x, y); + to_cell.put(to_x, to_y); } + + // Draw bottom line (fire source) + const src_cell = self.fire[STEPS]; + src_cell.put(x, self.terminal_buffer.height - 1); } } diff --git a/src/auth.zig b/src/auth.zig index 35eee5e..0b468da 100644 --- a/src/auth.zig +++ b/src/auth.zig @@ -41,8 +41,7 @@ pub fn authenticate(options: AuthOptions, current_environment: Environment, logi const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty}); // Set the XDG environment variables - setXdgSessionEnv(current_environment.display_server); - try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names); + try setXdgEnv(tty_str, current_environment); // Open the PAM session var credentials = [_:null]?[*:0]const u8{ login, password }; @@ -96,7 +95,7 @@ pub fn authenticate(options: AuthOptions, current_environment: Environment, logi child_pid = try std.posix.fork(); if (child_pid == 0) { - startSession(options, pwd, handle, current_environment) catch |e| { + startSession(options, tty_str, pwd, handle, current_environment) catch |e| { shared_err.writeError(e); std.process.exit(1); }; @@ -132,6 +131,7 @@ pub fn authenticate(options: AuthOptions, current_environment: Environment, logi fn startSession( options: AuthOptions, + tty_str: [:0]u8, pwd: *interop.pwd.passwd, handle: ?*interop.pam.pam_handle, current_environment: Environment, @@ -155,6 +155,9 @@ fn startSession( // Set up the environment try initEnv(pwd, options.path); + // Reset the XDG environment variables + try setXdgEnv(tty_str, current_environment); + // Set the PAM variables const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle); if (pam_env_vars == null) return error.GetEnvListFailed; @@ -177,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), } } @@ -193,15 +197,14 @@ fn initEnv(pwd: *interop.pwd.passwd, path_env: ?[:0]const u8) !void { } } -fn setXdgSessionEnv(display_server: enums.DisplayServer) void { - _ = interop.stdlib.setenv("XDG_SESSION_TYPE", switch (display_server) { +fn setXdgEnv(tty_str: [:0]u8, environment: Environment) !void { + _ = interop.stdlib.setenv("XDG_SESSION_TYPE", switch (environment.display_server) { .wayland => "wayland", .shell => "tty", .xinitrc, .x11 => "x11", + .custom => "unspecified", }, 0); -} -fn setXdgEnv(tty_str: [:0]u8, maybe_desktop_name: ?[:0]const u8, maybe_xdg_desktop_names: ?[:0]const u8) !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 @@ -214,10 +217,10 @@ fn setXdgEnv(tty_str: [:0]u8, maybe_desktop_name: ?[:0]const u8, maybe_xdg_deskt _ = interop.stdlib.setenv("XDG_RUNTIME_DIR", uid_str, 0); } - if (maybe_xdg_desktop_names) |xdg_desktop_names| _ = interop.stdlib.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names, 0); + if (environment.xdg_desktop_names) |xdg_desktop_names| _ = interop.stdlib.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names, 0); _ = interop.stdlib.setenv("XDG_SESSION_CLASS", "user", 0); _ = interop.stdlib.setenv("XDG_SESSION_ID", "1", 0); - if (maybe_desktop_name) |desktop_name| _ = interop.stdlib.setenv("XDG_SESSION_DESKTOP", desktop_name, 0); + if (environment.xdg_session_desktop) |desktop_name| _ = interop.stdlib.setenv("XDG_SESSION_DESKTOP", desktop_name, 0); _ = interop.stdlib.setenv("XDG_SEAT", "seat0", 0); _ = interop.stdlib.setenv("XDG_VTNR", tty_str, 0); } @@ -461,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 558668b..92e0552 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -28,8 +28,10 @@ cmatrix_max_codepoint: u16 = 0x7B, colormix_col1: u32 = 0x00FF0000, colormix_col2: u32 = 0x000000FF, colormix_col3: u32 = 0x20000000, -console_dev: []const u8 = "/dev/console", +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, doom_top_color: u32 = 0x00FF0000, doom_middle_color: u32 = 0x00FFFF00, doom_bottom_color: u32 = 0x00FFFFFF, @@ -48,6 +50,7 @@ input_len: u8 = 34, lang: []const u8 = "en", load: bool = true, login_cmd: ?[]const u8 = null, +login_defs_path: []const u8 = "/etc/login.defs", logout_cmd: ?[]const u8 = null, margin_box_h: u8 = 2, margin_box_v: u8 = 1, diff --git a/src/config/Lang.zig b/src/config/Lang.zig index 3360b71..7175e0f 100644 --- a/src/config/Lang.zig +++ b/src/config/Lang.zig @@ -7,17 +7,18 @@ 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", err_chdir: []const u8 = "failed to open home folder", err_config: []const u8 = "unable to parse config file", -err_console_dev: []const u8 = "failed to access console", err_dgn_oob: []const u8 = "log message", err_domain: []const u8 = "invalid domain", err_empty_password: []const u8 = "empty password not allowed", err_envlist: []const u8 = "failed to get envlist", err_hostname: []const u8 = "failed to get hostname", +err_lock_state: []const u8 = "failed to get lock state", err_mlock: []const u8 = "failed to lock password memory", err_null: []const u8 = "null pointer", err_numlock: []const u8 = "failed to set numlock", @@ -43,7 +44,9 @@ err_perm_group: []const u8 = "failed to downgrade group permissions", err_perm_user: []const u8 = "failed to downgrade user permissions", err_pwnam: []const u8 = "failed to get user info", err_sleep: []const u8 = "failed to execute sleep command", +err_switch_tty: []const u8 = "failed to switch tty", err_tty_ctrl: []const u8 = "tty control transfer failed", +err_no_users: []const u8 = "no users found", err_user_gid: []const u8 = "failed to set user GID", err_user_init: []const u8 = "failed to initialize user", err_user_uid: []const u8 = "failed to set user UID", diff --git a/src/config/migrator.zig b/src/config/migrator.zig index 2d2e34e..5f1269b 100644 --- a/src/config/migrator.zig +++ b/src/config/migrator.zig @@ -26,6 +26,7 @@ const removed_properties = [_][]const u8{ "term_restore_cursor_cmd", "x_cmd_setup", "wayland_cmd", + "console_dev", }; var temporary_allocator = std.heap.page_allocator; 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/interop.zig b/src/interop.zig index 9fc5017..8540c21 100644 --- a/src/interop.zig +++ b/src/interop.zig @@ -36,7 +36,7 @@ pub const stdlib = @cImport({ pub const pwd = @cImport({ @cInclude("pwd.h"); // We include a FreeBSD-specific header here since login_cap.h references - // the passwd struct directly, so we can't import it separately' + // the passwd struct directly, so we can't import it separately if (builtin.os.tag == .freebsd) @cInclude("login_cap.h"); }); @@ -75,23 +75,21 @@ pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 { return buf[0..len]; } -pub fn switchTty(console_dev: []const u8, tty: u8) !void { - const fd = try std.posix.open(console_dev, .{ .ACCMODE = .WRONLY }, 0); - defer std.posix.close(fd); +pub fn switchTty(tty: u8) !void { + var status = std.c.ioctl(std.c.STDIN_FILENO, vt.VT_ACTIVATE, tty); + if (status != 0) return error.FailedToActivateTty; - _ = std.c.ioctl(fd, vt.VT_ACTIVATE, tty); - _ = std.c.ioctl(fd, vt.VT_WAITACTIVE, tty); + status = std.c.ioctl(std.c.STDIN_FILENO, vt.VT_WAITACTIVE, tty); + if (status != 0) return error.FailedToWaitForActiveTty; } -pub fn getLockState(console_dev: []const u8) !struct { +pub fn getLockState() !struct { numlock: bool, capslock: bool, } { - const fd = try std.posix.open(console_dev, .{ .ACCMODE = .RDONLY }, 0); - defer std.posix.close(fd); - var led: LedState = undefined; - _ = std.c.ioctl(fd, get_led_state, &led); + const status = std.c.ioctl(std.c.STDIN_FILENO, get_led_state, &led); + if (status != 0) return error.FailedToGetLockState; return .{ .numlock = (led & numlock_led) != 0, @@ -101,11 +99,12 @@ pub fn getLockState(console_dev: []const u8) !struct { pub fn setNumlock(val: bool) !void { var led: LedState = undefined; - _ = std.c.ioctl(0, get_led_state, &led); + var status = std.c.ioctl(std.c.STDIN_FILENO, get_led_state, &led); + if (status != 0) return error.FailedToGetNumlock; const numlock = (led & numlock_led) != 0; if (numlock != val) { - const status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led); + status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led); if (status != 0) return error.FailedToSetNumlock; } } diff --git a/src/main.zig b/src/main.zig index 17309df..0cc2c6e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -18,12 +18,15 @@ const TerminalBuffer = @import("tui/TerminalBuffer.zig"); const Session = @import("tui/components/Session.zig"); const Text = @import("tui/components/Text.zig"); const InfoLine = @import("tui/components/InfoLine.zig"); +const UserList = @import("tui/components/UserList.zig"); const Config = @import("config/Config.zig"); const Lang = @import("config/Lang.zig"); const Save = @import("config/Save.zig"); const migrator = @import("config/migrator.zig"); const SharedError = @import("SharedError.zig"); +const UidRange = @import("UidRange.zig"); +const StringList = std.ArrayListUnmanaged([]const u8); const Ini = ini.Ini; const DisplayServer = enums.DisplayServer; const Entry = Environment.Entry; @@ -102,6 +105,7 @@ pub fn main() !void { var lang: Lang = undefined; var save: Save = undefined; var config_load_failed = false; + var can_get_lock_state = true; if (res.args.help != 0) { try clap.help(stderr, clap.Help, ¶ms, .{}); @@ -289,10 +293,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| { @@ -300,7 +306,26 @@ pub fn main() !void { } } - var login = Text.init(allocator, &buffer, false, null); + var custom_session_dirs = std.mem.splitScalar(u8, config.custom_sessions, ':'); + while (custom_session_dirs.next()) |dir| { + try crawl(&session, lang, dir, .custom); + } + + var usernames = try getAllUsernames(allocator, config.login_defs_path); + defer { + for (usernames.items) |username| allocator.free(username); + usernames.deinit(allocator); + } + + if (usernames.items.len == 0) { + // If we have no usernames, simply add an error to the info line. + // This effectively means you can't login, since there would be no local + // accounts *and* no root account...but at this point, if that's the + // case, you have bigger problems to deal with in the first place. :D + try info_line.addMessage(lang.err_no_users, config.error_bg, config.error_fg); + } + + var login = try UserList.init(allocator, &buffer, usernames); defer login.deinit(); var password = Text.init(allocator, &buffer, true, config.asterisk); @@ -312,9 +337,17 @@ pub fn main() !void { // Load last saved username and desktop selection, if any if (config.load) { if (save.user) |user| { - try login.text.appendSlice(login.allocator, user); - login.end = user.len; - login.cursor = login.end; + // Find user with saved name, and switch over to it + // If it doesn't exist (anymore), we don't change the value + // Note that we could instead save the username index, but migrating + // from the raw username to an index is non-trivial and I'm lazy :P + for (usernames.items, 0..) |username, i| { + if (std.mem.eql(u8, username, user)) { + login.label.current = i; + break; + } + } + active_input = .password; } @@ -330,15 +363,13 @@ pub fn main() !void { const coordinates = buffer.calculateComponentCoordinates(); info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null); session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center); - login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); + login.label.position(coordinates.x, coordinates.y + 4, coordinates.visible_length, config.text_in_center); password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); switch (active_input) { .info_line => info_line.label.handle(null, insert_mode), .session => session.label.handle(null, insert_mode), - .login => login.handle(null, insert_mode) catch { - try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); - }, + .login => login.label.handle(null, insert_mode), .password => password.handle(null, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }, @@ -354,7 +385,7 @@ pub fn main() !void { animation = dummy.animation(); }, .doom => { - var doom = try Doom.init(allocator, &buffer, config.doom_top_color, config.doom_middle_color, config.doom_bottom_color); + var doom = try Doom.init(allocator, &buffer, config.doom_top_color, config.doom_middle_color, config.doom_bottom_color, config.doom_fire_height, config.doom_fire_spread); animation = doom.animation(); }, .matrix => { @@ -389,12 +420,10 @@ pub fn main() !void { var update = true; var resolution_changed = false; var auth_fails: u64 = 0; - var can_access_console_dev = true; - // Switch to selected TTY if possible - interop.switchTty(config.console_dev, config.tty) catch { - try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg); - can_access_console_dev = false; + // Switch to selected TTY + interop.switchTty(config.tty) catch { + try info_line.addMessage(lang.err_switch_tty, config.error_bg, config.error_fg); }; while (run) { @@ -458,7 +487,7 @@ pub fn main() !void { const coordinates = buffer.calculateComponentCoordinates(); info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null); session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center); - login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length); + login.label.position(coordinates.x, coordinates.y + 4, coordinates.visible_length, config.text_in_center); password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length); resolution_changed = false; @@ -467,9 +496,7 @@ pub fn main() !void { switch (active_input) { .info_line => info_line.label.handle(null, insert_mode), .session => session.label.handle(null, insert_mode), - .login => login.handle(null, insert_mode) catch { - try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); - }, + .login => login.label.handle(null, insert_mode), .password => password.handle(null, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }, @@ -546,9 +573,10 @@ pub fn main() !void { buffer.drawLabel(label_txt, buffer.box_x, buffer.box_y + buffer.box_height); } - if (can_access_console_dev) draw_lock_state: { - const lock_state = interop.getLockState(config.console_dev) catch { - try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg); + if (can_get_lock_state) draw_lock_state: { + const lock_state = interop.getLockState() catch { + try info_line.addMessage(lang.err_lock_state, config.error_bg, config.error_fg); + can_get_lock_state = false; break :draw_lock_state; }; @@ -564,7 +592,7 @@ pub fn main() !void { } session.label.draw(); - login.draw(); + login.label.draw(); password.draw(); } else { std.Thread.sleep(std.time.ns_per_ms * 10); @@ -653,10 +681,7 @@ pub fn main() !void { }, termbox.TB_KEY_CTRL_C => run = false, termbox.TB_KEY_CTRL_U => { - if (active_input == .login) { - login.clear(); - update = true; - } else if (active_input == .password) { + if (active_input == .password) { password.clear(); update = true; } @@ -719,7 +744,7 @@ pub fn main() !void { defer file.close(); const save_data = Save{ - .user = login.text.items, + .user = login.getCurrentUser(), .session_index = session.label.current, }; ini.writeFromStruct(save_data, file.writer(), null, .{}) catch break :save_last_settings; @@ -732,7 +757,7 @@ pub fn main() !void { defer shared_err.deinit(); { - const login_text = try allocator.dupeZ(u8, login.text.items); + const login_text = try allocator.dupeZ(u8, login.getCurrentUser()); defer allocator.free(login_text); const password_text = try allocator.dupeZ(u8, password.text.items); defer allocator.free(password_text); @@ -838,9 +863,7 @@ pub fn main() !void { switch (active_input) { .info_line => info_line.label.handle(&event, insert_mode), .session => session.label.handle(&event, insert_mode), - .login => login.handle(&event, insert_mode) catch { - try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); - }, + .login => login.label.handle(&event, insert_mode), .password => password.handle(&event, insert_mode) catch { try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg); }, @@ -864,11 +887,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,44 +909,111 @@ 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, }); } } +fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8) !StringList { + const uid_range = try getUserIdRange(allocator, login_defs_path); + + var usernames: StringList = .empty; + var maybe_entry = interop.pwd.getpwent(); + + while (maybe_entry != null) { + const entry = maybe_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.pw_uid >= uid_range.uid_min and entry.pw_uid <= uid_range.uid_max or entry.pw_uid == 0) { + const pw_name_slice = entry.pw_name[0..std.mem.len(entry.pw_name)]; + const username = try allocator.dupe(u8, pw_name_slice); + + try usernames.append(allocator, username); + } + + maybe_entry = interop.pwd.getpwent(); + } + + interop.pwd.endpwent(); + return usernames; +} + +// This is very bad parsing, but we only need to get 2 values... and the format +// of the file doesn't seem to be standard? So this should be fine... +fn getUserIdRange(allocator: std.mem.Allocator, login_defs_path: []const u8) !UidRange { + const login_defs_file = try std.fs.cwd().openFile(login_defs_path, .{}); + defer login_defs_file.close(); + + const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16)); + defer allocator.free(login_defs_buffer); + + var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n'); + var uid_range = UidRange{}; + + while (iterator.next()) |line| { + const trimmed_line = std.mem.trim(u8, line, " \n\r\t"); + + if (std.mem.startsWith(u8, trimmed_line, "UID_MIN")) { + uid_range.uid_min = try parseValue(std.c.uid_t, "UID_MIN", trimmed_line); + } else if (std.mem.startsWith(u8, trimmed_line, "UID_MAX")) { + uid_range.uid_max = try parseValue(std.c.uid_t, "UID_MAX", trimmed_line); + } + } + + return uid_range; +} + +fn parseValue(comptime T: type, name: []const u8, buffer: []const u8) !T { + var iterator = std.mem.splitAny(u8, buffer, " \t"); + var maybe_value: ?T = null; + + while (iterator.next()) |slice| { + // Skip the slice if it's empty (whitespace) or is the name of the + // property (e.g. UID_MIN or UID_MAX) + if (slice.len == 0 or std.mem.eql(u8, slice, name)) continue; + maybe_value = std.fmt.parseInt(T, slice, 10) catch continue; + } + + return maybe_value orelse error.ValueNotFound; +} + fn adjustBrightness(allocator: std.mem.Allocator, cmd: []const u8) !void { var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", cmd }, allocator); brightness.stdout_behavior = .Ignore; diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index ee72f4f..86240b9 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -208,9 +208,9 @@ pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u32, bg: u32) vo const utf8view = std.unicode.Utf8View.init(text) catch return; var utf8 = utf8view.iterator(); - var i = x; - while (utf8.nextCodepoint()) |codepoint| : (i += 1) { - _ = termbox.tb_set_cell(@intCast(i), yc, codepoint, fg, bg); + var i: c_int = @intCast(x); + while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) { + _ = termbox.tb_set_cell(i, yc, codepoint, fg, bg); } } @@ -219,10 +219,10 @@ pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: usize, y: us const utf8view = std.unicode.Utf8View.init(text) catch return; var utf8 = utf8view.iterator(); - var i: usize = 0; - while (utf8.nextCodepoint()) |codepoint| : (i += 1) { - if (i >= max_length) break; - _ = termbox.tb_set_cell(@intCast(i + x), yc, codepoint, self.fg, self.bg); + var i: c_int = @intCast(x); + while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) { + if (i - @as(c_int, @intCast(x)) >= max_length) break; + _ = termbox.tb_set_cell(i, yc, codepoint, self.fg, self.bg); } } @@ -236,7 +236,8 @@ pub fn drawCharMultiple(self: TerminalBuffer, char: u32, x: usize, y: usize, len pub fn strWidth(str: []const u8) !u8 { const utf8view = try std.unicode.Utf8View.init(str); var utf8 = utf8view.iterator(); - var i: u8 = 0; - while (utf8.nextCodepoint()) |_| i += 1; - return i; + var i: c_int = 0; + while (utf8.nextCodepoint()) |codepoint| i += termbox.tb_wcwidth(codepoint); + + return @intCast(i); } diff --git a/src/tui/components/UserList.zig b/src/tui/components/UserList.zig new file mode 100644 index 0000000..41eff39 --- /dev/null +++ b/src/tui/components/UserList.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const TerminalBuffer = @import("../TerminalBuffer.zig"); +const generic = @import("generic.zig"); + +const StringList = std.ArrayListUnmanaged([]const u8); +const Allocator = std.mem.Allocator; + +const UsernameText = generic.CyclableLabel([]const u8); + +const UserList = @This(); + +label: UsernameText, + +pub fn init(allocator: Allocator, buffer: *TerminalBuffer, usernames: StringList) !UserList { + var userList = UserList{ + .label = UsernameText.init(allocator, buffer, drawItem), + }; + + for (usernames.items) |username| { + if (username.len == 0) continue; + + try userList.label.addItem(username); + } + + return userList; +} + +pub fn deinit(self: *UserList) void { + self.label.deinit(); +} + +pub fn getCurrentUser(self: UserList) []const u8 { + return self.label.list.items[self.label.current]; +} + +fn drawItem(label: *UsernameText, username: []const u8, _: usize, _: usize) bool { + const length = @min(username.len, label.visible_length - 3); + if (length == 0) return false; + + const x = if (label.text_in_center) (label.x + (label.visible_length - username.len) / 2) else (label.x + 2); + label.first_char_x = x + username.len; + + label.buffer.drawLabel(username, x, label.y); + return true; +}