feat: split shell control connection

This commit is contained in:
Samuel Onoja committed 2026-09-25 06:52:26 +01:00
1 parent 0c6fefacc1
commit e511c0c31d
2 files changed
+70 -16

No files matched your search

+47 -14
View File
@@ -34,8 +34,55 @@ pub(crate) fn prepare_command(
state: &mut Ferese, state: &mut Ferese,
command: &mut Command, command: &mut Command,
capabilities: ClientCapabilities, capabilities: ClientCapabilities,
) -> io::Result<Vec<UnixStream>> {
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::<Vec<_>>();
// 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<UnixStream> { ) -> io::Result<UnixStream> {
let (server, client) = UnixStream::pair()?; let (server, client) = UnixStream::pair()?;
state.display_handle.insert_client( state.display_handle.insert_client(
server, server,
Arc::new(ClientState { 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) Ok(client)
} }
+23 -2
View File
@@ -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::{ use ferese_protocols::shell::v1::client::{
ferese_shell_manager_v1::FereseShellManagerV1, ferese_shell_manager_v1::FereseShellManagerV1,
@@ -10,7 +16,7 @@ use wayland_client::{
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let expect_hidden = std::env::args_os().any(|argument| argument == "--expect-hidden"); 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 mut queue = connection.new_event_queue();
let qh = queue.handle(); let qh = queue.handle();
connection.display().get_registry(&qh, ()); connection.display().get_registry(&qh, ());
@@ -62,6 +68,21 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
fn control_connection() -> Result<Connection, Box<dyn Error>> {
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::<RawFd>()?;
// 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)] #[derive(Default)]
struct ProbeState { struct ProbeState {
manager: Option<FereseShellManagerV1>, manager: Option<FereseShellManagerV1>,