From 6d3d4dcabb9acc334443c5ad6d9df75dce92879b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 1 May 2025 17:31:42 +0200 Subject: [PATCH] config: add toplevel-tag window criteria --- jay-config/src/_private.rs | 1 + jay-config/src/_private/client.rs | 2 ++ jay-config/src/window.rs | 4 ++++ src/config/handler.rs | 1 + src/criteria/tlm.rs | 10 +++++++++- src/criteria/tlm/tlm_matchers/tlmm_string.rs | 17 ++++++++++++++++- src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs | 12 +++++++++++- src/ifs/xdg_toplevel_tag_manager_v1.rs | 12 +++++++++++- src/tree/toplevel.rs | 6 +++--- toml-config/src/config.rs | 2 ++ toml-config/src/config/parsers/window_match.rs | 17 ++++++++++++++++- 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 ++++++++ 15 files changed, 106 insertions(+), 8 deletions(-) diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index 62ff5b09..881493e5 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -123,4 +123,5 @@ pub enum WindowCriterionIpc { pub enum WindowCriterionStringField { Title, AppId, + Tag, } diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 6bdd2f1e..68fc1678 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1663,6 +1663,8 @@ impl ConfigClient { WindowCriterion::Focus(seat) => WindowCriterionIpc::SeatFocus(seat), WindowCriterion::Fullscreen => WindowCriterionIpc::Fullscreen, WindowCriterion::JustMapped => WindowCriterionIpc::JustMapped, + WindowCriterion::Tag(t) => string!(t, Tag, false), + WindowCriterion::TagRegex(t) => string!(t, Tag, 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 a5ef9036..3ee6bef9 100644 --- a/jay-config/src/window.rs +++ b/jay-config/src/window.rs @@ -260,6 +260,10 @@ pub enum WindowCriterion<'a> { /// This is true for one iteration of the compositor's main loop immediately after the /// window has been mapped. JustMapped, + /// Matches the toplevel-tag of the window verbatim. + Tag(&'a str), + /// Matches the toplevel-tag of the window with a regular expression. + TagRegex(&'a str), } impl WindowCriterion<'_> { diff --git a/src/config/handler.rs b/src/config/handler.rs index 2241dc08..0f17561f 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1990,6 +1990,7 @@ impl ConfigProxyHandler { match *field { WindowCriterionStringField::Title => mgr.title(needle), WindowCriterionStringField::AppId => mgr.app_id(needle), + WindowCriterionStringField::Tag => mgr.tag(needle), } } WindowCriterionIpc::Types(t) => mgr.kind(*t), diff --git a/src/criteria/tlm.rs b/src/criteria/tlm.rs index 75b6fa08..042e245f 100644 --- a/src/criteria/tlm.rs +++ b/src/criteria/tlm.rs @@ -18,7 +18,7 @@ use { tlmm_just_mapped::TlmMatchJustMapped, tlmm_kind::TlmMatchKind, tlmm_seat_focus::TlmMatchSeatFocus, - tlmm_string::{TlmMatchAppId, TlmMatchTitle}, + tlmm_string::{TlmMatchAppId, TlmMatchTag, TlmMatchTitle}, tlmm_urgent::TlmMatchUrgent, tlmm_visible::TlmMatchVisible, }, @@ -51,6 +51,7 @@ bitflags! { TL_CHANGED_SEAT_FOCI = 1 << 7, TL_CHANGED_FULLSCREEN = 1 << 8, TL_CHANGED_JUST_MAPPED = 1 << 9, + TL_CHANGED_TAG = 1 << 10, } type TlmFixedRootMatcher = FixedRootMatcher; @@ -76,6 +77,7 @@ pub struct RootMatchers { kinds: TlmRootMatcherMap, clients: CopyHashMap>, title: TlmRootMatcherMap, + tag: TlmRootMatcherMap, app_id: TlmRootMatcherMap, seat_foci: TlmRootMatcherMap, } @@ -205,6 +207,7 @@ impl TlMatcherManager { conditional!(TL_CHANGED_TITLE, title); conditional!(TL_CHANGED_APP_ID, app_id); conditional!(TL_CHANGED_SEAT_FOCI, seat_foci); + conditional!(TL_CHANGED_TAG, tag); fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_URGENT, urgent); @@ -277,6 +280,7 @@ impl TlMatcherManager { conditional!(TL_CHANGED_TITLE, title); conditional!(TL_CHANGED_APP_ID, app_id); conditional!(TL_CHANGED_SEAT_FOCI, seat_foci); + conditional!(TL_CHANGED_TAG, tag); fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_URGENT, urgent); @@ -299,6 +303,10 @@ impl TlMatcherManager { self.root(TlmMatchAppId::new(string)) } + pub fn tag(&self, string: CritLiteralOrRegex) -> Rc { + self.root(TlmMatchTag::new(string)) + } + pub fn floating(&self) -> Rc { self.floating[true].clone() } diff --git a/src/criteria/tlm/tlm_matchers/tlmm_string.rs b/src/criteria/tlm/tlm_matchers/tlmm_string.rs index 967d8032..352c08b4 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_string.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_string.rs @@ -3,16 +3,18 @@ use crate::{ crit_matchers::critm_string::{CritMatchString, StringAccess}, tlm::{RootMatchers, TlmRootMatcherMap}, }, - tree::ToplevelData, + tree::{ToplevelData, ToplevelType}, }; pub type TlmMatchString = CritMatchString; pub type TlmMatchTitle = TlmMatchString; pub type TlmMatchAppId = TlmMatchString; +pub type TlmMatchTag = TlmMatchString; pub struct TitleAccess; pub struct AppIdAccess; +pub struct TagAccess; impl StringAccess for TitleAccess { fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { @@ -33,3 +35,16 @@ impl StringAccess for AppIdAccess { &roots.app_id } } + +impl StringAccess for TagAccess { + fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { + if let ToplevelType::XdgToplevel(data) = &data.kind { + return f(&data.tag.borrow()); + } + false + } + + fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap> { + &roots.tag + } +} diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 0931ff8d..9bdc3d3c 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -92,6 +92,11 @@ pub enum Decoration { Server, } +#[derive(Debug)] +pub struct XdgToplevelToplevelData { + pub tag: RefCell, +} + pub struct XdgToplevel { pub id: XdgToplevelId, pub state: Rc, @@ -112,6 +117,7 @@ pub struct XdgToplevel { is_mapped: Cell, dialog: CloneCell>>, extents_set: Cell, + pub data: Rc, } impl Debug for XdgToplevel { @@ -135,6 +141,9 @@ impl XdgToplevel { } let state = &surface.surface.client.state; let node_id = state.node_ids.next(); + let data = Rc::new(XdgToplevelToplevelData { + tag: Default::default(), + }); Self { id, state: state.clone(), @@ -154,7 +163,7 @@ impl XdgToplevel { state, String::new(), Some(surface.surface.client.clone()), - ToplevelType::XdgToplevel, + ToplevelType::XdgToplevel(data.clone()), node_id, slf, ), @@ -162,6 +171,7 @@ impl XdgToplevel { is_mapped: Cell::new(false), dialog: Default::default(), extents_set: Cell::new(false), + data, } } diff --git a/src/ifs/xdg_toplevel_tag_manager_v1.rs b/src/ifs/xdg_toplevel_tag_manager_v1.rs index 69fc3db1..cdcecdab 100644 --- a/src/ifs/xdg_toplevel_tag_manager_v1.rs +++ b/src/ifs/xdg_toplevel_tag_manager_v1.rs @@ -1,9 +1,11 @@ use { crate::{ client::{Client, ClientError}, + criteria::tlm::TL_CHANGED_TAG, globals::{Global, GlobalName}, leaks::Tracker, object::{Object, Version}, + tree::ToplevelNodeBase, wire::{XdgToplevelTagManagerV1Id, xdg_toplevel_tag_manager_v1::*}, }, std::rc::Rc, @@ -72,9 +74,17 @@ impl XdgToplevelTagManagerV1RequestHandler for XdgToplevelTagManagerV1 { fn set_toplevel_tag( &self, - _req: SetToplevelTag<'_>, + req: SetToplevelTag<'_>, _slf: &Rc, ) -> Result<(), Self::Error> { + let tl = self.client.lookup(req.toplevel)?; + let tag = &mut *tl.data.tag.borrow_mut(); + if tag == req.tag { + return Ok(()); + } + tag.clear(); + tag.push_str(req.tag); + tl.tl_data().property_changed(TL_CHANGED_TAG); Ok(()) } diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index d328b2ee..dd8daa83 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -16,7 +16,7 @@ use { jay_screencast::JayScreencast, jay_toplevel::JayToplevel, wl_seat::{NodeSeatState, SeatId, collect_kb_foci, collect_kb_foci2}, - wl_surface::WlSurface, + wl_surface::{WlSurface, xdg_surface::xdg_toplevel::XdgToplevelToplevelData}, }, rect::Rect, state::State, @@ -277,7 +277,7 @@ impl ToplevelOpt { pub enum ToplevelType { Container, Placeholder, - XdgToplevel, + XdgToplevel(Rc), XWindow, } @@ -286,7 +286,7 @@ impl ToplevelType { match self { ToplevelType::Container => window::CONTAINER, ToplevelType::Placeholder => window::PLACEHOLDER, - ToplevelType::XdgToplevel => window::XDG_TOPLEVEL, + ToplevelType::XdgToplevel { .. } => window::XDG_TOPLEVEL, ToplevelType::XWindow => window::X_WINDOW, } } diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index dc35cbce..bd95a0e3 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -265,6 +265,8 @@ pub struct WindowMatch { pub focused: Option, pub fullscreen: Option, pub just_mapped: Option, + pub tag: Option, + pub tag_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 864e897c..f0f3eba8 100644 --- a/toml-config/src/config/parsers/window_match.rs +++ b/toml-config/src/config/parsers/window_match.rs @@ -56,7 +56,18 @@ impl Parser for WindowMatchParser<'_> { title, title_regex, ), - (app_id, app_id_regex, floating, visible, urgent, focused, fullscreen, just_mapped), + ( + app_id, + app_id_regex, + floating, + visible, + urgent, + focused, + fullscreen, + just_mapped, + tag, + tag_regex, + ), ) = ext.extract(( ( opt(str("name")), @@ -78,6 +89,8 @@ impl Parser for WindowMatchParser<'_> { opt(bol("focused")), opt(bol("fullscreen")), opt(bol("just-mapped")), + opt(str("tag")), + opt(str("tag-regex")), ), ))?; let mut not = None; @@ -129,6 +142,8 @@ impl Parser for WindowMatchParser<'_> { focused: focused.despan(), fullscreen: fullscreen.despan(), just_mapped: just_mapped.despan(), + tag: tag.despan_into(), + tag_regex: tag_regex.despan_into(), types, client, }) diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index 42c75b24..c58f0f26 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -258,6 +258,8 @@ impl Rule for WindowRule { value!(TitleRegex, title_regex); value!(AppId, app_id); value!(AppIdRegex, app_id_regex); + value!(Tag, tag); + value!(TagRegex, tag_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 7413ab70..1bb7c8a8 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1819,6 +1819,14 @@ "just-mapped": { "type": "boolean", "description": "Matches if the window has/hasn't just been mapped.\n\nThis is true for one iteration of the compositor's main loop immediately after the\nwindow has been mapped.\n" + }, + "tag": { + "type": "string", + "description": "Matches the toplevel-tag of the window verbatim." + }, + "tag-regex": { + "type": "string", + "description": "Matches the toplevel-tag 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 b8c5192d..e5f0e8a4 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -4073,6 +4073,18 @@ The table has the following fields: The value of this field should be a boolean. +- `tag` (optional): + + Matches the toplevel-tag of the window verbatim. + + The value of this field should be a string. + +- `tag-regex` (optional): + + Matches the toplevel-tag 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 79b7294d..134dba12 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3511,6 +3511,14 @@ WindowMatch: This is true for one iteration of the compositor's main loop immediately after the window has been mapped. + tag: + kind: string + required: false + description: Matches the toplevel-tag of the window verbatim. + tag-regex: + kind: string + required: false + description: Matches the toplevel-tag of the window with a regular expression. WindowMatchExactly: