From 3e7ca00565cdb0e4dfaabf73ed60fd7d2b636cee Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 21:59:30 +0100 Subject: [PATCH] cli: add run-tagged subcommand --- src/cli.rs | 6 ++- src/cli/run_tagged.rs | 74 +++++++++++++++++++++++++++++++++ src/ifs.rs | 1 + src/ifs/jay_acceptor_request.rs | 60 ++++++++++++++++++++++++++ src/ifs/jay_compositor.rs | 22 ++++++++++ wire/jay_acceptor_request.txt | 9 ++++ wire/jay_compositor.txt | 5 +++ 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/cli/run_tagged.rs create mode 100644 src/ifs/jay_acceptor_request.rs create mode 100644 wire/jay_acceptor_request.txt diff --git a/src/cli.rs b/src/cli.rs index 866457e9..c741b944 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,6 +11,7 @@ mod quit; mod randr; mod reexec; mod run_privileged; +mod run_tagged; pub mod screenshot; mod seat_test; mod set_log_level; @@ -24,7 +25,7 @@ use { cli::{ clients::ClientsArgs, color_management::ColorManagementArgs, damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs, - reexec::ReexecArgs, tree::TreeArgs, xwayland::XwaylandArgs, + reexec::ReexecArgs, run_tagged::RunTaggedArgs, tree::TreeArgs, xwayland::XwaylandArgs, }, compositor::start_compositor, format::{Format, ref_formats}, @@ -72,6 +73,8 @@ pub enum Cmd { Idle(IdleArgs), /// Run a privileged program. RunPrivileged(RunPrivilegedArgs), + /// Run a program with a connection tag. + RunTagged(RunTaggedArgs), /// Tests the events produced by a seat. SeatTest(SeatTestArgs), /// Run the desktop portal. @@ -245,6 +248,7 @@ pub fn main() { Cmd::Idle(a) => idle::main(cli.global, a), Cmd::Unlock => unlock::main(cli.global), Cmd::RunPrivileged(a) => run_privileged::main(cli.global, a), + Cmd::RunTagged(a) => run_tagged::main(cli.global, a), Cmd::SeatTest(a) => seat_test::main(cli.global, a), Cmd::Portal => portal::run_freestanding(cli.global), Cmd::Randr(a) => randr::main(cli.global, a), diff --git a/src/cli/run_tagged.rs b/src/cli/run_tagged.rs new file mode 100644 index 00000000..d2baa230 --- /dev/null +++ b/src/cli/run_tagged.rs @@ -0,0 +1,74 @@ +use { + crate::{ + cli::GlobalArgs, + compositor::WAYLAND_DISPLAY, + tools::tool_client::{Handle, ToolClient, with_tool_client}, + utils::{errorfmt::ErrorFmt, oserror::OsError}, + wire::{jay_acceptor_request, jay_compositor}, + }, + clap::{Args, ValueHint}, + std::{cell::Cell, env, rc::Rc}, + uapi::UstrPtr, +}; + +#[derive(Args, Debug)] +pub struct RunTaggedArgs { + /// Specifies a tag to apply to all spawned wayland connections. + tag: String, + /// The program to run. + #[clap(required = true, trailing_var_arg = true, value_hint = ValueHint::CommandWithArguments)] + pub program: Vec, +} + +pub fn main(global: GlobalArgs, run_tagged_args: RunTaggedArgs) { + with_tool_client(global.log_level.into(), |tc| async move { + let run_tagged = Rc::new(RunTagged { tc: tc.clone() }); + run_tagged.run(run_tagged_args).await; + }); +} + +struct RunTagged { + tc: Rc, +} + +impl RunTagged { + async fn run(&self, args: RunTaggedArgs) { + let tc = &self.tc; + let comp = tc.jay_compositor().await; + let req = tc.id(); + tc.send(jay_compositor::GetTaggedAcceptor { + self_id: comp, + id: req, + tag: &args.tag, + }); + let res = Rc::new(Cell::new(None)); + jay_acceptor_request::Done::handle(&tc, req, res.clone(), |res, ev| { + res.set(Some(Ok(ev.name.to_owned()))); + }); + jay_acceptor_request::Failed::handle(&tc, req, res.clone(), |res, ev| { + res.set(Some(Err(ev.msg.to_owned()))); + }); + tc.round_trip().await; + match res.take().unwrap() { + Ok(n) => { + unsafe { + env::set_var(WAYLAND_DISPLAY, &n); + } + let mut argv = UstrPtr::new(); + for arg in &args.program { + argv.push(arg.as_str()); + } + let program = args.program[0].as_str(); + let res = uapi::execvp(program, &argv).unwrap_err(); + fatal!( + "Could not execute `{}`: {}", + program, + ErrorFmt(OsError::from(res)), + ); + } + Err(msg) => { + fatal!("Could not create acceptor: {}", msg); + } + } + } +} diff --git a/src/ifs.rs b/src/ifs.rs index 1036af14..2814a144 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -11,6 +11,7 @@ pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod head_management; pub mod ipc; +pub mod jay_acceptor_request; pub mod jay_client_query; pub mod jay_color_management; pub mod jay_compositor; diff --git a/src/ifs/jay_acceptor_request.rs b/src/ifs/jay_acceptor_request.rs new file mode 100644 index 00000000..b9a73184 --- /dev/null +++ b/src/ifs/jay_acceptor_request.rs @@ -0,0 +1,60 @@ +use { + crate::{ + client::{Client, ClientError}, + leaks::Tracker, + object::{Object, Version}, + utils::errorfmt::ErrorFmt, + wire::{JayAcceptorRequestId, jay_acceptor_request::*}, + }, + std::{error::Error, rc::Rc}, + thiserror::Error, +}; + +pub struct JayAcceptorRequest { + pub id: JayAcceptorRequestId, + pub client: Rc, + pub tracker: Tracker, + pub version: Version, +} + +impl JayAcceptorRequest { + pub fn send_done(&self, name: &str) { + self.client.event(Done { + self_id: self.id, + name, + }); + } + + pub fn send_failed(&self, err: impl Error) { + let msg = &ErrorFmt(err).to_string(); + self.client.event(Failed { + self_id: self.id, + msg, + }); + } +} + +impl JayAcceptorRequestRequestHandler for JayAcceptorRequest { + type Error = JayAcceptorRequestError; + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = JayAcceptorRequest; + version = self.version; +} + +impl Object for JayAcceptorRequest {} + +simple_add_obj!(JayAcceptorRequest); + +#[derive(Debug, Error)] +pub enum JayAcceptorRequestError { + #[error(transparent)] + ClientError(Box), +} +efrom!(JayAcceptorRequestError, ClientError); diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 40ca1f1d..dc740dcc 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -4,6 +4,7 @@ use { client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError, ClientId}, globals::{Global, GlobalName}, ifs::{ + jay_acceptor_request::JayAcceptorRequest, jay_client_query::JayClientQuery, jay_color_management::JayColorManagement, jay_ei_session_builder::JayEiSessionBuilder, @@ -515,6 +516,27 @@ impl JayCompositorRequestHandler for JayCompositor { self.client.add_client_obj(&obj)?; Ok(()) } + + fn get_tagged_acceptor( + &self, + req: GetTaggedAcceptor<'_>, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let obj = Rc::new(JayAcceptorRequest { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + version: self.version, + }); + track!(self.client, obj); + self.client.add_client_obj(&obj)?; + let state = &self.client.state; + match state.tagged_acceptors.get(state, req.tag) { + Ok(d) => obj.send_done(&d), + Err(e) => obj.send_failed(e), + } + Ok(()) + } } object_base! { diff --git a/wire/jay_acceptor_request.txt b/wire/jay_acceptor_request.txt new file mode 100644 index 00000000..e3b0105e --- /dev/null +++ b/wire/jay_acceptor_request.txt @@ -0,0 +1,9 @@ +request destroy { } + +event done { + name: str, +} + +event failed { + msg: str, +} diff --git a/wire/jay_compositor.txt b/wire/jay_compositor.txt index 1a4657a8..2f5b74b8 100644 --- a/wire/jay_compositor.txt +++ b/wire/jay_compositor.txt @@ -121,6 +121,11 @@ request create_tree_query (since = 18) { id: id(jay_tree_query), } +request get_tagged_acceptor (since = 25) { + id: id(jay_acceptor_request), + tag: str, +} + # events event client_id {