diff --git a/src/api/msg.rs b/src/api/msg.rs index a8100a9..cad0b12 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -3,6 +3,8 @@ // The MessagePack format for these is a one-element map where the element's key is the enum name and its // value is a map of the enum's values +mod window_rules; + use crate::{ layout::Layout, output::OutputName, diff --git a/src/api/msg/window_rules.rs b/src/api/msg/window_rules.rs new file mode 100644 index 0000000..3ad783c --- /dev/null +++ b/src/api/msg/window_rules.rs @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::num::NonZeroU32; + +use smithay::wayland::{compositor, shell::xdg::XdgToplevelSurfaceData}; + +use crate::{ + output::OutputName, + state::{State, WithState}, + tag::TagId, + window::{window_state::FullscreenOrMaximized, WindowElement}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum WindowRuleCondition { + /// This condition is met when any of the conditions provided is met. + CondAny(Vec), + /// This condition is met when all of the conditions provided are met. + CondAll(Vec), + /// This condition is met when the class matches. + Class(String), + /// This condition is met when the title matches. + Title(String), + /// This condition is met when the tag matches. + Tag(TagId), +} + +impl WindowRuleCondition { + /// RefCell Safety: This method uses RefCells on `window`. + pub fn is_met(&self, state: &State, window: &WindowElement) -> bool { + match self { + WindowRuleCondition::CondAny(conds) => { + conds.iter().any(|cond| Self::is_met(cond, state, window)) + } + WindowRuleCondition::CondAll(conds) => { + conds.iter().all(|cond| Self::is_met(cond, state, window)) + } + WindowRuleCondition::Class(class) => { + let Some(wl_surf) = window.wl_surface() else { + return false; + }; + + let current_class = compositor::with_states(&wl_surf, |states| { + states + .data_map + .get::() + .expect("XdgToplevelSurfaceData wasn't in surface's data map") + .lock() + .expect("Failed to lock Mutex") + .app_id + .clone() + }); + + current_class.as_ref() == Some(class) + } + WindowRuleCondition::Title(title) => { + let Some(wl_surf) = window.wl_surface() else { + return false; + }; + + let current_title = compositor::with_states(&wl_surf, |states| { + states + .data_map + .get::() + .expect("XdgToplevelSurfaceData wasn't in surface's data map") + .lock() + .expect("Failed to lock Mutex") + .title + .clone() + }); + + current_title.as_ref() == Some(title) + } + WindowRuleCondition::Tag(tag) => { + let Some(tag) = tag.tag(state) else { + return false; + }; + + window.with_state(|state| state.tags.contains(&tag)) + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum WindowRule { + /// Set the output the window will open on. + Output(OutputName), + /// Set the tags the output will have on open. + Tags(Vec), + /// Set the window to floating or tiled on open. + FloatingOrTiled(FloatingOrTiled), + /// Set the window to fullscreen, maximized, or force it to neither. + FullscreenOrMaximized(FullscreenOrMaximized), + /// Set the window's initial size. + Size(NonZeroU32, NonZeroU32), + /// Set the window's initial location. If the window is tiled, it will snap to this position + /// when set to floating. + Location(i32, i32), +} + +// TODO: just skip serializing fields on the other FloatingOrTiled +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum FloatingOrTiled { + Floating, + Tiled, +} diff --git a/src/window/window_state.rs b/src/window/window_state.rs index ff81b47..9cf99d2 100644 --- a/src/window/window_state.rs +++ b/src/window/window_state.rs @@ -301,7 +301,7 @@ impl FloatingOrTiled { } } -#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum FullscreenOrMaximized { Neither, Fullscreen,