All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Third triple audit (quality, performance, security). Key fixes: - Blur padding offset: texture at (-pad,-pad) prevents edge darkening on all sides - Wallpaper loads after lock.lock() — disk I/O no longer delays lock acquisition - begin_verification disconnects old signal handler before registering new one - resume_async resets failed_attempts to prevent premature exhaustion - Unknown VerifyStatus with done=true triggers restart instead of hanging - symlink_metadata() replaces separate is_file()+is_symlink() (TOCTOU) - faillock_warning dead code removed, blur sigma clamped to [0,100] - Redundant Zeroizing<Vec<u8>> removed, on_verify_status restricted to pub(crate) - Warn logging for non-UTF-8 GECOS and avatar path errors - Default impl for FingerprintListener, 3 new tests (47 total)
104 lines
3.7 KiB
Rust
104 lines
3.7 KiB
Rust
// ABOUTME: Current user detection and avatar loading for the lockscreen.
|
|
// ABOUTME: Retrieves user info from the system (nix getuid, AccountsService, ~/.face).
|
|
|
|
use nix::unistd::{getuid, User as NixUser};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
const DEFAULT_ACCOUNTSSERVICE_DIR: &str = "/var/lib/AccountsService/icons";
|
|
const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonlock";
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct User {
|
|
pub username: String,
|
|
pub display_name: String,
|
|
pub home: PathBuf,
|
|
pub uid: u32,
|
|
}
|
|
|
|
pub fn get_current_user() -> Option<User> {
|
|
let uid = getuid();
|
|
let nix_user = NixUser::from_uid(uid).ok()??;
|
|
let gecos = match nix_user.gecos.to_str() {
|
|
Ok(s) => s.to_string(),
|
|
Err(_) => {
|
|
log::warn!("GECOS field is not valid UTF-8, falling back to username");
|
|
String::new()
|
|
}
|
|
};
|
|
let display_name = if !gecos.is_empty() {
|
|
let first = gecos.split(',').next().unwrap_or("");
|
|
if first.is_empty() { nix_user.name.clone() } else { first.to_string() }
|
|
} else { nix_user.name.clone() };
|
|
Some(User { username: nix_user.name, display_name, home: nix_user.dir, uid: uid.as_raw() })
|
|
}
|
|
|
|
pub fn get_avatar_path(home: &Path, username: &str) -> Option<PathBuf> {
|
|
get_avatar_path_with(home, username, Path::new(DEFAULT_ACCOUNTSSERVICE_DIR))
|
|
}
|
|
|
|
pub fn get_avatar_path_with(home: &Path, username: &str, accountsservice_dir: &Path) -> Option<PathBuf> {
|
|
// ~/.face takes priority — single stat via symlink_metadata to avoid TOCTOU
|
|
let face = home.join(".face");
|
|
if let Ok(meta) = face.symlink_metadata() {
|
|
if meta.is_file() && !meta.file_type().is_symlink() { return Some(face); }
|
|
}
|
|
// AccountsService icon
|
|
if accountsservice_dir.exists() {
|
|
let icon = accountsservice_dir.join(username);
|
|
if let Ok(meta) = icon.symlink_metadata() {
|
|
if meta.is_file() && !meta.file_type().is_symlink() { return Some(icon); }
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn get_default_avatar_path() -> String {
|
|
format!("{GRESOURCE_PREFIX}/default-avatar.svg")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fs;
|
|
|
|
#[test] fn current_user_exists() {
|
|
let u = get_current_user();
|
|
assert!(u.is_some());
|
|
let u = u.unwrap();
|
|
assert!(!u.username.is_empty());
|
|
}
|
|
|
|
#[test] fn face_file_priority() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let face = dir.path().join(".face"); fs::write(&face, "img").unwrap();
|
|
let icons = dir.path().join("icons"); fs::create_dir(&icons).unwrap();
|
|
let icon = icons.join("test"); fs::write(&icon, "img").unwrap();
|
|
assert_eq!(get_avatar_path_with(dir.path(), "test", &icons), Some(face));
|
|
}
|
|
|
|
#[test] fn accountsservice_fallback() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let icons = dir.path().join("icons"); fs::create_dir(&icons).unwrap();
|
|
let icon = icons.join("test"); fs::write(&icon, "img").unwrap();
|
|
assert_eq!(get_avatar_path_with(dir.path(), "test", &icons), Some(icon));
|
|
}
|
|
|
|
#[test] fn no_avatar() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
assert!(get_avatar_path_with(dir.path(), "test", Path::new("/nonexistent")).is_none());
|
|
}
|
|
|
|
#[test] fn rejects_symlink() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let real = dir.path().join("real"); fs::write(&real, "x").unwrap();
|
|
std::os::unix::fs::symlink(&real, dir.path().join(".face")).unwrap();
|
|
assert!(get_avatar_path_with(dir.path(), "test", Path::new("/nonexistent")).is_none());
|
|
}
|
|
|
|
#[test] fn default_avatar_gresource() {
|
|
let p = get_default_avatar_path();
|
|
assert!(p.contains("moonlock"));
|
|
assert!(p.contains("default-avatar.svg"));
|
|
}
|
|
}
|