Skip to content
This repository has been archived by the owner on Dec 23, 2022. It is now read-only.

x11: Implement image transfer using the MIT-SHM extension #23

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclude = ["examples"]
default = ["x11", "wayland", "wayland-dlopen"]
wayland = ["wayland-backend", "wayland-client", "nix"]
wayland-dlopen = ["wayland-sys/dlopen"]
x11 = ["x11-dl"]
x11 = ["nix", "x11-dl"]

[dependencies]
thiserror = "1.0.30"
Expand Down
157 changes: 151 additions & 6 deletions src/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
//! addition, we may also want to blit to a pixmap instead of a window.

use crate::SwBufError;
use nix::libc::{shmat, shmctl, shmdt, shmget, IPC_PRIVATE, IPC_RMID};
use raw_window_handle::{XlibDisplayHandle, XlibWindowHandle};

use std::io;
use std::mem;
use std::os::raw::{c_char, c_uint};
use std::ptr::{copy_nonoverlapping, null_mut, NonNull};

use x11_dl::xlib::{Display, Visual, Xlib, ZPixmap, GC};
use x11_dl::xshm::{XShmSegmentInfo, Xext as XShm};

/// The handle to an X11 drawing context.
pub struct X11Impl {
Expand All @@ -18,7 +25,10 @@ pub struct X11Impl {
display_handle: XlibDisplayHandle,

/// Reference to the X11 shared library.
lib: Xlib,
xlib: Xlib,

/// Reference to the X11 shared memory library.
xshm: Option<ShmExtension>,

/// The graphics context for drawing.
gc: GC,
Expand All @@ -30,6 +40,27 @@ pub struct X11Impl {
depth: i32,
}

/// SHM-specific information.
struct ShmExtension {
/// The shared memory library.
xshm: XShm,

/// Pointer to the shared memory segment, as well as its current size.
shmseg: Option<ShmSegment>,
}

/// An SHM segment.
struct ShmSegment {
/// The shared memory segment ID.
id: i32,

/// The shared memory segment pointer.
ptr: NonNull<i8>,

/// The size of the shared memory segment.
size: usize,
}

impl X11Impl {
/// Create a new `X11Impl` from a `XlibWindowHandle` and `XlibDisplayHandle`.
///
Expand Down Expand Up @@ -74,19 +105,96 @@ impl X11Impl {
let visual = (lib.XDefaultVisual)(display_handle.display as *mut Display, screen);
let depth = (lib.XDefaultDepth)(display_handle.display as *mut Display, screen);

// See if we can load the XShm extension.
let xshm = XShm::open()
.ok()
.filter(|shm| (shm.XShmQueryExtension)(display_handle.display as *mut Display) == 0);

Ok(Self {
window_handle,
display_handle,
lib,
xlib: lib,
xshm: xshm.map(|xshm| ShmExtension { xshm, shmseg: None }),
gc,
visual,
depth,
})
}

pub(crate) unsafe fn set_buffer(&mut self, buffer: &[u32], width: u16, height: u16) {
if let Err(e) = self.shm_set(buffer, width, height) {
eprintln!("XShm not available, falling back to XImage: {e}");
self.fallback_set(buffer, width, height);
}
}

/// Set the buffer to the given image using shared memory.
unsafe fn shm_set(&mut self, buffer: &[u32], width: u16, height: u16) -> io::Result<()> {
let shm_ext = match self.xshm.as_mut() {
Some(shm_ext) => shm_ext,
None => return Err(io::Error::new(io::ErrorKind::Other, "XShm not available")),
};

// Get the size of the shared memory segment.
let shmseg_size = (width as usize)
.checked_mul(height as usize)
.and_then(|size| size.checked_mul(4))
.expect("Buffer size overflow");

// Create the shared memory segment if it doesn't exist, or if it's the wrong size.
let shmseg = match &mut shm_ext.shmseg {
None => shm_ext.shmseg.insert(ShmSegment::new(shmseg_size)?),
Some(ref shmseg) if shmseg.size < shmseg_size => {
shm_ext.shmseg.insert(ShmSegment::new(shmseg_size)?)
}
Some(shmseg) => shmseg,
};

// Create the basic image.
let mut seg: XShmSegmentInfo = mem::zeroed();
let image = (shm_ext.xshm.XShmCreateImage)(
self.display_handle.display as *mut Display,
self.visual,
self.depth as u32,
ZPixmap,
shmseg.ptr.as_ptr(),
&mut seg,
width as u32,
height as u32,
);

// Populate the SHM segment with our buffer.
copy_nonoverlapping(buffer.as_ptr() as *mut i8, shmseg.ptr.as_ptr(), shmseg_size);
seg.shmid = shmseg.id;
seg.shmaddr = shmseg.ptr.as_ptr();
(shm_ext.xshm.XShmAttach)(self.display_handle.display as *mut Display, &mut seg);

// Put the image on the window.
(shm_ext.xshm.XShmPutImage)(
self.display_handle.display as *mut Display,
self.window_handle.window,
self.gc,
image,
0,
0,
0,
0,
width as u32,
height as u32,
0,
);

// Detach the segment and destroy the image.
(shm_ext.xshm.XShmDetach)(self.display_handle.display as *mut Display, &mut seg);
(self.xlib.XDestroyImage)(image);

Ok(())
}

/// Fall back to using `XPutImage` to draw the buffer.
unsafe fn fallback_set(&mut self, buffer: &[u32], width: u16, height: u16) {
// Create the image from the buffer.
let image = (self.lib.XCreateImage)(
let image = (self.xlib.XCreateImage)(
self.display_handle.display as *mut Display,
self.visual,
self.depth as u32,
Expand All @@ -100,7 +208,7 @@ impl X11Impl {
);

// Draw the image to the window.
(self.lib.XPutImage)(
(self.xlib.XPutImage)(
self.display_handle.display as *mut Display,
self.window_handle.window,
self.gc,
Expand All @@ -114,7 +222,44 @@ impl X11Impl {
);

// Delete the image data.
(*image).data = std::ptr::null_mut();
(self.lib.XDestroyImage)(image);
(*image).data = null_mut();
(self.xlib.XDestroyImage)(image);
}
}

impl ShmSegment {
/// Create a new `ShmSegment` with the given size.
fn new(size: usize) -> io::Result<Self> {
unsafe {
// Create the shared memory segment.
let id = shmget(IPC_PRIVATE, size, 0o600);
if id == -1 {
return Err(io::Error::last_os_error());
}

// Get the pointer it maps to.
let ptr = shmat(id, null_mut(), 0);
let ptr = match NonNull::new(ptr as *mut i8) {
Some(ptr) => ptr,
None => {
shmctl(id, IPC_RMID, null_mut());
return Err(io::Error::last_os_error());
}
};

Ok(Self { id, ptr, size })
}
}
}

impl Drop for ShmSegment {
fn drop(&mut self) {
unsafe {
// Detach the shared memory segment.
shmdt(self.ptr.as_ptr() as _);

// Delete the shared memory segment.
shmctl(self.id, IPC_RMID, null_mut());
}
}
}