diff --git a/Cargo.lock b/Cargo.lock index fede8be1..6dc42fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2053,6 +2053,7 @@ version = "0.43.0" dependencies = [ "atty", "clap", + "cluFlock", "color-eyre", "console-subscriber", "ctrlc", @@ -2081,6 +2082,8 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-tracy", + "wayland-backend", + "wayland-scanner", "xkbcommon", ] diff --git a/Cargo.toml b/Cargo.toml index 03e611bc..68342dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ path = "src/main.rs" [features] default = ["wayland"] wayland = ["dep:smithay", "dep:xkbcommon"] -xwayland = ["smithay/xwayland"] +xwayland_rootful = [] +xwayland_rootless = ["smithay/xwayland"] profile_tokio = ["dep:console-subscriber", "tokio/tracing"] profile_app = ["dep:tracing-tracy"] @@ -63,6 +64,9 @@ ctrlc = "3.4.1" libc = "0.2.148" input-event-codes = "5.16.8" nix = "0.27.1" +wayland-scanner = "0.31.0" +wayland-backend = "0.3.2" +cluFlock = "1.2.7" [dependencies.smithay] # git = "https://github.com/technobaboo/smithay.git" # Until we get stereokit to understand OES samplers and external textures diff --git a/src/main.rs b/src/main.rs index 4cf8a0ae..82422235 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use crate::objects::input::mouse_pointer::MousePointer; use crate::objects::input::sk_controller::SkController; use crate::objects::input::sk_hand::SkHand; use crate::objects::play_space::PlaySpace; +use crate::wayland::X_DISPLAY; use self::core::eventloop::EventLoop; use clap::Parser; @@ -235,8 +236,10 @@ fn main() { if let Some(wayland_socket) = wayland.socket_name.as_ref() { startup_command.env("WAYLAND_DISPLAY", &wayland_socket); } - #[cfg(feature = "xwayland")] - startup_command.env("DISPLAY", format!(":{}", wayland.xwayland_state.display)); + startup_command.env( + "DISPLAY", + format!(":{}", X_DISPLAY.get().cloned().unwrap_or_default()), + ); startup_command.env("GDK_BACKEND", "wayland"); startup_command.env("QT_QPA_PLATFORM", "wayland"); startup_command.env("MOZ_ENABLE_WAYLAND", "1"); diff --git a/src/nodes/root.rs b/src/nodes/root.rs index 03412ae8..ed718a94 100644 --- a/src/nodes/root.rs +++ b/src/nodes/root.rs @@ -4,7 +4,10 @@ use crate::core::client::Client; use crate::core::client_state::{ClientState, ClientStateInternal}; use crate::core::registry::Registry; use crate::core::scenegraph::MethodResponseSender; +#[cfg(feature = "wayland")] use crate::wayland::WAYLAND_DISPLAY; +#[cfg(feature = "xwayland")] +use crate::wayland::X_DISPLAY; use crate::STARDUST_INSTANCE; use color_eyre::eyre::Result; use glam::Mat4; @@ -125,7 +128,10 @@ pub fn get_connection_environment_flex( { var_env_insert!(env, WAYLAND_DISPLAY); #[cfg(feature = "xwayland")] - var_env_insert!(env, DISPLAY); + env.insert( + "DISPLAY".to_string(), + format!(":{}", X_DISPLAY.get().unwrap()), + ); env.insert("GDK_BACKEND".to_string(), "wayland".to_string()); env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string()); env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string()); diff --git a/src/wayland/drm.rs b/src/wayland/drm.rs new file mode 100644 index 00000000..90b6ae31 --- /dev/null +++ b/src/wayland/drm.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-3.0-only + +// Re-export only the actual code, and then only use this re-export +// The `generated` module below is just some boilerplate to properly isolate stuff +// and avoid exposing internal details. +// +// You can use all the types from my_protocol as if they went from `wayland_client::protocol`. +pub use generated::wl_drm; + +#[allow(non_upper_case_globals, non_camel_case_types)] +mod generated { + use smithay::reexports::wayland_server::{self, protocol::*}; + + pub mod __interfaces { + use smithay::reexports::wayland_server::protocol::__interfaces::*; + wayland_scanner::generate_interfaces!("src/wayland/wayland-drm.xml"); + } + use self::__interfaces::*; + + wayland_scanner::generate_server_code!("src/wayland/wayland-drm.xml"); +} + +use super::state::WaylandState; +use smithay::{ + backend::allocator::{ + dmabuf::{Dmabuf, DmabufFlags}, + Fourcc, Modifier, + }, + reexports::wayland_server::{ + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, + }, +}; +use std::convert::TryFrom; + +impl GlobalDispatch for WaylandState { + fn bind( + state: &mut WaylandState, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &(), + data_init: &mut DataInit<'_, WaylandState>, + ) { + let drm_instance = data_init.init(resource, ()); + + drm_instance.device("/dev/dri/renderD128".to_string()); + if drm_instance.version() >= 2 { + drm_instance.capabilities(wl_drm::Capability::Prime as u32); + } + for format in state.drm_formats.iter() { + if let Ok(converted) = wl_drm::Format::try_from(*format as u32) { + drm_instance.format(converted as u32); + } + } + } + + fn can_view(_client: Client, _global_dataa: &()) -> bool { + true + } +} + +impl Dispatch for WaylandState { + fn request( + state: &mut WaylandState, + _client: &Client, + drm: &wl_drm::WlDrm, + request: wl_drm::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, WaylandState>, + ) { + match request { + wl_drm::Request::Authenticate { .. } => drm.authenticated(), + wl_drm::Request::CreateBuffer { .. } => drm.post_error( + wl_drm::Error::InvalidName, + String::from("Flink handles are unsupported, use PRIME"), + ), + wl_drm::Request::CreatePlanarBuffer { .. } => drm.post_error( + wl_drm::Error::InvalidName, + String::from("Flink handles are unsupported, use PRIME"), + ), + wl_drm::Request::CreatePrimeBuffer { + id, + name, + width, + height, + format, + offset0, + stride0, + .. + } => { + let format = match Fourcc::try_from(format) { + Ok(format) => { + if !state.drm_formats.contains(&format) { + drm.post_error( + wl_drm::Error::InvalidFormat, + String::from("Format not advertised by wl_drm"), + ); + return; + } + format + } + Err(_) => { + drm.post_error( + wl_drm::Error::InvalidFormat, + String::from("Format unknown / not advertised by wl_drm"), + ); + return; + } + }; + + if width < 1 || height < 1 { + drm.post_error( + wl_drm::Error::InvalidFormat, + String::from("width or height not positive"), + ); + return; + } + + let mut dma = Dmabuf::builder((width, height), format, DmabufFlags::empty()); + dma.add_plane(name, 0, offset0 as u32, stride0 as u32, Modifier::Invalid); + match dma.build() { + Some(dmabuf) => { + state.dmabuf_tx.send((dmabuf.clone(), None)).unwrap(); + data_init.init(id, dmabuf); + } + None => { + // Buffer import failed. The protocol documentation heavily implies killing the + // client is the right thing to do here. + drm.post_error( + wl_drm::Error::InvalidName, + "dmabuf global was destroyed on server", + ); + } + } + } + } + } +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index 50f1b5ca..48b18158 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -5,9 +5,18 @@ mod seat; mod state; mod surface; // mod xdg_activation; +mod drm; mod xdg_shell; -#[cfg(feature = "xwayland")] -pub mod xwayland; +#[cfg(feature = "xwayland_rootful")] +pub mod xwayland_rootful; +#[cfg(feature = "xwayland_rootful")] +use self::xwayland_rootful::X11Lock; +#[cfg(feature = "xwayland_rootful")] +use crate::wayland::xwayland_rootful::start_xwayland; +#[cfg(feature = "xwayland_rootless")] +pub mod xwayland_rootless; +#[cfg(feature = "xwayland_rootless")] +use self::xwayland_rootless::XWaylandState; use self::{state::WaylandState, surface::CORE_SURFACES}; use crate::wayland::seat::SeatData; @@ -40,6 +49,7 @@ use tokio::{ }; use tracing::{debug_span, info, instrument}; +pub static X_DISPLAY: OnceCell = OnceCell::new(); pub static WAYLAND_DISPLAY: OnceCell = OnceCell::new(); pub static SERIAL_COUNTER: CounterU32 = CounterU32::new(0); @@ -87,10 +97,12 @@ pub struct Wayland { pub socket_name: Option, join_handle: JoinHandle>, renderer: GlesRenderer, - dmabuf_rx: UnboundedReceiver<(Dmabuf, dmabuf::ImportNotifier)>, + dmabuf_rx: UnboundedReceiver<(Dmabuf, Option)>, wayland_state: Arc>, - #[cfg(feature = "xwayland")] - pub xwayland_state: xwayland::XWaylandState, + #[cfg(feature = "xwayland_rootful")] + pub x_lock: X11Lock, + #[cfg(feature = "xwayland_rootless")] + pub xwayland_state: XWaylandState, } impl Wayland { pub fn new() -> Result { @@ -108,8 +120,9 @@ impl Wayland { let (dmabuf_tx, dmabuf_rx) = mpsc::unbounded_channel(); let display = Arc::new(DisplayWrapper(Mutex::new(display), display_handle.clone())); - #[cfg(feature = "xwayland")] - let xwayland_state = xwayland::XWaylandState::create(&display_handle)?; + + #[cfg(feature = "xwayland_rootless")] + let xwayland_state = XWaylandState::create(&display_handle)?; let wayland_state = WaylandState::new(display_handle, &renderer, dmabuf_tx); let socket = ListeningSocket::bind_auto("wayland", 0..33)?; @@ -120,6 +133,8 @@ impl Wayland { if let Some(socket_name) = &socket_name { let _ = WAYLAND_DISPLAY.set(socket_name.clone()); } + #[cfg(feature = "xwayland_rootful")] + let x_display = start_xwayland(socket.as_raw_fd())?; info!(socket_name, "Wayland active"); let join_handle = Wayland::start_loop(display.clone(), socket, wayland_state.clone())?; @@ -131,7 +146,9 @@ impl Wayland { renderer, dmabuf_rx, wayland_state, - #[cfg(feature = "xwayland")] + #[cfg(feature = "xwayland_rootful")] + x_lock: x_display, + #[cfg(feature = "xwayland_rootless")] xwayland_state, }) } @@ -183,7 +200,9 @@ impl Wayland { pub fn update(&mut self, sk: &impl StereoKitDraw) { while let Ok((dmabuf, notifier)) = self.dmabuf_rx.try_recv() { if self.renderer.import_dmabuf(&dmabuf, None).is_err() { - notifier.failed(); + if let Some(notifier) = notifier { + notifier.failed(); + } } } for core_surface in CORE_SURFACES.get_valid_contents() { diff --git a/src/wayland/state.rs b/src/wayland/state.rs index b2a82538..236d6237 100644 --- a/src/wayland/state.rs +++ b/src/wayland/state.rs @@ -1,8 +1,13 @@ -use crate::wayland::seat::SeatData; +use super::DisplayWrapper; +use crate::wayland::{drm::wl_drm::WlDrm, seat::SeatData}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use smithay::{ - backend::{allocator::dmabuf::Dmabuf, egl::EGLDevice, renderer::gles::GlesRenderer}, + backend::{ + allocator::{dmabuf::Dmabuf, Fourcc}, + egl::EGLDevice, + renderer::gles::GlesRenderer, + }, delegate_dmabuf, delegate_output, delegate_shm, output::{Mode, Output, Scale, Subpixel}, reexports::{ @@ -32,8 +37,6 @@ use std::sync::{Arc, Weak}; use tokio::sync::mpsc::UnboundedSender; use tracing::{info, warn}; -use super::DisplayWrapper; - pub struct ClientState { pub id: OnceCell, pub compositor_state: CompositorClientState, @@ -71,7 +74,8 @@ pub struct WaylandState { pub kde_decoration_state: KdeDecorationState, pub shm_state: ShmState, dmabuf_state: (DmabufState, DmabufGlobal, Option), - dmabuf_tx: UnboundedSender<(Dmabuf, dmabuf::ImportNotifier)>, + pub drm_formats: Vec, + pub dmabuf_tx: UnboundedSender<(Dmabuf, Option)>, pub output: Output, } @@ -79,7 +83,7 @@ impl WaylandState { pub fn new( display_handle: DisplayHandle, renderer: &GlesRenderer, - dmabuf_tx: UnboundedSender<(Dmabuf, dmabuf::ImportNotifier)>, + dmabuf_tx: UnboundedSender<(Dmabuf, Option)>, ) -> Arc> { let compositor_state = CompositorState::new::(&display_handle); // let xdg_activation_state = XdgActivationState::new::(&display_handle); @@ -88,13 +92,14 @@ impl WaylandState { let shm_state = ShmState::new::(&display_handle, vec![]); let render_node = EGLDevice::device_for_display(renderer.egl_context().display()) .and_then(|device| device.try_get_render_node()); - let dmabuf_formats = renderer .egl_context() .dmabuf_render_formats() .iter() .cloned() .collect::>(); + let drm_formats = dmabuf_formats.iter().map(|f| f.code).collect(); + let dmabuf_default_feedback = match render_node { Ok(Some(node)) => DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats.clone()) .build() @@ -148,6 +153,7 @@ impl WaylandState { display_handle.create_global::(3, ()); display_handle.create_global::(5, ()); display_handle.create_global::(1, ()); + display_handle.create_global::(2, ()); info!("Init Wayland compositor"); @@ -160,6 +166,7 @@ impl WaylandState { // xdg_activation_state, kde_decoration_state, shm_state, + drm_formats, dmabuf_state, dmabuf_tx, output, @@ -191,7 +198,7 @@ impl DmabufHandler for WaylandState { dmabuf: Dmabuf, notifier: dmabuf::ImportNotifier, ) { - self.dmabuf_tx.send((dmabuf, notifier)).unwrap(); + self.dmabuf_tx.send((dmabuf, Some(notifier))).unwrap(); } } delegate_dmabuf!(WaylandState); diff --git a/src/wayland/wayland-drm.xml b/src/wayland/wayland-drm.xml new file mode 100644 index 00000000..a710f2d4 --- /dev/null +++ b/src/wayland/wayland-drm.xml @@ -0,0 +1,189 @@ + + + + + Copyright © 2008-2011 Kristian Høgsberg + Copyright © 2010-2011 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that\n the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bitmask of capabilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/wayland/xwayland_rootful.rs b/src/wayland/xwayland_rootful.rs new file mode 100644 index 00000000..b17f0103 --- /dev/null +++ b/src/wayland/xwayland_rootful.rs @@ -0,0 +1,267 @@ +use crate::core::{client::get_env, task}; +use crate::STOP_NOTIFIER; +use smithay::reexports::rustix; +use smithay::reexports::rustix::io::{fcntl_setfd, Errno, FdFlags}; +use smithay::reexports::rustix::net::SocketAddrUnix; +use std::io::{Read, Write}; +use std::{ + io::ErrorKind, + os::{ + fd::{AsRawFd, BorrowedFd, RawFd}, + unix::process::CommandExt, + }, + process::{ChildStdout, Command, Stdio}, +}; +use tokio::net::{UnixListener, UnixStream}; +use tokio::task::AbortHandle; +use tracing::{debug, info, warn}; + +use super::X_DISPLAY; + +pub fn start_xwayland(wayland_socket: RawFd) -> std::io::Result { + let (mut lock, listener) = bind_socket()?; + + let abort_handle = task::new(|| "X11 Client Acceptor", async move { + loop { + let Ok((stream, _)) = tokio::select! { + _ = STOP_NOTIFIER.notified() => break, + e = listener.accept() => e, + } else { + continue; + }; + + let Ok((x_wm_x11, _x_wm_me)) = UnixStream::pair() else { + continue; + }; + let Ok(env) = stream + .peer_cred() + .and_then(|c| c.pid().ok_or(ErrorKind::Other.into())) + .and_then(get_env) + else { + continue; + }; + + let _ = spawn_xwayland( + lock.display, + wayland_socket, + x_wm_x11, + stream.as_raw_fd(), + env.get("STARDUST_STARTUP_TOKEN").cloned(), + ); + } + }) + .map_err(|_| ErrorKind::Other)? + .abort_handle(); + lock.x_abort_handle.replace(abort_handle); + let _ = X_DISPLAY.set(lock.display); + Ok(lock) +} + +/// Find a free X11 display slot and setup +pub(crate) fn bind_socket() -> Result<(X11Lock, UnixListener), std::io::Error> { + for d in 0..33 { + // if fails, try the next one + if let Ok(lock) = X11Lock::grab(d) { + // we got a lockfile, try and create the socket + match open_x11_socket_for_display(d) { + Ok(socket) => return Ok((lock, socket)), + Err(err) => warn!(display = d, "Failed to create sockets: {}", err), + } + } + } + // If we reach here, all values from 0 to 32 failed + // we need to stop trying at some point + + Err(std::io::Error::new( + std::io::ErrorKind::AddrInUse, + "Could not find a free socket for the XServer.", + )) +} + +#[derive(Debug)] +pub(crate) struct X11Lock { + display: u32, + x_abort_handle: Option, +} + +impl X11Lock { + /// Try to grab a lockfile for given X display number + fn grab(number: u32) -> Result { + debug!(display = number, "Attempting to aquire an X11 display lock"); + let filename = format!("/tmp/.X{}-lock", number); + let lockfile = ::std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&filename); + match lockfile { + Ok(mut file) => { + // we got it, write our PID in it and we're good + let ret = file.write_fmt(format_args!( + "{:>10}\n", + rustix::process::Pid::as_raw(Some(rustix::process::getpid())) + )); + if ret.is_err() { + // write to the file failed ? we abandon + ::std::mem::drop(file); + let _ = ::std::fs::remove_file(&filename); + Err(()) + } else { + debug!(display = number, "X11 lock acquired"); + // we got the lockfile and wrote our pid to it, all is good + Ok(X11Lock { + display: number, + x_abort_handle: None, + }) + } + } + Err(_) => { + debug!(display = number, "Failed to acquire lock"); + // we could not open the file, now we try to read it + // and if it contains the pid of a process that no longer + // exist (so if a previous x server claimed it and did not + // exit gracefully and remove it), we claim it + // if we can't open it, give up + let mut file = ::std::fs::File::open(&filename).map_err(|_| ())?; + let mut spid = [0u8; 11]; + file.read_exact(&mut spid).map_err(|_| ())?; + ::std::mem::drop(file); + let pid = rustix::process::Pid::from_raw( + ::std::str::from_utf8(&spid) + .map_err(|_| ())? + .trim() + .parse::() + .map_err(|_| ())?, + ) + .ok_or(())?; + if let Err(Errno::SRCH) = rustix::process::test_kill_process(pid) { + // no process whose pid equals the contents of the lockfile exists + // remove the lockfile and try grabbing it again + if let Ok(()) = ::std::fs::remove_file(filename) { + debug!( + display = number, + "Lock was blocked by a defunct X11 server, trying again" + ); + return X11Lock::grab(number); + } else { + // we could not remove the lockfile, abort + return Err(()); + } + } + // if we reach here, this lockfile exists and is probably in use, give up + Err(()) + } + } + } + + pub(crate) fn display(&self) -> u32 { + self.display + } +} + +impl Drop for X11Lock { + fn drop(&mut self) { + info!("Cleaning up X11 lock."); + // Cleanup all the X11 files + if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X11-unix/X{}", self.display)) { + warn!(error = ?e, "Failed to remove X11 socket"); + } + if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X{}-lock", self.display)) { + warn!(error = ?e, "Failed to remove X11 lockfile"); + } + if let Some(join_handle) = self.x_abort_handle.take() { + join_handle.abort(); + } + } +} + +/// Open the two unix sockets an X server listens on +/// +/// Should only be done after the associated lockfile is acquired! +fn open_x11_socket_for_display(display: u32) -> rustix::io::Result { + let path = format!("/tmp/.X11-unix/X{}", display); + let _ = ::std::fs::remove_file(&path); + // We know this path is not too long, these unwrap cannot fail + let fs_addr = SocketAddrUnix::new(path.as_bytes()).unwrap(); + open_socket(fs_addr) +} + +/// Open an unix socket for listening and bind it to given path +fn open_socket(addr: SocketAddrUnix) -> rustix::io::Result { + // create an unix stream socket + let fd = rustix::net::socket_with( + rustix::net::AddressFamily::UNIX, + rustix::net::SocketType::STREAM, + rustix::net::SocketFlags::CLOEXEC, + None, + )?; + // bind it to requested address + rustix::net::bind_unix(&fd, &addr)?; + rustix::net::listen(&fd, 1)?; + Ok(UnixListener::from_std(std::os::unix::net::UnixListener::from(fd)).unwrap()) +} + +fn spawn_xwayland( + display: u32, + wayland_socket: RawFd, + wm_socket: UnixStream, + listen_socket: RawFd, + stardust_startup_token: Option, +) -> std::io::Result { + let mut command = Command::new("sh"); + + // We use output stream to communicate because FD is easier to handle than exit code. + command.stdout(Stdio::piped()); + + let mut xwayland_args = format!(":{} -geometry 1920x1080", display); + xwayland_args.push_str(&format!(" -listenfd {}", listen_socket)); + + // This command let sh to: + // * Set up signal handler for USR1 + // * Launch Xwayland with USR1 ignored so Xwayland will signal us when it is ready (also redirect + // Xwayland's STDOUT to STDERR so its output, if any, won't distract us) + // * Print "S" and exit if USR1 is received + command.arg("-c").arg(format!( + "trap 'echo S' USR1; (trap '' USR1; exec Xwayland {}) 1>&2 & wait", + xwayland_args + )); + + // Setup the environment: clear everything except PATH and XDG_RUNTIME_DIR + command.env_clear(); + for (key, value) in std::env::vars_os() { + if key.to_str() == Some("PATH") || key.to_str() == Some("XDG_RUNTIME_DIR") { + command.env(key, value); + continue; + } + } + command.env("WAYLAND_SOCKET", format!("{}", wayland_socket.as_raw_fd())); + command.env( + "STARDUST_STARTUP_TOKEN", + stardust_startup_token.unwrap_or_default(), + ); + + unsafe { + let wayland_socket_fd = wayland_socket.as_raw_fd(); + let wm_socket_fd = wm_socket.as_raw_fd(); + command.pre_exec(move || { + // unset the CLOEXEC flag from the sockets we need to pass + // to xwayland + unset_cloexec(wayland_socket_fd)?; + unset_cloexec(wm_socket_fd)?; + unset_cloexec(listen_socket)?; + Ok(()) + }); + } + + let mut child = command.spawn()?; + Ok(child.stdout.take().expect("stdout should be piped")) +} + +/// Remove the `O_CLOEXEC` flag from this `Fd` +/// +/// This means that the `Fd` will *not* be automatically +/// closed when we `exec()` into XWayland +unsafe fn unset_cloexec(fd: RawFd) -> std::io::Result<()> { + let fd = BorrowedFd::borrow_raw(fd); + fcntl_setfd(fd, FdFlags::empty())?; + Ok(()) +} diff --git a/src/wayland/xwayland.rs b/src/wayland/xwayland_rootless.rs similarity index 76% rename from src/wayland/xwayland.rs rename to src/wayland/xwayland_rootless.rs index bb7f578d..28316ae4 100644 --- a/src/wayland/xwayland.rs +++ b/src/wayland/xwayland_rootless.rs @@ -1,6 +1,6 @@ use super::{ seat::{KeyboardEvent, PointerEvent, SeatData}, - state::ClientState, + X_DISPLAY, }; use crate::{ nodes::{ @@ -32,8 +32,6 @@ use std::{ffi::OsStr, iter::empty, sync::Arc, time::Duration}; use tokio::sync::oneshot; use tracing::debug; -pub static DISPLAY: OnceCell = OnceCell::new(); - pub struct XWaylandState { pub display: u32, event_loop_signal: LoopSignal, @@ -59,10 +57,14 @@ impl XWaylandState { client_fd: _, display: _, } => { - handler.seat = client.get_data::().map(|s| s.seat.clone()); - handler.wm = - X11Wm::start_wm(handle.clone(), dh.clone(), connection, client) - .ok(); + handler.seat.client.set(client.id()).unwrap(); + handler + .wm + .set( + X11Wm::start_wm(handle.clone(), dh.clone(), connection, client) + .unwrap(), + ) + .unwrap(); } XWaylandEvent::Exited => (), } @@ -81,15 +83,15 @@ impl XWaylandState { event_loop_signal: event_loop.get_signal(), }); let mut handler = XWaylandHandler { + wm: OnceCell::new(), + seat: SeatData::new(&dh), wayland_display_handle: dh, - wm: None, - seat: None, }; event_loop.run(Duration::from_millis(100), &mut handler, |_| ()) }); let state = rx.blocking_recv()?; - let _ = DISPLAY.set(format!(":{}", state.display)); + let _ = X_DISPLAY.set(state.display); Ok(state) } @@ -102,8 +104,8 @@ impl Drop for XWaylandState { struct XWaylandHandler { wayland_display_handle: DisplayHandle, - wm: Option, - seat: Option>, + wm: OnceCell, + seat: Arc, } impl XWaylandHandler { fn panel_item(&self, window: &X11Surface) -> Option>> { @@ -115,7 +117,7 @@ impl XWaylandHandler { impl XwmHandler for XWaylandHandler { fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm { - self.wm.as_mut().unwrap() + self.wm.get_mut().unwrap() } fn new_window(&mut self, _xwm: XwmId, window: X11Surface) { @@ -136,14 +138,16 @@ impl XwmHandler for XWaylandHandler { let _ = window.set_maximized(true); let dh = self.wayland_display_handle.clone(); - let seat = self.seat.clone().unwrap(); + let seat = self.seat.clone(); CoreSurface::add_to( self.wayland_display_handle.clone(), &window.wl_surface().unwrap(), { let window = window.clone(); move || { - let Some(wl_surface) = window.wl_surface() else {return}; + let Some(wl_surface) = window.wl_surface() else { + return; + }; let seat = seat.clone(); window.user_data().insert_if_missing_threadsafe(|| { let panel_item = PanelItem::create( @@ -164,7 +168,10 @@ impl XwmHandler for XWaylandHandler { } }, move |_| { - let Some(panel_item) = window.user_data().get::>>() else {return}; + let Some(panel_item) = window.user_data().get::>>() + else { + return; + }; panel_item.toplevel_size_changed( [ window.geometry().size.w as u32, @@ -181,6 +188,9 @@ impl XwmHandler for XWaylandHandler { fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) { debug!(?window, "Unmap X window"); + if let Some(panel_item) = window.user_data().get::>>() { + panel_item.drop_toplevel(); + } } fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) { debug!(?window, "Destroy X window"); @@ -210,7 +220,9 @@ impl XwmHandler for XWaylandHandler { } fn move_request(&mut self, _xwm: XwmId, window: X11Surface, button: u32) { - let Some(panel_item) = self.panel_item(&window) else {return}; + let Some(panel_item) = self.panel_item(&window) else { + return; + }; debug!(?window, button, "X window requests move"); panel_item.toplevel_move_request(); } @@ -221,7 +233,9 @@ impl XwmHandler for XWaylandHandler { button: u32, resize_edge: ResizeEdge, ) { - let Some(panel_item) = self.panel_item(&window) else {return}; + let Some(panel_item) = self.panel_item(&window) else { + return; + }; debug!(?window, button, ?resize_edge, "X window requests resize"); let (up, down, left, right) = match resize_edge { ResizeEdge::Top => (true, false, false, false), @@ -239,12 +253,16 @@ impl XwmHandler for XWaylandHandler { fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { let _ = window.set_fullscreen(true); - let Some(panel_item) = self.panel_item(&window) else {return}; + let Some(panel_item) = self.panel_item(&window) else { + return; + }; panel_item.toplevel_fullscreen_active(true); } fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { let _ = window.set_fullscreen(false); - let Some(panel_item) = self.panel_item(&window) else {return}; + let Some(panel_item) = self.panel_item(&window) else { + return; + }; panel_item.toplevel_fullscreen_active(true); } } @@ -273,63 +291,6 @@ impl X11Backend { // } } impl Backend for X11Backend { - // fn start_data(&self, id: &str) -> Result { - // let size = ( - // self.toplevel.geometry().size.w as u32, - // self.toplevel.geometry().size.h as u32, - // ); - // let toplevel_state = ( - // None::, - // self.toplevel.title(), - // None::, - // ( - // self.toplevel.geometry().size.w as u32, - // self.toplevel.geometry().size.h as u32, - // ), - // self.toplevel.min_size().map(|s| (s.w as u32, s.h as u32)), - // self.toplevel.max_size().map(|s| (s.w as u32, s.w as u32)), - // ((0_i32, 0_i32), size), - // vec![0_u32; 0], - // ); - // let info = ( - // None::<(Vector2, Vector2)>, - // toplevel_state, - // Vec::::new(), - // None::, - // None::, - // ); - // Ok(serialize((id, info))?.into()) - // } - // fn serialize_toplevel(&self) -> Result { - // let toplevel_state = ( - // None::, - // self.toplevel.title(), - // None::, - // ( - // self.toplevel.geometry().size.w, - // self.toplevel.geometry().size.h, - // ), - // self.toplevel.min_size().map(|s| (s.w, s.h)), - // self.toplevel.max_size().map(|s| (s.w, s.w)), - // ); - // let data = serialize(&toplevel_state)?; - // Ok(data.into()) - // } - - // fn set_toplevel_capabilities(&self, _capabilities: Vec) {} - - // fn set_toplevel_size( - // &self, - // size: Option>, - // states: Vec, - // _bounds: Option>, - // ) { - // let _ = self.toplevel.configure( - // size.map(|s| Rectangle::from_loc_and_size((0, 0), (s.x as i32, s.y as i32))), - // ); - // let _ = self.toplevel.set_maximized(states.contains(&1)); - // } - fn start_data(&self) -> Result { Ok(PanelItemInitData { cursor: None, @@ -364,7 +325,9 @@ impl Backend for X11Backend { keyboard_grab: self._keyboard_grab.lock().clone(), }) } - fn close_toplevel(&self) {} + fn close_toplevel(&self) { + let _ = self.toplevel.close(); + } fn auto_size_toplevel(&self) { let _ = self.toplevel.configure(None); @@ -380,19 +343,27 @@ impl Backend for X11Backend { } fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc) { - let Some(wl_surface) = self.wl_surface_from_id(&surface) else {return}; - let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else {return}; + let Some(wl_surface) = self.wl_surface_from_id(&surface) else { + return; + }; + let Some(core_surface) = CoreSurface::from_wl_surface(&wl_surface) else { + return; + }; core_surface.apply_material(model_part); } fn pointer_motion(&self, surface: &SurfaceID, position: Vector2) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; self.seat .pointer_event(&surface, PointerEvent::Motion(position)); } fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; self.seat.pointer_event( &surface, PointerEvent::Button { @@ -407,7 +378,9 @@ impl Backend for X11Backend { scroll_distance: Option>, scroll_steps: Option>, ) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; self.seat.pointer_event( &surface, PointerEvent::Scroll { @@ -418,9 +391,13 @@ impl Backend for X11Backend { } fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec) { - let Some(surface) = self.wl_surface_from_id(surface) else {return}; + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; let keymaps = KEYMAPS.lock(); - let Some(keymap) = keymaps.get(keymap_id).cloned() else {return}; + let Some(keymap) = keymaps.get(keymap_id).cloned() else { + return; + }; if self.seat.set_keymap(keymap, vec![surface.clone()]).is_err() { return; } @@ -429,9 +406,25 @@ impl Backend for X11Backend { &surface, KeyboardEvent::Key { key: key.abs() as u32, - state: if key < 0 { 1 } else { 0 }, + state: key < 0, }, ); } } + + fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2) { + let Some(surface) = self.wl_surface_from_id(surface) else { + return; + }; + self.seat.touch_down(&surface, id, position) + } + fn touch_move(&self, id: u32, position: Vector2) { + self.seat.touch_move(id, position) + } + fn touch_up(&self, id: u32) { + self.seat.touch_up(id) + } + fn reset_touches(&self) { + self.seat.reset_touches() + } }