From 9bf79bf23cadc73ad2fcc67d8df83315cd7ed67b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 2 May 2025 17:48:44 +0200 Subject: [PATCH] config: add sandbox client criteria --- jay-config/src/_private.rs | 7 +- jay-config/src/_private/client.rs | 14 ++-- jay-config/src/client.rs | 14 ++++ src/acceptor.rs | 9 ++- src/client.rs | 6 ++ src/config/handler.rs | 13 ++- src/criteria.rs | 1 - src/criteria/clm.rs | 54 ++++++++++--- src/criteria/clm/clm_matchers.rs | 4 +- .../clm/clm_matchers/clmm_sandboxed.rs | 14 ++++ src/criteria/clm/clm_matchers/clmm_string.rs | 79 +++++++++++++++++++ src/criteria/crit_matchers/critm_string.rs | 1 - src/security_context_acceptor.rs | 32 +++++--- src/xwayland.rs | 2 + toml-config/src/config.rs | 7 ++ .../src/config/parsers/client_match.rs | 48 +++++++++-- toml-config/src/rules.rs | 31 +++++++- toml-spec/spec/spec.generated.json | 28 +++++++ toml-spec/spec/spec.generated.md | 77 ++++++++++++++++++ toml-spec/spec/spec.yaml | 70 ++++++++++++++++ 20 files changed, 465 insertions(+), 46 deletions(-) create mode 100644 src/criteria/clm/clm_matchers/clmm_sandboxed.rs create mode 100644 src/criteria/clm/clm_matchers/clmm_string.rs diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index 32c28af9..95188583 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -81,7 +81,12 @@ pub enum ClientCriterionIpc { field: ClientCriterionStringField, regex: bool, }, + Sandboxed, } #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] -pub enum ClientCriterionStringField {} +pub enum ClientCriterionStringField { + SandboxEngine, + SandboxAppId, + SandboxInstanceId, +} diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index fd0208db..2a9d1639 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -3,8 +3,8 @@ use { crate::{ _private::{ - ClientCriterionIpc, Config, ConfigEntry, ConfigEntryGen, GenericCriterionIpc, - PollableId, VERSION, WireMode, bincode_ops, + ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen, + GenericCriterionIpc, PollableId, VERSION, WireMode, bincode_ops, ipc::{ ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource, }, @@ -1501,7 +1501,6 @@ impl ConfigClient { criterion: ClientCriterion<'_>, child: bool, ) -> (ClientMatcher, bool) { - #[expect(unused_macros)] macro_rules! string { ($t:expr, $field:ident, $regex:expr) => { ClientCriterionIpc::String { @@ -1530,15 +1529,20 @@ impl ConfigClient { 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)), + 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 }); get_response!( res, diff --git a/jay-config/src/client.rs b/jay-config/src/client.rs index e60e100f..601053f4 100644 --- a/jay-config/src/client.rs +++ b/jay-config/src/client.rs @@ -63,6 +63,20 @@ pub enum ClientCriterion<'a> { Any(&'a [ClientCriterion<'a>]), /// Matches if an exact number of the contained criteria match. 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<'_> { diff --git a/src/acceptor.rs b/src/acceptor.rs index e2c2cc70..41d0b468 100644 --- a/src/acceptor.rs +++ b/src/acceptor.rs @@ -2,6 +2,7 @@ use { crate::{ async_engine::SpawnedFuture, client::{CAPS_DEFAULT, ClientCaps}, + security_context_acceptor::AcceptorMetadata, state::State, utils::{errorfmt::ErrorFmt, oserror::OsError, xrd::xrd}, }, @@ -170,6 +171,7 @@ impl Acceptor { } async fn accept(fd: Rc, state: Rc, effective_caps: ClientCaps) { + let metadata = Rc::new(AcceptorMetadata::default()); loop { let fd = match state.ring.accept(&fd, c::SOCK_CLOEXEC).await { Ok(fd) => fd, @@ -179,9 +181,10 @@ async fn accept(fd: Rc, state: Rc, effective_caps: ClientCaps) { } }; let id = state.clients.id(); - if let Err(e) = state - .clients - .spawn(id, &state, fd, effective_caps, ClientCaps::all()) + if let Err(e) = + state + .clients + .spawn(id, &state, fd, effective_caps, ClientCaps::all(), &metadata) { log::error!("Could not spawn a client: {}", ErrorFmt(e)); break; diff --git a/src/client.rs b/src/client.rs index 42075314..5fdc960d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,6 +13,7 @@ use { }, leaks::Tracker, object::{Interface, Object, ObjectId, WL_DISPLAY_ID}, + security_context_acceptor::AcceptorMetadata, state::State, utils::{ activation_token::ActivationToken, @@ -125,6 +126,7 @@ impl Clients { socket: Rc, effective_caps: ClientCaps, bounding_caps: ClientCaps, + acceptor: &Rc, ) -> Result<(), ClientError> { let Some((uid, pid)) = get_socket_creds(&socket) else { return Ok(()); @@ -138,6 +140,7 @@ impl Clients { effective_caps, bounding_caps, false, + acceptor, )?; Ok(()) } @@ -152,6 +155,7 @@ impl Clients { effective_caps: ClientCaps, bounding_caps: ClientCaps, is_xwayland: bool, + acceptor: &Rc, ) -> Result, ClientError> { let data = Rc::new_cyclic(|slf| Client { id, @@ -183,6 +187,7 @@ impl Clients { focus_stealing_serial: Default::default(), changed_properties: Default::default(), destroyed: Default::default(), + acceptor: acceptor.clone(), }); track!(data, data); let display = Rc::new(WlDisplay::new(&data)); @@ -306,6 +311,7 @@ pub struct Client { pub focus_stealing_serial: Cell>, pub changed_properties: Cell, pub destroyed: CopyHashMap>>>, + pub acceptor: Rc, } pub const NUM_CACHED_SERIAL_RANGES: usize = 64; diff --git a/src/config/handler.rs b/src/config/handler.rs index ace6a277..82cb3454 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -41,7 +41,8 @@ use { bincode::Options, jay_config::{ _private::{ - ClientCriterionIpc, GenericCriterionIpc, PollableId, WireMode, bincode_ops, + ClientCriterionIpc, ClientCriterionStringField, GenericCriterionIpc, PollableId, + WireMode, bincode_ops, ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource}, }, Axis, Direction, Workspace, @@ -1868,7 +1869,6 @@ impl ConfigProxyHandler { field, regex, } => { - #[expect(unused_variables)] let needle = match *regex { true => { let regex = Regex::new(string).map_err(CphError::InvalidRegex)?; @@ -1876,8 +1876,15 @@ impl ConfigProxyHandler { } 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 { crit: criterion.clone(), diff --git a/src/criteria.rs b/src/criteria.rs index 2da4f95b..b55827ae 100644 --- a/src/criteria.rs +++ b/src/criteria.rs @@ -84,7 +84,6 @@ pub trait CritMgrExt: CritMgr { upstream.not(self) } - #[expect(dead_code)] fn root(&self, criterion: T) -> Rc> where T: CritRootCriterion, diff --git a/src/criteria/clm.rs b/src/criteria/clm.rs index 19538ef0..44e77ce7 100644 --- a/src/criteria/clm.rs +++ b/src/criteria/clm.rs @@ -4,16 +4,28 @@ use { crate::{ client::{Client, ClientId}, criteria::{ - CritDestroyListener, CritMatcherId, CritMatcherIds, CritUpstreamNode, FixedRootMatcher, - RootMatcherMap, - crit_graph::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner}, + CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, + CritUpstreamNode, FixedRootMatcher, RootMatcherMap, + clm::clm_matchers::{ + clmm_sandboxed::ClmMatchSandboxed, + clmm_string::{ + ClmMatchSandboxAppId, ClmMatchSandboxEngine, ClmMatchSandboxInstanceId, + }, + }, + crit_graph::{ + CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner, + }, crit_leaf::{CritLeafEvent, CritLeafMatcher}, crit_matchers::critm_constant::CritMatchConstant, }, state::State, utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, queue::AsyncQueue}, }, - std::rc::{Rc, Weak}, + linearize::static_map, + std::{ + marker::PhantomData, + rc::{Rc, Weak}, + }, }; bitflags! { @@ -29,14 +41,18 @@ pub struct ClMatcherManager { changes: AsyncQueue>, leaf_events: Rc>>>, constant: ClmFixedRootMatcher>>, + sandboxed: ClmFixedRootMatcher, matchers: Rc, } -#[expect(dead_code)] type ClmRootMatcherMap = RootMatcherMap, T>; #[derive(Default)] -pub struct RootMatchers {} +pub struct RootMatchers { + sandbox_app_id: ClmRootMatcherMap, + sandbox_engine: ClmRootMatcherMap, + sandbox_instance_id: ClmRootMatcherMap, +} pub async fn handle_cl_changes(state: Rc) { let mgr = &state.cl_matcher_manager; @@ -56,14 +72,12 @@ pub async fn handle_cl_leaf_events(state: Rc) { } } -#[expect(dead_code)] pub type ClmUpstreamNode = dyn CritUpstreamNode>; pub type ClmLeafMatcher = CritLeafMatcher>; impl ClMatcherManager { pub fn new(ids: &Rc) -> Self { let matchers = Rc::new(RootMatchers::default()); - #[expect(unused_macros)] macro_rules! bool { ($name:ident) => {{ static_map! { @@ -77,6 +91,7 @@ impl ClMatcherManager { } Self { constant: CritMatchConstant::create(&matchers, ids), + sandboxed: bool!(ClmMatchSandboxed), changes: Default::default(), leaf_events: Default::default(), ids: ids.clone(), @@ -109,7 +124,6 @@ impl ClMatcherManager { } return; } - #[expect(unused_macros)] macro_rules! handlers { ($name:ident) => { self.matchers @@ -119,7 +133,6 @@ impl ClMatcherManager { .filter_map(|m| m.upgrade()) }; } - #[expect(unused_macros)] macro_rules! fixed { ($name:ident) => { self.$name[false].handle(data); @@ -128,7 +141,6 @@ impl ClMatcherManager { } if changed.contains(CL_CHANGED_NEW) { changed |= ClMatcherChange::all(); - #[expect(unused_macros)] macro_rules! unconditional { ($field:ident) => { 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); } } + + pub fn sandbox_engine(&self, string: CritLiteralOrRegex) -> Rc { + self.root(ClmMatchSandboxEngine::new(string)) + } + + pub fn sandbox_app_id(&self, string: CritLiteralOrRegex) -> Rc { + self.root(ClmMatchSandboxAppId::new(string)) + } + + pub fn sandbox_instance_id(&self, string: CritLiteralOrRegex) -> Rc { + self.root(ClmMatchSandboxInstanceId::new(string)) + } + + pub fn sandboxed(&self) -> Rc { + self.sandboxed[true].clone() + } } impl CritTarget for Rc { diff --git a/src/criteria/clm/clm_matchers.rs b/src/criteria/clm/clm_matchers.rs index 246a4f9c..58d2968b 100644 --- a/src/criteria/clm/clm_matchers.rs +++ b/src/criteria/clm/clm_matchers.rs @@ -1,4 +1,3 @@ -#[expect(unused_macros)] macro_rules! fixed_root_criterion { ($ty:ty, $field:ident) => { impl crate::criteria::crit_graph::CritFixedRootCriterionBase> @@ -17,3 +16,6 @@ macro_rules! fixed_root_criterion { } }; } + +pub mod clmm_sandboxed; +pub mod clmm_string; diff --git a/src/criteria/clm/clm_matchers/clmm_sandboxed.rs b/src/criteria/clm/clm_matchers/clmm_sandboxed.rs new file mode 100644 index 00000000..4988e9b1 --- /dev/null +++ b/src/criteria/clm/clm_matchers/clmm_sandboxed.rs @@ -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> for ClmMatchSandboxed { + fn matches(&self, data: &Rc) -> bool { + data.acceptor.sandboxed + } +} diff --git a/src/criteria/clm/clm_matchers/clmm_string.rs b/src/criteria/clm/clm_matchers/clmm_string.rs new file mode 100644 index 00000000..9e4c58c7 --- /dev/null +++ b/src/criteria/clm/clm_matchers/clmm_string.rs @@ -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 = CritMatchString, T>; + +pub type ClmMatchSandboxEngine = ClmMatchString>; +pub type ClmMatchSandboxAppId = ClmMatchString>; +pub type ClmMatchSandboxInstanceId = ClmMatchString>; + +pub struct AcceptorMetadataAccess(PhantomData); + +trait SandboxField: Sized + 'static { + fn field(meta: &AcceptorMetadata) -> &Option; + fn nodes( + roots: &RootMatchers, + ) -> &ClmRootMatcherMap>>; +} + +pub struct SandboxEngineField; +pub struct SandboxAppIdField; +pub struct SandboxInstanceIdField; + +impl StringAccess> for AcceptorMetadataAccess +where + T: SandboxField, +{ + fn with_string(data: &Rc, f: impl FnOnce(&str) -> bool) -> bool { + f(T::field(&data.acceptor).as_deref().unwrap_or_default()) + } + + fn nodes(roots: &RootMatchers) -> &ClmRootMatcherMap> { + T::nodes(roots) + } +} + +impl SandboxField for SandboxEngineField { + fn field(meta: &AcceptorMetadata) -> &Option { + &meta.sandbox_engine + } + + fn nodes( + roots: &RootMatchers, + ) -> &ClmRootMatcherMap>> { + &roots.sandbox_engine + } +} + +impl SandboxField for SandboxAppIdField { + fn field(meta: &AcceptorMetadata) -> &Option { + &meta.app_id + } + + fn nodes( + roots: &RootMatchers, + ) -> &ClmRootMatcherMap>> { + &roots.sandbox_app_id + } +} + +impl SandboxField for SandboxInstanceIdField { + fn field(meta: &AcceptorMetadata) -> &Option { + &meta.instance_id + } + + fn nodes( + roots: &RootMatchers, + ) -> &ClmRootMatcherMap>> { + &roots.sandbox_instance_id + } +} diff --git a/src/criteria/crit_matchers/critm_string.rs b/src/criteria/crit_matchers/critm_string.rs index b486ea0f..1464e2d6 100644 --- a/src/criteria/crit_matchers/critm_string.rs +++ b/src/criteria/crit_matchers/critm_string.rs @@ -22,7 +22,6 @@ where } impl CritMatchString { - #[expect(dead_code)] pub fn new(string: CritLiteralOrRegex) -> Self { Self { string, diff --git a/src/security_context_acceptor.rs b/src/security_context_acceptor.rs index 2a3e6141..dabdea37 100644 --- a/src/security_context_acceptor.rs +++ b/src/security_context_acceptor.rs @@ -24,9 +24,7 @@ linear_ids!(AcceptorIds, AcceptorId, u64); struct Acceptor { id: AcceptorId, state: Rc, - sandbox_engine: Option, - app_id: Option, - instance_id: Option, + metadata: Rc, listen_fd: Rc, close_fd: Rc, caps: ClientCaps, @@ -34,6 +32,14 @@ struct Acceptor { close_future: Cell>>, } +#[derive(Default)] +pub struct AcceptorMetadata { + pub sandboxed: bool, + pub sandbox_engine: Option, + pub app_id: Option, + pub instance_id: Option, +} + impl SecurityContextAcceptors { pub fn clear(&self) { for acceptor in self.acceptors.lock().drain_values() { @@ -54,9 +60,12 @@ impl SecurityContextAcceptors { let acceptor = Rc::new(Acceptor { id: self.ids.next(), state: state.clone(), - sandbox_engine, - app_id, - instance_id, + metadata: Rc::new(AcceptorMetadata { + sandboxed: true, + sandbox_engine, + app_id, + instance_id, + }), listen_fd: listen_fd.clone(), close_fd: close_fd.clone(), caps, @@ -100,7 +109,10 @@ impl Acceptor { } }; 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)); break; } @@ -119,9 +131,9 @@ impl Display for Acceptor { write!( f, "{}/{}/{}", - self.sandbox_engine.as_deref().unwrap_or(""), - self.app_id.as_deref().unwrap_or(""), - self.instance_id.as_deref().unwrap_or(""), + self.metadata.sandbox_engine.as_deref().unwrap_or(""), + self.metadata.app_id.as_deref().unwrap_or(""), + self.metadata.instance_id.as_deref().unwrap_or(""), ) } } diff --git a/src/xwayland.rs b/src/xwayland.rs index ab078f50..6d6fb1e1 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -12,6 +12,7 @@ use { wl_surface::x_surface::xwindow::{Xwindow, XwindowData}, }, io_uring::IoUringError, + security_context_acceptor::AcceptorMetadata, state::State, user_session::import_environment, utils::{ @@ -179,6 +180,7 @@ async fn run( ClientCaps::all(), ClientCaps::all(), true, + &Rc::new(AcceptorMetadata::default()), ); let client = match client { Ok(c) => c, diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index d01cbc93..ad678a8c 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -225,6 +225,13 @@ pub struct ClientRule { #[derive(Default, Debug, Clone)] pub struct ClientMatch { pub generic: GenericMatch, + pub sandbox_engine: Option, + pub sandbox_engine_regex: Option, + pub sandbox_app_id: Option, + pub sandbox_app_id_regex: Option, + pub sandbox_instance_id: Option, + pub sandbox_instance_id_regex: Option, + pub sandboxed: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/client_match.rs b/toml-config/src/config/parsers/client_match.rs index b3a1ca54..d82b5d9e 100644 --- a/toml-config/src/config/parsers/client_match.rs +++ b/toml-config/src/config/parsers/client_match.rs @@ -3,7 +3,7 @@ use { config::{ ClientMatch, GenericMatch, MatchExactly, context::Context, - extractor::{Extractor, ExtractorError, arr, n32, opt, str, val}, + extractor::{Extractor, ExtractorError, arr, bol, n32, opt, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, }, toml::{ @@ -36,13 +36,38 @@ impl Parser for ClientMatchParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); - let ((name, not_val, all_val, any_val, exactly_val),) = ext.extract((( - opt(str("name")), - opt(val("not")), - opt(arr("all")), - opt(arr("any")), - opt(val("exactly")), - ),))?; + let ( + ( + name, + not_val, + all_val, + 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; if let Some(value) = not_val { not = Some(Box::new(value.parse(&mut ClientMatchParser(self.0))?)); @@ -74,6 +99,13 @@ impl Parser for ClientMatchParser<'_> { any, 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(), }) } } diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index 87eff524..73cab65f 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -84,9 +84,36 @@ impl Rule for ClientRule { fn map_custom( _state: &Rc, - _all: &mut Vec>, - _match_: &Self::Match, + all: &mut Vec>, + match_: &Self::Match, ) -> 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(()) } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index eef59221..1c65d462 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -531,6 +531,34 @@ "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", "$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": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 1c5b44a0..b7a23796 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -794,6 +794,83 @@ The table has the following fields: 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. + ### `ClientMatchExactly` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index e44eac62..510292f3 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3167,6 +3167,76 @@ ClientMatch: { 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: