From 518095c7c2254e6799b8e9996708b3c5421ceeb5 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 20:06:22 +0100 Subject: [PATCH 1/6] shm: limit data accessed by ClientMemOffset --- src/cli/input.rs | 7 +++---- src/clientmem.rs | 4 ++-- src/ifs/jay_input.rs | 5 +++-- src/ifs/wl_buffer.rs | 5 +++-- src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs | 15 +++++---------- src/ifs/zwlr_gamma_control_v1.rs | 11 +++++------ src/it/tests/t0040_virtual_keyboard.rs | 5 +++-- 7 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/cli/input.rs b/src/cli/input.rs index 2506cbb0..62e76487 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -427,10 +427,9 @@ impl Input { async fn handle_keymap(&self, input: JayInputId) -> Vec { let data = Rc::new(RefCell::new(Vec::new())); jay_input::Keymap::handle(&self.tc, input, data.clone(), |d, map| { - let mem = Rc::new( - ClientMem::new_private(&map.keymap, map.keymap_len as _, true, None, None).unwrap(), - ) - .offset(0); + let len = map.keymap_len as _; + let mem = Rc::new(ClientMem::new_private(&map.keymap, len, true, None, None).unwrap()) + .offset(0, len); mem.read(d.borrow_mut().deref_mut()).unwrap(); }); self.tc.round_trip().await; diff --git a/src/clientmem.rs b/src/clientmem.rs index 8cb6a4fa..f19b212f 100644 --- a/src/clientmem.rs +++ b/src/clientmem.rs @@ -131,12 +131,12 @@ impl ClientMem { self.data.len() } - pub fn offset(self: &Rc, offset: usize) -> ClientMemOffset { + pub fn offset(self: &Rc, offset: usize, len: usize) -> ClientMemOffset { let mem = unsafe { &*self.data }; ClientMemOffset { mem: self.clone(), offset, - data: &mem[offset..], + data: &mem[offset..][..len], } } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index a06a2505..cfac6e6c 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -197,14 +197,15 @@ impl JayInput { where F: FnOnce(&Rc) -> Result<(), JayInputError>, { + let len = len as _; let cm = Rc::new(ClientMem::new_private( keymap, - len as _, + len, true, Some(&self.client), None, )?) - .offset(0); + .offset(0, len); let mut map = vec![]; cm.read(&mut map)?; self.or_error(|| { diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index c698eef1..331dc9f2 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -147,7 +147,8 @@ impl WlBuffer { if required > mem.len() as u64 { return Err(WlBufferError::OutOfBounds); } - let mem = Rc::new(mem.offset(offset)); + let size = bytes as usize; + let mem = Rc::new(mem.offset(offset, size)); let min_row_size = width as u64 * format.bpp as u64; if (stride as u64) < min_row_size { return Err(WlBufferError::StrideTooSmall); @@ -155,7 +156,7 @@ impl WlBuffer { let udmabuf_impossible = !mem.pool().is_sealed_memfd(); let dmabuf_buffer_params = match udmabuf { None => DmabufBufferParams { - size: bytes as usize, + size, udmabuf: None, udmabuf_offset: 0, udmabuf_size: 0, diff --git a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs index 07fb90db..ceaec57b 100644 --- a/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs +++ b/src/ifs/wl_seat/zwp_virtual_keyboard_v1.rs @@ -58,18 +58,13 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 { if req.size > MAX_SIZE { return Err(ZwpVirtualKeyboardV1Error::OversizedKeymap); } - let client_mem = ClientMem::new_private( - &req.fd, - req.size as usize - 1, - true, - Some(&self.client), - None, - ) - .map(Rc::new) - .map_err(ZwpVirtualKeyboardV1Error::MapKeymap)?; + let size = req.size as usize - 1; + let client_mem = ClientMem::new_private(&req.fd, size, true, Some(&self.client), None) + .map(Rc::new) + .map_err(ZwpVirtualKeyboardV1Error::MapKeymap)?; let mut map = vec![]; client_mem - .offset(0) + .offset(0, size) .read(&mut map) .map_err(ZwpVirtualKeyboardV1Error::ReadKeymap)?; let map = self diff --git a/src/ifs/zwlr_gamma_control_v1.rs b/src/ifs/zwlr_gamma_control_v1.rs index 0e361702..8467c6d1 100644 --- a/src/ifs/zwlr_gamma_control_v1.rs +++ b/src/ifs/zwlr_gamma_control_v1.rs @@ -111,22 +111,21 @@ impl ZwlrGammaControlV1RequestHandler for ZwlrGammaControlV1 { return Ok(()); }; - // 3 color channels - let data_size = gamma_lut_size * 3; + // 3 color channels of u16 + let data_size = size_of::() * (3 * gamma_lut_size) as usize; let mut gamma_lut = vec![]; Rc::new(ClientMem::new_private( &req.fd, - (2 * data_size) as _, + data_size, true, Some(&self.client), None, )?) - .offset(0) + .offset(0, data_size) .read(&mut gamma_lut)?; - let gamma_lut = &gamma_lut[..data_size as _]; - let gamma_lut = wayland_gamma_lut_to_drm_gamma_lut(gamma_lut); + let gamma_lut = wayland_gamma_lut_to_drm_gamma_lut(&gamma_lut); let gamma_lut = Rc::new(BackendGammaLut::new(gamma_lut)); if node.set_gamma_lut(Some(gamma_lut)).is_err() { fail(); diff --git a/src/it/tests/t0040_virtual_keyboard.rs b/src/it/tests/t0040_virtual_keyboard.rs index b8d125cc..bcb0db4b 100644 --- a/src/it/tests/t0040_virtual_keyboard.rs +++ b/src/it/tests/t0040_virtual_keyboard.rs @@ -149,8 +149,9 @@ async fn test(run: Rc) -> TestResult { } fn read_keymap(fd: &Rc, size: usize) -> String { - let client_mem = ClientMem::new_private(fd, size - 1, true, None, None).unwrap(); - let client_mem = Rc::new(client_mem).offset(0); + let size = size - 1; + let client_mem = ClientMem::new_private(fd, size, true, None, None).unwrap(); + let client_mem = Rc::new(client_mem).offset(0, size); let mut v = vec![]; client_mem.read(&mut v).unwrap(); v.as_bstr().to_string() From 75400e672dfea659103b3122e2c11159402c6883 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 20:57:24 +0100 Subject: [PATCH 2/6] compositor: add tagged acceptors --- src/compositor.rs | 1 + src/main.rs | 1 + src/security_context_acceptor.rs | 3 + src/state.rs | 3 + src/tagged_acceptor.rs | 201 +++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+) create mode 100644 src/tagged_acceptor.rs diff --git a/src/compositor.rs b/src/compositor.rs index 3193ceaa..1f7fe681 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -324,6 +324,7 @@ fn start_compositor2( keyboard_state_ids: Default::default(), physical_keyboard_ids: Default::default(), security_context_acceptors: Default::default(), + tagged_acceptors: Default::default(), cursor_user_group_ids: Default::default(), cursor_user_ids: Default::default(), cursor_user_groups: Default::default(), diff --git a/src/main.rs b/src/main.rs index 138ab397..ad8c8db2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,6 +97,7 @@ mod screenshoter; mod security_context_acceptor; mod sighand; mod state; +mod tagged_acceptor; mod tasks; mod text; mod theme; diff --git a/src/security_context_acceptor.rs b/src/security_context_acceptor.rs index 74e941b6..6df64edd 100644 --- a/src/security_context_acceptor.rs +++ b/src/security_context_acceptor.rs @@ -39,6 +39,8 @@ pub struct AcceptorMetadata { pub sandbox_engine: Option, pub app_id: Option, pub instance_id: Option, + #[expect(dead_code)] + pub tag: Option, } impl SecurityContextAcceptors { @@ -67,6 +69,7 @@ impl SecurityContextAcceptors { sandbox_engine, app_id, instance_id, + tag: None, }), listen_fd: listen_fd.clone(), close_fd: close_fd.clone(), diff --git a/src/state.rs b/src/state.rs index c8396b26..ee07b0f7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -91,6 +91,7 @@ use { renderer::Renderer, scale::Scale, security_context_acceptor::SecurityContextAcceptors, + tagged_acceptor::TaggedAcceptors, theme::{Color, Theme}, time::Time, tree::{ @@ -239,6 +240,7 @@ pub struct State { pub keyboard_state_ids: KeyboardStateIds, pub physical_keyboard_ids: PhysicalKeyboardIds, pub security_context_acceptors: SecurityContextAcceptors, + pub tagged_acceptors: TaggedAcceptors, pub cursor_user_group_ids: CursorUserGroupIds, pub cursor_user_ids: CursorUserIds, pub cursor_user_groups: CopyHashMap>, @@ -1086,6 +1088,7 @@ impl State { self.workspace_watchers.clear(); self.toplevel_lists.clear(); self.security_context_acceptors.clear(); + self.tagged_acceptors.clear(); self.slow_clients.clear(); for h in self.input_device_handlers.borrow_mut().drain_values() { h.async_event.clear(); diff --git a/src/tagged_acceptor.rs b/src/tagged_acceptor.rs new file mode 100644 index 00000000..43788ac5 --- /dev/null +++ b/src/tagged_acceptor.rs @@ -0,0 +1,201 @@ +use { + crate::{ + async_engine::SpawnedFuture, + client::ClientCaps, + security_context_acceptor::AcceptorMetadata, + state::State, + utils::{errorfmt::ErrorFmt, numcell::NumCell, oserror::OsError, xrd::xrd}, + }, + ahash::AHashMap, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, + thiserror::Error, + uapi::{Errno, OwnedFd, Ustring, c, format_ustr}, +}; + +#[derive(Debug, Error)] +pub enum TaggedAcceptorError { + #[error("XDG_RUNTIME_DIR is not set")] + XrdNotSet, + #[error("XDG_RUNTIME_DIR ({0:?}) is too long to form a unix socket address")] + XrdTooLong(String), + #[error("Could not create a wayland socket")] + SocketFailed(#[source] OsError), + #[error("Could not stat the existing socket")] + SocketStat(#[source] OsError), + #[error("Could not start listening for incoming connections")] + ListenFailed(#[source] OsError), + #[error("Could not open the lock file")] + OpenLockFile(#[source] OsError), + #[error("Could not lock the lock file")] + LockLockFile(#[source] OsError), + #[error("Could not bind the socket to an address")] + BindFailed(#[source] OsError), +} + +#[derive(Default)] +pub struct TaggedAcceptors { + acceptors: RefCell>>, + next_name: NumCell, +} + +struct Acceptor { + socket: AllocatedSocket, + tag: String, + state: Rc, + metadata: Rc, + future: Cell>>, +} + +impl TaggedAcceptors { + pub fn clear(&self) { + let acceptors = self.acceptors.take(); + for (_, acceptor) in acceptors { + acceptor.kill(); + } + } + + #[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) { + return Ok(acceptor.socket.name.clone()); + } + let acceptor = Rc::new(Acceptor { + socket: self.allocate_socket()?, + tag: tag.to_owned(), + state: state.clone(), + metadata: Rc::new(AcceptorMetadata { + secure: false, + sandboxed: false, + sandbox_engine: Default::default(), + app_id: Default::default(), + instance_id: Default::default(), + tag: Some(tag.to_owned()), + }), + future: Default::default(), + }); + log::info!("Creating tagged acceptor `{tag}`"); + acceptor.future.set(Some( + state.eng.spawn("tagged accept", acceptor.clone().accept()), + )); + acceptors.insert(tag.to_owned(), acceptor.clone()); + Ok(acceptor.socket.name.clone()) + } + + fn allocate_socket(&self) -> Result { + let xrd = xrd().ok_or(TaggedAcceptorError::XrdNotSet)?; + let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0) + .map(Rc::new) + .map_err(Into::into) + .map_err(TaggedAcceptorError::SocketFailed)?; + loop { + let i = self.next_name.fetch_add(1) + 1000; + if let Some(s) = bind_socket(&socket, &xrd, i)? { + return Ok(s); + } + } + } +} + +impl Acceptor { + fn kill(&self) { + log::info!("Destroying tagged acceptor `{}`", self.tag); + self.future.take(); + self.state + .tagged_acceptors + .acceptors + .borrow_mut() + .remove(&self.tag); + } + + async fn accept(self: Rc) { + let s = &self.state; + loop { + let fd = match s.ring.accept(&self.socket.socket, c::SOCK_CLOEXEC).await { + Ok(fd) => fd, + Err(e) => { + log::error!("Could not accept a client: {}", ErrorFmt(e)); + break; + } + }; + let id = s.clients.id(); + if let Err(e) = s + .clients + .spawn(id, s, fd, ClientCaps::all(), false, &self.metadata) + { + log::error!("Could not spawn a client: {}", ErrorFmt(e)); + break; + } + } + self.kill(); + } +} + +struct AllocatedSocket { + // wayland-x + name: Rc, + // /run/user/1000/wayland-x + path: Ustring, + socket: Rc, + // /run/user/1000/wayland-x.lock + lock_path: Ustring, + _lock_fd: OwnedFd, +} + +impl Drop for AllocatedSocket { + fn drop(&mut self) { + let _ = uapi::unlink(&self.path); + let _ = uapi::unlink(&self.lock_path); + } +} + +fn bind_socket( + fd: &Rc, + xrd: &str, + id: u64, +) -> Result, TaggedAcceptorError> { + let mut addr: c::sockaddr_un = uapi::pod_zeroed(); + addr.sun_family = c::AF_UNIX as _; + let name = Rc::new(format!("wayland-{}", id)); + let path = format_ustr!("{}/{}", xrd, name); + let lock_path = format_ustr!("{}.lock", path.display()); + if path.len() + 1 > addr.sun_path.len() { + return Err(TaggedAcceptorError::XrdTooLong(xrd.to_string())); + } + let lock_fd = uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644) + .map_err(Into::into) + .map_err(TaggedAcceptorError::OpenLockFile)?; + if let Err(e) = uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB) { + if e.0 == c::EWOULDBLOCK { + return Ok(None); + } + return Err(TaggedAcceptorError::LockLockFile(e.into())); + } + match uapi::lstat(&path) { + Ok(_) => { + log::info!("Unlinking {}", path.display()); + let _ = uapi::unlink(&path); + } + Err(Errno(c::ENOENT)) => {} + Err(e) => return Err(TaggedAcceptorError::SocketStat(e.into())), + } + let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]); + sun_path[..path.len()].copy_from_slice(path.as_bytes()); + sun_path[path.len()] = 0; + uapi::bind(fd.raw(), &addr) + .map_err(Into::into) + .map_err(TaggedAcceptorError::BindFailed)?; + if let Err(e) = uapi::listen(fd.raw(), 4096) { + return Err(TaggedAcceptorError::ListenFailed(e.into())); + } + Ok(Some(AllocatedSocket { + name, + path, + socket: fd.clone(), + lock_path, + _lock_fd: lock_fd, + })) +} From 596909cd2565fb957538fc0d81b0c16222a77cf4 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 20:55:45 +0100 Subject: [PATCH 3/6] cli: print client tags --- src/cli/clients.rs | 5 +++++ src/ifs/jay_client_query.rs | 12 +++++++++++- src/ifs/jay_compositor.rs | 2 +- src/security_context_acceptor.rs | 1 - src/tools/tool_client.rs | 2 +- wire/jay_client_query.txt | 4 ++++ 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/cli/clients.rs b/src/cli/clients.rs index cd6f268a..64e1c8b3 100644 --- a/src/cli/clients.rs +++ b/src/cli/clients.rs @@ -157,6 +157,7 @@ pub struct Client { pub is_xwayland: bool, pub comm: Option, pub exe: Option, + pub tag: Option, } pub async fn handle_client_query( @@ -201,6 +202,9 @@ pub async fn handle_client_query( Exe::handle(tl, id, c.clone(), |c, event| { last!(c).exe = Some(event.exe.to_string()); }); + Tag::handle(tl, id, c.clone(), |c, event| { + last!(c).tag = Some(event.tag.to_string()); + }); tl.round_trip().await; mem::take(&mut *c.borrow_mut()) .into_iter() @@ -239,5 +243,6 @@ impl ClientPrinter<'_> { bol!(is_xwayland, "xwayland"); opt!(comm, "comm"); opt!(exe, "exe"); + opt!(tag, "tag"); } } diff --git a/src/ifs/jay_client_query.rs b/src/ifs/jay_client_query.rs index 2a20e109..bba06f2b 100644 --- a/src/ifs/jay_client_query.rs +++ b/src/ifs/jay_client_query.rs @@ -9,7 +9,7 @@ use { jay_client_query::{ AddAll, AddId, Comm, Destroy, Done, End, Exe, Execute, IsXwayland, JayClientQueryRequestHandler, Pid, SandboxAppId, SandboxEngine, SandboxInstanceId, - Sandboxed, Start, Uid, + Sandboxed, Start, Tag, Uid, }, }, }, @@ -26,6 +26,8 @@ pub struct JayClientQuery { all: Cell, } +const TAG_SINCE: Version = Version(25); + impl JayClientQuery { pub fn new(client: &Rc, id: JayClientQueryId, version: Version) -> Self { Self { @@ -95,6 +97,14 @@ impl JayClientQueryRequestHandler for JayClientQuery { instance_id, }); } + if self.version >= TAG_SINCE + && let Some(tag) = &client.acceptor.tag + { + self.client.event(Tag { + self_id: self.id, + tag, + }); + } self.client.event(End { self_id: self.id }); }; if self.all.get() { diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 611ab54c..40ca1f1d 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 24 + 25 } fn required_caps(&self) -> ClientCaps { diff --git a/src/security_context_acceptor.rs b/src/security_context_acceptor.rs index 6df64edd..0a5f9bb0 100644 --- a/src/security_context_acceptor.rs +++ b/src/security_context_acceptor.rs @@ -39,7 +39,6 @@ pub struct AcceptorMetadata { pub sandbox_engine: Option, pub app_id: Option, pub instance_id: Option, - #[expect(dead_code)] pub tag: Option, } diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 32b307ec..0b720d5c 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -335,7 +335,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(24), + version: s.jay_compositor.1.min(25), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/wire/jay_client_query.txt b/wire/jay_client_query.txt index ef841292..f2f97d05 100644 --- a/wire/jay_client_query.txt +++ b/wire/jay_client_query.txt @@ -47,3 +47,7 @@ event comm { event exe { exe: str, } + +event tag (since = 25) { + tag: str, +} From 8b19315f509633733e5cea7a52268e40a41e5efb Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 20:53:09 +0100 Subject: [PATCH 4/6] config: allow matching on client tag --- docs/window-and-client-rules.md | 1 + jay-config/src/_private.rs | 1 + jay-config/src/_private/client.rs | 2 ++ jay-config/src/client.rs | 4 ++++ src/config/handler.rs | 1 + src/criteria/clm.rs | 9 ++++++- src/criteria/clm/clm_matchers/clmm_string.rs | 24 +++++++++++++++---- toml-config/src/config.rs | 2 ++ .../src/config/parsers/client_match.rs | 10 ++++++-- toml-config/src/rules.rs | 2 ++ toml-spec/spec/spec.generated.json | 8 +++++++ toml-spec/spec/spec.generated.md | 12 ++++++++++ toml-spec/spec/spec.yaml | 8 +++++++ 13 files changed, 76 insertions(+), 8 deletions(-) diff --git a/docs/window-and-client-rules.md b/docs/window-and-client-rules.md index 88a9590b..6d4f846d 100644 --- a/docs/window-and-client-rules.md +++ b/docs/window-and-client-rules.md @@ -204,6 +204,7 @@ The full specification of client criteria can be found in - `is-xwayland` - Matches if the client is/isn't Xwayland. - `comm`, `comm-regex` - Matches the `/proc/self/comm` of the client. - `exe`, `exe-regex` - Matches the `/proc/self/exe` of the client. +- `tag`, `tag-regex` - Matches the tag of the client. ## Window Rules diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index 45034dd0..facb7c17 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -99,6 +99,7 @@ pub enum ClientCriterionStringField { SandboxInstanceId, Comm, Exe, + Tag, } #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index e4cfbd0d..651c57d2 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1764,6 +1764,8 @@ impl ConfigClient { ClientCriterion::CommRegex(t) => string!(t, Comm, true), ClientCriterion::Exe(t) => string!(t, Exe, false), ClientCriterion::ExeRegex(t) => string!(t, Exe, true), + ClientCriterion::Tag(t) => string!(t, Tag, false), + ClientCriterion::TagRegex(t) => string!(t, Tag, true), }; let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion }); get_response!( diff --git a/jay-config/src/client.rs b/jay-config/src/client.rs index 0048a465..507e39e3 100644 --- a/jay-config/src/client.rs +++ b/jay-config/src/client.rs @@ -91,6 +91,10 @@ pub enum ClientCriterion<'a> { Exe(&'a str), /// Matches the `/proc/pid/exe` of the client with a regular expression. ExeRegex(&'a str), + /// Matches the tag of the client verbatim. + Tag(&'a str), + /// Matches the tag of the client with a regular expression. + TagRegex(&'a str), } impl ClientCriterion<'_> { diff --git a/src/config/handler.rs b/src/config/handler.rs index f5f4e3c9..4f7dfe69 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -2107,6 +2107,7 @@ impl ConfigProxyHandler { } ClientCriterionStringField::Comm => mgr.comm(needle), ClientCriterionStringField::Exe => mgr.exe(needle), + ClientCriterionStringField::Tag => mgr.tag(needle), } } ClientCriterionIpc::Sandboxed => mgr.sandboxed(), diff --git a/src/criteria/clm.rs b/src/criteria/clm.rs index 99b0ea8b..be88c270 100644 --- a/src/criteria/clm.rs +++ b/src/criteria/clm.rs @@ -12,7 +12,7 @@ use { clmm_sandboxed::ClmMatchSandboxed, clmm_string::{ ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine, - ClmMatchSandboxInstanceId, + ClmMatchSandboxInstanceId, ClmMatchTag, }, clmm_uid::ClmMatchUid, }, @@ -61,6 +61,7 @@ pub struct RootMatchers { pid: ClmRootMatcherMap, comm: ClmRootMatcherMap, exe: ClmRootMatcherMap, + tag: ClmRootMatcherMap, } impl RootMatchers { @@ -72,6 +73,7 @@ impl RootMatchers { self.pid.clear(); self.comm.clear(); self.exe.clear(); + self.tag.clear(); } } @@ -181,6 +183,7 @@ impl ClMatcherManager { unconditional!(pid); unconditional!(comm); unconditional!(exe); + unconditional!(tag); fixed!(sandboxed); fixed!(is_xwayland); self.constant[true].handle(data); @@ -222,6 +225,10 @@ impl ClMatcherManager { pub fn exe(&self, string: CritLiteralOrRegex) -> Rc { self.root(ClmMatchExe::new(string)) } + + pub fn tag(&self, string: CritLiteralOrRegex) -> Rc { + self.root(ClmMatchTag::new(string)) + } } impl CritTarget for Rc { diff --git a/src/criteria/clm/clm_matchers/clmm_string.rs b/src/criteria/clm/clm_matchers/clmm_string.rs index 626c5f3d..a5877adc 100644 --- a/src/criteria/clm/clm_matchers/clmm_string.rs +++ b/src/criteria/clm/clm_matchers/clmm_string.rs @@ -15,6 +15,7 @@ pub type ClmMatchString = CritMatchString, T>; pub type ClmMatchSandboxEngine = ClmMatchString>; pub type ClmMatchSandboxAppId = ClmMatchString>; pub type ClmMatchSandboxInstanceId = ClmMatchString>; +pub type ClmMatchTag = ClmMatchString>; pub type ClmMatchComm = ClmMatchString; pub type ClmMatchExe = ClmMatchString; @@ -22,7 +23,7 @@ pub struct AcceptorMetadataAccess(PhantomData); pub struct CommAccess; pub struct ExeAccess; -trait SandboxField: Sized + 'static { +trait AcceptorMetadataField: Sized + 'static { fn field(meta: &AcceptorMetadata) -> &Option; fn nodes( roots: &RootMatchers, @@ -32,10 +33,11 @@ trait SandboxField: Sized + 'static { pub struct SandboxEngineField; pub struct SandboxAppIdField; pub struct SandboxInstanceIdField; +pub struct TagField; impl StringAccess> for AcceptorMetadataAccess where - T: SandboxField, + T: AcceptorMetadataField, { fn with_string(data: &Rc, f: impl FnOnce(&str) -> bool) -> bool { f(T::field(&data.acceptor).as_deref().unwrap_or_default()) @@ -46,7 +48,7 @@ where } } -impl SandboxField for SandboxEngineField { +impl AcceptorMetadataField for SandboxEngineField { fn field(meta: &AcceptorMetadata) -> &Option { &meta.sandbox_engine } @@ -58,7 +60,7 @@ impl SandboxField for SandboxEngineField { } } -impl SandboxField for SandboxAppIdField { +impl AcceptorMetadataField for SandboxAppIdField { fn field(meta: &AcceptorMetadata) -> &Option { &meta.app_id } @@ -70,7 +72,7 @@ impl SandboxField for SandboxAppIdField { } } -impl SandboxField for SandboxInstanceIdField { +impl AcceptorMetadataField for SandboxInstanceIdField { fn field(meta: &AcceptorMetadata) -> &Option { &meta.instance_id } @@ -82,6 +84,18 @@ impl SandboxField for SandboxInstanceIdField { } } +impl AcceptorMetadataField for TagField { + fn field(meta: &AcceptorMetadata) -> &Option { + &meta.tag + } + + fn nodes( + roots: &RootMatchers, + ) -> &ClmRootMatcherMap>> { + &roots.tag + } +} + impl StringAccess> for CommAccess { fn with_string(data: &Rc, f: impl FnOnce(&str) -> bool) -> bool { f(&data.pid_info.comm) diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index e15b175b..65fdf9e8 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -278,6 +278,8 @@ pub struct ClientMatch { pub comm_regex: Option, pub exe: Option, pub exe_regex: Option, + pub tag: Option, + pub tag_regex: 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 013e7646..24ecbc08 100644 --- a/toml-config/src/config/parsers/client_match.rs +++ b/toml-config/src/config/parsers/client_match.rs @@ -54,12 +54,14 @@ impl Parser for ClientMatchParser<'_> { sandbox_instance_id_regex, uid, pid, - is_xwayland, comm, comm_regex, exe, exe_regex, + tag, + tag_regex, ), + (is_xwayland,), ) = ext.extract(( ( opt(str("name")), @@ -78,12 +80,14 @@ impl Parser for ClientMatchParser<'_> { opt(str("sandbox-instance-id-regex")), opt(s32("uid")), opt(s32("pid")), - opt(bol("is-xwayland")), opt(str("comm")), opt(str("comm-regex")), opt(str("exe")), opt(str("exe-regex")), + opt(str("tag")), + opt(str("tag-regex")), ), + (opt(bol("is-xwayland")),), ))?; let mut not = None; if let Some(value) = not_val { @@ -130,6 +134,8 @@ impl Parser for ClientMatchParser<'_> { comm_regex: comm_regex.despan_into(), exe: exe.despan_into(), exe_regex: exe_regex.despan_into(), + tag: tag.despan_into(), + tag_regex: tag_regex.despan_into(), }) } } diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index ef8da965..b67a2cca 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -127,6 +127,8 @@ impl Rule for ClientRule { value_ref!(CommRegex, comm_regex); value_ref!(Exe, exe); value_ref!(ExeRegex, exe_regex); + value_ref!(Tag, tag); + value_ref!(TagRegex, tag_regex); value!(Uid, uid); value!(Pid, pid); bool!(Sandboxed, sandboxed); diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 29052508..ef992aba 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -742,6 +742,14 @@ "exe-regex": { "type": "string", "description": "Matches the `/proc/pid/exe` of the client with a regular expression." + }, + "tag": { + "type": "string", + "description": "Matches the tag of the client verbatim." + }, + "tag-regex": { + "type": "string", + "description": "Matches the tag of the client with a regular expression." } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 58d2beb1..71bc4ef9 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -1242,6 +1242,18 @@ The table has the following fields: The value of this field should be a string. +- `tag` (optional): + + Matches the tag of the client verbatim. + + The value of this field should be a string. + +- `tag-regex` (optional): + + Matches the tag of the client 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 de751055..c4336c9b 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3823,6 +3823,14 @@ ClientMatch: kind: string required: false description: Matches the `/proc/pid/exe` of the client with a regular expression. + tag: + kind: string + required: false + description: Matches the tag of the client verbatim. + tag-regex: + kind: string + required: false + description: Matches the tag of the client with a regular expression. ClientMatchExactly: From a1df5752629ef4183bb73026df630019fcafd818 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 21:00:20 +0100 Subject: [PATCH 5/6] 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: From 3e7ca00565cdb0e4dfaabf73ed60fd7d2b636cee Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 27 Feb 2026 21:59:30 +0100 Subject: [PATCH 6/6] 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 {