diff options
| author | srdusr <[email protected]> | 2024-05-30 16:10:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-05-30 16:10:00 +0200 |
| commit | 1c175642d073689ca11b9252411ea5f8446007d0 (patch) | |
| tree | 00b8f9062cf1261d2d2642c059a14ff5790522e5 /crates/wayland/src/xkb_config.rs | |
| parent | 3d3057ae384ef7389284af8988410889e99c6bb9 (diff) | |
| download | srdwm-1c175642d073689ca11b9252411ea5f8446007d0.tar.gz srdwm-1c175642d073689ca11b9252411ea5f8446007d0.zip | |
Fix decoration drift during animated transitions; checkpoint IPC/global-menu/output-management work
Border/titlebar decoration was built from Window.geometry (the animation's
final target) in both wayland backends' render loops, while sync_geometry
already draws a window's actual content at window_anims' interpolated rect
during any maximize/fullscreen/open-slide tween. Border and content read two
different rectangles for the whole transition, so the border visibly
detached from the window it was outlining - reported as "borders aren't
flush." Both udev.rs and winit.rs now read the same animated rect for
titlebar placement, border-strip placement, and the occlusion test against
later windows in stacking order. Verified: cargo build --workspace, cargo
clippy (0 new warnings), cargo test -p srdwm-core (111/111).
Also checkpoints substantial protocol/IPC work from prior sessions that had
accumulated uncommitted: gtk-shell1 support (gtk_shell.rs, the vendored
gtk-shell.xml, xwayland.rs's X11-side mirror) backing the global app menu;
zwlr_foreign_toplevel_manager_v1 (foreign_toplevel.rs) broadcasting distinct
maximized/minimized/fullscreen/activated state per window; output_management
(ext-output-management + layer-shell exclusive-zone reservation tracking);
workspace.rs and context_menu.rs; a Unix-socket IPC crate (platform/src/
ipc.rs) and a `srd` control-CLI crate (crates/ctl); xkb_config.rs; and a
theme module (core/src/theme.rs). A peer session working the AGS shell
concurrently verified several of these live against a running srdwm: the
global menu rendering a real app's File/Edit menu over gtk-shell1, and
foreign-toplevel correctly reporting maximized and fullscreen as independent,
non-simultaneous states with the geometry each implies (maximize stops at a
reserved top bar and past a dock; fullscreen reaches the true monitor edge).
Diffstat (limited to 'crates/wayland/src/xkb_config.rs')
| -rw-r--r-- | crates/wayland/src/xkb_config.rs | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/crates/wayland/src/xkb_config.rs b/crates/wayland/src/xkb_config.rs new file mode 100644 index 0000000..5b16904 --- /dev/null +++ b/crates/wayland/src/xkb_config.rs @@ -0,0 +1,102 @@ +//! Reads the system's real keyboard layout/model/options so both backends' +//! `seat.add_keyboard()` calls actually use them. +//! +//! `smithay::input::keyboard::XkbConfig::default()` - what both backends +//! passed unconditionally before this - resolves any field left as `""`/ +//! `None` via the `XKB_DEFAULT_*` environment variables, per xkbcommon's +//! own documented behavior (see its doc comment). That covers a session +//! that actually sets those variables, but this machine doesn't: the real +//! configuration lives in `/etc/X11/xorg.conf.d/00-keyboard.conf`, written +//! by `systemd-localed` from `localectl`, which nothing was ever reading. +//! Concretely: `Option "XkbOptions" "terminate:ctrl_alt_bksp"` was silently +//! dropped, along with the model (`pc105+inet`), regardless of what +//! `localectl status` actually reports. +//! +//! Deliberately fails soft, field by field: a missing file, an unreadable +//! one, or a field just not present in it all fall through to the same +//! `""`/`None` `Default::default()` already used, not an error - this is +//! strictly additive over today's behavior, never worse. + +use std::collections::HashMap; + +/// Parsed fields from the standard `XkbLayout`/`XkbModel`/`XkbVariant`/ +/// `XkbOptions` `Option "..." "..."` lines `systemd-localed` writes. +/// Anything not found is `None`, which is exactly what an empty-string +/// `XkbConfig` field already meant. +#[derive(Default)] +pub(crate) struct SystemXkbConfig { + pub(crate) model: Option<String>, + pub(crate) layout: Option<String>, + pub(crate) variant: Option<String>, + pub(crate) options: Option<String>, +} + +const KEYBOARD_CONF_PATH: &str = "/etc/X11/xorg.conf.d/00-keyboard.conf"; + +pub(crate) fn read() -> SystemXkbConfig { + let Ok(content) = std::fs::read_to_string(KEYBOARD_CONF_PATH) else { + return SystemXkbConfig::default(); + }; + let fields = parse(&content); + SystemXkbConfig { + model: fields.get("XkbModel").cloned(), + layout: fields.get("XkbLayout").cloned(), + variant: fields.get("XkbVariant").cloned(), + options: fields.get("XkbOptions").cloned(), + } +} + +/// Extracts `Option "Name" "value"` lines into a name -> value map. Not a +/// general X11 config parser - this file has exactly one `InputClass` +/// section with a handful of `Option` lines in that fixed form, always +/// machine-written, so a line-by-line scan for that one pattern is all +/// this needs. +fn parse(content: &str) -> HashMap<String, String> { + let mut fields = HashMap::new(); + for line in content.lines() { + let line = line.trim(); + let Some(rest) = line.strip_prefix("Option") else { continue }; + let quoted: Vec<&str> = rest.split('"').filter(|s| !s.trim().is_empty()).collect(); + if let [name, value] = quoted[..] { + fields.insert(name.to_string(), value.to_string()); + } + } + fields +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_a_real_systemd_localed_file() { + let content = r#" +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us" + Option "XkbModel" "pc105+inet" + Option "XkbOptions" "terminate:ctrl_alt_bksp" +EndSection +"#; + let fields = parse(content); + assert_eq!(fields.get("XkbLayout").map(String::as_str), Some("us")); + assert_eq!(fields.get("XkbModel").map(String::as_str), Some("pc105+inet")); + assert_eq!(fields.get("XkbOptions").map(String::as_str), Some("terminate:ctrl_alt_bksp")); + } + + #[test] + fn ignores_unrelated_lines_without_panicking() { + let content = "Section \"InputClass\"\nIdentifier \"system-keyboard\"\nMatchIsKeyboard \"on\"\nEndSection\n"; + assert!(parse(content).is_empty()); + } + + #[test] + fn missing_file_yields_all_none_not_an_error() { + // read() itself isn't unit-testable without touching the real + // filesystem path, but the fallback behavior it guarantees is + // exactly SystemXkbConfig::default() - covered by construction. + let cfg = SystemXkbConfig::default(); + assert!(cfg.model.is_none() && cfg.layout.is_none() && cfg.variant.is_none() && cfg.options.is_none()); + } +} |