1
0
Fork 0
forked from wry/wry

config: add WM_WINDOW_ROLE window criteria

This commit is contained in:
Julian Orth 2025-05-03 14:01:39 +02:00
parent faa0b27ef8
commit 5ad6ca4dd3
14 changed files with 81 additions and 6 deletions

View file

@ -126,4 +126,5 @@ pub enum WindowCriterionStringField {
Tag, Tag,
XClass, XClass,
XInstance, XInstance,
XRole,
} }

View file

@ -1669,6 +1669,8 @@ impl ConfigClient {
WindowCriterion::XClassRegex(t) => string!(t, XClass, true), WindowCriterion::XClassRegex(t) => string!(t, XClass, true),
WindowCriterion::XInstance(t) => string!(t, XInstance, false), WindowCriterion::XInstance(t) => string!(t, XInstance, false),
WindowCriterion::XInstanceRegex(t) => string!(t, XInstance, true), 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 }); let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
get_response!( get_response!(

View file

@ -272,6 +272,10 @@ pub enum WindowCriterion<'a> {
XInstance(&'a str), XInstance(&'a str),
/// Matches the X instance of the window with a regular expression. /// Matches the X instance of the window with a regular expression.
XInstanceRegex(&'a str), 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<'_> { impl WindowCriterion<'_> {

View file

@ -1993,6 +1993,7 @@ impl ConfigProxyHandler {
WindowCriterionStringField::Tag => mgr.tag(needle), WindowCriterionStringField::Tag => mgr.tag(needle),
WindowCriterionStringField::XClass => mgr.class(needle), WindowCriterionStringField::XClass => mgr.class(needle),
WindowCriterionStringField::XInstance => mgr.instance(needle), WindowCriterionStringField::XInstance => mgr.instance(needle),
WindowCriterionStringField::XRole => mgr.role(needle),
} }
} }
WindowCriterionIpc::Types(t) => mgr.kind(*t), WindowCriterionIpc::Types(t) => mgr.kind(*t),

View file

@ -19,7 +19,8 @@ use {
tlmm_kind::TlmMatchKind, tlmm_kind::TlmMatchKind,
tlmm_seat_focus::TlmMatchSeatFocus, tlmm_seat_focus::TlmMatchSeatFocus,
tlmm_string::{ tlmm_string::{
TlmMatchAppId, TlmMatchClass, TlmMatchInstance, TlmMatchTag, TlmMatchTitle, TlmMatchAppId, TlmMatchClass, TlmMatchInstance, TlmMatchRole, TlmMatchTag,
TlmMatchTitle,
}, },
tlmm_urgent::TlmMatchUrgent, tlmm_urgent::TlmMatchUrgent,
tlmm_visible::TlmMatchVisible, tlmm_visible::TlmMatchVisible,
@ -55,6 +56,7 @@ bitflags! {
TL_CHANGED_JUST_MAPPED = 1 << 9, TL_CHANGED_JUST_MAPPED = 1 << 9,
TL_CHANGED_TAG = 1 << 10, TL_CHANGED_TAG = 1 << 10,
TL_CHANGED_CLASS_INST = 1 << 11, TL_CHANGED_CLASS_INST = 1 << 11,
TL_CHANGED_ROLE = 1 << 12,
} }
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>; type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
@ -85,6 +87,7 @@ pub struct RootMatchers {
seat_foci: TlmRootMatcherMap<TlmMatchSeatFocus>, seat_foci: TlmRootMatcherMap<TlmMatchSeatFocus>,
class: TlmRootMatcherMap<TlmMatchClass>, class: TlmRootMatcherMap<TlmMatchClass>,
instance: TlmRootMatcherMap<TlmMatchInstance>, instance: TlmRootMatcherMap<TlmMatchInstance>,
role: TlmRootMatcherMap<TlmMatchRole>,
} }
pub async fn handle_tl_changes(state: Rc<State>) { pub async fn handle_tl_changes(state: Rc<State>) {
@ -215,6 +218,7 @@ impl TlMatcherManager {
conditional!(TL_CHANGED_TAG, tag); conditional!(TL_CHANGED_TAG, tag);
conditional!(TL_CHANGED_CLASS_INST, class); conditional!(TL_CHANGED_CLASS_INST, class);
conditional!(TL_CHANGED_CLASS_INST, instance); conditional!(TL_CHANGED_CLASS_INST, instance);
conditional!(TL_CHANGED_ROLE, role);
fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_FLOATING, floating);
fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_VISIBLE, visible);
fixed_conditional!(TL_CHANGED_URGENT, urgent); fixed_conditional!(TL_CHANGED_URGENT, urgent);
@ -290,6 +294,7 @@ impl TlMatcherManager {
conditional!(TL_CHANGED_TAG, tag); conditional!(TL_CHANGED_TAG, tag);
conditional!(TL_CHANGED_CLASS_INST, class); conditional!(TL_CHANGED_CLASS_INST, class);
conditional!(TL_CHANGED_CLASS_INST, instance); conditional!(TL_CHANGED_CLASS_INST, instance);
conditional!(TL_CHANGED_ROLE, role);
fixed_conditional!(TL_CHANGED_FLOATING, floating); fixed_conditional!(TL_CHANGED_FLOATING, floating);
fixed_conditional!(TL_CHANGED_VISIBLE, visible); fixed_conditional!(TL_CHANGED_VISIBLE, visible);
fixed_conditional!(TL_CHANGED_URGENT, urgent); fixed_conditional!(TL_CHANGED_URGENT, urgent);
@ -355,6 +360,10 @@ impl TlMatcherManager {
pub fn instance(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> { pub fn instance(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
self.root(TlmMatchInstance::new(string)) self.root(TlmMatchInstance::new(string))
} }
pub fn role(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
self.root(TlmMatchRole::new(string))
}
} }
impl CritTarget for ToplevelData { impl CritTarget for ToplevelData {

View file

@ -13,12 +13,14 @@ pub type TlmMatchAppId = TlmMatchString<AppIdAccess>;
pub type TlmMatchTag = TlmMatchString<TagAccess>; pub type TlmMatchTag = TlmMatchString<TagAccess>;
pub type TlmMatchClass = TlmMatchString<ClassAccess>; pub type TlmMatchClass = TlmMatchString<ClassAccess>;
pub type TlmMatchInstance = TlmMatchString<InstanceAccess>; pub type TlmMatchInstance = TlmMatchString<InstanceAccess>;
pub type TlmMatchRole = TlmMatchString<RoleAccess>;
pub struct TitleAccess; pub struct TitleAccess;
pub struct AppIdAccess; pub struct AppIdAccess;
pub struct TagAccess; pub struct TagAccess;
pub struct ClassAccess; pub struct ClassAccess;
pub struct InstanceAccess; pub struct InstanceAccess;
pub struct RoleAccess;
impl StringAccess<ToplevelData> for TitleAccess { impl StringAccess<ToplevelData> for TitleAccess {
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
@ -78,3 +80,16 @@ impl StringAccess<ToplevelData> for InstanceAccess {
&roots.instance &roots.instance
} }
} }
impl StringAccess<ToplevelData> 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<TlmMatchString<Self>> {
&roots.role
}
}

View file

@ -93,7 +93,7 @@ pub struct XwindowInfo {
pub instance: RefCell<Option<String>>, pub instance: RefCell<Option<String>>,
pub class: RefCell<Option<String>>, pub class: RefCell<Option<String>>,
pub title: RefCell<Option<String>>, pub title: RefCell<Option<String>>,
pub role: RefCell<Option<BString>>, pub role: RefCell<Option<String>>,
pub protocols: CopyHashMap<u32, ()>, pub protocols: CopyHashMap<u32, ()>,
pub window_types: CopyHashMap<u32, ()>, pub window_types: CopyHashMap<u32, ()>,
pub never_focus: Cell<bool>, pub never_focus: Cell<bool>,

View file

@ -4,7 +4,7 @@ use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
client::Client, client::Client,
criteria::tlm::TL_CHANGED_CLASS_INST, criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE},
ifs::{ ifs::{
ipc::{ ipc::{
DataOfferId, DataSourceId, DynDataOffer, DynDataSource, IpcLocation, IpcVtable, DataOfferId, DataSourceId, DynDataOffer, DynDataSource, IpcLocation, IpcVtable,
@ -60,7 +60,7 @@ use {
xwayland::{XWaylandError, XWaylandEvent}, xwayland::{XWaylandError, XWaylandEvent},
}, },
ahash::{AHashMap, AHashSet}, ahash::{AHashMap, AHashSet},
bstr::ByteSlice, bstr::{ByteSlice, ByteVec},
futures_util::{FutureExt, select}, futures_util::{FutureExt, select},
smallvec::SmallVec, smallvec::SmallVec,
std::{ std::{
@ -1086,6 +1086,11 @@ impl Wm {
} }
async fn load_window_wm_window_role(&self, data: &Rc<XwindowData>) { async fn load_window_wm_window_role(&self, data: &Rc<XwindowData>) {
let property_changed = || {
if let Some(window) = data.window.get() {
window.toplevel_data.property_changed(TL_CHANGED_ROLE);
}
};
let mut buf = vec![]; let mut buf = vec![];
match self match self
.c .c
@ -1101,6 +1106,7 @@ impl Wm {
} }
Err(XconError::PropertyUnavailable) => { Err(XconError::PropertyUnavailable) => {
data.info.role.borrow_mut().take(); data.info.role.borrow_mut().take();
property_changed();
return; return;
} }
Err(e) => { Err(e) => {
@ -1112,7 +1118,8 @@ impl Wm {
} }
} }
// log::info!("{} role {}", data.window_id, buf.as_bstr()); // 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<XwindowData>) { async fn load_window_wm_class(&self, data: &Rc<XwindowData>) {

View file

@ -271,6 +271,8 @@ pub struct WindowMatch {
pub x_class_regex: Option<String>, pub x_class_regex: Option<String>,
pub x_instance: Option<String>, pub x_instance: Option<String>,
pub x_instance_regex: Option<String>, pub x_instance_regex: Option<String>,
pub x_role: Option<String>,
pub x_role_regex: Option<String>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -68,7 +68,7 @@ impl Parser for WindowMatchParser<'_> {
tag, tag,
tag_regex, 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(( ) = ext.extract((
( (
opt(str("name")), opt(str("name")),
@ -98,6 +98,8 @@ impl Parser for WindowMatchParser<'_> {
opt(str("x-class-regex")), opt(str("x-class-regex")),
opt(str("x-instance")), opt(str("x-instance")),
opt(str("x-instance-regex")), opt(str("x-instance-regex")),
opt(str("x-role")),
opt(str("x-role-regex")),
), ),
))?; ))?;
let mut not = None; let mut not = None;
@ -155,6 +157,8 @@ impl Parser for WindowMatchParser<'_> {
x_class_regex: x_class_regex.despan_into(), x_class_regex: x_class_regex.despan_into(),
x_instance: x_instance.despan_into(), x_instance: x_instance.despan_into(),
x_instance_regex: x_instance_regex.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, types,
client, client,
}) })

View file

@ -264,6 +264,8 @@ impl Rule for WindowRule {
value!(XClassRegex, x_class_regex); value!(XClassRegex, x_class_regex);
value!(XInstance, x_instance); value!(XInstance, x_instance);
value!(XInstanceRegex, x_instance_regex); value!(XInstanceRegex, x_instance_regex);
value!(XRole, x_role);
value!(XRoleRegex, x_role_regex);
bool!(Floating, floating); bool!(Floating, floating);
bool!(Visible, visible); bool!(Visible, visible);
bool!(Urgent, urgent); bool!(Urgent, urgent);

View file

@ -1843,6 +1843,14 @@
"x-instance-regex": { "x-instance-regex": {
"type": "string", "type": "string",
"description": "Matches the X instance of the window with a regular expression." "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": [] "required": []

View file

@ -4109,6 +4109,18 @@ The table has the following fields:
The value of this field should be a string. 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.
<a name="types-WindowMatchExactly"></a> <a name="types-WindowMatchExactly"></a>
### `WindowMatchExactly` ### `WindowMatchExactly`

View file

@ -3535,6 +3535,14 @@ WindowMatch:
kind: string kind: string
required: false required: false
description: Matches the X instance of the window with a regular expression. 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: WindowMatchExactly: