srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/xkb_config.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-05-30 16:10:00 +0200
committersrdusr <[email protected]>2024-05-30 16:10:00 +0200
commit1c175642d073689ca11b9252411ea5f8446007d0 (patch)
tree00b8f9062cf1261d2d2642c059a14ff5790522e5 /crates/wayland/src/xkb_config.rs
parent3d3057ae384ef7389284af8988410889e99c6bb9 (diff)
downloadsrdwm-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.rs102
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());
+ }
+}