diff options
| author | srdusr <[email protected]> | 2024-04-02 01:07:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-04-02 01:07:00 +0200 |
| commit | 43175c78a5b450eb108738187b72e80d36a7bf5d (patch) | |
| tree | dbbadbcc18162f766f5af653d81882aaaa6f5e41 /crates/core/src/manager.rs | |
| parent | 8110bb2773b6c841029a51eca7971f42a36f480c (diff) | |
| download | srdwm-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/manager.rs')
| -rw-r--r-- | crates/core/src/manager.rs | 81 |
1 files changed, 78 insertions, 3 deletions
diff --git a/crates/core/src/manager.rs b/crates/core/src/manager.rs index 1fdd7eb..1f69413 100644 --- a/crates/core/src/manager.rs +++ b/crates/core/src/manager.rs @@ -2,6 +2,7 @@ use crate::geometry::Rect; use crate::layout::{Layout, MasterStackLayout, NoOpLayout, TilingConfig}; use crate::monitor::{Monitor, MonitorId}; use crate::placement::{PlacementConfig, SmartPlacement, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH}; +use crate::rules::WindowRule; use crate::window::{ResizeEdge, TitlebarHit, Window, WindowId}; use crate::workspace::{Workspace, WorkspaceId}; use std::collections::HashMap; @@ -47,6 +48,7 @@ pub struct WindowManager { pub placement: PlacementConfig, drag: Option<DragState>, resize: Option<ResizeState>, + rules: Vec<WindowRule>, } impl Default for WindowManager { @@ -76,9 +78,16 @@ impl WindowManager { placement: PlacementConfig::default(), drag: None, resize: None, + rules: Vec::new(), } } + /// Registers a window rule; on every subsequent `add_window`, the first + /// rule whose matcher matches the new window has its actions applied. + pub fn add_rule(&mut self, rule: WindowRule) { + self.rules.push(rule); + } + pub fn register_layout(&mut self, name: impl Into<String>, layout: Box<dyn Layout>) { self.layouts.insert(name.into(), layout); } @@ -119,19 +128,45 @@ impl WindowManager { /// it's left for the next `arrange_workspace` call to place. pub fn add_window(&mut self, mut window: Window) -> WindowId { let id = window.id; - window.workspace = self.current_workspace; + let actions = self.rules.iter().find(|r| r.matcher.matches(&window)).map(|r| r.actions.clone()); + + let workspace = actions.as_ref().and_then(|a| a.workspace).unwrap_or(self.current_workspace); + window.workspace = workspace; + if let Some(a) = &actions { + if let Some(floating) = a.floating { + window.floating = floating; + } + if let Some(decorated) = a.decorated { + window.decorated = decorated; + } + if let Some(color) = a.border_color { + window.border_color = color; + } + if let Some(width) = a.border_width { + window.border_width = width; + } + } + if let Some(monitor) = self.primary_monitor() { window.monitor = monitor.id; - let layout_name = self.workspace(self.current_workspace).map(|w| w.layout.clone()).unwrap_or_default(); + let layout_name = self.workspace(workspace).map(|w| w.layout.clone()).unwrap_or_default(); if layout_name != "tiling" { - let existing: Vec<Rect> = self.windows_on_workspace(self.current_workspace).map(|w| w.geometry).collect(); + let existing: Vec<Rect> = self.windows_on_workspace(workspace).map(|w| w.geometry).collect(); let size = (window.geometry.width, window.geometry.height); window.geometry = SmartPlacement::place(monitor, &existing, size, &self.placement); } } + if let Some(geometry) = actions.as_ref().and_then(|a| a.geometry) { + window.geometry = geometry; + } + let maximize = actions.as_ref().and_then(|a| a.maximized).unwrap_or(false); + self.windows.insert(id, window); self.order.push(id); self.focused = Some(id); + if maximize { + self.toggle_maximize(id); + } id } @@ -711,6 +746,46 @@ mod tests { } #[test] + fn matching_rule_floats_new_window_on_add() { + let mut wm = wm_with_monitor(); + wm.set_layout(wm.current_workspace(), "tiling"); + wm.add_rule(WindowRule { + matcher: crate::rules::WindowMatch { title_contains: Some("calculator".into()), class: None }, + actions: crate::rules::WindowRuleActions { floating: Some(true), ..Default::default() }, + }); + let id = wm.alloc_window_id(); + wm.add_window(Window::new(id, "Calculator")); + assert!(wm.is_floating(id)); + } + + #[test] + fn non_matching_rule_leaves_window_untouched() { + let mut wm = wm_with_monitor(); + wm.add_rule(WindowRule { + matcher: crate::rules::WindowMatch { title_contains: Some("calculator".into()), class: None }, + actions: crate::rules::WindowRuleActions { floating: Some(true), ..Default::default() }, + }); + let id = wm.alloc_window_id(); + wm.add_window(Window::new(id, "Terminal")); + assert!(!wm.is_floating(id)); + } + + #[test] + fn rule_assigns_window_to_target_workspace() { + let mut wm = wm_with_monitor(); + let target = wm.add_workspace("scratch", "dynamic"); + wm.add_rule(WindowRule { + matcher: crate::rules::WindowMatch { title_contains: None, class: Some("scratchpad".into()) }, + actions: crate::rules::WindowRuleActions { workspace: Some(target), ..Default::default() }, + }); + let id = wm.alloc_window_id(); + let mut w = Window::new(id, "notes"); + w.app_id = "scratchpad".into(); + wm.add_window(w); + assert_eq!(wm.window(id).unwrap().workspace, target); + } + + #[test] fn removing_a_workspace_reassigns_its_windows() { let mut wm = wm_with_monitor(); let ws2 = wm.add_workspace("2", "dynamic"); |