From a6c40a002b566ee475e1a25f5a229d5a577c619e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Mon, 4 Jan 2021 08:17:16 +0100 Subject: [PATCH] Add an X11 EventSource to calloop This commit creates an event source for calloop that receives X11 events from the X11 server. Signed-off-by: Uli Schlachter --- anvil/src/state.rs | 2 +- anvil/src/xwayland.rs | 88 ------------ anvil/src/xwayland/mod.rs | 169 +++++++++++++++++++++++ anvil/src/xwayland/x11rb_event_source.rs | 66 +++++++++ 4 files changed, 236 insertions(+), 89 deletions(-) delete mode 100644 anvil/src/xwayland.rs create mode 100644 anvil/src/xwayland/mod.rs create mode 100644 anvil/src/xwayland/x11rb_event_source.rs diff --git a/anvil/src/state.rs b/anvil/src/state.rs index dca38e3..1bffee3 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -161,7 +161,7 @@ impl AnvilState { #[cfg(feature = "xwayland")] let _xwayland = { - let xwm = XWm::new(); + let xwm = XWm::new(handle.clone(), log.clone()); XWayland::init(xwm, handle.clone(), display.clone(), &mut (), log.clone()).unwrap() }; diff --git a/anvil/src/xwayland.rs b/anvil/src/xwayland.rs deleted file mode 100644 index 81ccb3e..0000000 --- a/anvil/src/xwayland.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::os::unix::net::UnixStream; - -use smithay:: { - reexports::wayland_server::Client, - xwayland::XWindowManager, -}; - -use x11rb::{ - connection::Connection as _, - protocol::{ - composite::{ConnectionExt as _, Redirect}, - xproto::{ - ChangeWindowAttributesAux, ConnectionExt as _, EventMask, WindowClass, - }, - }, - rust_connection::{DefaultStream, RustConnection}, -}; - -/// 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; - -impl XWm { - pub fn new() -> Self { - Self - } -} - -impl XWindowManager for XWm { - fn xwayland_ready(&mut self, connection: UnixStream, _client: Client) { - let _wm = X11State::start_wm(connection); - } - - fn xwayland_exited(&mut self) {} -} - -x11rb::atom_manager! { - Atoms: AtomsCookie { - WM_S0, - } -} - -/// The actual runtime state of the XWayland integration. -struct X11State { -} - -impl X11State { - fn start_wm(connection: UnixStream) -> Result> { - // Create an X11 connection. XWayland only uses screen 0. - let screen = 0; - let stream = DefaultStream::from_unix_stream(connection)?; - let conn = RustConnection::connect_to_stream(stream, screen)?; - let atoms = Atoms::new(&conn)?.reply()?; - - let screen = &conn.setup().roots[0]; - - // Actually become the WM by redirecting some operations - conn.change_window_attributes( - screen.root, - &ChangeWindowAttributesAux::default().event_mask(EventMask::SubstructureRedirect), - )?; - - // Tell XWayland that we are the WM by acquiring the WM_S0 selection. No X11 clients are accepted before this. - let win = conn.generate_id()?; - conn.create_window( - screen.root_depth, - win, - screen.root, - // x, y, width, height, border width - 0, - 0, - 1, - 1, - 0, - WindowClass::InputOutput, - x11rb::COPY_FROM_PARENT, - &Default::default(), - )?; - conn.set_selection_owner(win, atoms.WM_S0, x11rb::CURRENT_TIME)?; - - // XWayland wants us to do this to function properly...? - conn.composite_redirect_subwindows(screen.root, Redirect::Manual)?; - - conn.flush()?; - - Ok(X11State {}) - } -} diff --git a/anvil/src/xwayland/mod.rs b/anvil/src/xwayland/mod.rs new file mode 100644 index 0000000..3cc9b2b --- /dev/null +++ b/anvil/src/xwayland/mod.rs @@ -0,0 +1,169 @@ +use std::{convert::TryFrom, os::unix::net::UnixStream, rc::Rc}; + +use smithay::{ + reexports::{ + calloop::LoopHandle, + wayland_server::Client, + }, + xwayland::XWindowManager, +}; + +use x11rb::{ + connection::Connection as _, + errors::ReplyOrIdError, + protocol::{ + composite::{ConnectionExt as _, Redirect}, + xproto::{ + ChangeWindowAttributesAux, ConfigWindow, ConfigureWindowAux, ConnectionExt as _, EventMask, + WindowClass, + }, + Event, + }, + rust_connection::{DefaultStream, RustConnection}, +}; + +use crate::AnvilState; + +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, + log: slog::Logger, +} + +impl XWm { + pub fn new(handle: LoopHandle, log: slog::Logger) -> Self { + Self { handle, log } + } +} + +impl XWindowManager for XWm { + fn xwayland_ready(&mut self, connection: UnixStream, _client: Client) { + let (mut wm, source) = X11State::start_wm(connection, self.log.clone()).unwrap(); + self.handle + .insert_source(source, move |events, _, _| { + for event in events.into_iter() { + wm.handle_event(event)?; + } + Ok(()) + }) + .unwrap(); + } + + fn xwayland_exited(&mut self) {} +} + +x11rb::atom_manager! { + Atoms: AtomsCookie { + WM_S0, + WL_SURFACE_ID, + } +} + +/// The actual runtime state of the XWayland integration. +struct X11State { + conn: Rc, + atoms: Atoms, + log: slog::Logger, +} + +impl X11State { + fn start_wm(connection: UnixStream, log: slog::Logger) -> Result<(Self, X11Source), Box> { + // Create an X11 connection. XWayland only uses screen 0. + let screen = 0; + let stream = DefaultStream::from_unix_stream(connection)?; + let conn = RustConnection::connect_to_stream(stream, screen)?; + let atoms = Atoms::new(&conn)?.reply()?; + + let screen = &conn.setup().roots[0]; + + // Actually become the WM by redirecting some operations + conn.change_window_attributes( + screen.root, + &ChangeWindowAttributesAux::default().event_mask(EventMask::SubstructureRedirect), + )?; + + // Tell XWayland that we are the WM by acquiring the WM_S0 selection. No X11 clients are accepted before this. + let win = conn.generate_id()?; + conn.create_window( + screen.root_depth, + win, + screen.root, + // x, y, width, height, border width + 0, + 0, + 1, + 1, + 0, + WindowClass::InputOutput, + x11rb::COPY_FROM_PARENT, + &Default::default(), + )?; + conn.set_selection_owner(win, atoms.WM_S0, x11rb::CURRENT_TIME)?; + + // XWayland wants us to do this to function properly...? + conn.composite_redirect_subwindows(screen.root, Redirect::Manual)?; + + conn.flush()?; + + let conn = Rc::new(conn); + let wm = Self { + conn: Rc::clone(&conn), + atoms, + log, + }; + + Ok((wm, X11Source::new(conn))) + } + + fn handle_event(&mut self, event: Event) -> Result<(), ReplyOrIdError> { + debug!(self.log, "X11: Got event {:?}", event); + match event { + Event::ConfigureRequest(r) => { + // Just grant the wish + let mut aux = ConfigureWindowAux::default(); + if r.value_mask & u16::from(ConfigWindow::StackMode) != 0 { + aux = aux.stack_mode(r.stack_mode); + } + if r.value_mask & u16::from(ConfigWindow::Sibling) != 0 { + aux = aux.sibling(r.sibling); + } + if r.value_mask & u16::from(ConfigWindow::X) != 0 { + aux = aux.x(i32::try_from(r.x).unwrap()); + } + if r.value_mask & u16::from(ConfigWindow::Y) != 0 { + aux = aux.y(i32::try_from(r.y).unwrap()); + } + if r.value_mask & u16::from(ConfigWindow::Width) != 0 { + aux = aux.width(u32::try_from(r.width).unwrap()); + } + if r.value_mask & u16::from(ConfigWindow::Height) != 0 { + aux = aux.height(u32::try_from(r.height).unwrap()); + } + if r.value_mask & u16::from(ConfigWindow::BorderWidth) != 0 { + aux = aux.border_width(u32::try_from(r.border_width).unwrap()); + } + self.conn.configure_window(r.window, &aux)?; + } + Event::MapRequest(r) => { + // Just grant the wish + self.conn.map_window(r.window)?; + } + Event::ClientMessage(msg) => { + if msg.type_ == self.atoms.WL_SURFACE_ID { + let id = msg.data.as_data32()[0]; + info!( + self.log, + "X11 surface {:x?} corresponds to WlSurface {:x}", msg.window, id, + ); + } + } + _ => {} + } + Ok(()) + } +} diff --git a/anvil/src/xwayland/x11rb_event_source.rs b/anvil/src/xwayland/x11rb_event_source.rs new file mode 100644 index 0000000..aefaa7f --- /dev/null +++ b/anvil/src/xwayland/x11rb_event_source.rs @@ -0,0 +1,66 @@ +use std::{ + io::{Error as IOError, ErrorKind, Result as IOResult}, + os::unix::io::AsRawFd, + rc::Rc, +}; + +use x11rb::{ + connection::Connection as _, errors::ReplyOrIdError, protocol::Event, rust_connection::RustConnection, +}; + +use smithay::reexports::calloop::{ + generic::{Fd, Generic}, + EventSource, Interest, Mode, Poll, Readiness, Token, +}; + +pub struct X11Source { + connection: Rc, + generic: Generic, +} + +impl X11Source { + pub fn new(connection: Rc) -> Self { + let fd = Fd(connection.stream().as_raw_fd()); + let generic = Generic::new(fd, Interest::Readable, Mode::Level); + Self { connection, generic } + } +} + +impl EventSource for X11Source { + type Event = Vec; + type Metadata = (); + type Ret = Result<(), ReplyOrIdError>; + + fn process_events(&mut self, _readiness: Readiness, _token: Token, callback: C) -> IOResult<()> + where + C: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + fn inner(conn: &RustConnection, mut callback: C) -> Result<(), ReplyOrIdError> + where + C: FnMut(Vec, &mut ()) -> Result<(), ReplyOrIdError>, + { + let mut events = Vec::new(); + while let Some(event) = conn.poll_for_event()? { + events.push(event); + } + if !events.is_empty() { + callback(events, &mut ())?; + } + conn.flush()?; + Ok(()) + } + inner(&self.connection, callback).map_err(|err| IOError::new(ErrorKind::Other, err)) + } + + fn register(&mut self, poll: &mut Poll, token: Token) -> IOResult<()> { + self.generic.register(poll, token) + } + + fn reregister(&mut self, poll: &mut Poll, token: Token) -> IOResult<()> { + self.generic.reregister(poll, token) + } + + fn unregister(&mut self, poll: &mut Poll) -> IOResult<()> { + self.generic.unregister(poll) + } +}