config: add client-rule infrastructure
This commit is contained in:
parent
17e715cde4
commit
fd2163d658
32 changed files with 1804 additions and 27 deletions
|
|
@ -4,7 +4,7 @@ mod logging;
|
|||
pub(crate) mod string_error;
|
||||
|
||||
use {
|
||||
crate::video::Mode,
|
||||
crate::{client::ClientMatcher, video::Mode},
|
||||
bincode::Options,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::marker::PhantomData,
|
||||
|
|
@ -64,3 +64,24 @@ impl WireMode {
|
|||
pub struct PollableId(pub u64);
|
||||
|
||||
pub const DEFAULT_SEAT_NAME: &str = "default";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum GenericCriterionIpc<T> {
|
||||
Matcher(T),
|
||||
Not(T),
|
||||
List { list: Vec<T>, all: bool },
|
||||
Exactly { list: Vec<T>, num: usize },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum ClientCriterionIpc {
|
||||
Generic(GenericCriterionIpc<ClientMatcher>),
|
||||
String {
|
||||
string: String,
|
||||
field: ClientCriterionStringField,
|
||||
regex: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum ClientCriterionStringField {}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@
|
|||
use {
|
||||
crate::{
|
||||
_private::{
|
||||
Config, ConfigEntry, ConfigEntryGen, PollableId, VERSION, WireMode, bincode_ops,
|
||||
ClientCriterionIpc, Config, ConfigEntry, ConfigEntryGen, GenericCriterionIpc,
|
||||
PollableId, VERSION, WireMode, bincode_ops,
|
||||
ipc::{
|
||||
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
|
||||
},
|
||||
logging,
|
||||
},
|
||||
Axis, Direction, ModifiedKeySym, PciId, Workspace,
|
||||
client::Client,
|
||||
client::{Client, ClientCriterion, ClientMatcher, MatchedClient},
|
||||
exec::Command,
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, acceleration::AccelProfile,
|
||||
|
|
@ -112,10 +113,16 @@ pub(crate) struct ConfigClient {
|
|||
status_task: Cell<Vec<JoinHandle<()>>>,
|
||||
i3bar_separator: RefCell<Option<Rc<String>>>,
|
||||
pressed_keysym: Cell<Option<KeySym>>,
|
||||
client_match_handlers: RefCell<HashMap<ClientMatcher, ClientMatchHandler>>,
|
||||
|
||||
feat_mod_mask: Cell<bool>,
|
||||
}
|
||||
|
||||
struct ClientMatchHandler {
|
||||
cb: Callback<MatchedClient>,
|
||||
latched: HashMap<Client, Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
struct Interest {
|
||||
result: Option<Result<(), String>>,
|
||||
waker: Option<Waker>,
|
||||
|
|
@ -245,6 +252,7 @@ pub unsafe extern "C" fn init(
|
|||
status_task: Default::default(),
|
||||
i3bar_separator: Default::default(),
|
||||
pressed_keysym: Cell::new(None),
|
||||
client_match_handlers: Default::default(),
|
||||
feat_mod_mask: Cell::new(false),
|
||||
});
|
||||
let init = unsafe { slice::from_raw_parts(init, size) };
|
||||
|
|
@ -280,6 +288,16 @@ macro_rules! get_response {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
enum GenericCriterion<'a, Crit, Matcher> {
|
||||
Matcher(Matcher),
|
||||
Not(&'a Crit),
|
||||
All(&'a [Crit]),
|
||||
Any(&'a [Crit]),
|
||||
Exactly(usize, &'a [Crit]),
|
||||
}
|
||||
|
||||
impl ConfigClient {
|
||||
fn send(&self, msg: &ClientMessage) {
|
||||
let mut buf = self.bufs.borrow_mut().pop().unwrap_or_default();
|
||||
|
|
@ -1419,6 +1437,151 @@ impl ConfigClient {
|
|||
self.send(&ClientMessage::ClientKill { client });
|
||||
}
|
||||
|
||||
fn create_generic_matcher<Crit, Matcher>(
|
||||
&self,
|
||||
criterion: GenericCriterion<'_, Crit, Matcher>,
|
||||
child: bool,
|
||||
create_child_matcher: impl Fn(Crit) -> (Matcher, bool),
|
||||
create_matcher: impl Fn(GenericCriterionIpc<Matcher>) -> Matcher,
|
||||
destroy_matcher: impl Fn(Matcher),
|
||||
) -> (Matcher, bool)
|
||||
where
|
||||
Crit: Copy,
|
||||
Matcher: Copy,
|
||||
{
|
||||
let mut ad_hoc = vec![];
|
||||
let mut create_child_matcher = |c: Crit| {
|
||||
let (m, original) = create_child_matcher(c);
|
||||
if original {
|
||||
ad_hoc.push(m);
|
||||
}
|
||||
m
|
||||
};
|
||||
let mut create_vec = |l: &[Crit]| {
|
||||
let mut list = Vec::with_capacity(l.len());
|
||||
for c in l {
|
||||
list.push(create_child_matcher(*c));
|
||||
}
|
||||
list
|
||||
};
|
||||
let criterion = match criterion {
|
||||
GenericCriterion::Matcher(m) => {
|
||||
if child {
|
||||
return (m, false);
|
||||
}
|
||||
GenericCriterionIpc::Matcher(m)
|
||||
}
|
||||
GenericCriterion::Not(c) => GenericCriterionIpc::Not(create_child_matcher(*c)),
|
||||
GenericCriterion::All(l) => GenericCriterionIpc::List {
|
||||
list: create_vec(l),
|
||||
all: true,
|
||||
},
|
||||
GenericCriterion::Any(l) => GenericCriterionIpc::List {
|
||||
list: create_vec(l),
|
||||
all: false,
|
||||
},
|
||||
GenericCriterion::Exactly(num, l) => GenericCriterionIpc::Exactly {
|
||||
list: create_vec(l),
|
||||
num,
|
||||
},
|
||||
};
|
||||
let matcher = create_matcher(criterion);
|
||||
for matcher in ad_hoc {
|
||||
destroy_matcher(matcher);
|
||||
}
|
||||
(matcher, true)
|
||||
}
|
||||
|
||||
pub fn create_client_matcher(&self, criterion: ClientCriterion<'_>) -> ClientMatcher {
|
||||
self.create_client_matcher_(criterion, false).0
|
||||
}
|
||||
|
||||
fn create_client_matcher_(
|
||||
&self,
|
||||
criterion: ClientCriterion<'_>,
|
||||
child: bool,
|
||||
) -> (ClientMatcher, bool) {
|
||||
#[expect(unused_macros)]
|
||||
macro_rules! string {
|
||||
($t:expr, $field:ident, $regex:expr) => {
|
||||
ClientCriterionIpc::String {
|
||||
string: $t.to_string(),
|
||||
field: ClientCriterionStringField::$field,
|
||||
regex: $regex,
|
||||
}
|
||||
};
|
||||
}
|
||||
let create_matcher = |criterion| {
|
||||
let res = self.send_with_response(&ClientMessage::CreateClientMatcher {
|
||||
criterion: ClientCriterionIpc::Generic(criterion),
|
||||
});
|
||||
get_response!(res, ClientMatcher(0), CreateClientMatcher { matcher });
|
||||
matcher
|
||||
};
|
||||
let destroy_matcher = |matcher| {
|
||||
self.send(&ClientMessage::DestroyClientMatcher { matcher });
|
||||
};
|
||||
let generic = |crit: GenericCriterion<ClientCriterion, ClientMatcher>| {
|
||||
self.create_generic_matcher::<ClientCriterion, ClientMatcher>(
|
||||
crit,
|
||||
child,
|
||||
|c| self.create_client_matcher_(c, true),
|
||||
create_matcher,
|
||||
destroy_matcher,
|
||||
)
|
||||
};
|
||||
#[expect(unused_variables)]
|
||||
let criterion = match criterion {
|
||||
ClientCriterion::Matcher(m) => return generic(GenericCriterion::Matcher(m)),
|
||||
ClientCriterion::Not(c) => return generic(GenericCriterion::Not(c)),
|
||||
ClientCriterion::All(c) => return generic(GenericCriterion::All(c)),
|
||||
ClientCriterion::Any(c) => return generic(GenericCriterion::Any(c)),
|
||||
ClientCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)),
|
||||
};
|
||||
#[expect(unreachable_code)]
|
||||
let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion });
|
||||
get_response!(
|
||||
res,
|
||||
(ClientMatcher(0), false),
|
||||
CreateClientMatcher { matcher }
|
||||
);
|
||||
(matcher, true)
|
||||
}
|
||||
|
||||
pub fn set_client_matcher_handler(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
cb: impl FnMut(MatchedClient) + 'static,
|
||||
) {
|
||||
let cb = Rc::new(RefCell::new(cb));
|
||||
let handlers = &mut *self.client_match_handlers.borrow_mut();
|
||||
let handler = handlers.entry(matcher).or_insert_with(|| {
|
||||
self.send(&ClientMessage::EnableClientMatcherEvents { matcher });
|
||||
ClientMatchHandler {
|
||||
cb: cb.clone(),
|
||||
latched: Default::default(),
|
||||
}
|
||||
});
|
||||
handler.cb = cb.clone();
|
||||
}
|
||||
|
||||
pub fn set_client_matcher_latch_handler(
|
||||
&self,
|
||||
matcher: ClientMatcher,
|
||||
client: Client,
|
||||
cb: impl FnOnce() + 'static,
|
||||
) {
|
||||
let handlers = &mut *self.client_match_handlers.borrow_mut();
|
||||
if let Some(handler) = handlers.get_mut(&matcher) {
|
||||
handler.latched.insert(client, Box::new(cb));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy_client_matcher(&self, matcher: ClientMatcher) {
|
||||
self.send(&ClientMessage::DestroyClientMatcher { matcher });
|
||||
self.client_match_handlers.borrow_mut().remove(&matcher);
|
||||
}
|
||||
|
||||
fn handle_msg(&self, msg: &[u8]) {
|
||||
self.handle_msg2(msg);
|
||||
self.dispatch_futures();
|
||||
|
|
@ -1681,6 +1844,30 @@ impl ConfigClient {
|
|||
run_cb("switch event", &cb, event);
|
||||
}
|
||||
}
|
||||
ServerMessage::ClientMatcherMatched { matcher, client } => {
|
||||
let cb = {
|
||||
let handlers = self.client_match_handlers.borrow();
|
||||
let Some(handler) = handlers.get(&matcher) else {
|
||||
return;
|
||||
};
|
||||
handler.cb.clone()
|
||||
};
|
||||
let matched = MatchedClient { matcher, client };
|
||||
cb.borrow_mut()(matched);
|
||||
}
|
||||
ServerMessage::ClientMatcherUnmatched { matcher, client } => {
|
||||
let cb = {
|
||||
let mut handlers = self.client_match_handlers.borrow_mut();
|
||||
let Some(handler) = handlers.get_mut(&matcher) else {
|
||||
return;
|
||||
};
|
||||
let Some(cb) = handler.latched.remove(&client) else {
|
||||
return;
|
||||
};
|
||||
cb
|
||||
};
|
||||
cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use {
|
||||
crate::{
|
||||
_private::{PollableId, WireMode},
|
||||
_private::{ClientCriterionIpc, PollableId, WireMode},
|
||||
Axis, Direction, PciId, Workspace,
|
||||
client::Client,
|
||||
client::{Client, ClientMatcher},
|
||||
input::{
|
||||
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, acceleration::AccelProfile,
|
||||
capability::Capability,
|
||||
|
|
@ -94,6 +94,14 @@ pub enum ServerMessage {
|
|||
input_device: InputDevice,
|
||||
event: SwitchEvent,
|
||||
},
|
||||
ClientMatcherMatched {
|
||||
matcher: ClientMatcher,
|
||||
client: Client,
|
||||
},
|
||||
ClientMatcherUnmatched {
|
||||
matcher: ClientMatcher,
|
||||
client: Client,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -664,6 +672,15 @@ pub enum ClientMessage<'a> {
|
|||
window: Window,
|
||||
pinned: bool,
|
||||
},
|
||||
CreateClientMatcher {
|
||||
criterion: ClientCriterionIpc,
|
||||
},
|
||||
DestroyClientMatcher {
|
||||
matcher: ClientMatcher,
|
||||
},
|
||||
EnableClientMatcherEvents {
|
||||
matcher: ClientMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -884,6 +901,9 @@ pub enum Response {
|
|||
GetWindowIsVisible {
|
||||
visible: bool,
|
||||
},
|
||||
CreateClientMatcher {
|
||||
matcher: ClientMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
//! Tools for inspecting and manipulating clients.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use {
|
||||
serde::{Deserialize, Serialize},
|
||||
std::ops::Deref,
|
||||
};
|
||||
|
||||
/// A client connected to the compositor.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
|
|
@ -34,3 +37,85 @@ impl Client {
|
|||
pub fn clients() -> Vec<Client> {
|
||||
get!().clients()
|
||||
}
|
||||
|
||||
/// A client matcher.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct ClientMatcher(pub u64);
|
||||
|
||||
/// A matched client.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct MatchedClient {
|
||||
pub(crate) matcher: ClientMatcher,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
/// A criterion for matching a client.
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientCriterion<'a> {
|
||||
/// Matches if the contained matcher matches.
|
||||
Matcher(ClientMatcher),
|
||||
/// Matches if the contained criterion does not match.
|
||||
Not(&'a ClientCriterion<'a>),
|
||||
/// Matches if all of the contained criteria match.
|
||||
All(&'a [ClientCriterion<'a>]),
|
||||
/// Matches if any of the contained criteria match.
|
||||
Any(&'a [ClientCriterion<'a>]),
|
||||
/// Matches if an exact number of the contained criteria match.
|
||||
Exactly(usize, &'a [ClientCriterion<'a>]),
|
||||
}
|
||||
|
||||
impl ClientCriterion<'_> {
|
||||
/// Converts the criterion to a matcher.
|
||||
pub fn to_matcher(self) -> ClientMatcher {
|
||||
get!(ClientMatcher(0)).create_client_matcher(self)
|
||||
}
|
||||
|
||||
/// Binds a function to execute when the criterion matches a client.
|
||||
///
|
||||
/// This leaks the matcher.
|
||||
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
|
||||
self.to_matcher().bind(cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMatcher {
|
||||
/// Destroys the matcher.
|
||||
///
|
||||
/// Any bound callback will no longer be executed.
|
||||
pub fn destroy(self) {
|
||||
get!().destroy_client_matcher(self);
|
||||
}
|
||||
|
||||
/// Sets a function to execute when the criterion matches a client.
|
||||
///
|
||||
/// Replaces any already bound callback.
|
||||
pub fn bind<F: FnMut(MatchedClient) + 'static>(self, cb: F) {
|
||||
get!().set_client_matcher_handler(self, cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchedClient {
|
||||
/// Returns the client that matched.
|
||||
pub fn client(self) -> Client {
|
||||
self.client
|
||||
}
|
||||
|
||||
/// Returns the matcher.
|
||||
pub fn matcher(self) -> ClientMatcher {
|
||||
self.matcher
|
||||
}
|
||||
|
||||
/// Latches a function to be executed when the client no longer matches the criteria.
|
||||
pub fn latch<F: FnOnce() + 'static>(self, cb: F) {
|
||||
get!().set_client_matcher_latch_handler(self.matcher, self.client, cb);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MatchedClient {
|
||||
type Target = Client;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue