diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index 6d666c2f..97d49c27 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -126,4 +126,5 @@ pub enum WindowCriterionStringField { Tag, XClass, XInstance, + XRole, } diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 14cd901b..963664a6 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1669,6 +1669,8 @@ impl ConfigClient { WindowCriterion::XClassRegex(t) => string!(t, XClass, true), WindowCriterion::XInstance(t) => string!(t, XInstance, false), WindowCriterion::XInstanceRegex(t) => string!(t, XInstance, true), + WindowCriterion::XRole(t) => string!(t, XRole, false), + WindowCriterion::XRoleRegex(t) => string!(t, XRole, 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 e2438e21..05ce9cbc 100644 --- a/jay-config/src/window.rs +++ b/jay-config/src/window.rs @@ -272,6 +272,10 @@ pub enum WindowCriterion<'a> { XInstance(&'a str), /// Matches the X instance of the window with a regular expression. XInstanceRegex(&'a str), + /// Matches the X role of the window verbatim. + XRole(&'a str), + /// Matches the X role of the window with a regular expression. + XRoleRegex(&'a str), } impl WindowCriterion<'_> { diff --git a/src/config/handler.rs b/src/config/handler.rs index 6b9660f0..06ca2982 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1993,6 +1993,7 @@ impl ConfigProxyHandler { WindowCriterionStringField::Tag => mgr.tag(needle), WindowCriterionStringField::XClass => mgr.class(needle), WindowCriterionStringField::XInstance => mgr.instance(needle), + WindowCriterionStringField::XRole => mgr.role(needle), } } WindowCriterionIpc::Types(t) => mgr.kind(*t), diff --git a/src/criteria/tlm.rs b/src/criteria/tlm.rs index e58a51ed..d22a7d48 100644 --- a/src/criteria/tlm.rs +++ b/src/criteria/tlm.rs @@ -19,7 +19,8 @@ use { tlmm_kind::TlmMatchKind, tlmm_seat_focus::TlmMatchSeatFocus, tlmm_string::{ - TlmMatchAppId, TlmMatchClass, TlmMatchInstance, TlmMatchTag, TlmMatchTitle, + TlmMatchAppId, TlmMatchClass, TlmMatchInstance, TlmMatchRole, TlmMatchTag, + TlmMatchTitle, }, tlmm_urgent::TlmMatchUrgent, tlmm_visible::TlmMatchVisible, @@ -55,6 +56,7 @@ bitflags! { TL_CHANGED_JUST_MAPPED = 1 << 9, TL_CHANGED_TAG = 1 << 10, TL_CHANGED_CLASS_INST = 1 << 11, + TL_CHANGED_ROLE = 1 << 12, } type TlmFixedRootMatcher = FixedRootMatcher; @@ -85,6 +87,7 @@ pub struct RootMatchers { seat_foci: TlmRootMatcherMap, class: TlmRootMatcherMap, instance: TlmRootMatcherMap, + role: TlmRootMatcherMap, } pub async fn handle_tl_changes(state: Rc) { @@ -215,6 +218,7 @@ impl TlMatcherManager { conditional!(TL_CHANGED_TAG, tag); conditional!(TL_CHANGED_CLASS_INST, class); conditional!(TL_CHANGED_CLASS_INST, instance); + conditional!(TL_CHANGED_ROLE, role); fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_URGENT, urgent); @@ -290,6 +294,7 @@ impl TlMatcherManager { conditional!(TL_CHANGED_TAG, tag); conditional!(TL_CHANGED_CLASS_INST, class); conditional!(TL_CHANGED_CLASS_INST, instance); + conditional!(TL_CHANGED_ROLE, role); fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_URGENT, urgent); @@ -355,6 +360,10 @@ impl TlMatcherManager { pub fn instance(&self, string: CritLiteralOrRegex) -> Rc { self.root(TlmMatchInstance::new(string)) } + + pub fn role(&self, string: CritLiteralOrRegex) -> Rc { + self.root(TlmMatchRole::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 6800ecdf..dc75b3b4 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_string.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_string.rs @@ -13,12 +13,14 @@ pub type TlmMatchAppId = TlmMatchString; pub type TlmMatchTag = TlmMatchString; pub type TlmMatchClass = TlmMatchString; pub type TlmMatchInstance = TlmMatchString; +pub type TlmMatchRole = TlmMatchString; pub struct TitleAccess; pub struct AppIdAccess; pub struct TagAccess; pub struct ClassAccess; pub struct InstanceAccess; +pub struct RoleAccess; impl StringAccess for TitleAccess { fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { @@ -78,3 +80,16 @@ impl StringAccess for InstanceAccess { &roots.instance } } + +impl StringAccess for RoleAccess { + fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { + if let ToplevelType::XWindow(data) = &data.kind { + return f(&data.info.role.borrow().as_deref().unwrap_or_default()); + } + false + } + + fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap> { + &roots.role + } +} diff --git a/src/ifs/wl_surface/x_surface/xwindow.rs b/src/ifs/wl_surface/x_surface/xwindow.rs index e0f497be..2bceaf68 100644 --- a/src/ifs/wl_surface/x_surface/xwindow.rs +++ b/src/ifs/wl_surface/x_surface/xwindow.rs @@ -93,7 +93,7 @@ pub struct XwindowInfo { pub instance: RefCell>, pub class: RefCell>, pub title: RefCell>, - pub role: RefCell>, + pub role: RefCell>, pub protocols: CopyHashMap, pub window_types: CopyHashMap, pub never_focus: Cell, diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 8f1d27b9..7d22e3e3 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -4,7 +4,7 @@ use { crate::{ async_engine::SpawnedFuture, client::Client, - criteria::tlm::TL_CHANGED_CLASS_INST, + criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE}, ifs::{ ipc::{ DataOfferId, DataSourceId, DynDataOffer, DynDataSource, IpcLocation, IpcVtable, @@ -60,7 +60,7 @@ use { xwayland::{XWaylandError, XWaylandEvent}, }, ahash::{AHashMap, AHashSet}, - bstr::ByteSlice, + bstr::{ByteSlice, ByteVec}, futures_util::{FutureExt, select}, smallvec::SmallVec, std::{ @@ -1086,6 +1086,11 @@ impl Wm { } async fn load_window_wm_window_role(&self, data: &Rc) { + let property_changed = || { + if let Some(window) = data.window.get() { + window.toplevel_data.property_changed(TL_CHANGED_ROLE); + } + }; let mut buf = vec![]; match self .c @@ -1101,6 +1106,7 @@ impl Wm { } Err(XconError::PropertyUnavailable) => { data.info.role.borrow_mut().take(); + property_changed(); return; } Err(e) => { @@ -1112,7 +1118,8 @@ impl Wm { } } // log::info!("{} role {}", data.window_id, buf.as_bstr()); - *data.info.role.borrow_mut() = Some(buf.into()); + *data.info.role.borrow_mut() = Some(buf.into_string_lossy()); + property_changed(); } async fn load_window_wm_class(&self, data: &Rc) { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 04076390..9effaf3d 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -271,6 +271,8 @@ pub struct WindowMatch { pub x_class_regex: Option, pub x_instance: Option, pub x_instance_regex: Option, + pub x_role: Option, + pub x_role_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 e2d5ef2b..ca4291bf 100644 --- a/toml-config/src/config/parsers/window_match.rs +++ b/toml-config/src/config/parsers/window_match.rs @@ -68,7 +68,7 @@ impl Parser for WindowMatchParser<'_> { tag, tag_regex, ), - (x_class, x_class_regex, x_instance, x_instance_regex), + (x_class, x_class_regex, x_instance, x_instance_regex, x_role, x_role_regex), ) = ext.extract(( ( opt(str("name")), @@ -98,6 +98,8 @@ impl Parser for WindowMatchParser<'_> { opt(str("x-class-regex")), opt(str("x-instance")), opt(str("x-instance-regex")), + opt(str("x-role")), + opt(str("x-role-regex")), ), ))?; let mut not = None; @@ -155,6 +157,8 @@ impl Parser for WindowMatchParser<'_> { x_class_regex: x_class_regex.despan_into(), x_instance: x_instance.despan_into(), x_instance_regex: x_instance_regex.despan_into(), + x_role: x_role.despan_into(), + x_role_regex: x_role_regex.despan_into(), types, client, }) diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index e03c03b9..b3b26228 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -264,6 +264,8 @@ impl Rule for WindowRule { value!(XClassRegex, x_class_regex); value!(XInstance, x_instance); value!(XInstanceRegex, x_instance_regex); + value!(XRole, x_role); + value!(XRoleRegex, x_role_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 72c6947d..6038425b 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1843,6 +1843,14 @@ "x-instance-regex": { "type": "string", "description": "Matches the X instance of the window with a regular expression." + }, + "x-role": { + "type": "string", + "description": "Matches the X role of the window verbatim." + }, + "x-role-regex": { + "type": "string", + "description": "Matches the X role 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 90d80aca..1bdd199b 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -4109,6 +4109,18 @@ The table has the following fields: The value of this field should be a string. +- `x-role` (optional): + + Matches the X role of the window verbatim. + + The value of this field should be a string. + +- `x-role-regex` (optional): + + Matches the X role 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 e3f76683..9f9dc04b 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3535,6 +3535,14 @@ WindowMatch: kind: string required: false description: Matches the X instance of the window with a regular expression. + x-role: + kind: string + required: false + description: Matches the X role of the window verbatim. + x-role-regex: + kind: string + required: false + description: Matches the X role of the window with a regular expression. WindowMatchExactly: