From e511c0c31d39ddb03c9298891ffed628ad133d6f Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 06:52:26 +0100 Subject: [PATCH] feat: split shell control connection --- compositor/src/private_client.rs | 61 +++++++++++++++----- tools/ferese-shell-control-probe/src/main.rs | 25 +++++++- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/compositor/src/private_client.rs b/compositor/src/private_client.rs index 8b8a6cd..4202be5 100644 --- a/compositor/src/private_client.rs +++ b/compositor/src/private_client.rs @@ -34,8 +34,55 @@ pub(crate) fn prepare_command( state: &mut Ferese, command: &mut Command, capabilities: ClientCapabilities, +) -> io::Result> { + let split_shell_control = capabilities.contains(ClientCapabilities::EFFECTS) + && capabilities.contains(ClientCapabilities::SHELL_CONTROL); + let primary_capabilities = if split_shell_control { + ClientCapabilities::EFFECTS + } else { + capabilities + }; + let primary = insert_private_client(state, primary_capabilities)?; + let mut clients = vec![primary]; + + command.env_remove("WAYLAND_DISPLAY"); + command.env_remove("FERESE_SHELL_CONTROL_SOCKET"); + command.env("WAYLAND_SOCKET", clients[0].as_raw_fd().to_string()); + + if split_shell_control { + let control = insert_private_client(state, ClientCapabilities::SHELL_CONTROL)?; + + command.env( + "FERESE_SHELL_CONTROL_SOCKET", + control.as_raw_fd().to_string(), + ); + clients.push(control); + } + + let inherited_fds = clients.iter().map(AsRawFd::as_raw_fd).collect::>(); + // SAFETY: fcntl(F_SETFD) is async-signal-safe. The closure only clears + // CLOEXEC on sockets already owned by this Command's parent process. + unsafe { + command.pre_exec(move || { + for fd in &inherited_fds { + if libc::fcntl(*fd, libc::F_SETFD, 0) == -1 { + return Err(io::Error::last_os_error()); + } + } + + Ok(()) + }); + } + + Ok(clients) +} + +fn insert_private_client( + state: &mut Ferese, + capabilities: ClientCapabilities, ) -> io::Result { let (server, client) = UnixStream::pair()?; + state.display_handle.insert_client( server, Arc::new(ClientState { @@ -44,20 +91,6 @@ pub(crate) fn prepare_command( }), )?; - let client_fd = client.as_raw_fd(); - command.env_remove("WAYLAND_DISPLAY"); - command.env("WAYLAND_SOCKET", client_fd.to_string()); - // SAFETY: fcntl(F_SETFD) is async-signal-safe. The closure only clears - // CLOEXEC on the socket already owned by this Command's parent process. - unsafe { - command.pre_exec(move || { - if libc::fcntl(client_fd, libc::F_SETFD, 0) == -1 { - return Err(io::Error::last_os_error()); - } - Ok(()) - }); - } - Ok(client) } diff --git a/tools/ferese-shell-control-probe/src/main.rs b/tools/ferese-shell-control-probe/src/main.rs index f9f26e9..4aed044 100644 --- a/tools/ferese-shell-control-probe/src/main.rs +++ b/tools/ferese-shell-control-probe/src/main.rs @@ -1,4 +1,10 @@ -use std::error::Error; +use std::{ + error::Error, + os::{ + fd::{FromRawFd, RawFd}, + unix::net::UnixStream, + }, +}; use ferese_protocols::shell::v1::client::{ ferese_shell_manager_v1::FereseShellManagerV1, @@ -10,7 +16,7 @@ use wayland_client::{ fn main() -> Result<(), Box> { let expect_hidden = std::env::args_os().any(|argument| argument == "--expect-hidden"); - let connection = Connection::connect_to_env()?; + let connection = control_connection()?; let mut queue = connection.new_event_queue(); let qh = queue.handle(); connection.display().get_registry(&qh, ()); @@ -62,6 +68,21 @@ fn main() -> Result<(), Box> { Ok(()) } +fn control_connection() -> Result> { + let Some(raw_fd) = std::env::var_os("FERESE_SHELL_CONTROL_SOCKET") else { + return Ok(Connection::connect_to_env()?); + }; + let fd = raw_fd + .to_str() + .ok_or("FERESE_SHELL_CONTROL_SOCKET is not valid UTF-8")? + .parse::()?; + // SAFETY: Ferese passes ownership of this inherited descriptor to the + // launched shell process. This function consumes it exactly once. + let socket = unsafe { UnixStream::from_raw_fd(fd) }; + + Ok(Connection::from_socket(socket)?) +} + #[derive(Default)] struct ProbeState { manager: Option,