fix: keyboard focus on built-in display to avoid evdi phantom grab (v0.8.4)
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 3s
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 3s
DisplayLink/evdi virtual displays enumerate as DVI-I-* before eDP-1 and were stealing the KeyboardMode::Exclusive grab on the first enumerated monitor, leaving the visible greeter surfaces without keyboard input. Introduce pick_primary_monitor_index() that prefers eDP/LVDS/DSI connectors for the keyboard grab and falls back to index 0 when no built-in panel is present. Pure, unit-tested; hotplug path unchanged.
This commit is contained in:
parent
48d363bb18
commit
97165d94f8
@ -45,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
|
||||
- `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)
|
||||
- `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
|
||||
- `main.rs` — Entry Point, GTK App, Layer Shell Setup, Multi-Monitor mit Hotplug via `items-changed` auf Monitor-ListModel (one greeter window per monitor; keyboard goes to the built-in display via `pick_primary_monitor_index`, falls back to first), systemd-journal-logger
|
||||
- `resources/style.css` — Catppuccin-inspiriertes Theme
|
||||
|
||||
## Design Decisions
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -575,7 +575,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "moongreet"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
dependencies = [
|
||||
"gdk-pixbuf",
|
||||
"gdk4",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "moongreet"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
edition = "2024"
|
||||
description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
|
||||
license = "MIT"
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
# Decisions
|
||||
|
||||
## 2026-04-23 – Keyboard focus on built-in display, not first enumerated monitor (v0.8.4)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
- **Why**: With a DisplayLink dock attached, the greeter showed its UI on all monitors but the password entry accepted no input. `display.monitors()` enumerated evdi phantom connectors (`DVI-I-*`) before the laptop panel (`eDP-1`); the v0.8.0 logic gave `KeyboardMode::Exclusive` to index 0, so the keyboard grab landed on an invisible surface. Symptom showed up on 2026-04-23 after kernel 6.19.11 → 6.19.12 + moongreet 0.8.0 → 0.8.2 changed evdi enumeration timing — previous Thursdays with the same dock worked.
|
||||
- **Tradeoffs**: Prefers built-in displays by connector-name pattern (`eDP*`/`LVDS*`/`DSI*`) rather than a generic "primary monitor" concept — Wayland has no portable primary signal, and gdk4's `primary_monitor()` was removed. Pattern-matching covers every current Linux laptop, at the cost of a tiny list to maintain if a new form factor ships a new connector type. Fallback is still index 0, so behavior on desktops without a built-in panel is unchanged.
|
||||
- **How**: New pure function `pick_primary_monitor_index()` in `main.rs` scans connector names and returns the built-in index (or 0). Used during initial enumeration to decide which window gets `KeyboardMode::Exclusive`. Hotplug branch unchanged — new monitors still get keyboard=false so focus never migrates off the panel. Unit-tested against evdi/eDP/LVDS/DSI/HDMI/DP mixes.
|
||||
|
||||
## 2026-04-21 – Ship polkit rule in moongreet instead of moonarch (v0.8.3)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
|
||||
90
src/main.rs
90
src/main.rs
@ -40,6 +40,26 @@ fn setup_layer_shell(window: >k::ApplicationWindow, keyboard: bool, layer: gtk
|
||||
window.set_anchor(gtk4_layer_shell::Edge::Right, true);
|
||||
}
|
||||
|
||||
/// Pick the index of the built-in display among the given connector names.
|
||||
///
|
||||
/// Prefers `eDP*` / `LVDS*` / `DSI*` over anything else — otherwise keyboard
|
||||
/// focus can land on DisplayLink/evdi phantom displays (connector `DVI-I-*`)
|
||||
/// that are enumerated before the laptop panel. Falls back to index 0 when
|
||||
/// no built-in connector is present.
|
||||
fn pick_primary_monitor_index<'a, I>(connectors: I) -> usize
|
||||
where
|
||||
I: IntoIterator<Item = Option<&'a str>>,
|
||||
{
|
||||
for (i, conn) in connectors.into_iter().enumerate() {
|
||||
if let Some(c) = conn
|
||||
&& (c.starts_with("eDP") || c.starts_with("LVDS") || c.starts_with("DSI"))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
fn activate(app: >k::Application) {
|
||||
let display = match gdk::Display::default() {
|
||||
Some(d) => d,
|
||||
@ -66,20 +86,37 @@ fn activate(app: >k::Application) {
|
||||
log::debug!("Layer shell: {use_layer_shell}");
|
||||
|
||||
if use_layer_shell {
|
||||
// One greeter window per monitor — only the first gets keyboard input
|
||||
// One greeter window per monitor — keyboard focus goes to the
|
||||
// built-in display so DisplayLink/evdi phantoms don't steal it.
|
||||
let monitors = display.monitors();
|
||||
log::debug!("Monitor count: {}", monitors.n_items());
|
||||
let mut first = true;
|
||||
for i in 0..monitors.n_items() {
|
||||
let count = monitors.n_items();
|
||||
log::debug!("Monitor count: {count}");
|
||||
|
||||
let connectors: Vec<Option<String>> = (0..count)
|
||||
.map(|i| {
|
||||
monitors
|
||||
.item(i)
|
||||
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
|
||||
.and_then(|m| m.connector())
|
||||
.map(|gs| gs.to_string())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let primary_idx = pick_primary_monitor_index(connectors.iter().map(|o| o.as_deref()));
|
||||
log::debug!(
|
||||
"Primary monitor: idx={primary_idx} connector={:?}",
|
||||
connectors.get(primary_idx).and_then(|o| o.as_deref())
|
||||
);
|
||||
|
||||
for i in 0..count {
|
||||
if let Some(monitor) = monitors
|
||||
.item(i)
|
||||
.and_then(|obj| obj.downcast::<gdk::Monitor>().ok())
|
||||
{
|
||||
let window = greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app);
|
||||
setup_layer_shell(&window, first, gtk4_layer_shell::Layer::Top);
|
||||
setup_layer_shell(&window, i as usize == primary_idx, gtk4_layer_shell::Layer::Top);
|
||||
window.set_monitor(Some(&monitor));
|
||||
window.present();
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +172,47 @@ fn setup_logging() {
|
||||
log::set_max_level(level);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::pick_primary_monitor_index;
|
||||
|
||||
#[test]
|
||||
fn prefers_edp_over_phantom_dvi() {
|
||||
assert_eq!(
|
||||
pick_primary_monitor_index([Some("DVI-I-1"), Some("eDP-1"), Some("DVI-I-2")]),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefers_lvds() {
|
||||
assert_eq!(pick_primary_monitor_index([Some("HDMI-A-1"), Some("LVDS-1")]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefers_dsi() {
|
||||
assert_eq!(pick_primary_monitor_index([Some("DP-1"), Some("DSI-1")]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn falls_back_to_first_without_builtin() {
|
||||
assert_eq!(
|
||||
pick_primary_monitor_index([Some("DVI-I-1"), Some("HDMI-A-1")]),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_missing_connector() {
|
||||
assert_eq!(pick_primary_monitor_index([None, Some("eDP-1")]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_returns_zero() {
|
||||
assert_eq!(pick_primary_monitor_index(std::iter::empty::<Option<&str>>()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
setup_logging();
|
||||
log::info!("Moongreet starting");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user