diff --git a/Cargo.toml b/Cargo.toml index 43df72c..a12bf95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,12 @@ keywords = ["wayland", "windowing"] rust-version = "1.65.0" [dependencies] -wayland-client = "0.30.2" -wayland-backend = "0.1.0" -calloop = "0.10.0" +wayland-client = "0.31.1" +wayland-backend = "0.3.0" +calloop = "0.12.1" log = { version = "0.4.19", optional = true } rustix = { version = "0.38.4", default-features = false, features = ["std"] } + +[dev-dependencies] +tempfile = "3.8.0" +wayland-protocols = { version = "0.31.0", features = ["client"] } diff --git a/examples/simplest-window.rs b/examples/simplest-window.rs new file mode 100644 index 0000000..424f481 --- /dev/null +++ b/examples/simplest-window.rs @@ -0,0 +1,240 @@ +use std::fs::File; +use std::os::unix::prelude::AsFd; + +use calloop::EventLoop; +use calloop_wayland_source::WaylandSource; +use wayland_client::protocol::{ + wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface, +}; +use wayland_client::{delegate_noop, Connection, Dispatch, QueueHandle, WEnum}; + +use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}; + +fn main() { + let conn = Connection::connect_to_env().unwrap(); + + let event_queue = conn.new_event_queue(); + let qhandle = event_queue.handle(); + + let display = conn.display(); + display.get_registry(&qhandle, ()); + + let mut event_loop: EventLoop = + EventLoop::try_new().expect("Failed to initialize the event loop!"); + let loop_handle = event_loop.handle(); + WaylandSource::new(conn, event_queue).insert(loop_handle).unwrap(); + let stop_handle = event_loop.get_signal(); + + let mut state = State { + running: true, + base_surface: None, + buffer: None, + wm_base: None, + xdg_surface: None, + configured: false, + stop_handle, + }; + + println!("Starting the example window app, press to quit."); + + event_loop.run(None, &mut state, |_| {}).unwrap(); +} + +struct State { + running: bool, + base_surface: Option, + buffer: Option, + wm_base: Option, + xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>, + configured: bool, + stop_handle: calloop::LoopSignal, +} + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { name, interface, .. } = event { + match &interface[..] { + "wl_compositor" => { + let compositor = + registry.bind::(name, 1, qh, ()); + let surface = compositor.create_surface(qh, ()); + state.base_surface = Some(surface); + + if state.wm_base.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + }, + "wl_shm" => { + let shm = registry.bind::(name, 1, qh, ()); + + let (init_w, init_h) = (320, 240); + + let mut file = tempfile::tempfile().unwrap(); + draw(&mut file, (init_w, init_h)); + let pool = shm.create_pool(file.as_fd(), (init_w * init_h * 4) as i32, qh, ()); + let buffer = pool.create_buffer( + 0, + init_w as i32, + init_h as i32, + (init_w * 4) as i32, + wl_shm::Format::Argb8888, + qh, + (), + ); + state.buffer = Some(buffer.clone()); + + if state.configured { + let surface = state.base_surface.as_ref().unwrap(); + surface.attach(Some(&buffer), 0, 0); + surface.commit(); + } + }, + "wl_seat" => { + registry.bind::(name, 1, qh, ()); + }, + "xdg_wm_base" => { + let wm_base = registry.bind::(name, 1, qh, ()); + state.wm_base = Some(wm_base); + + if state.base_surface.is_some() && state.xdg_surface.is_none() { + state.init_xdg_surface(qh); + } + }, + _ => {}, + } + } + } +} + +// Ignore events from these object types in this example. +delegate_noop!(State: ignore wl_compositor::WlCompositor); +delegate_noop!(State: ignore wl_surface::WlSurface); +delegate_noop!(State: ignore wl_shm::WlShm); +delegate_noop!(State: ignore wl_shm_pool::WlShmPool); +delegate_noop!(State: ignore wl_buffer::WlBuffer); + +fn draw(tmp: &mut File, (buf_x, buf_y): (u32, u32)) { + use std::cmp::min; + use std::io::Write; + let mut buf = std::io::BufWriter::new(tmp); + for y in 0..buf_y { + for x in 0..buf_x { + let a = 0xFF; + let r = min(((buf_x - x) * 0xFF) / buf_x, ((buf_y - y) * 0xFF) / buf_y); + let g = min((x * 0xFF) / buf_x, ((buf_y - y) * 0xFF) / buf_y); + let b = min(((buf_x - x) * 0xFF) / buf_x, (y * 0xFF) / buf_y); + + let color = (a << 24) + (r << 16) + (g << 8) + b; + buf.write_all(&color.to_ne_bytes()).unwrap(); + } + } + buf.flush().unwrap(); +} + +impl State { + fn init_xdg_surface(&mut self, qh: &QueueHandle) { + let wm_base = self.wm_base.as_ref().unwrap(); + let base_surface = self.base_surface.as_ref().unwrap(); + + let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ()); + let toplevel = xdg_surface.get_toplevel(qh, ()); + toplevel.set_title("A fantastic window!".into()); + + base_surface.commit(); + + self.xdg_surface = Some((xdg_surface, toplevel)); + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + wm_base: &xdg_wm_base::XdgWmBase, + event: xdg_wm_base::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + wm_base.pong(serial); + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + xdg_surface: &xdg_surface::XdgSurface, + event: xdg_surface::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_surface::Event::Configure { serial, .. } = event { + xdg_surface.ack_configure(serial); + state.configured = true; + let surface = state.base_surface.as_ref().unwrap(); + if let Some(ref buffer) = state.buffer { + surface.attach(Some(buffer), 0, 0); + surface.commit(); + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &xdg_toplevel::XdgToplevel, + event: xdg_toplevel::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let xdg_toplevel::Event::Close {} = event { + state.running = false; + } + } +} + +impl Dispatch for State { + fn event( + _: &mut Self, + seat: &wl_seat::WlSeat, + event: wl_seat::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_seat::Event::Capabilities { capabilities: WEnum::Value(capabilities) } = event { + if capabilities.contains(wl_seat::Capability::Keyboard) { + seat.get_keyboard(qh, ()); + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &wl_keyboard::WlKeyboard, + event: wl_keyboard::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if let wl_keyboard::Event::Key { key, .. } = event { + if key == 1 { + // ESC key + state.stop_handle.stop(); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6737e2c..c0998b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! let loop_handle = event_loop.handle(); //! //! // Insert the wayland source into the calloop's event loop. -//! WaylandSource::new(event_queue).unwrap().insert(loop_handle).unwrap(); +//! WaylandSource::new(connection, event_queue).insert(loop_handle).unwrap(); //! //! // This will start dispatching the event loop and processing pending wayland requests. //! while let Ok(_) = event_loop.dispatch(None, &mut ()) { @@ -28,8 +28,8 @@ //! } //! ``` +#![deny(unsafe_op_in_unsafe_fn)] use std::io; -use std::os::unix::io::{AsRawFd, RawFd}; use calloop::generic::Generic; use calloop::{ @@ -38,7 +38,7 @@ use calloop::{ }; use rustix::io::Errno; use wayland_backend::client::{ReadEventsGuard, WaylandError}; -use wayland_client::{DispatchError, EventQueue}; +use wayland_client::{Connection, DispatchError, EventQueue}; #[cfg(feature = "log")] use log::error as log_error; @@ -57,31 +57,53 @@ use std::eprintln as log_error; /// loop and automatically dispatch pending events on the event queue. #[derive(Debug)] pub struct WaylandSource { + // In theory, we could use the same event queue inside `connection_source` + // However, calloop's safety requirements mean that we cannot then give + // mutable access to the queue, which is incompatible with our current interface + // Additionally, `Connection` is cheaply cloneable, so it's not a huge burden queue: EventQueue, - fd: Generic, + connection_source: Generic, read_guard: Option, + /// Calloop's before_will_sleep method allows + /// skipping the sleeping by returning a `Token`. + /// We cannot produce this on the fly, so store it here instead + fake_token: Option, + // Some calloop event handlers don't support error handling, so we have to store the error + // for a short time until we reach a method which allows it + stored_error: Result<(), io::Error>, } impl WaylandSource { /// Wrap an [`EventQueue`] as a [`WaylandSource`]. - pub fn new(queue: EventQueue) -> Result, WaylandError> { - let guard = queue.prepare_read()?; - let fd = Generic::new(guard.connection_fd().as_raw_fd(), Interest::READ, Mode::Level); - drop(guard); + /// + /// `queue` must be from the connection `Connection`. + /// This is not a safety invariant, but not following this may cause + /// freezes or hangs + pub fn new(connection: Connection, queue: EventQueue) -> WaylandSource { + let connection_source = Generic::new(connection, Interest::READ, Mode::Level); - Ok(WaylandSource { queue, fd, read_guard: None }) + WaylandSource { + queue, + connection_source, + read_guard: None, + fake_token: None, + stored_error: Ok(()), + } } /// Access the underlying event queue /// - /// Note that you should be careful when interacting with it if you invoke - /// methods that interact with the wayland socket (such as `dispatch()` - /// or `prepare_read()`). These may interfere with the proper waking up - /// of this event source in the event loop. + /// Note that you should not replace this queue with a queue from a + /// different `Connection`, as that may cause freezes or other hangs. pub fn queue(&mut self) -> &mut EventQueue { &mut self.queue } + /// Access the connection to the Wayland server + pub fn connection(&self) -> &Connection { + self.connection_source.get_ref() + } + /// Insert this source into the given event loop. /// /// This adapter will pass the event loop's shared data as the `D` type for @@ -104,51 +126,37 @@ impl EventSource for WaylandSource { type Metadata = EventQueue; type Ret = Result; + const NEEDS_EXTRA_LIFECYCLE_EVENTS: bool = true; + fn process_events( &mut self, - readiness: Readiness, - token: Token, + _: Readiness, + _: Token, mut callback: F, ) -> Result where F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, { - let queue = &mut self.queue; - let read_guard = &mut self.read_guard; - - let action = self.fd.process_events(readiness, token, |_, _| { - // 1. read events from the socket if any are available - if let Some(guard) = read_guard.take() { - // might be None if some other thread read events before us, concurrently - if let Err(WaylandError::Io(err)) = guard.read() { - if err.kind() != io::ErrorKind::WouldBlock { - return Err(err); - } - } - } + debug_assert!(self.read_guard.is_none()); - // 2. dispatch any pending events in the queue - // This is done to ensure we are not waiting for messages that are already in - // the buffer. - Self::loop_callback_pending(queue, &mut callback)?; - *read_guard = Some(Self::prepare_read(queue)?); - - // 3. Once dispatching is finished, flush the responses to the compositor - if let Err(WaylandError::Io(e)) = queue.flush() { - if e.kind() != io::ErrorKind::WouldBlock { - // in case of error, forward it and fast-exit - return Err(e); - } - // WouldBlock error means the compositor could not process all - // our messages quickly. Either it is slowed - // down or we are a spammer. Should not really - // happen, if it does we do nothing and will flush again later - } + // Take the stored error + std::mem::replace(&mut self.stored_error, Ok(()))?; + + // We know that the event will either be a fake event + // produced in `before_will_sleep`, or a "real" event from the underlying + // source (self.queue_events). Our behaviour in both cases is the same. + // In theory we might want to call the process_events handler on the underlying + // event source. However, we know that Generic's `process_events` call is a + // no-op, so we just handle the event ourselves. - Ok(PostAction::Continue) - })?; + let queue = &mut self.queue; + // Dispatch any pending events in the queue + Self::loop_callback_pending(queue, &mut callback)?; + + // Once dispatching is finished, flush the responses to the compositor + flush_queue(queue)?; - Ok(action) + Ok(PostAction::Continue) } fn register( @@ -156,7 +164,8 @@ impl EventSource for WaylandSource { poll: &mut Poll, token_factory: &mut TokenFactory, ) -> calloop::Result<()> { - self.fd.register(poll, token_factory) + self.fake_token = Some(token_factory.token()); + self.connection_source.register(poll, token_factory) } fn reregister( @@ -164,44 +173,65 @@ impl EventSource for WaylandSource { poll: &mut Poll, token_factory: &mut TokenFactory, ) -> calloop::Result<()> { - self.fd.reregister(poll, token_factory) + self.connection_source.reregister(poll, token_factory) } fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { - self.fd.unregister(poll) + self.connection_source.unregister(poll) } - fn pre_run(&mut self, mut callback: F) -> calloop::Result<()> - where - F: FnMut((), &mut Self::Metadata) -> Self::Ret, - { + fn before_sleep(&mut self) -> calloop::Result> { debug_assert!(self.read_guard.is_none()); - // flush the display before starting to poll - if let Err(WaylandError::Io(err)) = self.queue.flush() { - if err.kind() != io::ErrorKind::WouldBlock { - // in case of error, don't prepare a read, if the error is persistent, it'll - // trigger in other wayland methods anyway - log_error!("Error trying to flush the wayland display: {}", err); - return Err(err.into()); - } - } + flush_queue(&mut self.queue)?; - // ensure we are not waiting for messages that are already in the buffer. - Self::loop_callback_pending(&mut self.queue, &mut callback)?; - self.read_guard = Some(Self::prepare_read(&mut self.queue)?); + self.read_guard = self.queue.prepare_read(); + match self.read_guard { + Some(_) => Ok(None), + // If getting the guard failed, that means that there are + // events in the queue, and so we need to handle the events instantly + // rather than waiting on an event in polling. We tell calloop this + // by returning Some here. Note that the readiness value is + // never used, so we just need some marker + None => Ok(Some((Readiness::EMPTY, self.fake_token.unwrap()))), + } + } - Ok(()) + fn before_handle_events(&mut self, events: calloop::EventIterator<'_>) { + // It's important that the guard isn't held whilst process_events calls occur + // This can use arbitrary user-provided code, which may want to use the wayland + // socket For example, creating a Vulkan surface needs access to the + // connection + let guard = self.read_guard.take(); + if events.count() > 0 { + // Read events from the socket if any are available + if let Some(Err(WaylandError::Io(err))) = guard.map(ReadEventsGuard::read) { + // If some other thread read events before us, concurrently, that's an expected + // case, so this error isn't an issue. Other error kinds do need to be returned, + // however + if err.kind() != io::ErrorKind::WouldBlock { + // before_handle_events doesn't allow returning errors + // For now, cache it in self until process_events is called + self.stored_error = Err(err); + } + } + } } +} - fn post_run(&mut self, _: F) -> calloop::Result<()> - where - F: FnMut((), &mut Self::Metadata) -> Self::Ret, - { - // Drop implementation of ReadEventsGuard will do cleanup - self.read_guard.take(); - Ok(()) +fn flush_queue(queue: &mut EventQueue) -> Result<(), calloop::Error> { + if let Err(WaylandError::Io(err)) = queue.flush() { + // WouldBlock error means the compositor could not process all + // our messages quickly. Either it is slowed + // down or we are a spammer. Should not really + // happen, if it does we do nothing and will flush again later + if err.kind() != io::ErrorKind::WouldBlock { + // in case of error, forward it and fast-exit + log_error!("Error trying to flush the wayland display: {}", err); + return Err(err.into()); + } } + Ok(()) } impl WaylandSource { @@ -237,16 +267,4 @@ impl WaylandSource { } } } - - fn prepare_read(queue: &mut EventQueue) -> io::Result { - queue.prepare_read().map_err(|err| match err { - WaylandError::Io(err) => err, - - WaylandError::Protocol(err) => { - log_error!("Protocol error received on display: {}", err); - - Errno::PROTO.into() - }, - }) - } }