Compare commits

...

2 Commits

Author SHA1 Message Date
nevaforget 4fa0dd0ead fix: GECOS subfield trimming and trailing backslash handling (v0.3.1)
display_name() now returns only the first GECOS subfield (before comma)
instead of the full GECOS string with room numbers and phone extensions.

split_shell_words() returns None for trailing backslashes instead of
silently ignoring them.
2026-03-28 00:07:29 +01:00
nevaforget 658328b39b feat: MOONGREET_NO_LAYER_SHELL env var for windowed development mode 2026-03-27 23:36:57 +01:00
4 changed files with 44 additions and 19 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "moongreet" name = "moongreet"
version = "0.3.0" version = "0.3.1"
edition = "2024" edition = "2024"
description = "A greetd greeter for Wayland with GTK4 and Layer Shell" description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
license = "MIT" license = "MIT"
+12 -4
View File
@@ -47,14 +47,16 @@ fn split_shell_words(s: &str) -> Option<Vec<String>> {
} }
'\\' if in_double => { '\\' if in_double => {
// In double quotes, backslash escapes the next char // In double quotes, backslash escapes the next char
if let Some(next) = chars.next() { match chars.next() {
current.push(next); Some(next) => current.push(next),
None => return None, // Trailing backslash
} }
} }
'\\' if !in_single && !in_double => { '\\' if !in_single && !in_double => {
// Outside quotes, backslash escapes the next char // Outside quotes, backslash escapes the next char
if let Some(next) = chars.next() { match chars.next() {
current.push(next); Some(next) => current.push(next),
None => return None, // Trailing backslash
} }
} }
c if c.is_whitespace() && !in_single && !in_double => { c if c.is_whitespace() && !in_single && !in_double => {
@@ -1189,6 +1191,12 @@ mod tests {
); );
} }
#[test]
fn shell_words_trailing_backslash() {
assert_eq!(split_shell_words(r"foo\"), None);
assert_eq!(split_shell_words(r#""foo\"#), None);
}
// -- login_worker tests -- // -- login_worker tests --
// These use a real Unix socket pair via UnixListener to simulate greetd. // These use a real Unix socket pair via UnixListener to simulate greetd.
+18 -12
View File
@@ -54,22 +54,28 @@ fn activate(app: &gtk::Application) {
let config = config::load_config(None); let config = config::load_config(None);
let bg_path = config::resolve_background_path(&config); let bg_path = config::resolve_background_path(&config);
let use_layer_shell = std::env::var("MOONGREET_NO_LAYER_SHELL").is_err();
// Main greeter window (login UI) — compositor picks focused monitor // Main greeter window (login UI) — compositor picks focused monitor
let greeter_window = greeter::create_greeter_window(&bg_path, &config, app); let greeter_window = greeter::create_greeter_window(&bg_path, &config, app);
setup_layer_shell(&greeter_window, true); if use_layer_shell {
setup_layer_shell(&greeter_window, true);
}
greeter_window.present(); greeter_window.present();
// Wallpaper-only windows on all monitors // Wallpaper-only windows on all monitors (only with layer shell)
let monitors = display.monitors(); if use_layer_shell {
for i in 0..monitors.n_items() { let monitors = display.monitors();
if let Some(monitor) = monitors for i in 0..monitors.n_items() {
.item(i) if let Some(monitor) = monitors
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok()) .item(i)
{ .and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
let wallpaper = greeter::create_wallpaper_window(&bg_path, app); {
setup_layer_shell(&wallpaper, false); let wallpaper = greeter::create_wallpaper_window(&bg_path, app);
wallpaper.set_monitor(Some(&monitor)); setup_layer_shell(&wallpaper, false);
wallpaper.present(); wallpaper.set_monitor(Some(&monitor));
wallpaper.present();
}
} }
} }
} }
+13 -2
View File
@@ -30,12 +30,12 @@ pub struct User {
} }
impl User { impl User {
/// Return the display name (GECOS if available, otherwise username). /// Return the display name (first GECOS subfield if available, otherwise username).
pub fn display_name(&self) -> &str { pub fn display_name(&self) -> &str {
if self.gecos.is_empty() { if self.gecos.is_empty() {
&self.username &self.username
} else { } else {
&self.gecos self.gecos.split(',').next().unwrap_or(&self.username)
} }
} }
} }
@@ -150,6 +150,17 @@ mod tests {
assert_eq!(users[0].home, PathBuf::from("/home/testuser")); assert_eq!(users[0].home, PathBuf::from("/home/testuser"));
} }
#[test]
fn gecos_subfield_trimmed() {
let dir = tempfile::tempdir().unwrap();
let path = make_passwd(
dir.path(),
"testuser:x:1000:1000:Test User,Room 123,555-1234:/home/testuser:/bin/bash\n",
);
let users = get_users(Some(&path));
assert_eq!(users[0].display_name(), "Test User");
}
#[test] #[test]
fn skip_system_users() { fn skip_system_users() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();