config: add window-rule infrastructure
This commit is contained in:
parent
a6257910bb
commit
59f8acdfde
26 changed files with 1829 additions and 38 deletions
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue