From 43175c78a5b450eb108738187b72e80d36a7bf5d Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Tue, 2 Apr 2024 01:07:00 +0200 Subject: Add window rules, real config validation, and a Wayland DRM/udev backend - srd.rule(): match windows by title/class, apply floating/maximized/ workspace/geometry/decoration actions on creation (crates/core/src/rules.rs) - srd.validate_config()/srd.debug.*: real range/format checks and status/profiling helpers, replacing the always-true stub - Wayland titlebar text rendering via fontdue, unit-tested without a display (crates/wayland/src/decoration.rs) - Wayland precise keybinding matching, replacing the "any Super-held key" heuristic, sharing the keysym table with X11 (moved to crates/core/src/keysyms.rs) - Wayland DRM/udev backend (crates/wayland/src/udev.rs): runs as the real compositor on a bare TTY via libseat/libinput/KMS, software rendering via Pixman + dumb buffers (no GBM/EGL required) - srdwm_platform::detect() fix, found via VM testing: a bare TTY with no DISPLAY/WAYLAND_DISPLAY now correctly resolves to Wayland instead of an X11 backend that can never work there - XWayland integration groundwork (crates/wayland/src/xwayland.rs): spawn, X11Wm, and full XwmHandler event routing into the same WindowManager/ Space pipeline as native clients. Windows don't render yet - a real glamor-vs-software-renderer conflict in XWayland's own fallback path, root-caused via WAYLAND_DEBUG tracing and documented in docs/IMPLEMENTATION_STATUS.md rather than worked around blind. All verified live in an isolated QEMU VM: X11 backend shows two decorated, correctly-tiled xterms with real title text; the DRM/udev Wayland backend opens the GPU, initializes input, and scans out a rendered frame via KMS page-flip. --- crates/core/src/rules.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 crates/core/src/rules.rs (limited to 'crates/core/src/rules.rs') diff --git a/crates/core/src/rules.rs b/crates/core/src/rules.rs new file mode 100644 index 0000000..4d881a5 --- /dev/null +++ b/crates/core/src/rules.rs @@ -0,0 +1,85 @@ +use crate::geometry::Rect; +use crate::window::Window; +use crate::workspace::WorkspaceId; + +/// Match criteria for a [`WindowRule`]. A matcher with every field `None` +/// matches nothing (an accidental `srd.rule({}, {...})` in config should be a +/// silent no-op, not "apply to every window"). +#[derive(Debug, Clone, Default)] +pub struct WindowMatch { + /// Case-insensitive substring match against `Window::title`. + pub title_contains: Option, + /// Case-insensitive exact match against `Window::app_id` (X11 `WM_CLASS` + /// / Wayland `app_id`). + pub class: Option, +} + +impl WindowMatch { + pub fn is_empty(&self) -> bool { + self.title_contains.is_none() && self.class.is_none() + } + + pub fn matches(&self, window: &Window) -> bool { + if self.is_empty() { + return false; + } + if let Some(t) = &self.title_contains { + if !window.title.to_lowercase().contains(&t.to_lowercase()) { + return false; + } + } + if let Some(c) = &self.class { + if !window.app_id.eq_ignore_ascii_case(c) { + return false; + } + } + true + } +} + +/// Actions applied once, when a matching window is first added. +#[derive(Debug, Clone, Default)] +pub struct WindowRuleActions { + pub floating: Option, + pub maximized: Option, + pub workspace: Option, + pub geometry: Option, + pub decorated: Option, + pub border_color: Option<(u8, u8, u8)>, + pub border_width: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct WindowRule { + pub matcher: WindowMatch, + pub actions: WindowRuleActions, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_matcher_matches_nothing() { + let w = Window::new(1, "anything"); + assert!(!WindowMatch::default().matches(&w)); + } + + #[test] + fn title_match_is_case_insensitive_substring() { + let w = Window::new(1, "Mozilla Firefox"); + let m = WindowMatch { title_contains: Some("firefox".into()), class: None }; + assert!(m.matches(&w)); + } + + #[test] + fn class_match_is_case_insensitive_exact() { + let mut w = Window::new(1, ""); + w.app_id = "Firefox".into(); + let m = WindowMatch { title_contains: None, class: Some("firefox".into()) }; + assert!(m.matches(&w)); + let mut w2 = Window::new(2, ""); + w2.app_id = "firefoxx".into(); + assert!(!m.matches(&w2)); + } +} -- cgit v1.2.3