srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/rules.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-04-02 01:07:00 +0200
committersrdusr <[email protected]>2024-04-02 01:07:00 +0200
commit43175c78a5b450eb108738187b72e80d36a7bf5d (patch)
treedbbadbcc18162f766f5af653d81882aaaa6f5e41 /crates/core/src/rules.rs
parent8110bb2773b6c841029a51eca7971f42a36f480c (diff)
downloadsrdwm-43175c78a5b450eb108738187b72e80d36a7bf5d.tar.gz
srdwm-43175c78a5b450eb108738187b72e80d36a7bf5d.zip
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.
Diffstat (limited to 'crates/core/src/rules.rs')
-rw-r--r--crates/core/src/rules.rs85
1 files changed, 85 insertions, 0 deletions
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<String>,
+ /// Case-insensitive exact match against `Window::app_id` (X11 `WM_CLASS`
+ /// / Wayland `app_id`).
+ pub class: Option<String>,
+}
+
+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<bool>,
+ pub maximized: Option<bool>,
+ pub workspace: Option<WorkspaceId>,
+ pub geometry: Option<Rect>,
+ pub decorated: Option<bool>,
+ pub border_color: Option<(u8, u8, u8)>,
+ pub border_width: Option<u32>,
+}
+
+#[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));
+ }
+}