x11: Add XCB support to the X11 backend (#52)
This commit is contained in:
parent
d5bb2c1c78
commit
3eeafad834
4 changed files with 296 additions and 81 deletions
|
|
@ -15,18 +15,21 @@ exclude = ["examples"]
|
||||||
default = ["x11", "wayland", "wayland-dlopen"]
|
default = ["x11", "wayland", "wayland-dlopen"]
|
||||||
wayland = ["wayland-backend", "wayland-client", "nix"]
|
wayland = ["wayland-backend", "wayland-client", "nix"]
|
||||||
wayland-dlopen = ["wayland-sys/dlopen"]
|
wayland-dlopen = ["wayland-sys/dlopen"]
|
||||||
x11 = ["x11-dl"]
|
x11 = ["bytemuck", "x11rb", "x11-dl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
raw-window-handle = "0.5.0"
|
raw-window-handle = "0.5.0"
|
||||||
|
log = "0.4.17"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||||
nix = { version = "0.26.1", optional = true }
|
nix = { version = "0.26.1", optional = true }
|
||||||
wayland-backend = { version = "0.1.0-beta.14", features = ["client_system"], optional = true }
|
wayland-backend = { version = "0.1.0-beta.14", features = ["client_system"], optional = true }
|
||||||
wayland-client = { version = "0.30.0-beta.14", optional = true }
|
wayland-client = { version = "0.30.0-beta.14", optional = true }
|
||||||
wayland-sys = "0.30.0"
|
wayland-sys = "0.30.0"
|
||||||
|
bytemuck = { version = "1.12.3", optional = true }
|
||||||
x11-dl = { version = "2.19.1", optional = true }
|
x11-dl = { version = "2.19.1", optional = true }
|
||||||
|
x11rb = { version = "0.11.0", features = ["allow-unsafe-code", "dl-libxcb"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
|
|
|
||||||
136
examples/libxcb.rs
Normal file
136
examples/libxcb.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
//! Example of using `softbuffer` with `libxcb`.
|
||||||
|
|
||||||
|
#[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))]
|
||||||
|
mod example {
|
||||||
|
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
|
||||||
|
use softbuffer::GraphicsContext;
|
||||||
|
use x11rb::{
|
||||||
|
connection::Connection,
|
||||||
|
protocol::{
|
||||||
|
xproto::{self, ConnectionExt as _},
|
||||||
|
Event,
|
||||||
|
},
|
||||||
|
xcb_ffi::XCBConnection,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) fn run() {
|
||||||
|
// Create a new XCB connection
|
||||||
|
let (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server");
|
||||||
|
|
||||||
|
// x11rb doesn't use raw-window-handle yet, so just create our own.
|
||||||
|
let mut display_handle = XcbDisplayHandle::empty();
|
||||||
|
display_handle.connection = conn.get_raw_xcb_connection() as *mut _;
|
||||||
|
display_handle.screen = screen as _;
|
||||||
|
|
||||||
|
// Create a new window.
|
||||||
|
let mut width = 640u16;
|
||||||
|
let mut height = 480u16;
|
||||||
|
|
||||||
|
let window = conn.generate_id().unwrap();
|
||||||
|
let screen = &conn.setup().roots[screen];
|
||||||
|
let (root_visual, root_parent) = (screen.root_visual, screen.root);
|
||||||
|
conn.create_window(
|
||||||
|
x11rb::COPY_FROM_PARENT as _,
|
||||||
|
window,
|
||||||
|
root_parent,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
xproto::WindowClass::COPY_FROM_PARENT,
|
||||||
|
root_visual,
|
||||||
|
&xproto::CreateWindowAux::new()
|
||||||
|
.background_pixel(screen.white_pixel)
|
||||||
|
.event_mask(xproto::EventMask::EXPOSURE | xproto::EventMask::STRUCTURE_NOTIFY),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.check()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut window_handle = XcbWindowHandle::empty();
|
||||||
|
window_handle.window = window as _;
|
||||||
|
window_handle.visual_id = root_visual as _;
|
||||||
|
|
||||||
|
// Create a new softbuffer context.
|
||||||
|
// SAFETY: The display and window handles outlive the context.
|
||||||
|
let mut context = unsafe {
|
||||||
|
GraphicsContext::from_raw(
|
||||||
|
RawWindowHandle::Xcb(window_handle),
|
||||||
|
RawDisplayHandle::Xcb(display_handle),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Register an atom for closing the window.
|
||||||
|
let wm_protocols_atom = conn
|
||||||
|
.intern_atom(false, "WM_PROTOCOLS".as_bytes())
|
||||||
|
.unwrap()
|
||||||
|
.reply()
|
||||||
|
.unwrap()
|
||||||
|
.atom;
|
||||||
|
let delete_window_atom = conn
|
||||||
|
.intern_atom(false, "WM_DELETE_WINDOW".as_bytes())
|
||||||
|
.unwrap()
|
||||||
|
.reply()
|
||||||
|
.unwrap()
|
||||||
|
.atom;
|
||||||
|
conn.change_property(
|
||||||
|
xproto::PropMode::REPLACE as _,
|
||||||
|
window,
|
||||||
|
wm_protocols_atom,
|
||||||
|
xproto::AtomEnum::ATOM,
|
||||||
|
32,
|
||||||
|
1,
|
||||||
|
&delete_window_atom.to_ne_bytes(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.check()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Map the window to the screen.
|
||||||
|
conn.map_window(window).unwrap().check().unwrap();
|
||||||
|
|
||||||
|
// Pump events.
|
||||||
|
loop {
|
||||||
|
let event = conn.wait_for_event().unwrap();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Expose(_) => {
|
||||||
|
// Draw a width x height red rectangle.
|
||||||
|
let red = 255 << 16;
|
||||||
|
let source = std::iter::repeat(red)
|
||||||
|
.take((width as usize * height as usize) as _)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Draw the buffer.
|
||||||
|
context.set_buffer(&source, width, height);
|
||||||
|
}
|
||||||
|
Event::ConfigureNotify(configure_notify) => {
|
||||||
|
width = configure_notify.width;
|
||||||
|
height = configure_notify.height;
|
||||||
|
}
|
||||||
|
Event::ClientMessage(cm) => {
|
||||||
|
if cm.data.as_data32()[0] == delete_window_atom {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the context and drop the window.
|
||||||
|
drop(context);
|
||||||
|
conn.destroy_window(window).unwrap().check().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))]
|
||||||
|
fn main() {
|
||||||
|
example::run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(feature = "x11", any(target_os = "linux", target_os = "freebsd"))))]
|
||||||
|
fn main() {
|
||||||
|
eprintln!("This example requires the `x11` feature to be enabled on a supported platform.");
|
||||||
|
}
|
||||||
|
|
@ -110,7 +110,14 @@ impl GraphicsContext {
|
||||||
RawWindowHandle::Xlib(xlib_window_handle),
|
RawWindowHandle::Xlib(xlib_window_handle),
|
||||||
RawDisplayHandle::Xlib(xlib_display_handle),
|
RawDisplayHandle::Xlib(xlib_display_handle),
|
||||||
) => Dispatch::X11(unsafe {
|
) => Dispatch::X11(unsafe {
|
||||||
x11::X11Impl::new(xlib_window_handle, xlib_display_handle)?
|
x11::X11Impl::from_xlib(xlib_window_handle, xlib_display_handle)?
|
||||||
|
}),
|
||||||
|
#[cfg(all(feature = "x11", any(target_os = "linux", target_os = "freebsd")))]
|
||||||
|
(
|
||||||
|
RawWindowHandle::Xcb(xcb_window_handle),
|
||||||
|
RawDisplayHandle::Xcb(xcb_display_handle),
|
||||||
|
) => Dispatch::X11(unsafe {
|
||||||
|
x11::X11Impl::from_xcb(xcb_window_handle, xcb_display_handle)?
|
||||||
}),
|
}),
|
||||||
#[cfg(all(feature = "wayland", any(target_os = "linux", target_os = "freebsd")))]
|
#[cfg(all(feature = "wayland", any(target_os = "linux", target_os = "freebsd")))]
|
||||||
(
|
(
|
||||||
|
|
|
||||||
227
src/x11.rs
227
src/x11.rs
|
|
@ -5,29 +5,29 @@
|
||||||
//! addition, we may also want to blit to a pixmap instead of a window.
|
//! addition, we may also want to blit to a pixmap instead of a window.
|
||||||
|
|
||||||
use crate::SwBufError;
|
use crate::SwBufError;
|
||||||
use raw_window_handle::{XlibDisplayHandle, XlibWindowHandle};
|
use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle};
|
||||||
use std::os::raw::{c_char, c_uint};
|
use std::fmt;
|
||||||
use x11_dl::xlib::{Display, Visual, Xlib, ZPixmap, GC};
|
|
||||||
|
use x11_dl::xlib::Display;
|
||||||
|
use x11_dl::xlib_xcb::Xlib_xcb;
|
||||||
|
|
||||||
|
use x11rb::connection::Connection;
|
||||||
|
use x11rb::protocol::xproto::{self, ConnectionExt as _, Gcontext, Window};
|
||||||
|
use x11rb::xcb_ffi::XCBConnection;
|
||||||
|
|
||||||
/// The handle to an X11 drawing context.
|
/// The handle to an X11 drawing context.
|
||||||
pub struct X11Impl {
|
pub struct X11Impl {
|
||||||
/// The window handle.
|
/// The handle to the XCB connection.
|
||||||
window_handle: XlibWindowHandle,
|
connection: XCBConnection,
|
||||||
|
|
||||||
/// The display handle.
|
/// The window to draw to.
|
||||||
display_handle: XlibDisplayHandle,
|
window: Window,
|
||||||
|
|
||||||
/// Reference to the X11 shared library.
|
/// The graphics context to use when drawing.
|
||||||
lib: Xlib,
|
gc: Gcontext,
|
||||||
|
|
||||||
/// The graphics context for drawing.
|
|
||||||
gc: GC,
|
|
||||||
|
|
||||||
/// Information about the screen to use for drawing.
|
|
||||||
visual: *mut Visual,
|
|
||||||
|
|
||||||
/// The depth (bits per pixel) of the drawing context.
|
/// The depth (bits per pixel) of the drawing context.
|
||||||
depth: i32,
|
depth: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl X11Impl {
|
impl X11Impl {
|
||||||
|
|
@ -36,90 +36,159 @@ impl X11Impl {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid.
|
/// The `XlibWindowHandle` and `XlibDisplayHandle` must be valid.
|
||||||
pub unsafe fn new(
|
pub unsafe fn from_xlib(
|
||||||
window_handle: XlibWindowHandle,
|
window_handle: XlibWindowHandle,
|
||||||
display_handle: XlibDisplayHandle,
|
display_handle: XlibDisplayHandle,
|
||||||
) -> Result<Self, SwBufError> {
|
) -> Result<Self, SwBufError> {
|
||||||
// Try to open the X11 shared library.
|
// TODO: We should cache the shared libraries.
|
||||||
let lib = match Xlib::open() {
|
|
||||||
Ok(lib) => lib,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(SwBufError::PlatformError(
|
|
||||||
Some("Failed to open Xlib".into()),
|
|
||||||
Some(Box::new(e)),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate the handles to ensure that they aren't incomplete.
|
// Try to open the XlibXCB shared library.
|
||||||
|
let lib_xcb = Xlib_xcb::open().swbuf_err("Failed to open XlibXCB shared library")?;
|
||||||
|
|
||||||
|
// Validate the display handle to ensure we can use it.
|
||||||
if display_handle.display.is_null() {
|
if display_handle.display.is_null() {
|
||||||
return Err(SwBufError::IncompleteDisplayHandle);
|
return Err(SwBufError::IncompleteDisplayHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the underlying XCB connection.
|
||||||
|
// SAFETY: The user has asserted that the display handle is valid.
|
||||||
|
let connection =
|
||||||
|
unsafe { (lib_xcb.XGetXCBConnection)(display_handle.display as *mut Display) };
|
||||||
|
|
||||||
|
// Construct the equivalent XCB display and window handles.
|
||||||
|
let mut xcb_display_handle = XcbDisplayHandle::empty();
|
||||||
|
xcb_display_handle.connection = connection;
|
||||||
|
xcb_display_handle.screen = display_handle.screen;
|
||||||
|
|
||||||
|
let mut xcb_window_handle = XcbWindowHandle::empty();
|
||||||
|
xcb_window_handle.window = window_handle.window as _;
|
||||||
|
xcb_window_handle.visual_id = window_handle.visual_id as _;
|
||||||
|
|
||||||
|
// SAFETY: If the user passed in valid Xlib handles, then these are valid XCB handles.
|
||||||
|
unsafe { Self::from_xcb(xcb_window_handle, xcb_display_handle) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `X11Impl` from a `XcbWindowHandle` and `XcbDisplayHandle`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The `XcbWindowHandle` and `XcbDisplayHandle` must be valid.
|
||||||
|
pub(crate) unsafe fn from_xcb(
|
||||||
|
window_handle: XcbWindowHandle,
|
||||||
|
display_handle: XcbDisplayHandle,
|
||||||
|
) -> Result<Self, SwBufError> {
|
||||||
|
// Check that the handles are valid.
|
||||||
|
if display_handle.connection.is_null() {
|
||||||
|
return Err(SwBufError::IncompleteDisplayHandle);
|
||||||
|
}
|
||||||
|
|
||||||
if window_handle.window == 0 {
|
if window_handle.window == 0 {
|
||||||
return Err(SwBufError::IncompleteWindowHandle);
|
return Err(SwBufError::IncompleteWindowHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the screen number from the handle.
|
// Wrap the display handle in an x11rb connection.
|
||||||
// NOTE: By default, XlibDisplayHandle sets the screen number to 0. If we see a zero,
|
// SAFETY: We don't own the connection, so don't drop it. We also assert that the connection is valid.
|
||||||
// it could mean either screen index zero, or that the screen number was not set. We
|
let connection = {
|
||||||
// can't tell which, so we'll just assume that the screen number was not set.
|
let result =
|
||||||
let screen = match display_handle.screen {
|
unsafe { XCBConnection::from_raw_xcb_connection(display_handle.connection, false) };
|
||||||
0 => unsafe { (lib.XDefaultScreen)(display_handle.display as *mut Display) },
|
|
||||||
screen => screen,
|
result.swbuf_err("Failed to wrap XCB connection")?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use the default graphics context, visual and depth for this screen.
|
let window = window_handle.window;
|
||||||
let gc = unsafe { (lib.XDefaultGC)(display_handle.display as *mut Display, screen) };
|
|
||||||
let visual =
|
// Start getting the depth of the window.
|
||||||
unsafe { (lib.XDefaultVisual)(display_handle.display as *mut Display, screen) };
|
let geometry_token = connection
|
||||||
let depth = unsafe { (lib.XDefaultDepth)(display_handle.display as *mut Display, screen) };
|
.get_geometry(window)
|
||||||
|
.swbuf_err("Failed to send geometry request")?;
|
||||||
|
|
||||||
|
// Create a new graphics context to draw to.
|
||||||
|
let gc = connection
|
||||||
|
.generate_id()
|
||||||
|
.swbuf_err("Failed to generate GC ID")?;
|
||||||
|
connection
|
||||||
|
.create_gc(
|
||||||
|
gc,
|
||||||
|
window,
|
||||||
|
&xproto::CreateGCAux::new().graphics_exposures(0),
|
||||||
|
)
|
||||||
|
.swbuf_err("Failed to send GC creation request")?
|
||||||
|
.check()
|
||||||
|
.swbuf_err("Failed to create GC")?;
|
||||||
|
|
||||||
|
// Finish getting the depth of the window.
|
||||||
|
let geometry_reply = geometry_token
|
||||||
|
.reply()
|
||||||
|
.swbuf_err("Failed to get geometry reply")?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
window_handle,
|
connection,
|
||||||
display_handle,
|
window,
|
||||||
lib,
|
|
||||||
gc,
|
gc,
|
||||||
visual,
|
depth: geometry_reply.depth,
|
||||||
depth,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
|
pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
|
||||||
// Create the image from the buffer.
|
// Draw the image to the buffer.
|
||||||
let image = unsafe {
|
let result = self.connection.put_image(
|
||||||
(self.lib.XCreateImage)(
|
xproto::ImageFormat::Z_PIXMAP,
|
||||||
self.display_handle.display as *mut Display,
|
self.window,
|
||||||
self.visual,
|
self.gc,
|
||||||
self.depth as u32,
|
width,
|
||||||
ZPixmap,
|
height,
|
||||||
0,
|
0,
|
||||||
(buffer.as_ptr()) as *mut c_char,
|
0,
|
||||||
width as u32,
|
0,
|
||||||
height as u32,
|
self.depth,
|
||||||
32,
|
bytemuck::cast_slice(buffer),
|
||||||
(width * 4) as i32,
|
);
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw the image to the window.
|
match result {
|
||||||
unsafe {
|
Err(e) => log::error!("Failed to draw image to window: {}", e),
|
||||||
(self.lib.XPutImage)(
|
Ok(token) => token.ignore_error(),
|
||||||
self.display_handle.display as *mut Display,
|
}
|
||||||
self.window_handle.window,
|
|
||||||
self.gc,
|
|
||||||
image,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
width as c_uint,
|
|
||||||
height as c_uint,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delete the image data.
|
|
||||||
unsafe { (*image).data = std::ptr::null_mut() };
|
|
||||||
unsafe { (self.lib.XDestroyImage)(image) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for X11Impl {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Close the graphics context that we created.
|
||||||
|
if let Ok(token) = self.connection.free_gc(self.gc) {
|
||||||
|
token.ignore_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenient wrapper to cast errors into SwBufError.
|
||||||
|
trait ResultExt<T, E> {
|
||||||
|
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SwBufError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: fmt::Debug + fmt::Display + 'static> ResultExt<T, E> for Result<T, E> {
|
||||||
|
fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SwBufError> {
|
||||||
|
self.map_err(|e| {
|
||||||
|
SwBufError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e))))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper around a library error.
|
||||||
|
///
|
||||||
|
/// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast
|
||||||
|
/// to this type.
|
||||||
|
struct LibraryError<E>(E);
|
||||||
|
|
||||||
|
impl<E: fmt::Debug> fmt::Debug for LibraryError<E> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: fmt::Display> fmt::Display for LibraryError<E> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue