From 51e752992fa10b3b608685f124374c14d8306917 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 3 May 2025 14:13:40 +0200 Subject: [PATCH] config: add workspace window criteria --- jay-config/src/_private.rs | 3 +++ jay-config/src/_private/client.rs | 3 +++ jay-config/src/window.rs | 6 ++++++ src/config/handler.rs | 4 ++++ src/criteria/tlm.rs | 10 +++++++++- src/criteria/tlm/tlm_matchers/tlmm_string.rs | 15 +++++++++++++++ src/tree/toplevel.rs | 3 ++- toml-config/src/config.rs | 2 ++ toml-config/src/config/parsers/window_match.rs | 15 ++++++++++++++- toml-config/src/rules.rs | 2 ++ toml-spec/spec/spec.generated.json | 8 ++++++++ toml-spec/spec/spec.generated.md | 12 ++++++++++++ toml-spec/spec/spec.yaml | 8 ++++++++ 13 files changed, 88 insertions(+), 3 deletions(-) diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index 97d49c27..d968223e 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -5,6 +5,7 @@ pub(crate) mod string_error; use { crate::{ + Workspace, client::ClientMatcher, input::Seat, video::Mode, @@ -117,6 +118,7 @@ pub enum WindowCriterionIpc { SeatFocus(Seat), Fullscreen, JustMapped, + Workspace(Workspace), } #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] @@ -127,4 +129,5 @@ pub enum WindowCriterionStringField { XClass, XInstance, XRole, + Workspace, } diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 963664a6..63eda9cf 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1671,6 +1671,9 @@ impl ConfigClient { WindowCriterion::XInstanceRegex(t) => string!(t, XInstance, true), WindowCriterion::XRole(t) => string!(t, XRole, false), WindowCriterion::XRoleRegex(t) => string!(t, XRole, true), + WindowCriterion::Workspace(t) => WindowCriterionIpc::Workspace(t), + WindowCriterion::WorkspaceName(t) => string!(t, Workspace, false), + WindowCriterion::WorkspaceNameRegex(t) => string!(t, Workspace, true), }; let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion }); get_response!( diff --git a/jay-config/src/window.rs b/jay-config/src/window.rs index 05ce9cbc..f7996d6f 100644 --- a/jay-config/src/window.rs +++ b/jay-config/src/window.rs @@ -276,6 +276,12 @@ pub enum WindowCriterion<'a> { XRole(&'a str), /// Matches the X role of the window with a regular expression. XRoleRegex(&'a str), + /// Matches the workspace the window. + Workspace(Workspace), + /// Matches the workspace name of the window verbatim. + WorkspaceName(&'a str), + /// Matches the workspace name of the window with a regular expression. + WorkspaceNameRegex(&'a str), } impl WindowCriterion<'_> { diff --git a/src/config/handler.rs b/src/config/handler.rs index 06ca2982..0b10eb2f 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1994,6 +1994,7 @@ impl ConfigProxyHandler { WindowCriterionStringField::XClass => mgr.class(needle), WindowCriterionStringField::XInstance => mgr.instance(needle), WindowCriterionStringField::XRole => mgr.role(needle), + WindowCriterionStringField::Workspace => mgr.workspace(needle), } } WindowCriterionIpc::Types(t) => mgr.kind(*t), @@ -2007,6 +2008,9 @@ impl ConfigProxyHandler { WindowCriterionIpc::SeatFocus(seat) => mgr.seat_focus(&*self.get_seat(*seat)?), WindowCriterionIpc::Fullscreen => mgr.fullscreen(), WindowCriterionIpc::JustMapped => mgr.just_mapped(), + WindowCriterionIpc::Workspace(w) => mgr.workspace(CritLiteralOrRegex::Literal( + self.get_workspace(*w)?.to_string(), + )), }; let cached = Rc::new(CachedCriterion { crit: criterion.clone(), diff --git a/src/criteria/tlm.rs b/src/criteria/tlm.rs index d22a7d48..cdb01a6f 100644 --- a/src/criteria/tlm.rs +++ b/src/criteria/tlm.rs @@ -20,7 +20,7 @@ use { tlmm_seat_focus::TlmMatchSeatFocus, tlmm_string::{ TlmMatchAppId, TlmMatchClass, TlmMatchInstance, TlmMatchRole, TlmMatchTag, - TlmMatchTitle, + TlmMatchTitle, TlmMatchWorkspace, }, tlmm_urgent::TlmMatchUrgent, tlmm_visible::TlmMatchVisible, @@ -57,6 +57,7 @@ bitflags! { TL_CHANGED_TAG = 1 << 10, TL_CHANGED_CLASS_INST = 1 << 11, TL_CHANGED_ROLE = 1 << 12, + TL_CHANGED_WORKSPACE = 1 << 13, } type TlmFixedRootMatcher = FixedRootMatcher; @@ -88,6 +89,7 @@ pub struct RootMatchers { class: TlmRootMatcherMap, instance: TlmRootMatcherMap, role: TlmRootMatcherMap, + workspace: TlmRootMatcherMap, } pub async fn handle_tl_changes(state: Rc) { @@ -219,6 +221,7 @@ impl TlMatcherManager { conditional!(TL_CHANGED_CLASS_INST, class); conditional!(TL_CHANGED_CLASS_INST, instance); conditional!(TL_CHANGED_ROLE, role); + conditional!(TL_CHANGED_WORKSPACE, workspace); fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_URGENT, urgent); @@ -295,6 +298,7 @@ impl TlMatcherManager { conditional!(TL_CHANGED_CLASS_INST, class); conditional!(TL_CHANGED_CLASS_INST, instance); conditional!(TL_CHANGED_ROLE, role); + conditional!(TL_CHANGED_WORKSPACE, workspace); fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_URGENT, urgent); @@ -364,6 +368,10 @@ impl TlMatcherManager { pub fn role(&self, string: CritLiteralOrRegex) -> Rc { self.root(TlmMatchRole::new(string)) } + + pub fn workspace(&self, string: CritLiteralOrRegex) -> Rc { + self.root(TlmMatchWorkspace::new(string)) + } } impl CritTarget for ToplevelData { diff --git a/src/criteria/tlm/tlm_matchers/tlmm_string.rs b/src/criteria/tlm/tlm_matchers/tlmm_string.rs index dc75b3b4..62413481 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_string.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_string.rs @@ -14,6 +14,7 @@ pub type TlmMatchTag = TlmMatchString; pub type TlmMatchClass = TlmMatchString; pub type TlmMatchInstance = TlmMatchString; pub type TlmMatchRole = TlmMatchString; +pub type TlmMatchWorkspace = TlmMatchString; pub struct TitleAccess; pub struct AppIdAccess; @@ -21,6 +22,7 @@ pub struct TagAccess; pub struct ClassAccess; pub struct InstanceAccess; pub struct RoleAccess; +pub struct WorkspaceAccess; impl StringAccess for TitleAccess { fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { @@ -93,3 +95,16 @@ impl StringAccess for RoleAccess { &roots.role } } + +impl StringAccess for WorkspaceAccess { + fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { + if let Some(ws) = data.workspace.get() { + return f(&ws.name); + } + false + } + + fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap> { + &roots.workspace + } +} diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 6992aac6..adcef45e 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -6,7 +6,7 @@ use { tlm::{ TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_FLOATING, TL_CHANGED_FULLSCREEN, TL_CHANGED_NEW, TL_CHANGED_TITLE, TL_CHANGED_URGENT, - TL_CHANGED_VISIBLE, TlMatcherChange, + TL_CHANGED_VISIBLE, TL_CHANGED_WORKSPACE, TlMatcherChange, }, }, ifs::{ @@ -131,6 +131,7 @@ impl ToplevelNode for T { let data = self.tl_data(); let prev = data.workspace.set(Some(ws.clone())); self.tl_set_workspace_ext(ws); + self.tl_data().property_changed(TL_CHANGED_WORKSPACE); let prev_id = prev.map(|p| p.output.get().id); let new_id = Some(ws.output.get().id); if prev_id != new_id { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 9effaf3d..553ec51b 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -273,6 +273,8 @@ pub struct WindowMatch { pub x_instance_regex: Option, pub x_role: Option, pub x_role_regex: Option, + pub workspace: Option, + pub workspace_regex: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/window_match.rs b/toml-config/src/config/parsers/window_match.rs index ca4291bf..b8479762 100644 --- a/toml-config/src/config/parsers/window_match.rs +++ b/toml-config/src/config/parsers/window_match.rs @@ -68,7 +68,16 @@ impl Parser for WindowMatchParser<'_> { tag, tag_regex, ), - (x_class, x_class_regex, x_instance, x_instance_regex, x_role, x_role_regex), + ( + x_class, + x_class_regex, + x_instance, + x_instance_regex, + x_role, + x_role_regex, + workspace, + workspace_regex, + ), ) = ext.extract(( ( opt(str("name")), @@ -100,6 +109,8 @@ impl Parser for WindowMatchParser<'_> { opt(str("x-instance-regex")), opt(str("x-role")), opt(str("x-role-regex")), + opt(str("workspace")), + opt(str("workspace-regex")), ), ))?; let mut not = None; @@ -159,6 +170,8 @@ impl Parser for WindowMatchParser<'_> { x_instance_regex: x_instance_regex.despan_into(), x_role: x_role.despan_into(), x_role_regex: x_role_regex.despan_into(), + workspace: workspace.despan_into(), + workspace_regex: workspace_regex.despan_into(), types, client, }) diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index b3b26228..f19d31fc 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -266,6 +266,8 @@ impl Rule for WindowRule { value!(XInstanceRegex, x_instance_regex); value!(XRole, x_role); value!(XRoleRegex, x_role_regex); + value!(WorkspaceName, workspace); + value!(WorkspaceNameRegex, workspace_regex); bool!(Floating, floating); bool!(Visible, visible); bool!(Urgent, urgent); diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 6038425b..d248c695 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1851,6 +1851,14 @@ "x-role-regex": { "type": "string", "description": "Matches the X role of the window with a regular expression." + }, + "workspace": { + "type": "string", + "description": "Matches the workspace of the window verbatim." + }, + "workspace-regex": { + "type": "string", + "description": "Matches the workspace of the window with a regular expression." } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 1bdd199b..2e249c4d 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -4121,6 +4121,18 @@ The table has the following fields: The value of this field should be a string. +- `workspace` (optional): + + Matches the workspace of the window verbatim. + + The value of this field should be a string. + +- `workspace-regex` (optional): + + Matches the workspace of the window with a regular expression. + + The value of this field should be a string. + ### `WindowMatchExactly` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 9f9dc04b..201a3c17 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3543,6 +3543,14 @@ WindowMatch: kind: string required: false description: Matches the X role of the window with a regular expression. + workspace: + kind: string + required: false + description: Matches the workspace of the window verbatim. + workspace-regex: + kind: string + required: false + description: Matches the workspace of the window with a regular expression. WindowMatchExactly: