Compare commits
4 Commits
b9b6f50974
...
ce9f2196ca
| Author | SHA1 | Date | |
|---|---|---|---|
| ce9f2196ca | |||
| 29ce185886 | |||
| 91b4289748 | |||
| 97165d94f8 |
@ -43,9 +43,9 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
|
||||
- `power.rs` — Reboot/Shutdown via loginctl
|
||||
- `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, 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
|
||||
- `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).
|
||||
- `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
|
||||
@ -60,9 +60,11 @@ cd pkg && makepkg -sf && sudo pacman -U moongreet-git-<version>-x86_64.pkg.tar.z
|
||||
- **Symmetrie mit moonlock/moonset**: Gleiche Patterns (i18n, config, users, power, GResource, GPU-Blur)
|
||||
- **Session-Validierung**: Relative Pfade erlaubt (greetd löst PATH auf), nur `..`/Null-Bytes werden abgelehnt
|
||||
- **GTK-Theme-Validierung**: Nur alphanumerisch + `_-+.` erlaubt, verhindert Path-Traversal über Config
|
||||
- **Cursor-Theme via GtkSettings**: GTK4 unter greetd liest `XCURSOR_THEME` env nicht zuverlässig — Cursor wird via `gtk::Settings::set_gtk_cursor_theme_name()` gesetzt, analog zu `gtk-theme`. Gleiche Validierung (`is_valid_gtk_theme`) gegen Path-Traversal.
|
||||
- **Journal-Logging**: `systemd-journal-logger` statt File-Logging — `journalctl -t moongreet`, Debug-Level per `MOONGREET_DEBUG` Env-Var
|
||||
- **File Permissions**: Cache-Verzeichnisse 0o700 via `DirBuilder::mode()`, Cache-Dateien 0o600
|
||||
- **Testbare Persistence**: `save_*_to`/`load_*_from` Varianten mit konfigurierbarem Pfad für Unit-Tests
|
||||
- **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 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.
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -575,7 +575,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "moongreet"
|
||||
version = "0.8.3"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"gdk-pixbuf",
|
||||
"gdk4",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "moongreet"
|
||||
version = "0.8.3"
|
||||
version = "0.10.0"
|
||||
edition = "2024"
|
||||
description = "A greetd greeter for Wayland with GTK4 and Layer Shell"
|
||||
license = "MIT"
|
||||
|
||||
28
DECISIONS.md
28
DECISIONS.md
@ -1,5 +1,33 @@
|
||||
# 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
|
||||
- **Why**: Cursor theme in the greeter was the default fallback even with `XCURSOR_THEME=Sweet-cursors` in `/etc/greetd/config.toml`'s `env` prefix. Cage forwards the env, but GTK4 does not honour `XCURSOR_THEME` reliably under greetd — it picks up the theme from `gtk-cursor-theme-name` on `GtkSettings`, and without a session-level settings.ini or GSettings override in the greeter user's home, that property stays at the GTK default. Adding an env-var hack worked for the wlroots pointer rendered by cage, but GTK widgets (button hover, text input) used their own wrong cursor.
|
||||
- **Tradeoffs**: Adds two config fields (`cursor-theme`, `cursor-size`) — symmetric with the existing `gtk-theme` field and justified by the same cause (GTK4 under greetd ignores the usual discovery paths). Alternative would have been a system-wide `/etc/gtk-4.0/settings.ini` with `gtk-cursor-theme-name=`, but that couples moongreet's appearance to the host system's GTK config and affects every GTK4 app running as any user.
|
||||
- **How**: `config.rs` gains `cursor_theme: Option<String>` and `cursor_size: Option<i32>` (range-validated 1–256). `greeter.rs::create_greeter_window` applies them via `gtk::Settings::set_gtk_cursor_theme_name()` and `set_gtk_cursor_theme_size()` directly after the existing `gtk-theme` handling, reusing `is_valid_gtk_theme()` for name validation. Moonarch's deployed config gains `cursor-theme = "Sweet-cursors"` + `cursor-size = 24`. The env-prefix hack in `/etc/greetd/config.toml` is now redundant.
|
||||
|
||||
## 2026-04-23 – Wallpaper-only windows on secondary monitors (v0.8.5)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
- **Why**: The v0.8.4 fix (keyboard grab on the built-in panel) only half-worked. The greeter still rejected keystrokes until the user moved the mouse to eDP-1 — Niri scopes layer-shell keyboard routing by active output, so even though the primary window was the sole `KeyboardMode::Exclusive` surface, keys went nowhere when another output was active. Hardcoding a compositor focus call (e.g. `niri msg action focus-monitor`) would tie moongreet to a specific compositor.
|
||||
- **Tradeoffs**: Reverts part of 2026-04-08: only the built-in panel shows the full greeter UI, other monitors go back to wallpaper-only. Users with multiple monitors lose the symmetric "login widget on every screen" look, but gain a reliable keyboard path regardless of which output the compositor considers active at startup. Compositor-agnostic — no Niri-specific IPC.
|
||||
- **How**: New `create_wallpaper_window()` in `greeter.rs` builds a minimal `ApplicationWindow` with the shared background `Picture` (same `blur_cache` as the primary) and no login widgets. `main.rs` uses `create_greeter_window()` for the index returned by `pick_primary_monitor_index()` and `create_wallpaper_window()` for the rest. Hotplugged monitors also get wallpaper-only windows. Both variants use `Layer::Top`; only the primary sets `KeyboardMode::Exclusive`.
|
||||
|
||||
## 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
|
||||
|
||||
@ -12,7 +12,7 @@ Part of the Moonarch ecosystem.
|
||||
- **Last user/session** — Remembered in `/var/cache/moongreet/`
|
||||
- **Power actions** — Reboot / Shutdown via `loginctl`
|
||||
- **Layer Shell** — Fullscreen via gtk4-layer-shell (TOP layer)
|
||||
- **Multi-monitor + hotplug** — Full greeter UI on all monitors (keyboard input on first), hotplugged monitors get windows automatically
|
||||
- **Multi-monitor + hotplug** — Login UI on the built-in display, wallpaper-only on other monitors; hotplugged monitors get wallpaper windows automatically
|
||||
- **GPU blur** — Background blur via GskBlurNode (shared cache across monitors)
|
||||
- **i18n** — German and English (auto-detected from system locale)
|
||||
- **Faillock warning** — Warns after 2 failed attempts, locked message after 3
|
||||
|
||||
@ -8,3 +8,8 @@ background = "/usr/share/backgrounds/wallpaper.jpg"
|
||||
# GTK theme name — must match a directory in /usr/share/themes/
|
||||
# Required because GTK4 under greetd does not reliably read settings.ini
|
||||
gtk-theme = "Colloid-Grey-Dark-Catppuccin"
|
||||
|
||||
# Cursor theme name — must match a directory in /usr/share/icons/
|
||||
# GTK4 under greetd does not honour XCURSOR_THEME, so set it here.
|
||||
cursor-theme = "Sweet-cursors"
|
||||
cursor-size = 24
|
||||
|
||||
@ -25,6 +25,10 @@ struct Appearance {
|
||||
background_blur: Option<f32>,
|
||||
#[serde(rename = "gtk-theme")]
|
||||
gtk_theme: Option<String>,
|
||||
#[serde(rename = "cursor-theme")]
|
||||
cursor_theme: Option<String>,
|
||||
#[serde(rename = "cursor-size")]
|
||||
cursor_size: Option<i32>,
|
||||
#[serde(rename = "fingerprint-enabled")]
|
||||
fingerprint_enabled: Option<bool>,
|
||||
}
|
||||
@ -35,6 +39,8 @@ pub struct Config {
|
||||
pub background_path: Option<String>,
|
||||
pub background_blur: Option<f32>,
|
||||
pub gtk_theme: Option<String>,
|
||||
pub cursor_theme: Option<String>,
|
||||
pub cursor_size: Option<i32>,
|
||||
pub fingerprint_enabled: bool,
|
||||
}
|
||||
|
||||
@ -44,6 +50,8 @@ impl Default for Config {
|
||||
background_path: None,
|
||||
background_blur: None,
|
||||
gtk_theme: None,
|
||||
cursor_theme: None,
|
||||
cursor_size: None,
|
||||
fingerprint_enabled: true,
|
||||
}
|
||||
}
|
||||
@ -82,6 +90,16 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
|
||||
if appearance.gtk_theme.is_some() {
|
||||
merged.gtk_theme = appearance.gtk_theme;
|
||||
}
|
||||
if appearance.cursor_theme.is_some() {
|
||||
merged.cursor_theme = appearance.cursor_theme;
|
||||
}
|
||||
if let Some(size) = appearance.cursor_size {
|
||||
if (1..=256).contains(&size) {
|
||||
merged.cursor_size = Some(size);
|
||||
} else {
|
||||
log::warn!("Ignoring cursor-size out of range (1–256): {size}");
|
||||
}
|
||||
}
|
||||
if let Some(fp) = appearance.fingerprint_enabled {
|
||||
merged.fingerprint_enabled = fp;
|
||||
}
|
||||
@ -98,7 +116,15 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Config result: background={:?}, blur={:?}, gtk_theme={:?}, fingerprint={}", merged.background_path, merged.background_blur, merged.gtk_theme, merged.fingerprint_enabled);
|
||||
log::debug!(
|
||||
"Config result: background={:?}, blur={:?}, gtk_theme={:?}, cursor_theme={:?}, cursor_size={:?}, fingerprint={}",
|
||||
merged.background_path,
|
||||
merged.background_blur,
|
||||
merged.gtk_theme,
|
||||
merged.cursor_theme,
|
||||
merged.cursor_size,
|
||||
merged.fingerprint_enabled
|
||||
);
|
||||
merged
|
||||
}
|
||||
|
||||
@ -321,6 +347,40 @@ mod tests {
|
||||
assert!(config.background_blur.is_none());
|
||||
}
|
||||
|
||||
// -- Cursor theme tests --
|
||||
|
||||
#[test]
|
||||
fn load_config_cursor_theme_and_size() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let conf = dir.path().join("moongreet.toml");
|
||||
fs::write(
|
||||
&conf,
|
||||
"[appearance]\ncursor-theme = \"Sweet-cursors\"\ncursor-size = 32\n",
|
||||
)
|
||||
.unwrap();
|
||||
let config = load_config(Some(&[conf]));
|
||||
assert_eq!(config.cursor_theme.as_deref(), Some("Sweet-cursors"));
|
||||
assert_eq!(config.cursor_size, Some(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_config_cursor_size_out_of_range_rejected() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let conf = dir.path().join("moongreet.toml");
|
||||
fs::write(&conf, "[appearance]\ncursor-size = 9999\n").unwrap();
|
||||
let config = load_config(Some(&[conf]));
|
||||
assert!(config.cursor_size.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_config_cursor_size_zero_rejected() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let conf = dir.path().join("moongreet.toml");
|
||||
fs::write(&conf, "[appearance]\ncursor-size = 0\n").unwrap();
|
||||
let config = load_config(Some(&[conf]));
|
||||
assert!(config.cursor_size.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_config_blur_inf_rejected() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
@ -259,6 +259,23 @@ pub fn create_greeter_window(
|
||||
}
|
||||
}
|
||||
|
||||
// Apply cursor theme from config — GTK4 under greetd does not read XCURSOR_THEME
|
||||
// reliably, so set the gtk-cursor-theme-name property directly.
|
||||
if let Some(ref cursor) = config.cursor_theme {
|
||||
if is_valid_gtk_theme(cursor) {
|
||||
if let Some(settings) = gtk::Settings::default() {
|
||||
settings.set_gtk_cursor_theme_name(Some(cursor));
|
||||
}
|
||||
} else {
|
||||
log::warn!("Ignoring invalid cursor theme name: {cursor}");
|
||||
}
|
||||
}
|
||||
if let Some(size) = config.cursor_size {
|
||||
if let Some(settings) = gtk::Settings::default() {
|
||||
settings.set_gtk_cursor_theme_size(size);
|
||||
}
|
||||
}
|
||||
|
||||
let strings = load_strings(None);
|
||||
let fingerprint_enabled = config.fingerprint_enabled;
|
||||
let all_users = users::get_users(None);
|
||||
|
||||
144
src/main.rs
144
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,20 +24,37 @@ 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);
|
||||
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,51 +81,41 @@ 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
|
||||
// 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();
|
||||
log::debug!("Monitor count: {}", monitors.n_items());
|
||||
let mut first = true;
|
||||
for i in 0..monitors.n_items() {
|
||||
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);
|
||||
window.set_monitor(Some(&monitor));
|
||||
window.present();
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
let count = monitors.n_items();
|
||||
log::debug!("Monitor count: {count}");
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
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())
|
||||
);
|
||||
|
||||
if let Some(monitor) = monitors
|
||||
.item(primary_idx as u32)
|
||||
.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, gtk4_layer_shell::Layer::Top);
|
||||
window.set_monitor(Some(&monitor));
|
||||
window.present();
|
||||
} else {
|
||||
log::error!("Primary monitor {primary_idx} not available — greeter will not be shown");
|
||||
}
|
||||
} else {
|
||||
// No layer shell — single window for development
|
||||
let greeter_window = greeter::create_greeter_window(bg_texture.as_ref(), &config, &blur_cache, app);
|
||||
greeter_window.present();
|
||||
}
|
||||
@ -135,6 +140,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