1
0
Fork 0
forked from wry/wry

config: add sandbox client criteria

This commit is contained in:
Julian Orth 2025-05-02 17:48:44 +02:00
parent fd2163d658
commit 9bf79bf23c
20 changed files with 465 additions and 46 deletions

View file

@ -81,7 +81,12 @@ pub enum ClientCriterionIpc {
field: ClientCriterionStringField, field: ClientCriterionStringField,
regex: bool, regex: bool,
}, },
Sandboxed,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
pub enum ClientCriterionStringField {} pub enum ClientCriterionStringField {
SandboxEngine,
SandboxAppId,
SandboxInstanceId,
}

View file

@ -3,8 +3,8 @@
use { use {
crate::{ crate::{
_private::{ _private::{
ClientCriterionIpc, Config, ConfigEntry, ConfigEntryGen, GenericCriterionIpc, ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen,
PollableId, VERSION, WireMode, bincode_ops, GenericCriterionIpc, PollableId, VERSION, WireMode, bincode_ops,
ipc::{ ipc::{
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource, ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
}, },
@ -1501,7 +1501,6 @@ impl ConfigClient {
criterion: ClientCriterion<'_>, criterion: ClientCriterion<'_>,
child: bool, child: bool,
) -> (ClientMatcher, bool) { ) -> (ClientMatcher, bool) {
#[expect(unused_macros)]
macro_rules! string { macro_rules! string {
($t:expr, $field:ident, $regex:expr) => { ($t:expr, $field:ident, $regex:expr) => {
ClientCriterionIpc::String { ClientCriterionIpc::String {
@ -1530,15 +1529,20 @@ impl ConfigClient {
destroy_matcher, destroy_matcher,
) )
}; };
#[expect(unused_variables)]
let criterion = match criterion { let criterion = match criterion {
ClientCriterion::Matcher(m) => return generic(GenericCriterion::Matcher(m)), ClientCriterion::Matcher(m) => return generic(GenericCriterion::Matcher(m)),
ClientCriterion::Not(c) => return generic(GenericCriterion::Not(c)), ClientCriterion::Not(c) => return generic(GenericCriterion::Not(c)),
ClientCriterion::All(c) => return generic(GenericCriterion::All(c)), ClientCriterion::All(c) => return generic(GenericCriterion::All(c)),
ClientCriterion::Any(c) => return generic(GenericCriterion::Any(c)), ClientCriterion::Any(c) => return generic(GenericCriterion::Any(c)),
ClientCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)), ClientCriterion::Exactly(n, c) => return generic(GenericCriterion::Exactly(n, c)),
ClientCriterion::SandboxEngine(t) => string!(t, SandboxEngine, false),
ClientCriterion::SandboxEngineRegex(t) => string!(t, SandboxEngine, true),
ClientCriterion::SandboxAppId(t) => string!(t, SandboxAppId, false),
ClientCriterion::SandboxAppIdRegex(t) => string!(t, SandboxAppId, true),
ClientCriterion::SandboxInstanceId(t) => string!(t, SandboxInstanceId, false),
ClientCriterion::SandboxInstanceIdRegex(t) => string!(t, SandboxInstanceId, true),
ClientCriterion::Sandboxed => ClientCriterionIpc::Sandboxed,
}; };
#[expect(unreachable_code)]
let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion }); let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion });
get_response!( get_response!(
res, res,

View file

@ -63,6 +63,20 @@ pub enum ClientCriterion<'a> {
Any(&'a [ClientCriterion<'a>]), Any(&'a [ClientCriterion<'a>]),
/// Matches if an exact number of the contained criteria match. /// Matches if an exact number of the contained criteria match.
Exactly(usize, &'a [ClientCriterion<'a>]), Exactly(usize, &'a [ClientCriterion<'a>]),
/// Matches the engine name of the client's sandbox verbatim.
SandboxEngine(&'a str),
/// Matches the engine name of the client's sandbox with a regular expression.
SandboxEngineRegex(&'a str),
/// Matches the app id of the client's sandbox verbatim.
SandboxAppId(&'a str),
/// Matches the app id of the client's sandbox with a regular expression.
SandboxAppIdRegex(&'a str),
/// Matches the instance id of the client's sandbox verbatim.
SandboxInstanceId(&'a str),
/// Matches the instance id of the client's sandbox with a regular expression.
SandboxInstanceIdRegex(&'a str),
/// Matches if the client is sandboxed.
Sandboxed,
} }
impl ClientCriterion<'_> { impl ClientCriterion<'_> {

View file

@ -2,6 +2,7 @@ use {
crate::{ crate::{
async_engine::SpawnedFuture, async_engine::SpawnedFuture,
client::{CAPS_DEFAULT, ClientCaps}, client::{CAPS_DEFAULT, ClientCaps},
security_context_acceptor::AcceptorMetadata,
state::State, state::State,
utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd}, utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd},
}, },
@ -170,6 +171,7 @@ impl Acceptor {
} }
async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) { async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) {
let metadata = Rc::new(AcceptorMetadata::default());
loop { loop {
let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await { let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await {
Ok(fd) => fd, Ok(fd) => fd,
@ -179,9 +181,10 @@ async fn accept(fd: Rc<OwnedFd>, state: Rc<State>, effective_caps: ClientCaps) {
} }
}; };
let id = state.clients.id(); let id = state.clients.id();
if let Err(e) = state if let Err(e) =
.clients state
.spawn(id, &state, fd, effective_caps, ClientCaps::all()) .clients
.spawn(id, &state, fd, effective_caps, ClientCaps::all(), &metadata)
{ {
log::error!("Could not spawn a client: {}", ErrorFmt(e)); log::error!("Could not spawn a client: {}", ErrorFmt(e));
break; break;

View file

@ -13,6 +13,7 @@ use {
}, },
leaks::Tracker, leaks::Tracker,
object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, object::{Interface, Object, ObjectId, WL_DISPLAY_ID},
security_context_acceptor::AcceptorMetadata,
state::State, state::State,
utils::{ utils::{
activation_token::ActivationToken, activation_token::ActivationToken,
@ -125,6 +126,7 @@ impl Clients {
socket: Rc<OwnedFd>, socket: Rc<OwnedFd>,
effective_caps: ClientCaps, effective_caps: ClientCaps,
bounding_caps: ClientCaps, bounding_caps: ClientCaps,
acceptor: &Rc<AcceptorMetadata>,
) -> Result<(), ClientError> { ) -> Result<(), ClientError> {
let Some((uid, pid)) = get_socket_creds(&socket) else { let Some((uid, pid)) = get_socket_creds(&socket) else {
return Ok(()); return Ok(());
@ -138,6 +140,7 @@ impl Clients {
effective_caps, effective_caps,
bounding_caps, bounding_caps,
false, false,
acceptor,
)?; )?;
Ok(()) Ok(())
} }
@ -152,6 +155,7 @@ impl Clients {
effective_caps: ClientCaps, effective_caps: ClientCaps,
bounding_caps: ClientCaps, bounding_caps: ClientCaps,
is_xwayland: bool, is_xwayland: bool,
acceptor: &Rc<AcceptorMetadata>,
) -> Result<Rc<Client>, ClientError> { ) -> Result<Rc<Client>, ClientError> {
let data = Rc::new_cyclic(|slf| Client { let data = Rc::new_cyclic(|slf| Client {
id, id,
@ -183,6 +187,7 @@ impl Clients {
focus_stealing_serial: Default::default(), focus_stealing_serial: Default::default(),
changed_properties: Default::default(), changed_properties: Default::default(),
destroyed: Default::default(), destroyed: Default::default(),
acceptor: acceptor.clone(),
}); });
track!(data, data); track!(data, data);
let display = Rc::new(WlDisplay::new(&data)); let display = Rc::new(WlDisplay::new(&data));
@ -306,6 +311,7 @@ pub struct Client {
pub focus_stealing_serial: Cell<Option<u64>>, pub focus_stealing_serial: Cell<Option<u64>>,
pub changed_properties: Cell<ClMatcherChange>, pub changed_properties: Cell<ClMatcherChange>,
pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Rc<Self>>>>, pub destroyed: CopyHashMap<CritMatcherId, Weak<dyn CritDestroyListener<Rc<Self>>>>,
pub acceptor: Rc<AcceptorMetadata>,
} }
pub const NUM_CACHED_SERIAL_RANGES: usize = 64; pub const NUM_CACHED_SERIAL_RANGES: usize = 64;

View file

@ -41,7 +41,8 @@ use {
bincode::Options, bincode::Options,
jay_config::{ jay_config::{
_private::{ _private::{
ClientCriterionIpc, GenericCriterionIpc, PollableId, WireMode, bincode_ops, ClientCriterionIpc, ClientCriterionStringField, GenericCriterionIpc, PollableId,
WireMode, bincode_ops,
ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource}, ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource},
}, },
Axis, Direction, Workspace, Axis, Direction, Workspace,
@ -1868,7 +1869,6 @@ impl ConfigProxyHandler {
field, field,
regex, regex,
} => { } => {
#[expect(unused_variables)]
let needle = match *regex { let needle = match *regex {
true => { true => {
let regex = Regex::new(string).map_err(CphError::InvalidRegex)?; let regex = Regex::new(string).map_err(CphError::InvalidRegex)?;
@ -1876,8 +1876,15 @@ impl ConfigProxyHandler {
} }
false => CritLiteralOrRegex::Literal(string.to_string()), false => CritLiteralOrRegex::Literal(string.to_string()),
}; };
match *field {} match *field {
ClientCriterionStringField::SandboxEngine => mgr.sandbox_engine(needle),
ClientCriterionStringField::SandboxAppId => mgr.sandbox_app_id(needle),
ClientCriterionStringField::SandboxInstanceId => {
mgr.sandbox_instance_id(needle)
}
}
} }
ClientCriterionIpc::Sandboxed => mgr.sandboxed(),
}; };
let cached = Rc::new(CachedCriterion { let cached = Rc::new(CachedCriterion {
crit: criterion.clone(), crit: criterion.clone(),

View file

@ -84,7 +84,6 @@ pub trait CritMgrExt: CritMgr {
upstream.not(self) upstream.not(self)
} }
#[expect(dead_code)]
fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>> fn root<T>(&self, criterion: T) -> Rc<dyn CritUpstreamNode<Self::Target>>
where where
T: CritRootCriterion<Self::Target>, T: CritRootCriterion<Self::Target>,

View file

@ -4,16 +4,28 @@ use {
crate::{ crate::{
client::{Client, ClientId}, client::{Client, ClientId},
criteria::{ criteria::{
CritDestroyListener, CritMatcherId, CritMatcherIds, CritUpstreamNode, FixedRootMatcher, CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt,
RootMatcherMap, CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
crit_graph::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner}, clm::clm_matchers::{
clmm_sandboxed::ClmMatchSandboxed,
clmm_string::{
ClmMatchSandboxAppId, ClmMatchSandboxEngine, ClmMatchSandboxInstanceId,
},
},
crit_graph::{
CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner,
},
crit_leaf::{CritLeafEvent, CritLeafMatcher}, crit_leaf::{CritLeafEvent, CritLeafMatcher},
crit_matchers::critm_constant::CritMatchConstant, crit_matchers::critm_constant::CritMatchConstant,
}, },
state::State, state::State,
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue}, utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue},
}, },
std::rc::{Rc, Weak}, linearize::static_map,
std::{
marker::PhantomData,
rc::{Rc, Weak},
},
}; };
bitflags! { bitflags! {
@ -29,14 +41,18 @@ pub struct ClMatcherManager {
changes: AsyncQueue<Rc<Client>>, changes: AsyncQueue<Rc<Client>>,
leaf_events: Rc<AsyncQueue<CritLeafEvent<Rc<Client>>>>, leaf_events: Rc<AsyncQueue<CritLeafEvent<Rc<Client>>>>,
constant: ClmFixedRootMatcher<CritMatchConstant<Rc<Client>>>, constant: ClmFixedRootMatcher<CritMatchConstant<Rc<Client>>>,
sandboxed: ClmFixedRootMatcher<ClmMatchSandboxed>,
matchers: Rc<RootMatchers>, matchers: Rc<RootMatchers>,
} }
#[expect(dead_code)]
type ClmRootMatcherMap<T> = RootMatcherMap<Rc<Client>, T>; type ClmRootMatcherMap<T> = RootMatcherMap<Rc<Client>, T>;
#[derive(Default)] #[derive(Default)]
pub struct RootMatchers {} pub struct RootMatchers {
sandbox_app_id: ClmRootMatcherMap<ClmMatchSandboxAppId>,
sandbox_engine: ClmRootMatcherMap<ClmMatchSandboxEngine>,
sandbox_instance_id: ClmRootMatcherMap<ClmMatchSandboxInstanceId>,
}
pub async fn handle_cl_changes(state: Rc<State>) { pub async fn handle_cl_changes(state: Rc<State>) {
let mgr = &state.cl_matcher_manager; let mgr = &state.cl_matcher_manager;
@ -56,14 +72,12 @@ pub async fn handle_cl_leaf_events(state: Rc<State>) {
} }
} }
#[expect(dead_code)]
pub type ClmUpstreamNode = dyn CritUpstreamNode<Rc<Client>>; pub type ClmUpstreamNode = dyn CritUpstreamNode<Rc<Client>>;
pub type ClmLeafMatcher = CritLeafMatcher<Rc<Client>>; pub type ClmLeafMatcher = CritLeafMatcher<Rc<Client>>;
impl ClMatcherManager { impl ClMatcherManager {
pub fn new(ids: &Rc<CritMatcherIds>) -> Self { pub fn new(ids: &Rc<CritMatcherIds>) -> Self {
let matchers = Rc::new(RootMatchers::default()); let matchers = Rc::new(RootMatchers::default());
#[expect(unused_macros)]
macro_rules! bool { macro_rules! bool {
($name:ident) => {{ ($name:ident) => {{
static_map! { static_map! {
@ -77,6 +91,7 @@ impl ClMatcherManager {
} }
Self { Self {
constant: CritMatchConstant::create(&matchers, ids), constant: CritMatchConstant::create(&matchers, ids),
sandboxed: bool!(ClmMatchSandboxed),
changes: Default::default(), changes: Default::default(),
leaf_events: Default::default(), leaf_events: Default::default(),
ids: ids.clone(), ids: ids.clone(),
@ -109,7 +124,6 @@ impl ClMatcherManager {
} }
return; return;
} }
#[expect(unused_macros)]
macro_rules! handlers { macro_rules! handlers {
($name:ident) => { ($name:ident) => {
self.matchers self.matchers
@ -119,7 +133,6 @@ impl ClMatcherManager {
.filter_map(|m| m.upgrade()) .filter_map(|m| m.upgrade())
}; };
} }
#[expect(unused_macros)]
macro_rules! fixed { macro_rules! fixed {
($name:ident) => { ($name:ident) => {
self.$name[false].handle(data); self.$name[false].handle(data);
@ -128,7 +141,6 @@ impl ClMatcherManager {
} }
if changed.contains(CL_CHANGED_NEW) { if changed.contains(CL_CHANGED_NEW) {
changed |= ClMatcherChange::all(); changed |= ClMatcherChange::all();
#[expect(unused_macros)]
macro_rules! unconditional { macro_rules! unconditional {
($field:ident) => { ($field:ident) => {
for m in handlers!($field) { for m in handlers!($field) {
@ -136,9 +148,29 @@ impl ClMatcherManager {
} }
}; };
} }
unconditional!(sandbox_instance_id);
unconditional!(sandbox_app_id);
unconditional!(sandbox_engine);
fixed!(sandboxed);
self.constant[true].handle(data); self.constant[true].handle(data);
} }
} }
pub fn sandbox_engine(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchSandboxEngine::new(string))
}
pub fn sandbox_app_id(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchSandboxAppId::new(string))
}
pub fn sandbox_instance_id(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchSandboxInstanceId::new(string))
}
pub fn sandboxed(&self) -> Rc<ClmUpstreamNode> {
self.sandboxed[true].clone()
}
} }
impl CritTarget for Rc<Client> { impl CritTarget for Rc<Client> {

View file

@ -1,4 +1,3 @@
#[expect(unused_macros)]
macro_rules! fixed_root_criterion { macro_rules! fixed_root_criterion {
($ty:ty, $field:ident) => { ($ty:ty, $field:ident) => {
impl crate::criteria::crit_graph::CritFixedRootCriterionBase<Rc<crate::client::Client>> impl crate::criteria::crit_graph::CritFixedRootCriterionBase<Rc<crate::client::Client>>
@ -17,3 +16,6 @@ macro_rules! fixed_root_criterion {
} }
}; };
} }
pub mod clmm_sandboxed;
pub mod clmm_string;

View file

@ -0,0 +1,14 @@
use {
crate::{client::Client, criteria::crit_graph::CritFixedRootCriterion},
std::rc::Rc,
};
pub struct ClmMatchSandboxed(pub bool);
fixed_root_criterion!(ClmMatchSandboxed, sandboxed);
impl CritFixedRootCriterion<Rc<Client>> for ClmMatchSandboxed {
fn matches(&self, data: &Rc<Client>) -> bool {
data.acceptor.sandboxed
}
}

View file

@ -0,0 +1,79 @@
use {
crate::{
client::Client,
criteria::{
clm::{ClmRootMatcherMap, RootMatchers},
crit_matchers::critm_string::{CritMatchString, StringAccess},
},
security_context_acceptor::AcceptorMetadata,
},
std::{marker::PhantomData, rc::Rc},
};
pub type ClmMatchString<T> = CritMatchString<Rc<Client>, T>;
pub type ClmMatchSandboxEngine = ClmMatchString<AcceptorMetadataAccess<SandboxEngineField>>;
pub type ClmMatchSandboxAppId = ClmMatchString<AcceptorMetadataAccess<SandboxAppIdField>>;
pub type ClmMatchSandboxInstanceId = ClmMatchString<AcceptorMetadataAccess<SandboxInstanceIdField>>;
pub struct AcceptorMetadataAccess<T>(PhantomData<T>);
trait SandboxField: Sized + 'static {
fn field(meta: &AcceptorMetadata) -> &Option<String>;
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>>;
}
pub struct SandboxEngineField;
pub struct SandboxAppIdField;
pub struct SandboxInstanceIdField;
impl<T> StringAccess<Rc<Client>> for AcceptorMetadataAccess<T>
where
T: SandboxField,
{
fn with_string(data: &Rc<Client>, f: impl FnOnce(&str) -> bool) -> bool {
f(T::field(&data.acceptor).as_deref().unwrap_or_default())
}
fn nodes(roots: &RootMatchers) -> &ClmRootMatcherMap<ClmMatchString<Self>> {
T::nodes(roots)
}
}
impl SandboxField for SandboxEngineField {
fn field(meta: &AcceptorMetadata) -> &Option<String> {
&meta.sandbox_engine
}
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
&roots.sandbox_engine
}
}
impl SandboxField for SandboxAppIdField {
fn field(meta: &AcceptorMetadata) -> &Option<String> {
&meta.app_id
}
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
&roots.sandbox_app_id
}
}
impl SandboxField for SandboxInstanceIdField {
fn field(meta: &AcceptorMetadata) -> &Option<String> {
&meta.instance_id
}
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<AcceptorMetadataAccess<Self>>> {
&roots.sandbox_instance_id
}
}

View file

@ -22,7 +22,6 @@ where
} }
impl<Target, A> CritMatchString<Target, A> { impl<Target, A> CritMatchString<Target, A> {
#[expect(dead_code)]
pub fn new(string: CritLiteralOrRegex) -> Self { pub fn new(string: CritLiteralOrRegex) -> Self {
Self { Self {
string, string,

View file

@ -24,9 +24,7 @@ linear_ids!(AcceptorIds, AcceptorId, u64);
struct Acceptor { struct Acceptor {
id: AcceptorId, id: AcceptorId,
state: Rc<State>, state: Rc<State>,
sandbox_engine: Option<String>, metadata: Rc<AcceptorMetadata>,
app_id: Option<String>,
instance_id: Option<String>,
listen_fd: Rc<OwnedFd>, listen_fd: Rc<OwnedFd>,
close_fd: Rc<OwnedFd>, close_fd: Rc<OwnedFd>,
caps: ClientCaps, caps: ClientCaps,
@ -34,6 +32,14 @@ struct Acceptor {
close_future: Cell<Option<SpawnedFuture<()>>>, close_future: Cell<Option<SpawnedFuture<()>>>,
} }
#[derive(Default)]
pub struct AcceptorMetadata {
pub sandboxed: bool,
pub sandbox_engine: Option<String>,
pub app_id: Option<String>,
pub instance_id: Option<String>,
}
impl SecurityContextAcceptors { impl SecurityContextAcceptors {
pub fn clear(&self) { pub fn clear(&self) {
for acceptor in self.acceptors.lock().drain_values() { for acceptor in self.acceptors.lock().drain_values() {
@ -54,9 +60,12 @@ impl SecurityContextAcceptors {
let acceptor = Rc::new(Acceptor { let acceptor = Rc::new(Acceptor {
id: self.ids.next(), id: self.ids.next(),
state: state.clone(), state: state.clone(),
sandbox_engine, metadata: Rc::new(AcceptorMetadata {
app_id, sandboxed: true,
instance_id, sandbox_engine,
app_id,
instance_id,
}),
listen_fd: listen_fd.clone(), listen_fd: listen_fd.clone(),
close_fd: close_fd.clone(), close_fd: close_fd.clone(),
caps, caps,
@ -100,7 +109,10 @@ impl Acceptor {
} }
}; };
let id = s.clients.id(); let id = s.clients.id();
if let Err(e) = s.clients.spawn(id, s, fd, self.caps, self.caps) { if let Err(e) = s
.clients
.spawn(id, s, fd, self.caps, self.caps, &self.metadata)
{
log::error!("Could not spawn a client: {}", ErrorFmt(e)); log::error!("Could not spawn a client: {}", ErrorFmt(e));
break; break;
} }
@ -119,9 +131,9 @@ impl Display for Acceptor {
write!( write!(
f, f,
"{}/{}/{}", "{}/{}/{}",
self.sandbox_engine.as_deref().unwrap_or(""), self.metadata.sandbox_engine.as_deref().unwrap_or(""),
self.app_id.as_deref().unwrap_or(""), self.metadata.app_id.as_deref().unwrap_or(""),
self.instance_id.as_deref().unwrap_or(""), self.metadata.instance_id.as_deref().unwrap_or(""),
) )
} }
} }

View file

@ -12,6 +12,7 @@ use {
wl_surface::x_surface::xwindow::{Xwindow, XwindowData}, wl_surface::x_surface::xwindow::{Xwindow, XwindowData},
}, },
io_uring::IoUringError, io_uring::IoUringError,
security_context_acceptor::AcceptorMetadata,
state::State, state::State,
user_session::import_environment, user_session::import_environment,
utils::{ utils::{
@ -179,6 +180,7 @@ async fn run(
ClientCaps::all(), ClientCaps::all(),
ClientCaps::all(), ClientCaps::all(),
true, true,
&Rc::new(AcceptorMetadata::default()),
); );
let client = match client { let client = match client {
Ok(c) => c, Ok(c) => c,

View file

@ -225,6 +225,13 @@ pub struct ClientRule {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct ClientMatch { pub struct ClientMatch {
pub generic: GenericMatch<Self>, pub generic: GenericMatch<Self>,
pub sandbox_engine: Option<String>,
pub sandbox_engine_regex: Option<String>,
pub sandbox_app_id: Option<String>,
pub sandbox_app_id_regex: Option<String>,
pub sandbox_instance_id: Option<String>,
pub sandbox_instance_id_regex: Option<String>,
pub sandboxed: Option<bool>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -3,7 +3,7 @@ use {
config::{ config::{
ClientMatch, GenericMatch, MatchExactly, ClientMatch, GenericMatch, MatchExactly,
context::Context, context::Context,
extractor::{Extractor, ExtractorError, arr, n32, opt, str, val}, extractor::{Extractor, ExtractorError, arr, bol, n32, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parser::{DataType, ParseResult, Parser, UnexpectedDataType},
}, },
toml::{ toml::{
@ -36,13 +36,38 @@ impl Parser for ClientMatchParser<'_> {
table: &IndexMap<Spanned<String>, Spanned<Value>>, table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> { ) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table); let mut ext = Extractor::new(self.0, span, table);
let ((name, not_val, all_val, any_val, exactly_val),) = ext.extract((( let (
opt(str("name")), (
opt(val("not")), name,
opt(arr("all")), not_val,
opt(arr("any")), all_val,
opt(val("exactly")), any_val,
),))?; exactly_val,
sandboxed,
sandbox_engine,
sandbox_engine_regex,
sandbox_app_id,
sandbox_app_id_regex,
),
(sandbox_instance_id, sandbox_instance_id_regex),
) = ext.extract((
(
opt(str("name")),
opt(val("not")),
opt(arr("all")),
opt(arr("any")),
opt(val("exactly")),
opt(bol("sandboxed")),
opt(str("sandbox-engine")),
opt(str("sandbox-engine-regex")),
opt(str("sandbox-app-id")),
opt(str("sandbox-app-id-regex")),
),
(
opt(str("sandbox-instance-id")),
opt(str("sandbox-instance-id-regex")),
),
))?;
let mut not = None; let mut not = None;
if let Some(value) = not_val { if let Some(value) = not_val {
not = Some(Box::new(value.parse(&mut ClientMatchParser(self.0))?)); not = Some(Box::new(value.parse(&mut ClientMatchParser(self.0))?));
@ -74,6 +99,13 @@ impl Parser for ClientMatchParser<'_> {
any, any,
exactly, exactly,
}, },
sandbox_engine: sandbox_engine.despan_into(),
sandbox_engine_regex: sandbox_engine_regex.despan_into(),
sandbox_app_id: sandbox_app_id.despan_into(),
sandbox_app_id_regex: sandbox_app_id_regex.despan_into(),
sandbox_instance_id: sandbox_instance_id.despan_into(),
sandbox_instance_id_regex: sandbox_instance_id_regex.despan_into(),
sandboxed: sandboxed.despan(),
}) })
} }
} }

View file

@ -84,9 +84,36 @@ impl Rule for ClientRule {
fn map_custom( fn map_custom(
_state: &Rc<State>, _state: &Rc<State>,
_all: &mut Vec<MatcherTemp<Self>>, all: &mut Vec<MatcherTemp<Self>>,
_match_: &Self::Match, match_: &Self::Match,
) -> Option<()> { ) -> Option<()> {
let m = |c: ClientCriterion<'_>| MatcherTemp(c.to_matcher());
macro_rules! value_ref {
($ty:ident, $field:ident) => {
if let Some(value) = &match_.$field {
all.push(m(ClientCriterion::$ty(value)));
}
};
}
macro_rules! bool {
($ty:ident, $field:ident) => {
if let Some(value) = &match_.$field {
let crit = ClientCriterion::$ty;
let matcher = match value {
false => m(ClientCriterion::Not(&crit)),
true => m(crit),
};
all.push(matcher);
}
};
}
value_ref!(SandboxEngine, sandbox_engine);
value_ref!(SandboxEngineRegex, sandbox_engine_regex);
value_ref!(SandboxAppId, sandbox_app_id);
value_ref!(SandboxAppIdRegex, sandbox_app_id_regex);
value_ref!(SandboxInstanceId, sandbox_instance_id);
value_ref!(SandboxInstanceIdRegex, sandbox_instance_id_regex);
bool!(Sandboxed, sandboxed);
Some(()) Some(())
} }

View file

@ -531,6 +531,34 @@
"exactly": { "exactly": {
"description": "Matches if a specific number of contained criteria match.\n\n- Example:\n\n ```toml\n # Matches any client that is either steam or sandboxed by flatpak but not both.\n [[clients]]\n match.exactly.num = 1\n match.exactly.list = [\n { sandbox-engine = \"org.flatpak\" },\n { sandbox-app-id = \"com.valvesoftware.Steam\" },\n ]\n ```\n", "description": "Matches if a specific number of contained criteria match.\n\n- Example:\n\n ```toml\n # Matches any client that is either steam or sandboxed by flatpak but not both.\n [[clients]]\n match.exactly.num = 1\n match.exactly.list = [\n { sandbox-engine = \"org.flatpak\" },\n { sandbox-app-id = \"com.valvesoftware.Steam\" },\n ]\n ```\n",
"$ref": "#/$defs/ClientMatchExactly" "$ref": "#/$defs/ClientMatchExactly"
},
"sandboxed": {
"type": "boolean",
"description": "Matches if the client is/isn't sandboxed.\n\n- Example:\n\n ```toml\n [[clients]]\n match.sandboxed = true\n ```\n"
},
"sandbox-engine": {
"type": "string",
"description": "Matches the engine name of the client's sandbox verbatim.\n\n- Example:\n\n ```toml\n [[clients]]\n match.sandbox-engine = \"org.flatpak\"\n ```\n"
},
"sandbox-engine-regex": {
"type": "string",
"description": "Matches the engine name of the client's sandbox with a regular expression.\n\n- Example:\n\n ```toml\n [[clients]]\n match.sandbox-engine = \"flatpak\"\n ```\n"
},
"sandbox-app-id": {
"type": "string",
"description": "Matches the app id of the client's sandbox verbatim.\n\n- Example:\n\n ```toml\n [[clients]]\n match.sandbox-app-id = \"com.spotify.Client\"\n ```\n"
},
"sandbox-app-id-regex": {
"type": "string",
"description": "Matches the app id of the client's sandbox with a regular expression.\n\n- Example:\n\n ```toml\n [[clients]]\n match.sandbox-app-id-regex = \"(?i)spotify\"\n ```\n"
},
"sandbox-instance-id": {
"type": "string",
"description": "Matches the instance id of the client's sandbox verbatim.\n"
},
"sandbox-instance-id-regex": {
"type": "string",
"description": "Matches the instance id of the client's sandbox with a regular expression.\n"
} }
}, },
"required": [] "required": []

View file

@ -794,6 +794,83 @@ The table has the following fields:
The value of this field should be a [ClientMatchExactly](#types-ClientMatchExactly). The value of this field should be a [ClientMatchExactly](#types-ClientMatchExactly).
- `sandboxed` (optional):
Matches if the client is/isn't sandboxed.
- Example:
```toml
[[clients]]
match.sandboxed = true
```
The value of this field should be a boolean.
- `sandbox-engine` (optional):
Matches the engine name of the client's sandbox verbatim.
- Example:
```toml
[[clients]]
match.sandbox-engine = "org.flatpak"
```
The value of this field should be a string.
- `sandbox-engine-regex` (optional):
Matches the engine name of the client's sandbox with a regular expression.
- Example:
```toml
[[clients]]
match.sandbox-engine = "flatpak"
```
The value of this field should be a string.
- `sandbox-app-id` (optional):
Matches the app id of the client's sandbox verbatim.
- Example:
```toml
[[clients]]
match.sandbox-app-id = "com.spotify.Client"
```
The value of this field should be a string.
- `sandbox-app-id-regex` (optional):
Matches the app id of the client's sandbox with a regular expression.
- Example:
```toml
[[clients]]
match.sandbox-app-id-regex = "(?i)spotify"
```
The value of this field should be a string.
- `sandbox-instance-id` (optional):
Matches the instance id of the client's sandbox verbatim.
The value of this field should be a string.
- `sandbox-instance-id-regex` (optional):
Matches the instance id of the client's sandbox with a regular expression.
The value of this field should be a string.
<a name="types-ClientMatchExactly"></a> <a name="types-ClientMatchExactly"></a>
### `ClientMatchExactly` ### `ClientMatchExactly`

View file

@ -3167,6 +3167,76 @@ ClientMatch:
{ sandbox-app-id = "com.valvesoftware.Steam" }, { sandbox-app-id = "com.valvesoftware.Steam" },
] ]
``` ```
sandboxed:
kind: boolean
required: false
description: |
Matches if the client is/isn't sandboxed.
- Example:
```toml
[[clients]]
match.sandboxed = true
```
sandbox-engine:
kind: string
required: false
description: |
Matches the engine name of the client's sandbox verbatim.
- Example:
```toml
[[clients]]
match.sandbox-engine = "org.flatpak"
```
sandbox-engine-regex:
kind: string
required: false
description: |
Matches the engine name of the client's sandbox with a regular expression.
- Example:
```toml
[[clients]]
match.sandbox-engine = "flatpak"
```
sandbox-app-id:
kind: string
required: false
description: |
Matches the app id of the client's sandbox verbatim.
- Example:
```toml
[[clients]]
match.sandbox-app-id = "com.spotify.Client"
```
sandbox-app-id-regex:
kind: string
required: false
description: |
Matches the app id of the client's sandbox with a regular expression.
- Example:
```toml
[[clients]]
match.sandbox-app-id-regex = "(?i)spotify"
```
sandbox-instance-id:
kind: string
required: false
description: |
Matches the instance id of the client's sandbox verbatim.
sandbox-instance-id-regex:
kind: string
required: false
description: |
Matches the instance id of the client's sandbox with a regular expression.
ClientMatchExactly: ClientMatchExactly: