Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35f1a17cdf | |||
| 48d363bb18 | |||
| 448e4212e3 | |||
| cd42df1095 |
@@ -1,7 +1,5 @@
|
|||||||
# Moongreet
|
# Moongreet
|
||||||
|
|
||||||
**Name**: Selene (Mondgöttin — passend zu Moon-greet)
|
|
||||||
|
|
||||||
## Projekt
|
## Projekt
|
||||||
|
|
||||||
Moongreet ist ein greetd-Greeter für Wayland, gebaut mit Rust + gtk4-rs + gtk4-layer-shell.
|
Moongreet ist ein greetd-Greeter für Wayland, gebaut mit Rust + gtk4-rs + gtk4-layer-shell.
|
||||||
@@ -47,7 +45,7 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
|
|||||||
- `fingerprint.rs` — fprintd D-Bus Probe (gio::DBusProxy) — Geräteerkennung und Enrollment-Check für UI-Feedback
|
- `fingerprint.rs` — fprintd D-Bus Probe (gio::DBusProxy) — Geräteerkennung und Enrollment-Check für UI-Feedback
|
||||||
- `config.rs` — TOML-Config ([appearance] background, gtk-theme, fingerprint-enabled) + Wallpaper-Fallback + Blur-Validierung (finite, clamp 0–200)
|
- `config.rs` — TOML-Config ([appearance] background, gtk-theme, fingerprint-enabled) + Wallpaper-Fallback + Blur-Validierung (finite, clamp 0–200)
|
||||||
- `greeter.rs` — GTK4 UI (Overlay-Layout), Login-Flow via greetd IPC (Multi-Stage-Auth für fprintd), Faillock-Warnung, Avatar-Cache, Last-User/Last-Session Persistence (0o700 Dirs, 0o600 Files)
|
- `greeter.rs` — GTK4 UI (Overlay-Layout), Login-Flow via greetd IPC (Multi-Stage-Auth für fprintd), Faillock-Warnung, Avatar-Cache, Last-User/Last-Session Persistence (0o700 Dirs, 0o600 Files)
|
||||||
- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor (one greeter window per monitor, first gets keyboard), systemd-journal-logger
|
- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor mit Hotplug via `items-changed` auf Monitor-ListModel (one greeter window per monitor, first gets keyboard), systemd-journal-logger
|
||||||
- `resources/style.css` — Catppuccin-inspiriertes Theme
|
- `resources/style.css` — Catppuccin-inspiriertes Theme
|
||||||
|
|
||||||
## Design Decisions
|
## Design Decisions
|
||||||
|
|||||||
Generated
+1
-1
@@ -575,7 +575,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moongreet"
|
name = "moongreet"
|
||||||
version = "0.7.4"
|
version = "0.8.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gdk-pixbuf",
|
"gdk-pixbuf",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "moongreet"
|
name = "moongreet"
|
||||||
version = "0.8.0"
|
version = "0.8.4"
|
||||||
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"
|
||||||
|
|||||||
+24
-3
@@ -1,5 +1,26 @@
|
|||||||
# Decisions
|
# Decisions
|
||||||
|
|
||||||
|
## 2026-04-24 – Audit fix: shrink password-in-memory window (v0.8.4)
|
||||||
|
|
||||||
|
- **Who**: ClaudeCode, Dom
|
||||||
|
- **Why**: Security audit flagged the GTK password path as holding more copies of the plaintext password in memory than necessary. `attempt_login` wrapped the already-`Zeroizing<String>` caller value into a second `Zeroizing<String>` (`password.to_string()`), and the GTK `GString` backing `entry.text()` persisted in libc malloc'd memory until the allocator reused the page.
|
||||||
|
- **Tradeoffs**: The GTK `GString` and the libc `strdup` copy on the PAM FFI boundary remain non-zeroizable — this is an inherent GTK/libc limitation, already documented in CLAUDE.md. This change reduces the Rust-owned copies to one and clears the `PasswordEntry` text field immediately after extraction to shorten the GTK-side window.
|
||||||
|
- **How**: (1) `attempt_login` now takes `password: Zeroizing<String>` by value instead of `&str`, moving ownership into the `spawn_blocking` closure. (2) The redundant `Zeroizing::new(password.to_string())` inside `attempt_login` is removed. (3) `password_entry.set_text("")` is called right after the password is extracted from the activate handler, shortening the lifetime of the GTK-internal buffer.
|
||||||
|
|
||||||
|
## 2026-04-21 – Ship polkit rule in moongreet instead of moonarch (v0.8.3)
|
||||||
|
|
||||||
|
- **Who**: ClaudeCode, Dom
|
||||||
|
- **Why**: Reboot/shutdown from the greeter silently failed on a fresh install. The polkit rule that grants the `greeter` user `org.freedesktop.login1.{reboot,power-off}` lived in the moonarch repo but was never installed by any PKGBUILD. The laptop worked only because the rule had been hand-deployed once.
|
||||||
|
- **Tradeoffs**: Rule ownership moves from moonarch (system defaults) to moongreet (greeter-specific auth). Cleaner boundary — moonarch no longer needs to know about the greeter's auth requirements — but it means moongreet is now responsible for a system polkit rule that ties it to a fixed username (`greeter`).
|
||||||
|
- **How**: Source file moved to `moongreet/config/polkit/50-moongreet-power.rules`, installed to `/etc/polkit-1/rules.d/` by `moongreet-git/PKGBUILD`. Old file removed from the moonarch repo.
|
||||||
|
|
||||||
|
## 2026-04-09 – Monitor hotplug via ListModel items-changed
|
||||||
|
|
||||||
|
- **Who**: ClaudeCode, Dom
|
||||||
|
- **Why**: Greeter windows were only created at startup. If a monitor was hotplugged (e.g. HDMI reconnect), it would show no greeter UI. Aligned with moonlock's hotplug fix (same day).
|
||||||
|
- **Tradeoffs**: Hotplugged monitors get greeter windows without keyboard input (keyboard stays on the primary monitor). Acceptable — user can still interact on the primary screen.
|
||||||
|
- **How**: Connect to `display.monitors().connect_items_changed()` and create new greeter windows for added monitors. Shared state (config, texture, blur_cache) moved to Rc for the closure.
|
||||||
|
|
||||||
## 2026-04-08 – Show greeter UI on all monitors instead of just one
|
## 2026-04-08 – Show greeter UI on all monitors instead of just one
|
||||||
|
|
||||||
- **Who**: ClaudeCode, Dom
|
- **Who**: ClaudeCode, Dom
|
||||||
@@ -44,7 +65,7 @@
|
|||||||
|
|
||||||
## 2026-03-28 – Remove embedded wallpaper from binary
|
## 2026-03-28 – Remove embedded wallpaper from binary
|
||||||
|
|
||||||
- **Who**: Selene, Dom
|
- **Who**: ClaudeCode, Dom
|
||||||
- **Why**: Wallpaper is installed by moonarch to /usr/share/moonarch/wallpaper.jpg. Embedding a 374K JPEG in the binary is redundant. GTK background color (Catppuccin Mocha base) is a clean fallback.
|
- **Why**: Wallpaper is installed by moonarch to /usr/share/moonarch/wallpaper.jpg. Embedding a 374K JPEG in the binary is redundant. GTK background color (Catppuccin Mocha base) is a clean fallback.
|
||||||
- **Tradeoffs**: Without moonarch installed AND without config, greeter shows plain dark background instead of wallpaper. Acceptable — that's the expected minimal state.
|
- **Tradeoffs**: Without moonarch installed AND without config, greeter shows plain dark background instead of wallpaper. Acceptable — that's the expected minimal state.
|
||||||
- **How**: Remove wallpaper.jpg from GResources, return None from resolve_background_path when no file found, skip wallpaper window creation and background picture when no path available.
|
- **How**: Remove wallpaper.jpg from GResources, return None from resolve_background_path when no file found, skip wallpaper window creation and background picture when no path available.
|
||||||
@@ -58,13 +79,13 @@
|
|||||||
|
|
||||||
## 2026-03-28 – Optional background blur via `image` crate (superseded)
|
## 2026-03-28 – Optional background blur via `image` crate (superseded)
|
||||||
|
|
||||||
- **Who**: Selene, Dom
|
- **Who**: ClaudeCode, Dom
|
||||||
- **Why**: Blurred wallpaper as greeter background is a common UX pattern for login screens
|
- **Why**: Blurred wallpaper as greeter background is a common UX pattern for login screens
|
||||||
- **Tradeoffs**: Adds `image` crate dependency (~15 transitive crates); CPU-side Gaussian blur at load time adds startup latency proportional to image size and sigma. Acceptable because blur runs once and the texture is shared across monitors.
|
- **Tradeoffs**: Adds `image` crate dependency (~15 transitive crates); CPU-side Gaussian blur at load time adds startup latency proportional to image size and sigma. Acceptable because blur runs once and the texture is shared across monitors.
|
||||||
- **How**: `load_background_texture(bg_path, blur_radius)` loads texture, optionally applies `imageops::blur()`, returns blurred `gdk::Texture`. Config option `background-blur: Option<f32>` in `[appearance]` TOML section.
|
- **How**: `load_background_texture(bg_path, blur_radius)` loads texture, optionally applies `imageops::blur()`, returns blurred `gdk::Texture`. Config option `background-blur: Option<f32>` in `[appearance]` TOML section.
|
||||||
|
|
||||||
## 2026-03-28 – Audit fixes for shared wallpaper texture (v0.4.1)
|
## 2026-03-28 – Audit fixes for shared wallpaper texture (v0.4.1)
|
||||||
- **Who**: Selene, Dominik
|
- **Who**: ClaudeCode, Dominik
|
||||||
- **Why**: Quality, performance, and security audits flagged issues in `load_background_texture()`, debug logging, and greetd error handling
|
- **Why**: Quality, performance, and security audits flagged issues in `load_background_texture()`, debug logging, and greetd error handling
|
||||||
- **Tradeoffs**: GResource path now requires UTF-8 (returns `None` for non-UTF-8 instead of aborting); 50 MB wallpaper limit is generous but prevents OOM; debug logging off by default trades observability for security
|
- **Tradeoffs**: GResource path now requires UTF-8 (returns `None` for non-UTF-8 instead of aborting); 50 MB wallpaper limit is generous but prevents OOM; debug logging off by default trades observability for security
|
||||||
- **How**: GResource branch via `resources_lookup_data()` + `from_bytes()` (no abort), file size limit, error details only at debug level, `MOONGREET_DEBUG` env var for log level, greetd retry path truncation matching `show_greetd_error()`
|
- **How**: GResource branch via `resources_lookup_data()` + `from_bytes()` (no abort), file size limit, error details only at debug level, `MOONGREET_DEBUG` env var for log level, greetd retry path truncation matching `show_greetd_error()`
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Part of the Moonarch ecosystem.
|
|||||||
- **Last user/session** — Remembered in `/var/cache/moongreet/`
|
- **Last user/session** — Remembered in `/var/cache/moongreet/`
|
||||||
- **Power actions** — Reboot / Shutdown via `loginctl`
|
- **Power actions** — Reboot / Shutdown via `loginctl`
|
||||||
- **Layer Shell** — Fullscreen via gtk4-layer-shell (TOP layer)
|
- **Layer Shell** — Fullscreen via gtk4-layer-shell (TOP layer)
|
||||||
- **Multi-monitor** — Full greeter UI on all monitors (keyboard input on first)
|
- **Multi-monitor + hotplug** — Full greeter UI on all monitors (keyboard input on first), hotplugged monitors get windows automatically
|
||||||
- **GPU blur** — Background blur via GskBlurNode (shared cache across monitors)
|
- **GPU blur** — Background blur via GskBlurNode (shared cache across monitors)
|
||||||
- **i18n** — German and English (auto-detected from system locale)
|
- **i18n** — German and English (auto-detected from system locale)
|
||||||
- **Faillock warning** — Warns after 2 failed attempts, locked message after 3
|
- **Faillock warning** — Warns after 2 failed attempts, locked message after 3
|
||||||
@@ -60,6 +60,14 @@ sudo cp config/moongreet.toml /etc/moongreet/moongreet.toml
|
|||||||
user = "greeter"
|
user = "greeter"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
4. Install the polkit rule so the greeter user can reboot / power off:
|
||||||
|
```bash
|
||||||
|
sudo install -Dm644 config/polkit/50-moongreet-power.rules \
|
||||||
|
/etc/polkit-1/rules.d/50-moongreet-power.rules
|
||||||
|
```
|
||||||
|
Without this rule, `loginctl reboot` / `loginctl poweroff` fail because
|
||||||
|
greetd's greeter session is inactive in logind.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// ABOUTME: Allow the greeter user to reboot and power off without authentication.
|
||||||
|
// ABOUTME: Required because greetd's greeter session is inactive in logind.
|
||||||
|
|
||||||
|
polkit.addRule(function(action, subject) {
|
||||||
|
if (subject.user === "greeter" &&
|
||||||
|
(action.id === "org.freedesktop.login1.reboot" ||
|
||||||
|
action.id === "org.freedesktop.login1.reboot-multiple-sessions" ||
|
||||||
|
action.id === "org.freedesktop.login1.power-off" ||
|
||||||
|
action.id === "org.freedesktop.login1.power-off-multiple-sessions")) {
|
||||||
|
return polkit.Result.YES;
|
||||||
|
}
|
||||||
|
});
|
||||||
+6
-3
@@ -493,6 +493,10 @@ pub fn create_greeter_window(
|
|||||||
let Some(user) = user else { return };
|
let Some(user) = user else { return };
|
||||||
|
|
||||||
let password = Zeroizing::new(entry.text().to_string());
|
let password = Zeroizing::new(entry.text().to_string());
|
||||||
|
// Clear the GTK entry's internal buffer as early as possible. GTK allocates
|
||||||
|
// the backing `GString` via libc malloc, which `zeroize` cannot reach — the
|
||||||
|
// best we can do is shorten the window during which it resides in memory.
|
||||||
|
entry.set_text("");
|
||||||
|
|
||||||
let session = get_selected_session(&session_dropdown, &sessions_rc);
|
let session = get_selected_session(&session_dropdown, &sessions_rc);
|
||||||
let Some(session) = session else {
|
let Some(session) = session else {
|
||||||
@@ -502,7 +506,7 @@ pub fn create_greeter_window(
|
|||||||
|
|
||||||
attempt_login(
|
attempt_login(
|
||||||
&user,
|
&user,
|
||||||
&password,
|
password,
|
||||||
&session,
|
&session,
|
||||||
strings,
|
strings,
|
||||||
&state,
|
&state,
|
||||||
@@ -953,7 +957,7 @@ fn set_login_sensitive(
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn attempt_login(
|
fn attempt_login(
|
||||||
user: &User,
|
user: &User,
|
||||||
password: &str,
|
password: Zeroizing<String>,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
strings: &'static Strings,
|
strings: &'static Strings,
|
||||||
state: &Rc<RefCell<GreeterState>>,
|
state: &Rc<RefCell<GreeterState>>,
|
||||||
@@ -992,7 +996,6 @@ fn attempt_login(
|
|||||||
set_login_sensitive(password_entry, session_dropdown, false);
|
set_login_sensitive(password_entry, session_dropdown, false);
|
||||||
|
|
||||||
let username = user.username.clone();
|
let username = user.username.clone();
|
||||||
let password = Zeroizing::new(password.to_string());
|
|
||||||
let exec_cmd = session.exec_cmd.clone();
|
let exec_cmd = session.exec_cmd.clone();
|
||||||
let session_name = session.name.clone();
|
let session_name = session.name.clone();
|
||||||
let greetd_sock = state.borrow().greetd_sock.clone();
|
let greetd_sock = state.borrow().greetd_sock.clone();
|
||||||
|
|||||||
+29
@@ -11,9 +11,11 @@ mod sessions;
|
|||||||
mod users;
|
mod users;
|
||||||
|
|
||||||
use gdk4 as gdk;
|
use gdk4 as gdk;
|
||||||
|
use glib::clone;
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::{self as gtk, gio};
|
use gtk4::{self as gtk, gio};
|
||||||
use gtk4_layer_shell::LayerShell;
|
use gtk4_layer_shell::LayerShell;
|
||||||
|
use std::rc::Rc;
|
||||||
fn load_css(display: &gdk::Display) {
|
fn load_css(display: &gdk::Display) {
|
||||||
let css_provider = gtk::CssProvider::new();
|
let css_provider = gtk::CssProvider::new();
|
||||||
css_provider.load_from_resource("/dev/moonarch/moongreet/style.css");
|
css_provider.load_from_resource("/dev/moonarch/moongreet/style.css");
|
||||||
@@ -80,6 +82,33 @@ fn activate(app: >k::Application) {
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle monitor hotplug — create greeter windows for newly added monitors
|
||||||
|
// (without keyboard, since the primary monitor already has it)
|
||||||
|
let bg_texture = Rc::new(bg_texture);
|
||||||
|
let config = Rc::new(config);
|
||||||
|
monitors.connect_items_changed(clone!(
|
||||||
|
#[weak]
|
||||||
|
app,
|
||||||
|
#[strong]
|
||||||
|
blur_cache,
|
||||||
|
move |list, position, _removed, added| {
|
||||||
|
for i in position..position + added {
|
||||||
|
if let Some(monitor) = list
|
||||||
|
.item(i)
|
||||||
|
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
|
||||||
|
{
|
||||||
|
log::debug!("Monitor hotplug: creating greeter window");
|
||||||
|
let window = greeter::create_greeter_window(
|
||||||
|
bg_texture.as_ref().as_ref(), &config, &blur_cache, &app,
|
||||||
|
);
|
||||||
|
setup_layer_shell(&window, false, gtk4_layer_shell::Layer::Top);
|
||||||
|
window.set_monitor(Some(&monitor));
|
||||||
|
window.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
// No layer shell — single window for development
|
// No layer shell — single window for development
|
||||||
let greeter_window = greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app);
|
let greeter_window = greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app);
|
||||||
|
|||||||
Reference in New Issue
Block a user