xwayland: rework as an EventSource

Reorganize the XWayland abstraction into a calloop EventLoop.

Fixes #245
Fixes #203
This commit is contained in:
Victor Berger 2021-06-07 19:04:15 +02:00 committed by Victor Berger
parent 01b5c1a183
commit 671e2053e9
7 changed files with 190 additions and 157 deletions

View File

@ -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<BackendData> {
pub backend_data: BackendData,
@ -48,9 +46,8 @@ pub struct AnvilState<BackendData> {
pub start_time: std::time::Instant,
#[cfg(feature = "egl")]
pub egl_reader: Option<EGLBufferReader>,
// things we must keep alive
#[cfg(feature = "xwayland")]
_xwayland: XWayland<XWm<BackendData>>,
pub xwayland: XWayland<AnvilState<BackendData>>,
}
impl<BackendData: Backend + 'static> AnvilState<BackendData> {
@ -141,14 +138,19 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
.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<BackendData: Backend + 'static> AnvilState<BackendData> {
egl_reader,
start_time: std::time::Instant::now(),
#[cfg(feature = "xwayland")]
_xwayland,
xwayland,
}
}
}

View File

@ -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
*/

View File

@ -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) {

View File

@ -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<Backend> {
handle: LoopHandle<'static, AnvilState<Backend>>,
token: CompositorToken<Roles>,
window_map: Rc<RefCell<MyWindowMap>>,
log: slog::Logger,
}
impl<Backend> XWm<Backend> {
pub fn new(
handle: LoopHandle<'static, AnvilState<Backend>>,
token: CompositorToken<Roles>,
window_map: Rc<RefCell<MyWindowMap>>,
log: slog::Logger,
) -> Self {
Self {
handle,
token,
window_map,
log,
impl<BackendData: 'static> AnvilState<BackendData> {
pub fn start_xwayland(&mut self) {
if let Err(e) = self.xwayland.start() {
error!(self.log, "Failed to start XWayland: {}", e);
}
}
}
impl<Backend> XWindowManager for XWm<Backend> {
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<Backend> XWindowManager for XWm<Backend> {
.unwrap();
}
fn xwayland_exited(&mut self) {}
pub fn xwayland_exited(&mut self) {
error!(self.log, "Xwayland crashed");
}
}
x11rb::atom_manager! {

View File

@ -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};

View File

@ -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 {

View File

@ -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<WM: XWindowManager> {
inner: Rc<RefCell<Inner<WM>>>,
pub struct XWayland<Data> {
inner: Rc<RefCell<Inner<Data>>>,
}
/// 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<WM: XWindowManager + 'static> XWayland<WM> {
/// Start the XWayland server
pub fn init<L, T: Any, Data: 'static>(
wm: WM,
impl<Data: Any + 'static> XWayland<Data> {
/// Create a new XWayland manager
pub fn new<L>(
handle: LoopHandle<'static, Data>,
display: Rc<RefCell<Display>>,
data: &mut T,
logger: L,
) -> Result<XWayland<WM>, ()>
) -> (XWayland<Data>, XWaylandSource)
where
L: Into<Option<::slog::Logger>>,
{
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<WM: XWindowManager> Drop for XWayland<WM> {
impl<Data> Drop for XWayland<Data> {
fn drop(&mut self) {
self.inner.borrow_mut().shutdown();
}
@ -135,32 +142,25 @@ impl<WM: XWindowManager> Drop for XWayland<WM> {
struct XWaylandInstance {
display_lock: X11Lock,
wayland_client: Client,
wayland_client: Option<Client>,
startup_handler: Option<RegistrationToken>,
wm_fd: Option<UnixStream>,
started_at: ::std::time::Instant,
child_stdout: Option<ChildStdout>,
}
type SourceMaker<WM> = dyn FnMut(Rc<RefCell<Inner<WM>>>, RawFd) -> Result<RegistrationToken, ()>;
// Inner implementation of the XWayland manager
struct Inner<WM: XWindowManager> {
wm: WM,
source_maker: Box<SourceMaker<WM>>,
struct Inner<Data> {
sender: SyncSender<XWaylandEvent>,
handle: LoopHandle<'static, Data>,
wayland_display: Rc<RefCell<Display>>,
instance: Option<XWaylandInstance>,
kill_source: Box<dyn Fn(RegistrationToken)>,
log: ::slog::Logger,
}
// Launch an XWayland server
//
// Does nothing if there is already a launched instance
fn launch<WM: XWindowManager + 'static, T: Any>(
inner: &Rc<RefCell<Inner<WM>>>,
data: &mut T,
) -> Result<(), ()> {
fn launch<Data: Any>(inner: &Rc<RefCell<Inner<Data>>>) -> std::io::Result<()> {
let mut guard = inner.borrow_mut();
if guard.instance.is_some() {
return Ok(());
@ -168,64 +168,119 @@ fn launch<WM: XWindowManager + 'static, T: Any>(
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::<Data>(&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::<WM, T>(&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<WM: XWindowManager> Inner<WM> {
pub struct XWaylandSource {
channel: Channel<XWaylandEvent>,
}
impl calloop::EventSource for XWaylandSource {
type Event = XWaylandEvent;
type Metadata = ();
type Ret = ();
fn process_events<F>(
&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<Data> Inner<Data> {
// 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<WM: XWindowManager> Inner<WM> {
}
}
fn client_destroy<WM: XWindowManager + 'static, T: Any>(map: &::wayland_server::UserDataMap, data: &mut T) {
let inner = map.get::<Rc<RefCell<Inner<WM>>>>().unwrap();
// shutdown the server
let started_at = inner.borrow().instance.as_ref().map(|i| i.started_at);
fn client_destroy<Data: 'static>(map: &::wayland_server::UserDataMap) {
let inner = map.get::<Rc<RefCell<Inner<Data>>>>().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<WM: XWindowManager>(inner: &Rc<RefCell<Inner<WM>>>) {
fn xwayland_ready<Data: 'static>(inner: &Rc<RefCell<Inner<Data>>>) {
// 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<WM: XWindowManager>(inner: &Rc<RefCell<Inner<WM>>>) {
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<WM: XWindowManager>(inner: &Rc<RefCell<Inner<WM>>>) {
::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);
}
}