diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7a992..682fe98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ - EGLBufferReader now checks if buffers are alive before using them. - LibSeat no longer panics on seat disable event. +- X11 backend will report an error when trying to present a dmabuf fails. ### Anvil diff --git a/anvil/src/x11.rs b/anvil/src/x11.rs index 8088448..04ec7d0 100644 --- a/anvil/src/x11.rs +++ b/anvil/src/x11.rs @@ -166,13 +166,13 @@ pub fn run_x11(log: Logger) { event_loop .handle() - .insert_source(backend, |event, _window, state| match event { - X11Event::CloseRequested => { + .insert_source(backend, |event, _, state| match event { + X11Event::CloseRequested { .. } => { state.running.store(false, Ordering::SeqCst); } - X11Event::Resized(size) => { - let size = { (size.w as i32, size.h as i32).into() }; + X11Event::Resized { new_size, .. } => { + let size = { (new_size.w as i32, new_size.h as i32).into() }; state.backend_data.mode = Mode { size, @@ -193,7 +193,7 @@ pub fn run_x11(log: Logger) { state.backend_data.render = true; } - X11Event::PresentCompleted | X11Event::Refresh => { + X11Event::PresentCompleted { .. } | X11Event::Refresh { .. } => { state.backend_data.render = true; } diff --git a/src/backend/drm/surface/atomic.rs b/src/backend/drm/surface/atomic.rs index c0f64e9..f381b20 100644 --- a/src/backend/drm/surface/atomic.rs +++ b/src/backend/drm/surface/atomic.rs @@ -354,7 +354,7 @@ impl AtomicDrmSurface { source, })?; - let test_fb = self.create_test_buffer(pending.mode.size())?; + let test_fb = self.create_test_buffer(mode.size())?; let req = self.build_request( &mut pending.connectors.iter(), &mut [].iter(), diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index 3bc5be0..c587dd2 100644 --- a/src/backend/drm/surface/gbm.rs +++ b/src/backend/drm/surface/gbm.rs @@ -309,8 +309,11 @@ where /// Fails if the mode is not compatible with the underlying /// [`crtc`](drm::control::crtc) or any of the /// pending [`connector`](drm::control::connector)s. - pub fn use_mode(&self, mode: Mode) -> Result<(), Error> { - self.drm.use_mode(mode).map_err(Error::DrmError) + pub fn use_mode(&mut self, mode: Mode) -> Result<(), Error> { + self.drm.use_mode(mode).map_err(Error::DrmError)?; + let (w, h) = mode.size(); + self.swapchain.resize(w as _, h as _); + Ok(()) } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index dd0c9ca..2f3d57f 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -115,7 +115,7 @@ pub enum SwapBuffersError { /// Operations will have no effect. Functions that read textures, buffers, etc. /// will return uninitialized data instead. #[error("The context has been lost, it needs to be recreated: {0}")] - ContextLost(Box), + ContextLost(Box), /// A temporary condition caused to rendering to fail. /// /// Depending on the underlying error this *might* require fixing internal state of the rendering backend, @@ -126,5 +126,5 @@ pub enum SwapBuffersError { /// If the root cause cannot be discovered and subsequent renderings also fail, it is advised to fallback to /// recreation. #[error("A temporary condition caused the page flip to fail: {0}")] - TemporaryFailure(Box), + TemporaryFailure(Box), } diff --git a/src/backend/x11/buffer.rs b/src/backend/x11/buffer.rs index 8389124..387145a 100644 --- a/src/backend/x11/buffer.rs +++ b/src/backend/x11/buffer.rs @@ -29,54 +29,23 @@ use std::sync::atomic::Ordering; -use super::{Window, X11Error}; +use super::{PresentError, Window, X11Error}; use drm_fourcc::DrmFourcc; use nix::fcntl; -use x11rb::connection::Connection; -use x11rb::protocol::dri3::ConnectionExt as _; -use x11rb::protocol::present::{self, ConnectionExt}; -use x11rb::protocol::xproto::PixmapWrapper; -use x11rb::rust_connection::{ConnectionError, ReplyOrIdError}; -use x11rb::utils::RawFdContainer; +use x11rb::{ + connection::Connection, + protocol::{ + dri3::ConnectionExt as _, + present::{self, ConnectionExt}, + xproto::PixmapWrapper, + }, + utils::RawFdContainer, +}; -use crate::backend::allocator::dmabuf::Dmabuf; -use crate::backend::allocator::Buffer; +use crate::backend::allocator::{dmabuf::Dmabuf, Buffer}; // Shm can be easily supported in the future using, xcb_shm_create_pixmap. -#[derive(Debug, thiserror::Error)] -pub enum CreatePixmapError { - #[error("An x11 protocol error occured")] - Protocol(X11Error), - - #[error("The Dmabuf had too many planes")] - TooManyPlanes, - - #[error("Duplicating the file descriptors for the dmabuf handles failed")] - DupFailed(String), - - #[error("Buffer had incorrect format, expected: {0}")] - IncorrectFormat(DrmFourcc), -} - -impl From for CreatePixmapError { - fn from(e: X11Error) -> Self { - CreatePixmapError::Protocol(e) - } -} - -impl From for CreatePixmapError { - fn from(e: ReplyOrIdError) -> Self { - X11Error::from(e).into() - } -} - -impl From for CreatePixmapError { - fn from(e: ConnectionError) -> Self { - X11Error::from(e).into() - } -} - pub trait PixmapWrapperExt<'c, C> where C: Connection, @@ -88,7 +57,7 @@ where connection: &'c C, window: &Window, dmabuf: &Dmabuf, - ) -> Result, CreatePixmapError>; + ) -> Result, X11Error>; /// Presents the pixmap to the window. /// @@ -108,9 +77,9 @@ where connection: &'c C, window: &Window, dmabuf: &Dmabuf, - ) -> Result, CreatePixmapError> { + ) -> Result, X11Error> { if dmabuf.format().code != window.format() { - return Err(CreatePixmapError::IncorrectFormat(window.format())); + return Err(PresentError::IncorrectFormat(window.format()).into()); } let mut fds = Vec::new(); @@ -121,7 +90,7 @@ where handle, fcntl::FcntlArg::F_DUPFD_CLOEXEC(3), // Set to 3 so the fd cannot become stdin, stdout or stderr ) - .map_err(|e| CreatePixmapError::DupFailed(e.to_string()))?; + .map_err(|e| PresentError::DupFailed(e.to_string()))?; fds.push(RawFdContainer::new(fd)) } @@ -129,7 +98,7 @@ where // We need dri3 >= 1.2 in order to use the enhanced dri3_pixmap_from_buffers function. let xid = if window.0.extensions.dri3 >= Some((1, 2)) { if dmabuf.num_planes() > 4 { - return Err(CreatePixmapError::TooManyPlanes); + return Err(PresentError::TooManyPlanes.into()); } let xid = connection.generate_id()?; @@ -165,7 +134,7 @@ where } else { // Old codepath can only create a pixmap using one plane from a dmabuf. if dmabuf.num_planes() != 1 { - return Err(CreatePixmapError::TooManyPlanes); + return Err(PresentError::TooManyPlanes.into()); } let xid = connection.generate_id()?; diff --git a/src/backend/x11/error.rs b/src/backend/x11/error.rs index 90c4d32..53d3826 100644 --- a/src/backend/x11/error.rs +++ b/src/backend/x11/error.rs @@ -6,24 +6,26 @@ use x11rb::rust_connection::{ConnectError, ConnectionError, ReplyError, ReplyOrI use crate::backend::{allocator::gbm::GbmConvertError, drm::CreateDrmNodeError}; +use super::PresentError; + /// An error emitted by the X11 backend during setup. #[derive(Debug, thiserror::Error)] pub enum X11Error { /// Connecting to the X server failed. #[error("Connecting to the X server failed")] - ConnectionFailed(ConnectError), + ConnectionFailed(#[from] ConnectError), /// A required X11 extension was not present or has the right version. #[error("{0}")] - MissingExtension(MissingExtensionError), + MissingExtension(#[from] MissingExtensionError), /// Some protocol error occurred during setup. #[error("Some protocol error occurred during setup")] - Protocol(ReplyOrIdError), + Protocol(#[from] ReplyOrIdError), /// Creating the window failed. #[error("Creating the window failed")] - CreateWindow(CreateWindowError), + CreateWindow(#[from] CreateWindowError), /// An X11 surface already exists for this window. #[error("An X11 surface already exists for this window")] @@ -42,13 +44,11 @@ pub enum X11Error { /// Failed to allocate buffers needed to present to the window. #[error("Failed to allocate buffers needed to present to the window")] - Allocation(AllocateBuffersError), -} + Allocation(#[from] AllocateBuffersError), -impl From for X11Error { - fn from(err: ConnectError) -> Self { - Self::ConnectionFailed(err) - } + /// Error while presenting to a window. + #[error(transparent)] + Present(#[from] PresentError), } impl From for X11Error { @@ -63,12 +63,6 @@ impl From for X11Error { } } -impl From for X11Error { - fn from(err: ReplyOrIdError) -> Self { - Self::Protocol(err) - } -} - /// An error that occurs when a required X11 extension is not present. #[derive(Debug, thiserror::Error)] pub enum MissingExtensionError { @@ -99,12 +93,6 @@ pub enum MissingExtensionError { }, } -impl From for X11Error { - fn from(err: MissingExtensionError) -> Self { - Self::MissingExtension(err) - } -} - /// An error which may occur when creating an X11 window. #[derive(Debug, thiserror::Error)] pub enum CreateWindowError { @@ -117,12 +105,6 @@ pub enum CreateWindowError { NoVisual, } -impl From for X11Error { - fn from(err: CreateWindowError) -> Self { - Self::CreateWindow(err) - } -} - /// An error which may occur when allocating buffers for presentation to the window. #[derive(Debug, thiserror::Error)] pub enum AllocateBuffersError { @@ -165,9 +147,3 @@ impl From for AllocateBuffersError { } } } - -impl From for X11Error { - fn from(err: AllocateBuffersError) -> Self { - Self::Allocation(err) - } -} diff --git a/src/backend/x11/input.rs b/src/backend/x11/input.rs index 3b5774f..92ede4b 100644 --- a/src/backend/x11/input.rs +++ b/src/backend/x11/input.rs @@ -1,5 +1,6 @@ //! Input backend implementation for the X11 backend. +use super::{window_inner::WindowInner, Window, WindowTemporary}; use crate::{ backend::input::{ self, Axis, AxisSource, ButtonState, Device, DeviceCapability, InputBackend, KeyState, @@ -7,6 +8,7 @@ use crate::{ }, utils::{Logical, Size}, }; +use std::sync::Weak; /// Marker used to define the `InputBackend` types for the X11 backend. #[derive(Debug)] @@ -43,12 +45,22 @@ impl Device for X11VirtualDevice { /// X11-Backend internal event wrapping `X11`'s types into a [`KeyboardKeyEvent`]. #[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub struct X11KeyboardInputEvent { pub(crate) time: u32, pub(crate) key: u32, pub(crate) count: u32, pub(crate) state: KeyState, + pub(crate) window: Weak, +} + +impl X11KeyboardInputEvent { + /// Returns a temporary reference to the window belonging to this event. + /// + /// Returns None if the window is not alive anymore. + pub fn window(&self) -> Option + '_> { + self.window.upgrade().map(Window).map(WindowTemporary) + } } impl input::Event for X11KeyboardInputEvent { @@ -77,11 +89,21 @@ impl KeyboardKeyEvent for X11KeyboardInputEvent { /// X11-Backend internal event wrapping `X11`'s types into a [`PointerAxisEvent`] #[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone)] pub struct X11MouseWheelEvent { pub(crate) time: u32, pub(crate) axis: Axis, pub(crate) amount: f64, + pub(crate) window: Weak, +} + +impl X11MouseWheelEvent { + /// Returns a temporary reference to the window belonging to this event. + /// + /// Returns None if the window is not alive anymore. + pub fn window(&self) -> Option + '_> { + self.window.upgrade().map(Window).map(WindowTemporary) + } } impl input::Event for X11MouseWheelEvent { @@ -115,11 +137,21 @@ impl PointerAxisEvent for X11MouseWheelEvent { /// X11-Backend internal event wrapping `X11`'s types into a [`PointerButtonEvent`] #[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone)] pub struct X11MouseInputEvent { pub(crate) time: u32, pub(crate) raw: u32, pub(crate) state: ButtonState, + pub(crate) window: Weak, +} + +impl X11MouseInputEvent { + /// Returns a temporary reference to the window belonging to this event. + /// + /// Returns None if the window is not alive anymore. + pub fn window(&self) -> Option + '_> { + self.window.upgrade().map(Window).map(WindowTemporary) + } } impl input::Event for X11MouseInputEvent { @@ -144,12 +176,22 @@ impl PointerButtonEvent for X11MouseInputEvent { /// X11-Backend internal event wrapping `X11`'s types into a [`PointerMotionAbsoluteEvent`] #[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone)] pub struct X11MouseMovedEvent { pub(crate) time: u32, pub(crate) x: f64, pub(crate) y: f64, pub(crate) size: Size, + pub(crate) window: Weak, +} + +impl X11MouseMovedEvent { + /// Returns a temporary reference to the window belonging to this event. + /// + /// Returns None if the window is not alive anymore. + pub fn window(&self) -> Option + '_> { + self.window.upgrade().map(Window).map(WindowTemporary) + } } impl input::Event for X11MouseMovedEvent { diff --git a/src/backend/x11/mod.rs b/src/backend/x11/mod.rs index c71d305..ec98786 100644 --- a/src/backend/x11/mod.rs +++ b/src/backend/x11/mod.rs @@ -79,15 +79,12 @@ mod error; #[macro_use] mod extension; mod input; +mod surface; mod window_inner; -use self::{buffer::PixmapWrapperExt, window_inner::WindowInner}; use crate::{ backend::{ - allocator::{ - dmabuf::{AsDmabuf, Dmabuf}, - Slot, Swapchain, - }, + allocator::Swapchain, drm::{node::path_to_type, CreateDrmNodeError, DrmNode, NodeType}, egl::{native::X11DefaultDisplay, EGLDevice, EGLDisplay, Error as EGLError}, input::{Axis, ButtonState, InputEvent, KeyState}, @@ -96,7 +93,6 @@ use crate::{ }; use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory}; use drm_fourcc::{DrmFourcc, DrmModifier}; -use gbm::BufferObject; use nix::{ fcntl::{self, OFlag}, sys::stat::Mode, @@ -104,12 +100,11 @@ use nix::{ use slog::{error, info, o, Logger}; use std::{ collections::HashMap, - io, mem, + io, os::unix::prelude::AsRawFd, sync::{ atomic::{AtomicU32, Ordering}, - mpsc::{self, Receiver}, - Arc, Mutex, MutexGuard, Weak, + mpsc, Arc, Mutex, Weak, }, }; use x11rb::{ @@ -118,38 +113,51 @@ use x11rb::{ protocol::{ self as x11, dri3::ConnectionExt as _, - xproto::{ - ColormapAlloc, ConnectionExt, CreateWindowAux, PixmapWrapper, VisualClass, WindowClass, - WindowWrapper, - }, + xproto::{ColormapAlloc, ConnectionExt, CreateWindowAux, VisualClass, WindowClass, WindowWrapper}, ErrorKind, }, rust_connection::{ReplyError, RustConnection}, }; +use self::{extension::Extensions, window_inner::WindowInner}; + pub use self::error::*; -use self::extension::Extensions; pub use self::input::*; +pub use self::surface::*; /// An event emitted by the X11 backend. #[derive(Debug)] pub enum X11Event { /// The X server has required the compositor to redraw the contents of window. - Refresh, + Refresh { + /// XID of the window + window_id: u32, + }, /// An input event occurred. Input(InputEvent), /// The window was resized. - Resized(Size), + Resized { + /// The new size of the window + new_size: Size, + /// XID of the window + window_id: u32, + }, /// The last buffer presented to the window has been displayed. /// /// When this event is scheduled, the next frame may be rendered. - PresentCompleted, + PresentCompleted { + /// XID of the window + window_id: u32, + }, /// The window has received a request to be closed. - CloseRequested, + CloseRequested { + /// XID of the window + window_id: u32, + }, } /// Represents an active connection to the X to manage events on the Window provided by the backend. @@ -161,16 +169,6 @@ pub struct X11Backend { inner: Arc>, } -atom_manager! { - pub(crate) Atoms: AtomCollectionCookie { - WM_PROTOCOLS, - WM_DELETE_WINDOW, - _NET_WM_NAME, - UTF8_STRING, - _SMITHAY_X11_BACKEND_CLOSE, - } -} - impl X11Backend { /// Initializes the X11 backend by connecting to the X server. pub fn new(logger: L) -> Result @@ -257,6 +255,7 @@ impl X11Backend { atoms, depth, visual_id, + devices: false, }; Ok(X11Backend { @@ -277,19 +276,6 @@ impl X11Backend { } } -/// An X11 surface which uses GBM to allocate and present buffers. -#[derive(Debug)] -pub struct X11Surface { - connection: Weak, - window: Weak, - resize: Receiver>, - swapchain: Swapchain>>, BufferObject<()>>, - format: DrmFourcc, - width: u16, - height: u16, - buffer: Option>>, -} - #[derive(Debug, thiserror::Error)] enum EGLInitError { #[error(transparent)] @@ -298,81 +284,6 @@ enum EGLInitError { IO(#[from] io::Error), } -fn egl_init(_: &X11Inner) -> Result { - let display = EGLDisplay::new(&X11DefaultDisplay, None)?; - let device = EGLDevice::device_for_display(&display)?; - let path = path_to_type(device.drm_device_path()?, NodeType::Render)?; - fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) - .map_err(Into::::into) - .and_then(|fd| { - DrmNode::from_fd(fd).map_err(|err| match err { - CreateDrmNodeError::Io(err) => err, - _ => unreachable!(), - }) - }) - .map_err(EGLInitError::IO) -} - -fn dri3_init(x11: &X11Inner) -> Result { - let connection = &x11.connection; - - // Determine which drm-device the Display is using. - let screen = &connection.setup().roots[x11.screen_number]; - // provider being NONE tells the X server to use the RandR provider. - let dri3 = match connection.dri3_open(screen.root, x11rb::NONE)?.reply() { - Ok(reply) => reply, - Err(err) => { - return Err(if let ReplyError::X11Error(ref protocol_error) = err { - match protocol_error.error_kind { - // Implementation is risen when the renderer is not capable of X server is not capable - // of rendering at all. - ErrorKind::Implementation => X11Error::CannotDirectRender, - // Match may occur when the node cannot be authenticated for the client. - ErrorKind::Match => X11Error::CannotDirectRender, - _ => err.into(), - } - } else { - err.into() - }); - } - }; - - // Take ownership of the container's inner value so we do not need to duplicate the fd. - // This is fine because the X server will always open a new file descriptor. - let drm_device_fd = dri3.device_fd.into_raw_fd(); - - let fd_flags = - fcntl::fcntl(drm_device_fd.as_raw_fd(), fcntl::F_GETFD).map_err(AllocateBuffersError::from)?; - - // Enable the close-on-exec flag. - fcntl::fcntl( - drm_device_fd, - fcntl::F_SETFD(fcntl::FdFlag::from_bits_truncate(fd_flags) | fcntl::FdFlag::FD_CLOEXEC), - ) - .map_err(AllocateBuffersError::from)?; - let drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::::into)?; - - if drm_node.ty() != NodeType::Render && drm_node.has_render() { - // Try to get the render node. - if let Some(path) = drm_node.dev_path_with_type(NodeType::Render) { - return Ok(fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) - .map_err(Into::::into) - .map_err(CreateDrmNodeError::Io) - .and_then(DrmNode::from_fd) - .unwrap_or_else(|err| { - slog::warn!(&x11.log, "Could not create render node from existing DRM node ({}), falling back to primary node", err); - drm_node - })); - } - } - - slog::warn!( - &x11.log, - "DRM Device does not have a render node, falling back to primary node" - ); - Ok(drm_node) -} - /// A handle to the X11 backend. /// /// This is the primary object used to interface with the backend. @@ -456,7 +367,12 @@ impl X11Handle { return Err(X11Error::InvalidWindow); } - let modifiers = modifiers.collect::>(); + let mut modifiers = modifiers.collect::>(); + // older dri3 versions do only support buffers with one plane. + // we need to make sure, we don't accidently allocate buffers with more. + if window.0.extensions.dri3 < Some((1, 2)) { + modifiers.retain(|modi| modi == &DrmModifier::Invalid || modi == &DrmModifier::Linear); + } let format = window.0.format; let size = window.size(); @@ -480,114 +396,13 @@ impl X11Handle { resize: recv, }) } -} -impl X11Surface { - /// Returns the window the surface presents to. - /// - /// This will return [`None`] if the window has been destroyed. - pub fn window(&self) -> Option + '_> { - let window = self.window.upgrade().map(Window).map(WindowTemporary); - - struct WindowTemporary(Window); - - impl AsRef for WindowTemporary { - fn as_ref(&self) -> &Window { - &self.0 - } - } - - window - } - - /// Returns a handle to the GBM device used to allocate buffers. - pub fn device(&self) -> MutexGuard<'_, gbm::Device> { - self.swapchain.allocator.lock().unwrap() - } - - /// Returns the format of the buffers the surface accepts. - pub fn format(&self) -> DrmFourcc { - self.format - } - - /// Returns the next buffer that will be presented to the Window and its age. - /// - /// You may bind this buffer to a renderer to render. - /// This function will return the same buffer until [`submit`](Self::submit) is called - /// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers. - pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> { - if let Some(new_size) = self.resize.try_iter().last() { - self.resize(new_size)?; - } - - if self.buffer.is_none() { - self.buffer = Some( - self.swapchain - .acquire() - .map_err(Into::::into)? - .ok_or(AllocateBuffersError::NoFreeSlots)?, - ); - } - - let slot = self.buffer.as_ref().unwrap(); - let age = slot.age(); - match slot.userdata().get::() { - Some(dmabuf) => Ok((dmabuf.clone(), age)), - None => { - let dmabuf = slot.export().map_err(Into::::into)?; - slot.userdata().insert_if_missing(|| dmabuf.clone()); - Ok((dmabuf, age)) - } - } - } - - /// Consume and submit the buffer to the window. - pub fn submit(&mut self) -> Result<(), AllocateBuffersError> { - if let Some(connection) = self.connection.upgrade() { - // Get a new buffer - let mut next = self - .swapchain - .acquire() - .map_err(Into::::into)? - .ok_or(AllocateBuffersError::NoFreeSlots)?; - - // Swap the buffers - if let Some(current) = self.buffer.as_mut() { - mem::swap(&mut next, current); - } - - let window = self.window().ok_or(AllocateBuffersError::WindowDestroyed)?; - - if let Ok(pixmap) = PixmapWrapper::with_dmabuf( - &*connection, - window.as_ref(), - next.userdata().get::().unwrap(), - ) { - // Now present the current buffer - let _ = pixmap.present(&*connection, window.as_ref()); - } - self.swapchain.submitted(next); - - // Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection. - let _ = connection.flush(); - } - Ok(()) - } - - /// Resets the internal buffers, e.g. to reset age values - pub fn reset_buffers(&mut self) { - self.swapchain.reset_buffers(); - self.buffer = None; - } - - fn resize(&mut self, size: Size) -> Result<(), AllocateBuffersError> { - self.swapchain.resize(size.w as u32, size.h as u32); - self.buffer = None; - - self.width = size.w; - self.height = size.h; - - Ok(()) + /// Get a temporary reference to a window by its XID + pub fn window_ref_from_id(&self, id: u32) -> Option + '_> { + X11Inner::window_ref_from_id(&self.inner, &id) + .and_then(|w| w.upgrade()) + .map(Window) + .map(WindowTemporary) } } @@ -711,11 +526,19 @@ impl PartialEq for Window { } } +struct WindowTemporary(Window); + +impl AsRef for WindowTemporary { + fn as_ref(&self) -> &Window { + &self.0 + } +} + impl EventSource for X11Backend { type Event = X11Event; /// The window the incoming events are applicable to. - type Metadata = Window; + type Metadata = (); type Ret = (); @@ -755,6 +578,16 @@ impl EventSource for X11Backend { } } +atom_manager! { + pub(crate) Atoms: AtomCollectionCookie { + WM_PROTOCOLS, + WM_DELETE_WINDOW, + _NET_WM_NAME, + UTF8_STRING, + _SMITHAY_X11_BACKEND_CLOSE, + } +} + #[derive(Debug)] pub(crate) struct X11Inner { log: Logger, @@ -768,30 +601,48 @@ pub(crate) struct X11Inner { atoms: Atoms, depth: x11::xproto::Depth, visual_id: u32, + devices: bool, } impl X11Inner { + fn window_ref_from_id(inner: &Arc>, id: &u32) -> Option> { + let mut inner = inner.lock().unwrap(); + inner.windows.retain(|_, weak| weak.upgrade().is_some()); + inner.windows.get(id).cloned() + } + fn process_event(inner: &Arc>, log: &Logger, event: x11::Event, callback: &mut F) where - F: FnMut(X11Event, &mut Window), + F: FnMut(X11Event, &mut ()), { - use self::X11Event::Input; - - fn window_from_id(inner: &Arc>, id: &u32) -> Option> { - inner - .lock() - .unwrap() - .windows - .get(id) - .cloned() - .and_then(|weak| weak.upgrade()) + { + let mut inner = inner.lock().unwrap(); + if !inner.windows.is_empty() && !inner.devices { + callback( + Input(InputEvent::DeviceAdded { + device: X11VirtualDevice, + }), + &mut (), + ); + inner.devices = true; + } else if inner.windows.is_empty() && inner.devices { + callback( + Input(InputEvent::DeviceRemoved { + device: X11VirtualDevice, + }), + &mut (), + ); + inner.devices = false; + } } + use self::X11Event::Input; + // If X11 is deadlocking somewhere here, make sure you drop your mutex guards. match event { x11::Event::ButtonPress(button_press) => { - if let Some(window) = window_from_id(inner, &button_press.event) { + if let Some(window) = X11Inner::window_ref_from_id(inner, &button_press.event) { // X11 decided to associate scroll wheel with a button, 4, 5, 6 and 7 for // up, down, right and left. For scrolling, a press event is emitted and a // release is them immediately followed for scrolling. This means we can @@ -834,9 +685,10 @@ impl X11Inner { _ => unreachable!(), }, + window, }, }), - &mut Window(window), + &mut (), ) } else { callback( @@ -845,9 +697,10 @@ impl X11Inner { time: button_press.time, raw: button_press.detail as u32, state: ButtonState::Pressed, + window, }, }), - &mut Window(window), + &mut (), ) } } @@ -861,22 +714,23 @@ impl X11Inner { return; } - if let Some(window) = window_from_id(inner, &button_release.event) { + if let Some(window) = X11Inner::window_ref_from_id(inner, &button_release.event) { callback( Input(InputEvent::PointerButton { event: X11MouseInputEvent { time: button_release.time, raw: button_release.detail as u32, state: ButtonState::Released, + window, }, }), - &mut Window(window), + &mut (), ); } } x11::Event::KeyPress(key_press) => { - if let Some(window) = window_from_id(inner, &key_press.event) { + if let Some(window) = X11Inner::window_ref_from_id(inner, &key_press.event) { // Do not hold the lock. let count = { inner.lock().unwrap().key_counter.fetch_add(1, Ordering::SeqCst) + 1 }; @@ -892,15 +746,16 @@ impl X11Inner { key: key_press.detail as u32 - 8, count, state: KeyState::Pressed, + window, }, }), - &mut Window(window), + &mut (), ) } } x11::Event::KeyRelease(key_release) => { - if let Some(window) = window_from_id(inner, &key_release.event) { + if let Some(window) = X11Inner::window_ref_from_id(inner, &key_release.event) { let count = { let key_counter = inner.lock().unwrap().key_counter.clone(); @@ -924,15 +779,18 @@ impl X11Inner { key: key_release.detail as u32 - 8, count, state: KeyState::Released, + window, }, }), - &mut Window(window), + &mut (), ); } } x11::Event::MotionNotify(motion_notify) => { - if let Some(window) = window_from_id(inner, &motion_notify.event) { + if let Some(window) = + X11Inner::window_ref_from_id(inner, &motion_notify.event).and_then(|w| w.upgrade()) + { // Use event_x/y since those are relative the the window receiving events. let x = motion_notify.event_x as f64; let y = motion_notify.event_y as f64; @@ -946,15 +804,18 @@ impl X11Inner { x, y, size: window_size, + window: Arc::downgrade(&window), }, }), - &mut Window(window), + &mut (), ) } } x11::Event::ConfigureNotify(configure_notify) => { - if let Some(window) = window_from_id(inner, &configure_notify.window) { + if let Some(window) = + X11Inner::window_ref_from_id(inner, &configure_notify.window).and_then(|w| w.upgrade()) + { let previous_size = { *window.size.lock().unwrap() }; // Did the size of the window change? @@ -971,8 +832,11 @@ impl X11Inner { } (callback)( - X11Event::Resized(configure_notify_size), - &mut Window(window.clone()), + X11Event::Resized { + new_size: configure_notify_size, + window_id: configure_notify.window, + }, + &mut (), ); if let Some(resize_sender) = window.resize.lock().unwrap().as_ref() { @@ -983,40 +847,61 @@ impl X11Inner { } x11::Event::EnterNotify(enter_notify) => { - if let Some(window) = window_from_id(inner, &enter_notify.event) { + if let Some(window) = + X11Inner::window_ref_from_id(inner, &enter_notify.event).and_then(|w| w.upgrade()) + { window.cursor_enter(); } } x11::Event::LeaveNotify(leave_notify) => { - if let Some(window) = window_from_id(inner, &leave_notify.event) { + if let Some(window) = + X11Inner::window_ref_from_id(inner, &leave_notify.event).and_then(|w| w.upgrade()) + { window.cursor_leave(); } } x11::Event::ClientMessage(client_message) => { - if let Some(window) = window_from_id(inner, &client_message.window) { + if let Some(window) = + X11Inner::window_ref_from_id(inner, &client_message.window).and_then(|w| w.upgrade()) + { if client_message.data.as_data32()[0] == window.atoms.WM_DELETE_WINDOW // Destroy the window? { - (callback)(X11Event::CloseRequested, &mut Window(window)); + (callback)( + X11Event::CloseRequested { + window_id: client_message.window, + }, + &mut (), + ); } } } x11::Event::Expose(expose) => { - if let Some(window) = window_from_id(inner, &expose.window) { - if expose.count == 0 { - (callback)(X11Event::Refresh, &mut Window(window)); - } + if expose.count == 0 { + (callback)( + X11Event::Refresh { + window_id: expose.window, + }, + &mut (), + ); } } x11::Event::PresentCompleteNotify(complete_notify) => { - if let Some(window) = window_from_id(inner, &complete_notify.window) { + if let Some(window) = + X11Inner::window_ref_from_id(inner, &complete_notify.window).and_then(|w| w.upgrade()) + { window.last_msc.store(complete_notify.msc, Ordering::SeqCst); - (callback)(X11Event::PresentCompleted, &mut Window(window)); + (callback)( + X11Event::PresentCompleted { + window_id: complete_notify.window, + }, + &mut (), + ); } } @@ -1032,3 +917,78 @@ impl X11Inner { } } } + +fn egl_init(_: &X11Inner) -> Result { + let display = EGLDisplay::new(&X11DefaultDisplay, None)?; + let device = EGLDevice::device_for_display(&display)?; + let path = path_to_type(device.drm_device_path()?, NodeType::Render)?; + fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) + .map_err(Into::::into) + .and_then(|fd| { + DrmNode::from_fd(fd).map_err(|err| match err { + CreateDrmNodeError::Io(err) => err, + _ => unreachable!(), + }) + }) + .map_err(EGLInitError::IO) +} + +fn dri3_init(x11: &X11Inner) -> Result { + let connection = &x11.connection; + + // Determine which drm-device the Display is using. + let screen = &connection.setup().roots[x11.screen_number]; + // provider being NONE tells the X server to use the RandR provider. + let dri3 = match connection.dri3_open(screen.root, x11rb::NONE)?.reply() { + Ok(reply) => reply, + Err(err) => { + return Err(if let ReplyError::X11Error(ref protocol_error) = err { + match protocol_error.error_kind { + // Implementation is risen when the renderer is not capable of X server is not capable + // of rendering at all. + ErrorKind::Implementation => X11Error::CannotDirectRender, + // Match may occur when the node cannot be authenticated for the client. + ErrorKind::Match => X11Error::CannotDirectRender, + _ => err.into(), + } + } else { + err.into() + }); + } + }; + + // Take ownership of the container's inner value so we do not need to duplicate the fd. + // This is fine because the X server will always open a new file descriptor. + let drm_device_fd = dri3.device_fd.into_raw_fd(); + + let fd_flags = + fcntl::fcntl(drm_device_fd.as_raw_fd(), fcntl::F_GETFD).map_err(AllocateBuffersError::from)?; + + // Enable the close-on-exec flag. + fcntl::fcntl( + drm_device_fd, + fcntl::F_SETFD(fcntl::FdFlag::from_bits_truncate(fd_flags) | fcntl::FdFlag::FD_CLOEXEC), + ) + .map_err(AllocateBuffersError::from)?; + let drm_node = DrmNode::from_fd(drm_device_fd).map_err(Into::::into)?; + + if drm_node.ty() != NodeType::Render && drm_node.has_render() { + // Try to get the render node. + if let Some(path) = drm_node.dev_path_with_type(NodeType::Render) { + return Ok(fcntl::open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty()) + .map_err(Into::::into) + .map_err(CreateDrmNodeError::Io) + .and_then(DrmNode::from_fd) + .unwrap_or_else(|err| { + slog::warn!(&x11.log, "Could not create render node from existing DRM node ({}), falling back to primary node", err); + drm_node + })); + } + } + + slog::warn!( + &x11.log, + "DRM Device does not have a render node, falling back to primary node" + ); + Ok(drm_node) +} diff --git a/src/backend/x11/surface.rs b/src/backend/x11/surface.rs new file mode 100644 index 0000000..1ad2e1f --- /dev/null +++ b/src/backend/x11/surface.rs @@ -0,0 +1,147 @@ +use std::{ + mem, + sync::{mpsc::Receiver, Arc, Mutex, MutexGuard, Weak}, +}; + +use drm_fourcc::DrmFourcc; +use gbm::BufferObject; +use x11rb::{connection::Connection, protocol::xproto::PixmapWrapper, rust_connection::RustConnection}; + +use crate::{ + backend::{ + allocator::{ + dmabuf::{AsDmabuf, Dmabuf}, + Slot, Swapchain, + }, + drm::DrmNode, + x11::{buffer::PixmapWrapperExt, window_inner::WindowInner, AllocateBuffersError, Window}, + }, + utils::{Logical, Size}, +}; + +use super::{WindowTemporary, X11Error}; + +/// An error that may occur when presenting. +#[derive(Debug, thiserror::Error)] +pub enum PresentError { + /// The dmabuf being presented has too many planes. + #[error("The Dmabuf had too many planes")] + TooManyPlanes, + + /// Duplicating the dmabuf handles failed. + #[error("Duplicating the file descriptors for the dmabuf handles failed")] + DupFailed(String), + + /// The format dmabuf presented does not match the format of the window. + #[error("Buffer had incorrect format, expected: {0}")] + IncorrectFormat(DrmFourcc), +} + +/// An X11 surface which uses GBM to allocate and present buffers. +#[derive(Debug)] +pub struct X11Surface { + pub(crate) connection: Weak, + pub(crate) window: Weak, + pub(crate) resize: Receiver>, + pub(crate) swapchain: Swapchain>>, BufferObject<()>>, + pub(crate) format: DrmFourcc, + pub(crate) width: u16, + pub(crate) height: u16, + pub(crate) buffer: Option>>, +} + +impl X11Surface { + /// Returns the window the surface presents to. + /// + /// This will return [`None`] if the window has been destroyed. + pub fn window(&self) -> Option + '_> { + self.window.upgrade().map(Window).map(WindowTemporary) + } + + /// Returns a handle to the GBM device used to allocate buffers. + pub fn device(&self) -> MutexGuard<'_, gbm::Device> { + self.swapchain.allocator.lock().unwrap() + } + + /// Returns the format of the buffers the surface accepts. + pub fn format(&self) -> DrmFourcc { + self.format + } + + /// Returns the next buffer that will be presented to the Window and its age. + /// + /// You may bind this buffer to a renderer to render. + /// This function will return the same buffer until [`submit`](Self::submit) is called + /// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers. + pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> { + if let Some(new_size) = self.resize.try_iter().last() { + self.resize(new_size); + } + + if self.buffer.is_none() { + self.buffer = Some( + self.swapchain + .acquire() + .map_err(Into::::into)? + .ok_or(AllocateBuffersError::NoFreeSlots)?, + ); + } + + let slot = self.buffer.as_ref().unwrap(); + let age = slot.age(); + match slot.userdata().get::() { + Some(dmabuf) => Ok((dmabuf.clone(), age)), + None => { + let dmabuf = slot.export().map_err(Into::::into)?; + slot.userdata().insert_if_missing(|| dmabuf.clone()); + Ok((dmabuf, age)) + } + } + } + + /// Consume and submit the buffer to the window. + pub fn submit(&mut self) -> Result<(), X11Error> { + if let Some(connection) = self.connection.upgrade() { + // Get a new buffer + let mut next = self + .swapchain + .acquire() + .map_err(Into::::into)? + .ok_or(AllocateBuffersError::NoFreeSlots)?; + + // Swap the buffers + if let Some(current) = self.buffer.as_mut() { + mem::swap(&mut next, current); + } + + let window = self.window().ok_or(AllocateBuffersError::WindowDestroyed)?; + let pixmap = PixmapWrapper::with_dmabuf( + &*connection, + window.as_ref(), + next.userdata().get::().unwrap(), + )?; + + // Now present the current buffer + let _ = pixmap.present(&*connection, window.as_ref())?; + self.swapchain.submitted(next); + + // Flush the connection after presenting to the window to ensure we don't run out of buffer space in the X11 connection. + let _ = connection.flush(); + } + Ok(()) + } + + /// Resets the internal buffers, e.g. to reset age values + pub fn reset_buffers(&mut self) { + self.swapchain.reset_buffers(); + self.buffer = None; + } + + fn resize(&mut self, size: Size) { + self.swapchain.resize(size.w as u32, size.h as u32); + self.buffer = None; + + self.width = size.w; + self.height = size.h; + } +} diff --git a/src/utils/geometry.rs b/src/utils/geometry.rs index 0018de6..dc62786 100644 --- a/src/utils/geometry.rs +++ b/src/utils/geometry.rs @@ -886,16 +886,21 @@ impl Rectangle { } /// Clamp rectangle to min and max corners resulting in the overlapping area of two rectangles + /// + /// Returns `None` if the two rectangles don't overlap #[inline] - pub fn intersection(self, other: impl Into>) -> Self { + pub fn intersection(self, other: impl Into>) -> Option { let other = other.into(); - Rectangle::from_extemities( + if !self.overlaps(other) { + return None; + } + Some(Rectangle::from_extemities( (self.loc.x.max(other.loc.x), self.loc.y.max(other.loc.y)), ( (self.loc.x.saturating_add(self.size.w)).min(other.loc.x.saturating_add(other.size.w)), (self.loc.y.saturating_add(self.size.h)).min(other.loc.y.saturating_add(other.size.h)), ), - ) + )) } /// Compute the bounding box of a given set of points diff --git a/src/wayland/seat/mod.rs b/src/wayland/seat/mod.rs index b8279a6..7055106 100644 --- a/src/wayland/seat/mod.rs +++ b/src/wayland/seat/mod.rs @@ -41,7 +41,8 @@ mod pointer; pub use self::{ keyboard::{ - keysyms, Error as KeyboardError, FilterResult, KeyboardHandle, Keysym, ModifiersState, XkbConfig, + keysyms, Error as KeyboardError, FilterResult, KeyboardHandle, Keysym, KeysymHandle, ModifiersState, + XkbConfig, }, pointer::{ AxisFrame, CursorImageAttributes, CursorImageStatus, GrabStartData, PointerGrab, PointerHandle,