1
0
Fork 0
forked from wry/wry

config: add window-rule infrastructure

This commit is contained in:
Julian Orth 2025-05-01 17:49:21 +02:00
parent a6257910bb
commit 59f8acdfde
26 changed files with 1829 additions and 38 deletions

View file

@ -4,7 +4,11 @@ mod logging;
pub(crate) mod string_error;
use {
crate::{client::ClientMatcher, video::Mode},
crate::{
client::ClientMatcher,
video::Mode,
window::{WindowMatcher, WindowType},
},
bincode::Options,
serde::{Deserialize, Serialize},
std::marker::PhantomData,
@ -95,3 +99,17 @@ pub enum ClientCriterionStringField {
Comm,
Exe,
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
pub enum WindowCriterionIpc {
Generic(GenericCriterionIpc<WindowMatcher>),
String {
string: String,
field: WindowCriterionStringField,
regex: bool,
},
Types(WindowType),
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
pub enum WindowCriterionStringField {}

View file

@ -4,7 +4,7 @@ use {
crate::{
_private::{
ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen,
GenericCriterionIpc, PollableId, VERSION, WireMode, bincode_ops,
GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc, WireMode, bincode_ops,
ipc::{
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
},
@ -31,7 +31,7 @@ use {
Transform, VrrMode,
connector_type::{CON_UNKNOWN, ConnectorType},
},
window::{Window, WindowType},
window::{MatchedWindow, Window, WindowCriterion, WindowMatcher, WindowType},
xwayland::XScalingMode,
},
bincode::Options,
@ -114,6 +114,7 @@ pub(crate) struct ConfigClient {
i3bar_separator: RefCell<Option<Rc<String>>>,
pressed_keysym: Cell<Option<KeySym>>,
client_match_handlers: RefCell<HashMap<ClientMatcher, ClientMatchHandler>>,
window_match_handlers: RefCell<HashMap<WindowMatcher, WindowMatchHandler>>,
feat_mod_mask: Cell<bool>,
}
@ -123,6 +124,11 @@ struct ClientMatchHandler {
latched: HashMap<Client, Box<dyn FnOnce()>>,
}
struct WindowMatchHandler {
cb: Callback<MatchedWindow>,
latched: HashMap<Window, Box<dyn FnOnce()>>,
}
struct Interest {
result: Option<Result<(), String>>,
waker: Option<Waker>,
@ -253,6 +259,7 @@ pub unsafe extern "C" fn init(
i3bar_separator: Default::default(),
pressed_keysym: Cell::new(None),
client_match_handlers: Default::default(),
window_match_handlers: Default::default(),
feat_mod_mask: Cell::new(false),
});
let init = unsafe { slice::from_raw_parts(init, size) };
@ -1593,6 +1600,95 @@ impl ConfigClient {
self.client_match_handlers.borrow_mut().remove(&matcher);
}
pub fn create_window_matcher(&self, criterion: WindowCriterion) -> WindowMatcher {
self.create_window_matcher_(criterion, false).0
}
fn create_window_matcher_(
&self,
criterion: WindowCriterion,
child: bool,
) -> (WindowMatcher, bool) {
#[expect(unused_macros)]
macro_rules! string {
($t:expr, $field:ident, $regex:expr) => {
WindowCriterionIpc::String {
string: $t.to_string(),
field: WindowCriterionStringField::$field,
regex: $regex,
}
};
}
let create_matcher = |criterion| {
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher {
criterion: WindowCriterionIpc::Generic(criterion),
});
get_response!(res, WindowMatcher(0), CreateWindowMatcher { matcher });
matcher
};
let destroy_matcher = |matcher| {
self.send(&ClientMessage::DestroyWindowMatcher { matcher });
};
let generic = |crit: GenericCriterion<WindowCriterion, WindowMatcher>| {
self.create_generic_matcher(
crit,
child,
|c| self.create_window_matcher_(c, true),
create_matcher,
destroy_matcher,
)
};
let criterion = match criterion {
WindowCriterion::Matcher(m) => return generic(GenericCriterion::Matcher(m)),
WindowCriterion::Not(c) => return generic(GenericCriterion::Not(c)),
WindowCriterion::All(c) => return generic(GenericCriterion::All(c)),
WindowCriterion::Any(c) => return generic(GenericCriterion::Any(c)),
WindowCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)),
WindowCriterion::Types(t) => WindowCriterionIpc::Types(t),
};
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
get_response!(
res,
(WindowMatcher(0), false),
CreateWindowMatcher { matcher }
);
(matcher, true)
}
pub fn set_window_matcher_handler(
&self,
matcher: WindowMatcher,
cb: impl FnMut(MatchedWindow) + 'static,
) {
let cb = Rc::new(RefCell::new(cb));
let handlers = &mut *self.window_match_handlers.borrow_mut();
let handler = handlers.entry(matcher).or_insert_with(|| {
self.send(&ClientMessage::EnableWindowMatcherEvents { matcher });
WindowMatchHandler {
cb: cb.clone(),
latched: Default::default(),
}
});
handler.cb = cb.clone();
}
pub fn set_window_matcher_latch_handler(
&self,
matcher: WindowMatcher,
window: Window,
cb: impl FnOnce() + 'static,
) {
let handlers = &mut *self.window_match_handlers.borrow_mut();
if let Some(handler) = handlers.get_mut(&matcher) {
handler.latched.insert(window, Box::new(cb));
}
}
pub fn destroy_window_matcher(&self, matcher: WindowMatcher) {
self.send(&ClientMessage::DestroyWindowMatcher { matcher });
self.window_match_handlers.borrow_mut().remove(&matcher);
}
fn handle_msg(&self, msg: &[u8]) {
self.handle_msg2(msg);
self.dispatch_futures();
@ -1879,6 +1975,30 @@ impl ConfigClient {
};
cb();
}
ServerMessage::WindowMatcherMatched { matcher, window } => {
let cb = {
let handlers = self.window_match_handlers.borrow();
let Some(handler) = handlers.get(&matcher) else {
return;
};
handler.cb.clone()
};
let matched = MatchedWindow { matcher, window };
cb.borrow_mut()(matched);
}
ServerMessage::WindowMatcherUnmatched { matcher, window } => {
let cb = {
let mut handlers = self.window_match_handlers.borrow_mut();
let Some(handler) = handlers.get_mut(&matcher) else {
return;
};
let Some(cb) = handler.latched.remove(&window) else {
return;
};
cb
};
cb();
}
}
}

View file

@ -1,6 +1,6 @@
use {
crate::{
_private::{ClientCriterionIpc, PollableId, WireMode},
_private::{ClientCriterionIpc, PollableId, WindowCriterionIpc, WireMode},
Axis, Direction, PciId, Workspace,
client::{Client, ClientMatcher},
input::{
@ -15,7 +15,7 @@ use {
ColorSpace, Connector, DrmDevice, Format, GfxApi, TearingMode, TransferFunction,
Transform, VrrMode, connector_type::ConnectorType,
},
window::{Window, WindowType},
window::{Window, WindowMatcher, WindowType},
xwayland::XScalingMode,
},
serde::{Deserialize, Serialize},
@ -102,6 +102,14 @@ pub enum ServerMessage {
matcher: ClientMatcher,
client: Client,
},
WindowMatcherMatched {
matcher: WindowMatcher,
window: Window,
},
WindowMatcherUnmatched {
matcher: WindowMatcher,
window: Window,
},
}
#[derive(Serialize, Deserialize, Debug)]
@ -681,6 +689,15 @@ pub enum ClientMessage<'a> {
EnableClientMatcherEvents {
matcher: ClientMatcher,
},
CreateWindowMatcher {
criterion: WindowCriterionIpc,
},
DestroyWindowMatcher {
matcher: WindowMatcher,
},
EnableWindowMatcherEvents {
matcher: WindowMatcher,
},
}
#[derive(Serialize, Deserialize, Debug)]
@ -904,6 +921,9 @@ pub enum Response {
CreateClientMatcher {
matcher: ClientMatcher,
},
CreateWindowMatcher {
matcher: WindowMatcher,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -3,6 +3,7 @@
use {
crate::{Axis, Direction, Workspace, client::Client},
serde::{Deserialize, Serialize},
std::ops::Deref,
};
/// A toplevel window.
@ -202,3 +203,87 @@ impl Window {
self.set_float_pinned(!self.float_pinned());
}
}
/// A window matcher.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct WindowMatcher(pub u64);
/// A matched window.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct MatchedWindow {
pub(crate) matcher: WindowMatcher,
pub(crate) window: Window,
}
/// A criterion for matching a window.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub enum WindowCriterion<'a> {
/// Matches if the contained matcher matches.
Matcher(WindowMatcher),
/// Matches if the contained criterion does not match.
Not(&'a WindowCriterion<'a>),
/// Matches if the window has one of the types.
Types(WindowType),
/// Matches if all of the contained criteria match.
All(&'a [WindowCriterion<'a>]),
/// Matches if any of the contained criteria match.
Any(&'a [WindowCriterion<'a>]),
/// Matches if an exact number of the contained criteria match.
Exactly(usize, &'a [WindowCriterion<'a>]),
}
impl WindowCriterion<'_> {
/// Converts the criterion to a matcher.
pub fn to_matcher(self) -> WindowMatcher {
get!(WindowMatcher(0)).create_window_matcher(self)
}
/// Binds a function to execute when the criterion matches a window.
///
/// This leaks the matcher.
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
self.to_matcher().bind(cb);
}
}
impl WindowMatcher {
/// Destroys the matcher.
///
/// Any bound callback will no longer be executed.
pub fn destroy(self) {
get!().destroy_window_matcher(self);
}
/// Sets a function to execute when the criterion matches a window.
///
/// Replaces any already bound callback.
pub fn bind<F: FnMut(MatchedWindow) + 'static>(self, cb: F) {
get!().set_window_matcher_handler(self, cb);
}
}
impl MatchedWindow {
/// Returns the window that matched.
pub fn window(self) -> Window {
self.window
}
/// Returns the matcher.
pub fn matcher(self) -> WindowMatcher {
self.matcher
}
/// Latches a function to be executed when the window no longer matches the criteria.
pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
get!().set_window_matcher_latch_handler(self.matcher, self.window, cb);
}
}
impl Deref for MatchedWindow {
type Target = Window;
fn deref(&self) -> &Self::Target {
&self.window
}
}