Skip to content

Commit

Permalink
Implement shared memory transfer for X11
Browse files Browse the repository at this point in the history
  • Loading branch information
notgull committed Dec 23, 2022
1 parent cf0b435 commit a2617ed
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 18 deletions.
11 changes: 0 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ on:
branches: [main]

jobs:
Check_Formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
with:
rust-version: stable
components: rustfmt
- name: Check Formatting
run: cargo +stable fmt --all -- --check

tests:
name: Tests
strategy:
Expand Down
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());
}
}
}

0 comments on commit a2617ed

Please sign in to comment.