diff --git a/anvil/src/state.rs b/anvil/src/state.rs index f305c8f..5ad74c2 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -23,11 +23,9 @@ use smithay::{ #[cfg(feature = "egl")] use smithay::backend::egl::display::EGLBufferReader; #[cfg(feature = "xwayland")] -use smithay::xwayland::XWayland; +use smithay::xwayland::{XWayland, XWaylandEvent}; use crate::shell::init_shell; -#[cfg(feature = "xwayland")] -use crate::xwayland::XWm; pub struct AnvilState { pub backend_data: BackendData, @@ -48,9 +46,8 @@ pub struct AnvilState { pub start_time: std::time::Instant, #[cfg(feature = "egl")] pub egl_reader: Option, - // things we must keep alive #[cfg(feature = "xwayland")] - _xwayland: XWayland>, + pub xwayland: XWayland>, } impl AnvilState { @@ -141,14 +138,19 @@ impl AnvilState { .expect("Failed to initialize the keyboard"); #[cfg(feature = "xwayland")] - let _xwayland = { - let xwm = XWm::new( - handle.clone(), - shell_handles.token, - shell_handles.window_map.clone(), - log.clone(), - ); - XWayland::init(xwm, handle.clone(), display.clone(), &mut (), log.clone()).unwrap() + let xwayland = { + let (xwayland, channel) = XWayland::new(handle.clone(), display.clone(), log.clone()); + let ret = handle.insert_source(channel, |event, _, anvil_state| match event { + XWaylandEvent::Ready { connection, client } => anvil_state.xwayland_ready(connection, client), + XWaylandEvent::Exited => anvil_state.xwayland_exited(), + }); + if let Err(e) = ret { + error!( + log, + "Failed to insert the XWaylandSource into the event loop: {}", e + ); + } + xwayland }; AnvilState { @@ -170,7 +172,7 @@ impl AnvilState { egl_reader, start_time: std::time::Instant::now(), #[cfg(feature = "xwayland")] - _xwayland, + xwayland, } } } diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index f637f98..1f51039 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -219,6 +219,12 @@ pub fn run_udev( .map_err(|e| -> IoError { e.into() }) .unwrap(); + /* + * Start XWayland if supported + */ + #[cfg(feature = "xwayland")] + state.start_xwayland(); + /* * And run our loop */ diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 8abe99b..635c97a 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -98,6 +98,9 @@ pub fn run_winit( let start_time = std::time::Instant::now(); let mut cursor_visible = true; + #[cfg(feature = "xwayland")] + state.start_xwayland(); + info!(log, "Initialization completed, starting the main loop."); while state.running.load(Ordering::SeqCst) { diff --git a/anvil/src/xwayland/mod.rs b/anvil/src/xwayland/mod.rs index d16f45b..5b8d55f 100644 --- a/anvil/src/xwayland/mod.rs +++ b/anvil/src/xwayland/mod.rs @@ -1,12 +1,8 @@ use std::{cell::RefCell, collections::HashMap, convert::TryFrom, os::unix::net::UnixStream, rc::Rc}; use smithay::{ - reexports::{ - calloop::LoopHandle, - wayland_server::{protocol::wl_surface::WlSurface, Client}, - }, + reexports::wayland_server::{protocol::wl_surface::WlSurface, Client}, wayland::compositor::CompositorToken, - xwayland::XWindowManager, }; use x11rb::{ @@ -33,35 +29,16 @@ use x11rb_event_source::X11Source; mod x11rb_event_source; -/// Implementation of [`smithay::xwayland::XWindowManager`] that is used for starting XWayland. -/// After XWayland was started, the actual state is kept in `X11State`. -pub struct XWm { - handle: LoopHandle<'static, AnvilState>, - token: CompositorToken, - window_map: Rc>, - log: slog::Logger, -} - -impl XWm { - pub fn new( - handle: LoopHandle<'static, AnvilState>, - token: CompositorToken, - window_map: Rc>, - log: slog::Logger, - ) -> Self { - Self { - handle, - token, - window_map, - log, +impl AnvilState { + pub fn start_xwayland(&mut self) { + if let Err(e) = self.xwayland.start() { + error!(self.log, "Failed to start XWayland: {}", e); } } -} -impl XWindowManager for XWm { - fn xwayland_ready(&mut self, connection: UnixStream, client: Client) { + pub fn xwayland_ready(&mut self, connection: UnixStream, client: Client) { let (wm, source) = - X11State::start_wm(connection, self.token, self.window_map.clone(), self.log.clone()).unwrap(); + X11State::start_wm(connection, self.ctoken, self.window_map.clone(), self.log.clone()).unwrap(); let wm = Rc::new(RefCell::new(wm)); client.data_map().insert_if_missing(|| Rc::clone(&wm)); self.handle @@ -75,7 +52,9 @@ impl XWindowManager for XWm { .unwrap(); } - fn xwayland_exited(&mut self) {} + pub fn xwayland_exited(&mut self) { + error!(self.log, "Xwayland crashed"); + } } x11rb::atom_manager! { diff --git a/src/xwayland/mod.rs b/src/xwayland/mod.rs index 28782c4..818117e 100644 --- a/src/xwayland/mod.rs +++ b/src/xwayland/mod.rs @@ -6,14 +6,13 @@ //! The starting point is the [`XWayland`](struct.XWayland.html) struct, which represents the //! running `XWayland` instance. Dropping it will shutdown XWayland. //! -//! You need to provide an implementation of the `XWindowManager` trait which gives you -//! access to the X11 WM connection and the `Client` associated with XWayland. You'll need -//! to treat XWayland (and all its X11 apps) as one special client, and play the role of -//! an X11 Window Manager. +//! You need to provide an implementation of a X11 Window Manager for XWayland to +//! function properly. You'll need to treat XWayland (and all its X11 apps) as one +//! special client, and play the role of an X11 Window Manager. //! //! Smithay does not provide any helper for doing that yet, but it is planned. mod x11_sockets; mod xserver; -pub use self::xserver::{XWayland, XWindowManager}; +pub use self::xserver::{XWayland, XWaylandEvent}; diff --git a/src/xwayland/x11_sockets.rs b/src/xwayland/x11_sockets.rs index ed33a54..9cec647 100644 --- a/src/xwayland/x11_sockets.rs +++ b/src/xwayland/x11_sockets.rs @@ -6,7 +6,7 @@ use std::{ use nix::{errno::Errno, sys::socket, Error as NixError, Result as NixResult}; /// Find a free X11 display slot and setup -pub(crate) fn prepare_x11_sockets(log: ::slog::Logger) -> Result<(X11Lock, [UnixStream; 2]), ()> { +pub(crate) fn prepare_x11_sockets(log: ::slog::Logger) -> Result<(X11Lock, [UnixStream; 2]), std::io::Error> { for d in 0..33 { // if fails, try the next one if let Ok(lock) = X11Lock::grab(d, log.clone()) { @@ -18,7 +18,10 @@ pub(crate) fn prepare_x11_sockets(log: ::slog::Logger) -> Result<(X11Lock, [Unix } // If we reach here, all values from 0 to 32 failed // we need to stop trying at some point - Err(()) + Err(std::io::Error::new( + std::io::ErrorKind::AddrInUse, + "Could not find a free socket for the XServer.", + )) } pub(crate) struct X11Lock { diff --git a/src/xwayland/xserver.rs b/src/xwayland/xserver.rs index ce052f1..9014e88 100644 --- a/src/xwayland/xserver.rs +++ b/src/xwayland/xserver.rs @@ -54,6 +54,7 @@ use std::{ }; use calloop::{ + channel::{sync_channel, Channel, SyncSender}, generic::{Fd, Generic}, Interest, LoopHandle, Mode, RegistrationToken, }; @@ -65,69 +66,75 @@ use wayland_server::{Client, Display, Filter}; use super::x11_sockets::{prepare_x11_sockets, X11Lock}; /// The XWayland handle -pub struct XWayland { - inner: Rc>>, +pub struct XWayland { + inner: Rc>>, } -/// Trait to be implemented by you WM for XWayland +/// Events generated by the XWayland manager /// -/// This is a very low-level trait, only notifying you -/// when the connection with XWayland is up, or when -/// it terminates. +/// This is a very low-level interface, only notifying you when the connection +/// with XWayland is up, or when it terminates. /// -/// You WM must be able handle the XWayland server connecting -/// then disconnecting several time in a row, but only a single -/// connection will be active at any given time. -pub trait XWindowManager { +/// Your WM code must be able handle the XWayland server connecting then +/// disconnecting several time in a row, but only a single connection will +/// be active at any given time. +pub enum XWaylandEvent { /// The XWayland server is ready - /// - /// Your privileged connection to it is this `UnixStream` - fn xwayland_ready(&mut self, connection: UnixStream, client: Client); - /// The XWayland server has exited - fn xwayland_exited(&mut self); + Ready { + /// Privileged X11 connection to XWayland + connection: UnixStream, + /// Wayland client representing XWayland + client: Client, + }, + /// The XWayland server exited + Exited, } -impl XWayland { - /// Start the XWayland server - pub fn init( - wm: WM, +impl XWayland { + /// Create a new XWayland manager + pub fn new( handle: LoopHandle<'static, Data>, display: Rc>, - data: &mut T, logger: L, - ) -> Result, ()> + ) -> (XWayland, XWaylandSource) where L: Into>, { let log = crate::slog_or_fallback(logger); + // We don't expect to ever have more than 2 messages in flight, if XWayland got ready and then died right away + let (sender, channel) = sync_channel(2); let inner = Rc::new(RefCell::new(Inner { - wm, - kill_source: { - let handle = handle.clone(); - Box::new(move |source| handle.kill(source)) - }, - source_maker: Box::new(move |inner, fd| { - handle - .insert_source( - Generic::new(Fd(fd), Interest::READ, Mode::Level), - move |evt, _, _| { - debug_assert!(evt.readable); - xwayland_ready(&inner); - Ok(()) - }, - ) - .map_err(|_| ()) - }), + handle, wayland_display: display, instance: None, + sender, log: log.new(o!("smithay_module" => "XWayland")), })); - launch(&inner, data)?; - Ok(XWayland { inner }) + (XWayland { inner }, XWaylandSource { channel }) + } + + /// Attempt to start the XWayland instance + /// + /// If it succeeds, you'll eventually receive an `XWaylandEvent::Ready` + /// through the source provided by `XWayland::new()` containing an + /// `UnixStream` representing your WM connection to XWayland, and the + /// wayland `Client` for XWayland. + /// + /// Does nothing if XWayland is already started or starting. + pub fn start(&self) -> std::io::Result<()> { + launch(&self.inner) + } + + /// Shutdown XWayland + /// + /// Does nothing if it was not already running, otherwise kills it and you will + /// later receive a `XWaylandEvent::Exited` event. + pub fn shutdown(&self) { + self.inner.borrow_mut().shutdown(); } } -impl Drop for XWayland { +impl Drop for XWayland { fn drop(&mut self) { self.inner.borrow_mut().shutdown(); } @@ -135,32 +142,25 @@ impl Drop for XWayland { struct XWaylandInstance { display_lock: X11Lock, - wayland_client: Client, + wayland_client: Option, startup_handler: Option, wm_fd: Option, - started_at: ::std::time::Instant, child_stdout: Option, } -type SourceMaker = dyn FnMut(Rc>>, RawFd) -> Result; - // Inner implementation of the XWayland manager -struct Inner { - wm: WM, - source_maker: Box>, +struct Inner { + sender: SyncSender, + handle: LoopHandle<'static, Data>, wayland_display: Rc>, instance: Option, - kill_source: Box, log: ::slog::Logger, } // Launch an XWayland server // // Does nothing if there is already a launched instance -fn launch( - inner: &Rc>>, - data: &mut T, -) -> Result<(), ()> { +fn launch(inner: &Rc>>) -> std::io::Result<()> { let mut guard = inner.borrow_mut(); if guard.instance.is_some() { return Ok(()); @@ -168,64 +168,119 @@ fn launch( info!(guard.log, "Starting XWayland"); - let (x_wm_x11, x_wm_me) = UnixStream::pair().map_err(|_| ())?; - let (wl_x11, wl_me) = UnixStream::pair().map_err(|_| ())?; + let (x_wm_x11, x_wm_me) = UnixStream::pair()?; + let (wl_x11, wl_me) = UnixStream::pair()?; let (lock, x_fds) = prepare_x11_sockets(guard.log.clone())?; // we have now created all the required sockets - // record launch time - let creation_time = ::std::time::Instant::now(); + // Setup the associated wayland client to be created in an idle callback, so that we don't need + // to access the dispatch_data *right now* + let idle_inner = inner.clone(); + guard.handle.insert_idle(move |data| { + let mut guard = idle_inner.borrow_mut(); + let guard = &mut *guard; + if let Some(ref mut instance) = guard.instance { + // create the wayland client for XWayland + let client = unsafe { + guard + .wayland_display + .borrow_mut() + .create_client(wl_me.into_raw_fd(), data) + }; + client.data_map().insert_if_missing(|| idle_inner.clone()); + client.add_destructor(Filter::new(|e: Arc<_>, _, _| client_destroy::(&e))); - // create the wayland client for XWayland - let client = unsafe { - guard - .wayland_display - .borrow_mut() - .create_client(wl_me.into_raw_fd(), data) - }; - client.data_map().insert_if_missing(|| inner.clone()); - client.add_destructor(Filter::new(|e: Arc<_>, _, mut data| { - client_destroy::(&e, data.get().unwrap()) - })); + instance.wayland_client = Some(client.clone()); + } + }); // all is ready, we can do the fork dance let child_stdout = match spawn_xwayland(lock.display(), wl_x11, x_wm_x11, &x_fds) { Ok(child_stdout) => child_stdout, Err(e) => { error!(guard.log, "XWayland failed to spawn"; "err" => format!("{:?}", e)); - return Err(()); + return Err(e); } }; - let startup_handler = (&mut *guard.source_maker)(inner.clone(), child_stdout.as_raw_fd())?; + let inner = inner.clone(); + let startup_handler = guard.handle.insert_source( + Generic::new(Fd(child_stdout.as_raw_fd()), Interest::READ, Mode::Level), + move |_, _, _| { + // the closure must be called exactly one time, this cannot panic + xwayland_ready(&inner); + Ok(()) + }, + )?; guard.instance = Some(XWaylandInstance { display_lock: lock, - wayland_client: client, startup_handler: Some(startup_handler), + wayland_client: None, wm_fd: Some(x_wm_me), - started_at: creation_time, child_stdout: Some(child_stdout), }); Ok(()) } -impl Inner { +pub struct XWaylandSource { + channel: Channel, +} + +impl calloop::EventSource for XWaylandSource { + type Event = XWaylandEvent; + type Metadata = (); + type Ret = (); + + fn process_events( + &mut self, + readiness: calloop::Readiness, + token: calloop::Token, + mut callback: F, + ) -> std::io::Result<()> + where + F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + self.channel + .process_events(readiness, token, |event, &mut ()| match event { + calloop::channel::Event::Msg(msg) => callback(msg, &mut ()), + calloop::channel::Event::Closed => {} + }) + } + + fn register(&mut self, poll: &mut calloop::Poll, token: calloop::Token) -> std::io::Result<()> { + self.channel.register(poll, token) + } + + fn reregister(&mut self, poll: &mut calloop::Poll, token: calloop::Token) -> std::io::Result<()> { + self.channel.reregister(poll, token) + } + + fn unregister(&mut self, poll: &mut calloop::Poll) -> std::io::Result<()> { + self.channel.unregister(poll) + } +} + +impl Inner { // Shutdown the XWayland server and cleanup everything fn shutdown(&mut self) { // don't do anything if not running if let Some(mut instance) = self.instance.take() { info!(self.log, "Shutting down XWayland."); - self.wm.xwayland_exited(); // kill the client - instance.wayland_client.kill(); + if let Some(client) = instance.wayland_client { + client.kill(); + } // remove the event source if let Some(s) = instance.startup_handler.take() { - (self.kill_source)(s); + self.handle.kill(s); } + // send error occurs if the user dropped the channel... We cannot do much except ignore. + let _ = self.sender.send(XWaylandEvent::Exited); + // All connections and lockfiles are cleaned by their destructors // Remove DISPLAY from the env @@ -238,32 +293,17 @@ impl Inner { } } -fn client_destroy(map: &::wayland_server::UserDataMap, data: &mut T) { - let inner = map.get::>>>().unwrap(); - - // shutdown the server - let started_at = inner.borrow().instance.as_ref().map(|i| i.started_at); +fn client_destroy(map: &::wayland_server::UserDataMap) { + let inner = map.get::>>>().unwrap(); inner.borrow_mut().shutdown(); - - // restart it, unless we really just started it, if it crashes right - // at startup there is no point - if started_at.map(|t| t.elapsed().as_secs()).unwrap_or(10) > 5 { - warn!(inner.borrow().log, "XWayland crashed, restarting."); - let _ = launch(&inner, data); - } else { - warn!( - inner.borrow().log, - "XWayland crashed less than 5 seconds after its startup, not restarting." - ); - } } -fn xwayland_ready(inner: &Rc>>) { +fn xwayland_ready(inner: &Rc>>) { + // Lots of re-borrowing to please the borrow-checker let mut guard = inner.borrow_mut(); - let inner = &mut *guard; + let guard = &mut *guard; // instance should never be None at this point - let instance = inner.instance.as_mut().unwrap(); - let wm = &mut inner.wm; + let instance = guard.instance.as_mut().unwrap(); // neither the child_stdout let child_stdout = instance.child_stdout.as_mut().unwrap(); @@ -272,7 +312,7 @@ fn xwayland_ready(inner: &Rc>>) { let success = match child_stdout.read(&mut buffer) { Ok(len) => len > 0 && buffer[0] == b'S', Err(e) => { - error!(inner.log, "Checking launch status failed"; "err" => format!("{:?}", e)); + error!(guard.log, "Checking launch status failed"; "err" => format!("{:?}", e)); false } }; @@ -282,21 +322,22 @@ fn xwayland_ready(inner: &Rc>>) { ::std::env::set_var("DISPLAY", format!(":{}", instance.display_lock.display())); // signal the WM - info!(inner.log, "XWayland is ready, signaling the WM."); - wm.xwayland_ready( - instance.wm_fd.take().unwrap(), // This is a bug if None - instance.wayland_client.clone(), - ); + info!(guard.log, "XWayland is ready, signaling the WM."); + // send error occurs if the user dropped the channel... We cannot do much except ignore. + let _ = guard.sender.send(XWaylandEvent::Ready { + connection: instance.wm_fd.take().unwrap(), // This is a bug if None + client: instance.wayland_client.clone().unwrap(), + }); } else { error!( - inner.log, + guard.log, "XWayland crashed at startup, will not try to restart it." ); } // in all cases, cleanup if let Some(s) = instance.startup_handler.take() { - (inner.kill_source)(s); + guard.handle.kill(s); } }