#![allow(clippy::uninlined_format_args)]
use crate::backend_interface::*;
use crate::error::{InitError, SwResultExt};
use crate::{util, Rect, SoftBufferError};
use raw_window_handle::{
HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle,
XcbWindowHandle,
};
use rustix::{
fd::{AsFd, BorrowedFd, OwnedFd},
mm, shm as posix_shm,
};
use std::{
collections::HashSet,
fmt,
fs::File,
io, mem,
num::{NonZeroU16, NonZeroU32},
ptr::{null_mut, NonNull},
slice,
sync::Arc,
};
use as_raw_xcb_connection::AsRawXcbConnection;
use x11rb::connection::{Connection, SequenceNumber};
use x11rb::cookie::Cookie;
use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};
use x11rb::protocol::shm::{self, ConnectionExt as _};
use x11rb::protocol::xproto::{self, ConnectionExt as _, ImageOrder, VisualClass, Visualid};
use x11rb::xcb_ffi::XCBConnection;
#[derive(Debug)]
pub struct X11DisplayImpl<D: ?Sized> {
connection: Option<XCBConnection>,
is_shm_available: bool,
supported_visuals: HashSet<Visualid>,
_display: D,
}
impl<D: HasDisplayHandle + ?Sized> ContextInterface<D> for Arc<X11DisplayImpl<D>> {
fn new(display: D) -> Result<Self, InitError<D>>
where
D: Sized,
{
let raw = display.display_handle()?.as_raw();
let xcb_handle = match raw {
RawDisplayHandle::Xcb(xcb_handle) => xcb_handle,
RawDisplayHandle::Xlib(xlib) => {
let connection = xlib.display.map(|display| {
unsafe {
let display = tiny_xlib::Display::from_ptr(display.as_ptr());
NonNull::new_unchecked(display.as_raw_xcb_connection()).cast()
}
});
XcbDisplayHandle::new(connection, xlib.screen)
}
_ => return Err(InitError::Unsupported(display)),
};
let connection = match xcb_handle.connection {
Some(connection) => {
let result =
unsafe { XCBConnection::from_raw_xcb_connection(connection.as_ptr(), false) };
result.swbuf_err("Failed to wrap XCB connection")?
}
None => {
tracing::info!("no XCB connection provided by the user, so spawning our own");
XCBConnection::connect(None)
.swbuf_err("Failed to spawn XCB connection")?
.0
}
};
let is_shm_available = is_shm_available(&connection);
if !is_shm_available {
tracing::warn!("SHM extension is not available. Performance may be poor.");
}
let supported_visuals = supported_visuals(&connection);
Ok(Arc::new(X11DisplayImpl {
connection: Some(connection),
is_shm_available,
supported_visuals,
_display: display,
}))
}
}
impl<D: ?Sized> X11DisplayImpl<D> {
fn connection(&self) -> &XCBConnection {
self.connection
.as_ref()
.expect("X11DisplayImpl::connection() called after X11DisplayImpl::drop()")
}
}
#[derive(Debug)]
pub struct X11Impl<D: ?Sized, W: ?Sized> {
display: Arc<X11DisplayImpl<D>>,
window: xproto::Window,
gc: xproto::Gcontext,
depth: u8,
visual_id: u32,
buffer: Buffer,
buffer_presented: bool,
size: Option<(NonZeroU16, NonZeroU16)>,
window_handle: W,
}
#[derive(Debug)]
enum Buffer {
Shm(ShmBuffer),
Wire(util::PixelBuffer),
}
#[derive(Debug)]
struct ShmBuffer {
seg: Option<(ShmSegment, shm::Seg)>,
done_processing: Option<SequenceNumber>,
}
impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> SurfaceInterface<D, W> for X11Impl<D, W> {
type Context = Arc<X11DisplayImpl<D>>;
type Buffer<'a>
= BufferImpl<'a, D, W>
where
Self: 'a;
fn new(window_src: W, display: &Arc<X11DisplayImpl<D>>) -> Result<Self, InitError<W>> {
let raw = window_src.window_handle()?.as_raw();
let window_handle = match raw {
RawWindowHandle::Xcb(xcb) => xcb,
RawWindowHandle::Xlib(xlib) => {
let window = NonZeroU32::new(xlib.window as u32)
.ok_or(SoftBufferError::IncompleteWindowHandle)?;
let mut xcb_window_handle = XcbWindowHandle::new(window);
xcb_window_handle.visual_id = NonZeroU32::new(xlib.visual_id as u32);
xcb_window_handle
}
_ => {
return Err(InitError::Unsupported(window_src));
}
};
tracing::trace!("new: window_handle={:X}", window_handle.window);
let window = window_handle.window.get();
let display2 = display.clone();
let tokens = {
let geometry_token = display2
.connection()
.get_geometry(window)
.swbuf_err("Failed to send geometry request")?;
let window_attrs_token = if window_handle.visual_id.is_none() {
Some(
display2
.connection()
.get_window_attributes(window)
.swbuf_err("Failed to send window attributes request")?,
)
} else {
None
};
(geometry_token, window_attrs_token)
};
let gc = display
.connection()
.generate_id()
.swbuf_err("Failed to generate GC ID")?;
display
.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")?;
let (geometry_reply, visual_id) = {
let (geometry_token, window_attrs_token) = tokens;
let geometry_reply = geometry_token
.reply()
.swbuf_err("Failed to get geometry reply")?;
let visual_id = match window_attrs_token {
None => window_handle.visual_id.unwrap().get(),
Some(window_attrs) => {
window_attrs
.reply()
.swbuf_err("Failed to get window attributes reply")?
.visual
}
};
(geometry_reply, visual_id)
};
if !display.supported_visuals.contains(&visual_id) {
return Err(SoftBufferError::PlatformError(
Some(format!(
"Visual 0x{visual_id:x} does not use softbuffer's pixel format and is unsupported"
)),
None,
)
.into());
}
let buffer = if display.is_shm_available {
Buffer::Shm(ShmBuffer {
seg: None,
done_processing: None,
})
} else {
Buffer::Wire(util::PixelBuffer(Vec::new()))
};
Ok(Self {
display: display.clone(),
window,
gc,
depth: geometry_reply.depth,
visual_id,
buffer,
buffer_presented: false,
size: None,
window_handle: window_src,
})
}
#[inline]
fn window(&self) -> &W {
&self.window_handle
}
fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
tracing::trace!(
"resize: window={:X}, size={}x{}",
self.window,
width,
height
);
let width: NonZeroU16 = width
.try_into()
.or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
let height: NonZeroU16 = height.try_into().or(Err(SoftBufferError::SizeOutOfRange {
width: width.into(),
height,
}))?;
if self.size != Some((width, height)) {
self.buffer_presented = false;
self.buffer
.resize(self.display.connection(), width.get(), height.get())
.swbuf_err("Failed to resize X11 buffer")?;
self.size = Some((width, height));
}
Ok(())
}
fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {
tracing::trace!("buffer_mut: window={:X}", self.window);
self.buffer.finish_wait(self.display.connection())?;
Ok(BufferImpl(self))
}
fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
tracing::trace!("fetch: window={:X}", self.window);
let (width, height) = self
.size
.expect("Must set size of surface before calling `fetch()`");
let reply = self
.display
.connection()
.get_image(
xproto::ImageFormat::Z_PIXMAP,
self.window,
0,
0,
width.get(),
height.get(),
u32::MAX,
)
.swbuf_err("Failed to send image fetching request")?
.reply()
.swbuf_err("Failed to fetch image from window")?;
if reply.depth == self.depth && reply.visual == self.visual_id {
let mut out = vec![0u32; reply.data.len() / 4];
bytemuck::cast_slice_mut::<u32, u8>(&mut out).copy_from_slice(&reply.data);
Ok(out)
} else {
Err(SoftBufferError::PlatformError(
Some("Mismatch between reply and window data".into()),
None,
))
}
}
}
#[derive(Debug)]
pub struct BufferImpl<'a, D: ?Sized, W: ?Sized>(&'a mut X11Impl<D, W>);
impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle + ?Sized> BufferInterface
for BufferImpl<'_, D, W>
{
fn width(&self) -> NonZeroU32 {
self.0.size.unwrap().0.into()
}
fn height(&self) -> NonZeroU32 {
self.0.size.unwrap().1.into()
}
#[inline]
fn pixels(&self) -> &[u32] {
unsafe { self.0.buffer.buffer() }
}
#[inline]
fn pixels_mut(&mut self) -> &mut [u32] {
unsafe { self.0.buffer.buffer_mut() }
}
fn age(&self) -> u8 {
if self.0.buffer_presented {
1
} else {
0
}
}
fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
let imp = self.0;
let (surface_width, surface_height) = imp
.size
.expect("Must set size of surface before calling `present_with_damage()`");
tracing::trace!("present: window={:X}", imp.window);
match imp.buffer {
Buffer::Wire(ref wire) => {
tracing::debug!("Falling back to non-SHM method for window drawing.");
imp.display
.connection()
.put_image(
xproto::ImageFormat::Z_PIXMAP,
imp.window,
imp.gc,
surface_width.get(),
surface_height.get(),
0,
0,
0,
imp.depth,
bytemuck::cast_slice(wire),
)
.map(|c| c.ignore_error())
.push_err()
.swbuf_err("Failed to draw image to window")?;
}
Buffer::Shm(ref mut shm) => {
if let Some((_, segment_id)) = shm.seg {
damage
.iter()
.try_for_each(|rect| {
let (src_x, src_y, dst_x, dst_y, width, height) = (|| {
Some((
u16::try_from(rect.x).ok()?,
u16::try_from(rect.y).ok()?,
i16::try_from(rect.x).ok()?,
i16::try_from(rect.y).ok()?,
u16::try_from(rect.width.get()).ok()?,
u16::try_from(rect.height.get()).ok()?,
))
})(
)
.ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;
imp.display
.connection()
.shm_put_image(
imp.window,
imp.gc,
surface_width.get(),
surface_height.get(),
src_x,
src_y,
width,
height,
dst_x,
dst_y,
imp.depth,
xproto::ImageFormat::Z_PIXMAP.into(),
false,
segment_id,
0,
)
.push_err()
.map(|c| c.ignore_error())
.swbuf_err("Failed to draw image to window")
})
.and_then(|()| {
shm.begin_wait(imp.display.connection())
.swbuf_err("Failed to draw image to window")
})?;
}
}
}
imp.buffer_presented = true;
Ok(())
}
fn present(self) -> Result<(), SoftBufferError> {
let (width, height) = self
.0
.size
.expect("Must set size of surface before calling `present()`");
self.present_with_damage(&[Rect {
x: 0,
y: 0,
width: width.into(),
height: height.into(),
}])
}
}
impl Buffer {
fn resize(
&mut self,
conn: &impl Connection,
width: u16,
height: u16,
) -> Result<(), PushBufferError> {
match self {
Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),
Buffer::Wire(wire) => {
wire.resize(total_len(width, height) / 4, 0);
Ok(())
}
}
}
fn finish_wait(&mut self, conn: &impl Connection) -> Result<(), SoftBufferError> {
if let Buffer::Shm(ref mut shm) = self {
shm.finish_wait(conn)
.swbuf_err("Failed to wait for X11 buffer")?;
}
Ok(())
}
#[inline]
unsafe fn buffer(&self) -> &[u32] {
match self {
Buffer::Shm(ref shm) => unsafe { shm.as_ref() },
Buffer::Wire(wire) => wire,
}
}
#[inline]
unsafe fn buffer_mut(&mut self) -> &mut [u32] {
match self {
Buffer::Shm(ref mut shm) => unsafe { shm.as_mut() },
Buffer::Wire(wire) => wire,
}
}
}
impl ShmBuffer {
fn alloc_segment(
&mut self,
conn: &impl Connection,
buffer_size: usize,
) -> Result<(), PushBufferError> {
let size = buffer_size.next_power_of_two();
let needs_realloc = match self.seg {
Some((ref seg, _)) => seg.size() < size,
None => true,
};
if needs_realloc {
let new_seg = ShmSegment::new(size, buffer_size)?;
self.associate(conn, new_seg)?;
} else if let Some((ref mut seg, _)) = self.seg {
seg.set_buffer_size(buffer_size);
}
Ok(())
}
#[inline]
unsafe fn as_ref(&self) -> &[u32] {
match self.seg.as_ref() {
Some((seg, _)) => {
let buffer_size = seg.buffer_size();
bytemuck::cast_slice(unsafe { &seg.as_ref()[..buffer_size] })
}
None => {
&[]
}
}
}
#[inline]
unsafe fn as_mut(&mut self) -> &mut [u32] {
match self.seg.as_mut() {
Some((seg, _)) => {
let buffer_size = seg.buffer_size();
bytemuck::cast_slice_mut(unsafe { &mut seg.as_mut()[..buffer_size] })
}
None => {
&mut []
}
}
}
fn associate(
&mut self,
conn: &impl Connection,
seg: ShmSegment,
) -> Result<(), PushBufferError> {
let new_id = conn.generate_id()?;
conn.shm_attach_fd(new_id, seg.as_fd().try_clone_to_owned().unwrap(), true)?
.ignore_error();
if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {
self.finish_wait(conn)?;
conn.shm_detach(old_id)?.ignore_error();
drop(old_seg);
}
Ok(())
}
fn begin_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {
let cookie = c.get_input_focus()?.sequence_number();
let old_cookie = self.done_processing.replace(cookie);
debug_assert!(old_cookie.is_none());
Ok(())
}
fn finish_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {
if let Some(done_processing) = self.done_processing.take() {
let cookie = Cookie::<_, xproto::GetInputFocusReply>::new(c, done_processing);
cookie.reply()?;
}
Ok(())
}
}
#[derive(Debug)]
struct ShmSegment {
id: File,
ptr: NonNull<i8>,
size: usize,
buffer_size: usize,
}
unsafe impl Send for ShmSegment {}
impl ShmSegment {
fn new(size: usize, buffer_size: usize) -> io::Result<Self> {
assert!(size >= buffer_size);
let id = File::from(create_shm_id()?);
id.set_len(size as u64)?;
let ptr = NonNull::new(unsafe {
mm::mmap(
null_mut(),
size,
mm::ProtFlags::READ | mm::ProtFlags::WRITE,
mm::MapFlags::SHARED,
&id,
0,
)?
})
.ok_or(io::Error::new(
io::ErrorKind::Other,
"unexpected null when mapping SHM segment",
))?
.cast();
Ok(Self {
id,
ptr,
size,
buffer_size,
})
}
unsafe fn as_ref(&self) -> &[i8] {
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
}
unsafe fn as_mut(&mut self) -> &mut [i8] {
unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
}
fn set_buffer_size(&mut self, buffer_size: usize) {
assert!(self.size >= buffer_size);
self.buffer_size = buffer_size
}
fn buffer_size(&self) -> usize {
self.buffer_size
}
fn size(&self) -> usize {
self.size
}
}
impl AsFd for ShmSegment {
fn as_fd(&self) -> BorrowedFd<'_> {
self.id.as_fd()
}
}
impl Drop for ShmSegment {
fn drop(&mut self) {
unsafe {
mm::munmap(self.ptr.as_ptr().cast(), self.size).ok();
}
}
}
impl<D: ?Sized> Drop for X11DisplayImpl<D> {
fn drop(&mut self) {
self.connection = None;
}
}
impl<D: ?Sized, W: ?Sized> Drop for X11Impl<D, W> {
fn drop(&mut self) {
if let Buffer::Shm(mut shm) = mem::replace(
&mut self.buffer,
Buffer::Wire(util::PixelBuffer(Vec::new())),
) {
shm.finish_wait(self.display.connection()).ok();
if let Some((segment, seg_id)) = shm.seg.take() {
if let Ok(token) = self.display.connection().shm_detach(seg_id) {
token.ignore_error();
}
drop(segment);
}
}
if let Ok(token) = self.display.connection().free_gc(self.gc) {
token.ignore_error();
}
}
}
fn create_shm_id() -> io::Result<OwnedFd> {
use posix_shm::{Mode, OFlags};
let mut rng = fastrand::Rng::new();
let mut name = String::with_capacity(23);
for i in 0..4 {
name.clear();
name.push_str("softbuffer-x11-");
name.extend(std::iter::repeat_with(|| rng.alphanumeric()).take(7));
match posix_shm::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RWXU,
) {
Ok(id) => {
posix_shm::unlink(&name).ok();
return Ok(id);
}
Err(rustix::io::Errno::EXIST) => {
tracing::warn!("x11: SHM ID collision at {} on try number {}", name, i);
}
Err(e) => return Err(e.into()),
};
}
Err(io::Error::new(
io::ErrorKind::Other,
"failed to generate a non-existent SHM name",
))
}
fn is_shm_available(c: &impl Connection) -> bool {
let seg = match ShmSegment::new(0x1000, 0x1000) {
Ok(seg) => seg,
Err(_) => return false,
};
let seg_id = match c.generate_id() {
Ok(id) => id,
Err(_) => return false,
};
let (attach, detach) = {
let attach = c.shm_attach_fd(seg_id, seg.as_fd().try_clone_to_owned().unwrap(), false);
let detach = c.shm_detach(seg_id);
match (attach, detach) {
(Ok(attach), Ok(detach)) => (attach, detach),
_ => return false,
}
};
matches!((attach.check(), detach.check()), (Ok(()), Ok(())))
}
fn supported_visuals(c: &impl Connection) -> HashSet<Visualid> {
if !c
.setup()
.pixmap_formats
.iter()
.any(|f| (f.depth == 24 || f.depth == 32) && f.bits_per_pixel == 32)
{
tracing::warn!("X11 server does not have a depth 24/32 format with 32 bits per pixel");
return HashSet::new();
}
#[cfg(target_endian = "little")]
let own_byte_order = ImageOrder::LSB_FIRST;
#[cfg(target_endian = "big")]
let own_byte_order = ImageOrder::MSB_FIRST;
let expected_masks = if c.setup().image_byte_order == own_byte_order {
(0xff0000, 0xff00, 0xff)
} else {
(0xff00, 0xff0000, 0xff000000)
};
c.setup()
.roots
.iter()
.flat_map(|screen| {
screen
.allowed_depths
.iter()
.filter(|depth| depth.depth == 24 || depth.depth == 32)
.flat_map(|depth| {
depth
.visuals
.iter()
.filter(|visual| {
visual.class == VisualClass::TRUE_COLOR
|| visual.class == VisualClass::DIRECT_COLOR
})
.filter(|visual| {
expected_masks == (visual.red_mask, visual.green_mask, visual.blue_mask)
})
.map(|visual| visual.visual_id)
})
})
.collect()
}
#[derive(Debug)]
enum PushBufferError {
X11(ReplyError),
XidExhausted,
System(io::Error),
}
impl fmt::Display for PushBufferError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::X11(e) => write!(f, "X11 error: {}", e),
Self::XidExhausted => write!(f, "XID space exhausted"),
Self::System(e) => write!(f, "System error: {}", e),
}
}
}
impl std::error::Error for PushBufferError {}
impl From<ConnectionError> for PushBufferError {
fn from(e: ConnectionError) -> Self {
Self::X11(ReplyError::ConnectionError(e))
}
}
impl From<ReplyError> for PushBufferError {
fn from(e: ReplyError) -> Self {
Self::X11(e)
}
}
impl From<ReplyOrIdError> for PushBufferError {
fn from(e: ReplyOrIdError) -> Self {
match e {
ReplyOrIdError::ConnectionError(e) => Self::X11(ReplyError::ConnectionError(e)),
ReplyOrIdError::X11Error(e) => Self::X11(ReplyError::X11Error(e)),
ReplyOrIdError::IdsExhausted => Self::XidExhausted,
}
}
}
impl From<io::Error> for PushBufferError {
fn from(e: io::Error) -> Self {
Self::System(e)
}
}
trait PushResultExt<T, E> {
fn push_err(self) -> Result<T, PushBufferError>;
}
impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {
fn push_err(self) -> Result<T, PushBufferError> {
self.map_err(Into::into)
}
}
#[inline(always)]
fn total_len(width: u16, height: u16) -> usize {
let width: usize = width.into();
let height: usize = height.into();
width
.checked_mul(height)
.and_then(|len| len.checked_mul(4))
.unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))
}