From a1df5752629ef4183bb73026df630019fcafd818 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 21:00:20 +0100 Subject: [PATCH] config: allow spawning clients with tags --- jay-config/src/_private/client.rs | 10 +++++++++- jay-config/src/_private/ipc.rs | 7 +++++++ jay-config/src/exec.rs | 8 ++++++++ src/config/handler.rs | 27 ++++++++++++++++++++++---- src/tagged_acceptor.rs | 1 - toml-config/src/config.rs | 1 + toml-config/src/config/parsers/exec.rs | 15 +++++++++++++- toml-config/src/lib.rs | 3 +++ toml-spec/spec/spec.generated.json | 4 ++++ toml-spec/spec/spec.generated.md | 6 ++++++ toml-spec/spec/spec.yaml | 5 +++++ 11 files changed, 80 insertions(+), 7 deletions(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 651c57d2..7c71461e 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -348,7 +348,15 @@ impl ConfigClient { .drain() .map(|(a, b)| (a, b.into_raw_fd())) .collect(); - if fds.is_empty() { + if command.tag.is_some() { + self.send(&ClientMessage::Run3 { + prog: &command.prog, + args: command.args.clone(), + env, + fds, + tag: command.tag.as_deref(), + }); + } else if fds.is_empty() { self.send(&ClientMessage::Run { prog: &command.prog, args: command.args.clone(), diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 41a672d1..ab1de845 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -834,6 +834,13 @@ pub enum ClientMessage<'a> { SetXWaylandEnabled { enabled: bool, }, + Run3 { + prog: &'a str, + args: Vec, + env: Vec<(String, String)>, + fds: Vec<(i32, i32)>, + tag: Option<&'a str>, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/exec.rs b/jay-config/src/exec.rs index 183d8f94..61074167 100644 --- a/jay-config/src/exec.rs +++ b/jay-config/src/exec.rs @@ -22,6 +22,7 @@ pub struct Command { pub(crate) args: Vec, pub(crate) env: HashMap, pub(crate) fds: RefCell>, + pub(crate) tag: Option, } impl Command { @@ -37,6 +38,7 @@ impl Command { args: vec![], env: Default::default(), fds: Default::default(), + tag: Default::default(), } } @@ -97,6 +99,12 @@ impl Command { self } + /// Adds a tag to Wayland connections created by the spawned command. + pub fn tag(&mut self, tag: &str) -> &mut Self { + self.tag = Some(tag.to_owned()); + self + } + /// Executes the command. /// /// This consumes all attached file descriptors. diff --git a/src/config/handler.rs b/src/config/handler.rs index 4f7dfe69..5208def1 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -8,7 +8,7 @@ use { }, client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId}, cmm::cmm_eotf::Eotf, - compositor::MAX_EXTENTS, + compositor::{MAX_EXTENTS, WAYLAND_DISPLAY}, config::ConfigProxy, criteria::{ CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode, @@ -26,6 +26,7 @@ use { output_schedule::map_cursor_hz, scale::Scale, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, + tagged_acceptor::TaggedAcceptorError, theme::{Color, ThemeSized}, tree::{ ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode, @@ -1823,9 +1824,18 @@ impl ConfigProxyHandler { &self, prog: &str, args: Vec, - env: Vec<(String, String)>, + mut env: Vec<(String, String)>, fds: Vec<(i32, i32)>, + tag: Option<&str>, ) -> Result<(), CphError> { + if let Some(tag) = tag { + let display = self + .state + .tagged_acceptors + .get(&self.state, tag) + .map_err(CphError::CreateTaggedAcceptor)?; + env.push((WAYLAND_DISPLAY.to_string(), display.to_string())); + } let fds: Vec<_> = fds .into_iter() .map(|(a, b)| (a, Rc::new(OwnedFd::new(b)))) @@ -2816,7 +2826,7 @@ impl ConfigProxyHandler { ClientMessage::GetSeats => self.handle_get_seats(), ClientMessage::RemoveSeat { .. } => {} ClientMessage::Run { prog, args, env } => { - self.handle_run(prog, args, env, vec![]).wrn("run")? + self.handle_run(prog, args, env, vec![], None).wrn("run")? } ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?, ClientMessage::SetColor { colorable, color } => { @@ -3024,7 +3034,7 @@ impl ConfigProxyHandler { args, env, fds, - } => self.handle_run(prog, args, env, fds).wrn("run")?, + } => self.handle_run(prog, args, env, fds, None).wrn("run")?, ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false), ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap), ClientMessage::GetConnectorName { connector } => self @@ -3394,6 +3404,13 @@ impl ConfigProxyHandler { ClientMessage::SetXWaylandEnabled { enabled } => self .handle_set_x_wayland_enabled(enabled) .wrn("set_x_wayland_enabled")?, + ClientMessage::Run3 { + prog, + args, + env, + fds, + tag, + } => self.handle_run(prog, args, env, fds, tag).wrn("run")?, } Ok(()) } @@ -3549,6 +3566,8 @@ enum CphError { UnknownFallbackOutputMode(FallbackOutputMode), #[error("Unknown tile state {0:?}")] UnknownTileState(ConfigTileState), + #[error("Could not create a tagged acceptor")] + CreateTaggedAcceptor(#[source] TaggedAcceptorError), } trait WithRequestName { diff --git a/src/tagged_acceptor.rs b/src/tagged_acceptor.rs index 43788ac5..f193d934 100644 --- a/src/tagged_acceptor.rs +++ b/src/tagged_acceptor.rs @@ -57,7 +57,6 @@ impl TaggedAcceptors { } } - #[expect(dead_code)] pub fn get(&self, state: &Rc, tag: &str) -> Result, TaggedAcceptorError> { let acceptors = &mut *self.acceptors.borrow_mut(); if let Some(acceptor) = acceptors.get(tag) { diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 65fdf9e8..45a62ab6 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -421,6 +421,7 @@ pub struct Exec { pub args: Vec, pub envs: Vec<(String, String)>, pub privileged: bool, + pub tag: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/exec.rs b/toml-config/src/config/parsers/exec.rs index bc0eaa61..2faf2397 100644 --- a/toml-config/src/config/parsers/exec.rs +++ b/toml-config/src/config/parsers/exec.rs @@ -53,6 +53,7 @@ impl Parser for ExecParser<'_> { args: vec![], envs: vec![], privileged: false, + tag: None, }) } @@ -70,6 +71,7 @@ impl Parser for ExecParser<'_> { args, envs: vec![], privileged: false, + tag: None, }) } @@ -79,12 +81,13 @@ impl Parser for ExecParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); - let (prog_opt, shell_opt, args_val, envs_val, privileged) = ext.extract(( + let (prog_opt, shell_opt, args_val, envs_val, privileged, tag) = ext.extract(( opt(str("prog")), opt(str("shell")), opt(arr("args")), opt(val("env")), recover(opt(bol("privileged"))), + opt(str("tag")), ))?; let prog; let mut args = vec![]; @@ -112,11 +115,21 @@ impl Parser for ExecParser<'_> { None => vec![], Some(e) => e.parse_map(&mut EnvParser)?, }; + if let Some(privileged) = privileged + && privileged.value + && tag.is_some() + { + log::warn!( + "Exec is privileged and tagged but tagged execs are always unprivileged: {}", + self.0.error3(privileged.span), + ); + } Ok(Exec { prog, args, envs, privileged: privileged.despan().unwrap_or(false), + tag: tag.despan_into(), }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index d3e4f1f6..db9058e6 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -1646,6 +1646,9 @@ fn create_command(exec: &Exec) -> Command { if exec.privileged { command.privileged(); } + if let Some(tag) = &exec.tag { + command.tag(tag); + } command } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index ef992aba..54a26aea 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1291,6 +1291,10 @@ "privileged": { "type": "boolean", "description": "If `true`, the executable gets access to privileged wayland protocols.\n\nThe default is `false`.\n" + }, + "tag": { + "type": "string", + "description": "Specifies a tag to apply to all spawned wayland connections.\n" } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 71bc4ef9..1c4b1c94 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -2723,6 +2723,12 @@ The table has the following fields: The value of this field should be a boolean. +- `tag` (optional): + + Specifies a tag to apply to all spawned wayland connections. + + The value of this field should be a string. + ### `FallbackOutputMode` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index c4336c9b..cdb0767a 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -858,6 +858,11 @@ Exec: If `true`, the executable gets access to privileged wayland protocols. The default is `false`. + tag: + kind: string + required: false + description: | + Specifies a tag to apply to all spawned wayland connections. SimpleActionName: