refactor: single greeter window, drop per-output keyboard grab (v0.10.0)
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The v0.8.0 → v0.8.5 multi-monitor story (login widget on primary only, wallpaper-only on secondaries, KeyboardMode::Exclusive on the primary surface, hotplug handler) was a pile of workarounds that kept breaking on real hardware — after switching the greeter compositor to niri, the pointer could not cross onto the primary output and keyboard tab did not reach the UI. Niri in a normal user session has none of these issues; the bug was moongreet's own output-scoped policy. Back to basics: one layer-shell window on the built-in display, KeyboardMode::OnDemand. Secondary outputs stay under compositor control. No hotplug callbacks, no wallpaper-only windows, no DisplayLink phantom workarounds. -81 / +26 lines.
This commit is contained in:
parent
29ce185886
commit
ce9f2196ca
@ -44,8 +44,8 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
|
||||
- `i18n.rs` — Locale-Erkennung (LANG / /etc/locale.conf) und String-Tabellen (DE/EN), alle UI- und Login-Fehlermeldungen
|
||||
- `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, cursor-theme, cursor-size, fingerprint-enabled) + Wallpaper-Fallback + Blur-Validierung (finite, clamp 0–200) + Cursor-Size-Validierung (range 1–256)
|
||||
- `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). Zweite Funktion `create_wallpaper_window()` für Sekundär-Monitore (Background only, keine Widgets)
|
||||
- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor mit Hotplug via `items-changed` auf Monitor-ListModel. Built-in display (via `pick_primary_monitor_index`) zeigt das Login-Widget mit `KeyboardMode::Exclusive`, alle anderen Monitore (inkl. Hotplug) zeigen nur Wallpaper mit `KeyboardMode::None`. Systemd-journal-logger.
|
||||
- `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. Ein einziges Greeter-Fenster, verankert am Built-in-Display (via `pick_primary_monitor_index`), `KeyboardMode::OnDemand` — moongreet ist ein normaler layer-shell-client, keine output-scoped policies. Sekundäre Monitore bleiben unter Compositor-Kontrolle. Systemd-journal-logger.
|
||||
- `resources/style.css` — Catppuccin-inspiriertes Theme
|
||||
|
||||
## Design Decisions
|
||||
@ -67,4 +67,4 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
|
||||
- **Shared Wallpaper Texture**: `gdk::Texture` wird einmal in `load_background_texture()` dekodiert und per Ref-Count an alle Fenster geteilt — vermeidet redundante JPEG-Dekodierung pro Monitor
|
||||
- **Wallpaper-Validierung**: GResource-Zweig via `resources_lookup_data()` + `from_bytes()` (kein Abort bei fehlendem Pfad), Dateigröße-Limit 50 MB, non-UTF-8-Pfade → `None`
|
||||
- **Error-Detail-Filterung**: GDK/greetd-Fehlerdetails nur auf `debug!`-Level, `warn!` ohne interne Details — verhindert Systeminfo-Leak ins Journal
|
||||
- **Single Greeter UI, Wallpaper auf Sekundären**: Login-Widget nur auf dem Built-in-Panel, andere Monitore bekommen `create_wallpaper_window()` mit `KeyboardMode::None`. Grund: Niri (und andere Compositor) scopen Layer-Shell Keyboard-Routing per Output — ein einziges Exclusive-Surface auf dem "falschen" Output verhindert nicht, dass Keys woanders versanden. Compositor-agnostisch, kein Niri-IPC.
|
||||
- **Single Greeter Window, keine Output-Policies**: Ein einziges layer-shell-fenster auf dem Built-in-Display, `KeyboardMode::OnDemand`. Sekundäre Outputs bleiben unter Compositor-Kontrolle. Grund: Die output-scoped policies aus v0.8.0–v0.8.5 (Exclusive-Keyboard auf Primary, Wallpaper-Only auf Secondaries, Hotplug-Callbacks) haben den Greeter bei realen Multi-Monitor-Setups wiederholt kaputt gemacht (Pointer kommt nicht zum Primary, Keyboard tabt nicht zur UI). Im User-Session-Niri gibt es diese Probleme nicht — moongreet verhält sich jetzt wie jeder normale layer-shell-client.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "moongreet"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
edition = "2024"
|
||||
description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
|
||||
license = "MIT"
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
# Decisions
|
||||
|
||||
## 2026-04-24 – Single greeter window, no per-output keyboard grab (v0.10.0)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
- **Why**: The v0.8.0 → v0.8.4 → v0.8.5 sequence accumulated multi-monitor logic (login widget on primary, wallpaper-only on secondaries, `KeyboardMode::Exclusive` on the primary surface) to work around keyboard-routing on specific hardware setups. After the moonarch greeter switched to niri, the symptoms returned: the UI was on eDP-1 but the pointer could not cross onto it, and keyboard tab did not reach the login widget. Niri in a normal user session never behaves like this — the issue was our greeter's self-imposed per-output scope, not the compositor. Every earlier "fix" made it more bespoke instead of making moongreet a well-behaved layer-shell client.
|
||||
- **Tradeoffs**: Reverts the multi-output story entirely. Secondary monitors get nothing from moongreet — the compositor decides what renders there (black, its own wallpaper, whatever). The "wallpaper on every screen" look is gone. In exchange, cursor and keyboard follow normal niri focus rules, nothing is grabbed, no hotplug callbacks, no DisplayLink phantom workarounds.
|
||||
- **How**: `main.rs::activate` builds **one** greeter window, anchors it to the built-in display picked by `pick_primary_monitor_index`, and calls `setup_layer_shell` with `KeyboardMode::OnDemand`. The hotplug `connect_items_changed` handler is gone. `create_wallpaper_window` is removed. `setup_layer_shell` no longer takes a `keyboard: bool` — there is only one policy.
|
||||
|
||||
## 2026-04-24 – Cursor theme via config instead of env (v0.9.0)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
|
||||
@ -219,31 +219,6 @@ fn create_background_picture(
|
||||
background
|
||||
}
|
||||
|
||||
/// Create a wallpaper-only window for secondary monitors.
|
||||
///
|
||||
/// Shows the same background (and shared blurred texture via `blur_cache`) as
|
||||
/// the primary greeter window, but carries no login widgets. Paired with
|
||||
/// `KeyboardMode::None` at the layer-shell level so the compositor never
|
||||
/// routes input here — preventing focus from landing on a secondary output.
|
||||
pub fn create_wallpaper_window(
|
||||
texture: Option<&gdk::Texture>,
|
||||
config: &Config,
|
||||
blur_cache: &Rc<RefCell<Option<gdk::Texture>>>,
|
||||
app: >k::Application,
|
||||
) -> gtk::ApplicationWindow {
|
||||
let window = gtk::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.build();
|
||||
window.add_css_class("greeter");
|
||||
window.set_default_size(1920, 1080);
|
||||
|
||||
if let Some(texture) = texture {
|
||||
window.set_child(Some(&create_background_picture(texture, config.background_blur, blur_cache)));
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
/// Shared mutable state for the greeter UI.
|
||||
struct GreeterState {
|
||||
selected_user: Option<User>,
|
||||
|
||||
59
src/main.rs
59
src/main.rs
@ -11,11 +11,9 @@ mod sessions;
|
||||
mod users;
|
||||
|
||||
use gdk4 as gdk;
|
||||
use glib::clone;
|
||||
use gtk4::prelude::*;
|
||||
use gtk4::{self as gtk, gio};
|
||||
use gtk4_layer_shell::LayerShell;
|
||||
use std::rc::Rc;
|
||||
fn load_css(display: &gdk::Display) {
|
||||
let css_provider = gtk::CssProvider::new();
|
||||
css_provider.load_from_resource("/dev/moonarch/moongreet/style.css");
|
||||
@ -26,14 +24,11 @@ fn load_css(display: &gdk::Display) {
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_layer_shell(window: >k::ApplicationWindow, keyboard: bool, layer: gtk4_layer_shell::Layer) {
|
||||
fn setup_layer_shell(window: >k::ApplicationWindow, layer: gtk4_layer_shell::Layer) {
|
||||
window.init_layer_shell();
|
||||
window.set_layer(layer);
|
||||
window.set_exclusive_zone(-1);
|
||||
if keyboard {
|
||||
window.set_keyboard_mode(gtk4_layer_shell::KeyboardMode::Exclusive);
|
||||
}
|
||||
// Anchor to all edges for fullscreen
|
||||
window.set_keyboard_mode(gtk4_layer_shell::KeyboardMode::OnDemand);
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Top, true);
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Bottom, true);
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Left, true);
|
||||
@ -86,8 +81,9 @@ fn activate(app: >k::Application) {
|
||||
log::debug!("Layer shell: {use_layer_shell}");
|
||||
|
||||
if use_layer_shell {
|
||||
// One greeter window per monitor — keyboard focus goes to the
|
||||
// built-in display so DisplayLink/evdi phantoms don't steal it.
|
||||
// Single greeter window anchored to the built-in display. Other
|
||||
// outputs stay under compositor control — the greeter is just a
|
||||
// normal layer-shell client, no per-output keyboard grabs.
|
||||
let monitors = display.monitors();
|
||||
let count = monitors.n_items();
|
||||
log::debug!("Monitor count: {count}");
|
||||
@ -108,51 +104,18 @@ fn activate(app: >k::Application) {
|
||||
connectors.get(primary_idx).and_then(|o| o.as_deref())
|
||||
);
|
||||
|
||||
for i in 0..count {
|
||||
if let Some(monitor) = monitors
|
||||
.item(i)
|
||||
.item(primary_idx as u32)
|
||||
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
|
||||
{
|
||||
let is_primary = i as usize == primary_idx;
|
||||
let window = if is_primary {
|
||||
greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app)
|
||||
} else {
|
||||
greeter::create_wallpaper_window(bg_texture.as_ref(), &config, &blur_cache, app)
|
||||
};
|
||||
setup_layer_shell(&window, is_primary, gtk4_layer_shell::Layer::Top);
|
||||
let window = greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app);
|
||||
setup_layer_shell(&window, gtk4_layer_shell::Layer::Top);
|
||||
window.set_monitor(Some(&monitor));
|
||||
window.present();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle monitor hotplug — hotplugged monitors get wallpaper-only
|
||||
// windows so keyboard focus never migrates off the primary.
|
||||
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 wallpaper window");
|
||||
let window = greeter::create_wallpaper_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 {
|
||||
// No layer shell — single window for development
|
||||
log::error!("Primary monitor {primary_idx} not available — greeter will not be shown");
|
||||
}
|
||||
} else {
|
||||
let greeter_window = greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app);
|
||||
greeter_window.present();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user