1//! Implementation of software buffering for X11.
2//!
3//! This module converts the input buffer into an XImage and then sends it over the wire to be
4//! drawn by the X server. The SHM extension is used if available.
5
6#![allow(clippy::uninlined_format_args)]
7
8use crate::backend_interface::*;
9use crate::error::{InitError, SwResultExt};
10use crate::{Rect, SoftBufferError};
11use raw_window_handle::{
12 HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle,
13 XcbWindowHandle,
14};
15use rustix::{
16 fd::{AsFd, BorrowedFd, OwnedFd},
17 mm, shm as posix_shm,
18};
19
20use std::{
21 collections::HashSet,
22 fmt,
23 fs::File,
24 io, mem,
25 num::{NonZeroU16, NonZeroU32},
26 ptr::{null_mut, NonNull},
27 slice,
28 sync::Arc,
29};
30
31use as_raw_xcb_connection::AsRawXcbConnection;
32use x11rb::connection::{Connection, SequenceNumber};
33use x11rb::cookie::Cookie;
34use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};
35use x11rb::protocol::shm::{self, ConnectionExt as _};
36use x11rb::protocol::xproto::{self, ConnectionExt as _, ImageOrder, VisualClass, Visualid};
37use x11rb::xcb_ffi::XCBConnection;
38
39pub struct X11DisplayImpl<D: ?Sized> {
40 /// The handle to the XCB connection.
41 connection: Option<XCBConnection>,
42
43 /// SHM extension is available.
44 is_shm_available: bool,
45
46 /// All visuals using softbuffer's pixel representation
47 supported_visuals: HashSet<Visualid>,
48
49 /// The generic display where the `connection` field comes from.
50 ///
51 /// Without `&mut`, the underlying connection cannot be closed without other unsafe behavior.
52 /// With `&mut`, the connection can be dropped without us knowing about it. Therefore, we
53 /// cannot provide `&mut` access to this field.
54 _display: D,
55}
56
57impl<D: HasDisplayHandle + ?Sized> ContextInterface<D> for Arc<X11DisplayImpl<D>> {
58 /// Create a new `X11DisplayImpl`.
59 fn new(display: D) -> Result<Self, InitError<D>>
60 where
61 D: Sized,
62 {
63 // Get the underlying libxcb handle.
64 let raw = display.display_handle()?.as_raw();
65 let xcb_handle = match raw {
66 RawDisplayHandle::Xcb(xcb_handle) => xcb_handle,
67 RawDisplayHandle::Xlib(xlib) => {
68 // Convert to an XCB handle.
69 let connection = xlib.display.map(|display| {
70 // Get the underlying XCB connection.
71 // SAFETY: The user has asserted that the display handle is valid.
72 unsafe {
73 let display = tiny_xlib::Display::from_ptr(display.as_ptr());
74 NonNull::new_unchecked(display.as_raw_xcb_connection()).cast()
75 }
76 });
77
78 // Construct the equivalent XCB display and window handles.
79 XcbDisplayHandle::new(connection, xlib.screen)
80 }
81 _ => return Err(InitError::Unsupported(display)),
82 };
83
84 // Validate the display handle to ensure we can use it.
85 let connection = match xcb_handle.connection {
86 Some(connection) => {
87 // Wrap the display handle in an x11rb connection.
88 // SAFETY: We don't own the connection, so don't drop it. We also assert that the connection is valid.
89 let result =
90 unsafe { XCBConnection::from_raw_xcb_connection(connection.as_ptr(), false) };
91
92 result.swbuf_err("Failed to wrap XCB connection")?
93 }
94 None => {
95 // The user didn't provide an XCB connection, so create our own.
96 log::info!("no XCB connection provided by the user, so spawning our own");
97 XCBConnection::connect(None)
98 .swbuf_err("Failed to spawn XCB connection")?
99 .0
100 }
101 };
102
103 let is_shm_available = is_shm_available(&connection);
104 if !is_shm_available {
105 log::warn!("SHM extension is not available. Performance may be poor.");
106 }
107
108 let supported_visuals = supported_visuals(&connection);
109
110 Ok(Arc::new(X11DisplayImpl {
111 connection: Some(connection),
112 is_shm_available,
113 supported_visuals,
114 _display: display,
115 }))
116 }
117}
118
119impl<D: ?Sized> X11DisplayImpl<D> {
120 fn connection(&self) -> &XCBConnection {
121 self.connection
122 .as_ref()
123 .expect(msg:"X11DisplayImpl::connection() called after X11DisplayImpl::drop()")
124 }
125}
126
127/// The handle to an X11 drawing context.
128pub struct X11Impl<D: ?Sized, W: ?Sized> {
129 /// X display this window belongs to.
130 display: Arc<X11DisplayImpl<D>>,
131
132 /// The window to draw to.
133 window: xproto::Window,
134
135 /// The graphics context to use when drawing.
136 gc: xproto::Gcontext,
137
138 /// The depth (bits per pixel) of the drawing context.
139 depth: u8,
140
141 /// The visual ID of the drawing context.
142 visual_id: u32,
143
144 /// The buffer we draw to.
145 buffer: Buffer,
146
147 /// Buffer has been presented.
148 buffer_presented: bool,
149
150 /// The current buffer width/height.
151 size: Option<(NonZeroU16, NonZeroU16)>,
152
153 /// Keep the window alive.
154 window_handle: W,
155}
156
157/// The buffer that is being drawn to.
158enum Buffer {
159 /// A buffer implemented using shared memory to prevent unnecessary copying.
160 Shm(ShmBuffer),
161
162 /// A normal buffer that we send over the wire.
163 Wire(Vec<u32>),
164}
165
166struct ShmBuffer {
167 /// The shared memory segment, paired with its ID.
168 seg: Option<(ShmSegment, shm::Seg)>,
169
170 /// A cookie indicating that the shared memory segment is ready to be used.
171 ///
172 /// We can't soundly read from or write to the SHM segment until the X server is done processing the
173 /// `shm::PutImage` request. However, the X server handles requests in order, which means that, if
174 /// we send a very small request after the `shm::PutImage` request, then the X server will have to
175 /// process that request before it can process the `shm::PutImage` request. Therefore, we can use
176 /// the reply to that small request to determine when the `shm::PutImage` request is done.
177 ///
178 /// In this case, we use `GetInputFocus` since it is a very small request.
179 ///
180 /// We store the sequence number instead of the `Cookie` since we cannot hold a self-referential
181 /// reference to the `connection` field.
182 done_processing: Option<SequenceNumber>,
183}
184
185impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> SurfaceInterface<D, W> for X11Impl<D, W> {
186 type Context = Arc<X11DisplayImpl<D>>;
187 type Buffer<'a> = BufferImpl<'a, D, W> where Self: 'a;
188
189 /// Create a new `X11Impl` from a `HasWindowHandle`.
190 fn new(window_src: W, display: &Arc<X11DisplayImpl<D>>) -> Result<Self, InitError<W>> {
191 // Get the underlying raw window handle.
192 let raw = window_src.window_handle()?.as_raw();
193 let window_handle = match raw {
194 RawWindowHandle::Xcb(xcb) => xcb,
195 RawWindowHandle::Xlib(xlib) => {
196 let window = match NonZeroU32::new(xlib.window as u32) {
197 Some(window) => window,
198 None => return Err(SoftBufferError::IncompleteWindowHandle.into()),
199 };
200 let mut xcb_window_handle = XcbWindowHandle::new(window);
201 xcb_window_handle.visual_id = NonZeroU32::new(xlib.visual_id as u32);
202 xcb_window_handle
203 }
204 _ => {
205 return Err(InitError::Unsupported(window_src));
206 }
207 };
208
209 log::trace!("new: window_handle={:X}", window_handle.window);
210 let window = window_handle.window.get();
211
212 // Run in parallel: start getting the window depth and (if necessary) visual.
213 let display2 = display.clone();
214 let tokens = {
215 let geometry_token = display2
216 .connection()
217 .get_geometry(window)
218 .swbuf_err("Failed to send geometry request")?;
219 let window_attrs_token = if window_handle.visual_id.is_none() {
220 Some(
221 display2
222 .connection()
223 .get_window_attributes(window)
224 .swbuf_err("Failed to send window attributes request")?,
225 )
226 } else {
227 None
228 };
229
230 (geometry_token, window_attrs_token)
231 };
232
233 // Create a new graphics context to draw to.
234 let gc = display
235 .connection()
236 .generate_id()
237 .swbuf_err("Failed to generate GC ID")?;
238 display
239 .connection()
240 .create_gc(
241 gc,
242 window,
243 &xproto::CreateGCAux::new().graphics_exposures(0),
244 )
245 .swbuf_err("Failed to send GC creation request")?
246 .check()
247 .swbuf_err("Failed to create GC")?;
248
249 // Finish getting the depth of the window.
250 let (geometry_reply, visual_id) = {
251 let (geometry_token, window_attrs_token) = tokens;
252 let geometry_reply = geometry_token
253 .reply()
254 .swbuf_err("Failed to get geometry reply")?;
255 let visual_id = match window_attrs_token {
256 None => window_handle.visual_id.unwrap().get(),
257 Some(window_attrs) => {
258 window_attrs
259 .reply()
260 .swbuf_err("Failed to get window attributes reply")?
261 .visual
262 }
263 };
264
265 (geometry_reply, visual_id)
266 };
267
268 if !display.supported_visuals.contains(&visual_id) {
269 return Err(SoftBufferError::PlatformError(
270 Some(format!(
271 "Visual 0x{visual_id:x} does not use softbuffer's pixel format and is unsupported"
272 )),
273 None,
274 )
275 .into());
276 }
277
278 // See if SHM is available.
279 let buffer = if display.is_shm_available {
280 // SHM is available.
281 Buffer::Shm(ShmBuffer {
282 seg: None,
283 done_processing: None,
284 })
285 } else {
286 // SHM is not available.
287 Buffer::Wire(Vec::new())
288 };
289
290 Ok(Self {
291 display: display.clone(),
292 window,
293 gc,
294 depth: geometry_reply.depth,
295 visual_id,
296 buffer,
297 buffer_presented: false,
298 size: None,
299 window_handle: window_src,
300 })
301 }
302
303 #[inline]
304 fn window(&self) -> &W {
305 &self.window_handle
306 }
307
308 fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {
309 log::trace!(
310 "resize: window={:X}, size={}x{}",
311 self.window,
312 width,
313 height
314 );
315
316 // Width and height should fit in u16.
317 let width: NonZeroU16 = width
318 .try_into()
319 .or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;
320 let height: NonZeroU16 = height.try_into().or(Err(SoftBufferError::SizeOutOfRange {
321 width: width.into(),
322 height,
323 }))?;
324
325 if self.size != Some((width, height)) {
326 self.buffer_presented = false;
327 self.buffer
328 .resize(self.display.connection(), width.get(), height.get())
329 .swbuf_err("Failed to resize X11 buffer")?;
330
331 // We successfully resized the buffer.
332 self.size = Some((width, height));
333 }
334
335 Ok(())
336 }
337
338 fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {
339 log::trace!("buffer_mut: window={:X}", self.window);
340
341 // Finish waiting on the previous `shm::PutImage` request, if any.
342 self.buffer.finish_wait(self.display.connection())?;
343
344 // We can now safely call `buffer_mut` on the buffer.
345 Ok(BufferImpl(self))
346 }
347
348 fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {
349 log::trace!("fetch: window={:X}", self.window);
350
351 let (width, height) = self
352 .size
353 .expect("Must set size of surface before calling `fetch()`");
354
355 // TODO: Is it worth it to do SHM here? Probably not.
356 let reply = self
357 .display
358 .connection()
359 .get_image(
360 xproto::ImageFormat::Z_PIXMAP,
361 self.window,
362 0,
363 0,
364 width.get(),
365 height.get(),
366 u32::MAX,
367 )
368 .swbuf_err("Failed to send image fetching request")?
369 .reply()
370 .swbuf_err("Failed to fetch image from window")?;
371
372 if reply.depth == self.depth && reply.visual == self.visual_id {
373 let mut out = vec![0u32; reply.data.len() / 4];
374 bytemuck::cast_slice_mut::<u32, u8>(&mut out).copy_from_slice(&reply.data);
375 Ok(out)
376 } else {
377 Err(SoftBufferError::PlatformError(
378 Some("Mismatch between reply and window data".into()),
379 None,
380 ))
381 }
382 }
383}
384
385pub struct BufferImpl<'a, D: ?Sized, W: ?Sized>(&'a mut X11Impl<D, W>);
386
387impl<'a, D: HasDisplayHandle + ?Sized, W: HasWindowHandle + ?Sized> BufferInterface
388 for BufferImpl<'a, D, W>
389{
390 #[inline]
391 fn pixels(&self) -> &[u32] {
392 // SAFETY: We called `finish_wait` on the buffer, so it is safe to call `buffer()`.
393 unsafe { self.0.buffer.buffer() }
394 }
395
396 #[inline]
397 fn pixels_mut(&mut self) -> &mut [u32] {
398 // SAFETY: We called `finish_wait` on the buffer, so it is safe to call `buffer_mut`.
399 unsafe { self.0.buffer.buffer_mut() }
400 }
401
402 fn age(&self) -> u8 {
403 if self.0.buffer_presented {
404 1
405 } else {
406 0
407 }
408 }
409
410 /// Push the buffer to the window.
411 fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {
412 let imp = self.0;
413
414 let (surface_width, surface_height) = imp
415 .size
416 .expect("Must set size of surface before calling `present_with_damage()`");
417
418 log::trace!("present: window={:X}", imp.window);
419
420 match imp.buffer {
421 Buffer::Wire(ref wire) => {
422 // This is a suboptimal strategy, raise a stink in the debug logs.
423 log::debug!("Falling back to non-SHM method for window drawing.");
424
425 imp.display
426 .connection()
427 .put_image(
428 xproto::ImageFormat::Z_PIXMAP,
429 imp.window,
430 imp.gc,
431 surface_width.get(),
432 surface_height.get(),
433 0,
434 0,
435 0,
436 imp.depth,
437 bytemuck::cast_slice(wire),
438 )
439 .map(|c| c.ignore_error())
440 .push_err()
441 .swbuf_err("Failed to draw image to window")?;
442 }
443
444 Buffer::Shm(ref mut shm) => {
445 // If the X server is still processing the last image, wait for it to finish.
446 // SAFETY: We know that we called finish_wait() before this.
447 // Put the image into the window.
448 if let Some((_, segment_id)) = shm.seg {
449 damage
450 .iter()
451 .try_for_each(|rect| {
452 let (src_x, src_y, dst_x, dst_y, width, height) = (|| {
453 Some((
454 u16::try_from(rect.x).ok()?,
455 u16::try_from(rect.y).ok()?,
456 i16::try_from(rect.x).ok()?,
457 i16::try_from(rect.y).ok()?,
458 u16::try_from(rect.width.get()).ok()?,
459 u16::try_from(rect.height.get()).ok()?,
460 ))
461 })(
462 )
463 .ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;
464 imp.display
465 .connection()
466 .shm_put_image(
467 imp.window,
468 imp.gc,
469 surface_width.get(),
470 surface_height.get(),
471 src_x,
472 src_y,
473 width,
474 height,
475 dst_x,
476 dst_y,
477 imp.depth,
478 xproto::ImageFormat::Z_PIXMAP.into(),
479 false,
480 segment_id,
481 0,
482 )
483 .push_err()
484 .map(|c| c.ignore_error())
485 .swbuf_err("Failed to draw image to window")
486 })
487 .and_then(|()| {
488 // Send a short request to act as a notification for when the X server is done processing the image.
489 shm.begin_wait(imp.display.connection())
490 .swbuf_err("Failed to draw image to window")
491 })?;
492 }
493 }
494 }
495
496 imp.buffer_presented = true;
497
498 Ok(())
499 }
500
501 fn present(self) -> Result<(), SoftBufferError> {
502 let (width, height) = self
503 .0
504 .size
505 .expect("Must set size of surface before calling `present()`");
506 self.present_with_damage(&[Rect {
507 x: 0,
508 y: 0,
509 width: width.into(),
510 height: height.into(),
511 }])
512 }
513}
514
515impl Buffer {
516 /// Resize the buffer to the given size.
517 fn resize(
518 &mut self,
519 conn: &impl Connection,
520 width: u16,
521 height: u16,
522 ) -> Result<(), PushBufferError> {
523 match self {
524 Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),
525 Buffer::Wire(wire) => {
526 wire.resize(total_len(width, height) / 4, 0);
527 Ok(())
528 }
529 }
530 }
531
532 /// Finish waiting for an ongoing `shm::PutImage` request, if there is one.
533 fn finish_wait(&mut self, conn: &impl Connection) -> Result<(), SoftBufferError> {
534 if let Buffer::Shm(ref mut shm) = self {
535 shm.finish_wait(conn)
536 .swbuf_err("Failed to wait for X11 buffer")?;
537 }
538
539 Ok(())
540 }
541
542 /// Get a reference to the buffer.
543 ///
544 /// # Safety
545 ///
546 /// `finish_wait()` must be called in between `shm::PutImage` requests and this function.
547 #[inline]
548 unsafe fn buffer(&self) -> &[u32] {
549 match self {
550 Buffer::Shm(ref shm) => unsafe { shm.as_ref() },
551 Buffer::Wire(wire) => wire,
552 }
553 }
554
555 /// Get a mutable reference to the buffer.
556 ///
557 /// # Safety
558 ///
559 /// `finish_wait()` must be called in between `shm::PutImage` requests and this function.
560 #[inline]
561 unsafe fn buffer_mut(&mut self) -> &mut [u32] {
562 match self {
563 Buffer::Shm(ref mut shm) => unsafe { shm.as_mut() },
564 Buffer::Wire(wire) => wire,
565 }
566 }
567}
568
569impl ShmBuffer {
570 /// Allocate a new `ShmSegment` of the given size.
571 fn alloc_segment(
572 &mut self,
573 conn: &impl Connection,
574 buffer_size: usize,
575 ) -> Result<(), PushBufferError> {
576 // Round the size up to the next power of two to prevent frequent reallocations.
577 let size = buffer_size.next_power_of_two();
578
579 // Get the size of the segment currently in use.
580 let needs_realloc = match self.seg {
581 Some((ref seg, _)) => seg.size() < size,
582 None => true,
583 };
584
585 // Reallocate if necessary.
586 if needs_realloc {
587 let new_seg = ShmSegment::new(size, buffer_size)?;
588 self.associate(conn, new_seg)?;
589 } else if let Some((ref mut seg, _)) = self.seg {
590 seg.set_buffer_size(buffer_size);
591 }
592
593 Ok(())
594 }
595
596 /// Get the SHM buffer as a reference.
597 ///
598 /// # Safety
599 ///
600 /// `finish_wait()` must be called before this function is.
601 #[inline]
602 unsafe fn as_ref(&self) -> &[u32] {
603 match self.seg.as_ref() {
604 Some((seg, _)) => {
605 let buffer_size = seg.buffer_size();
606
607 // SAFETY: No other code should be able to access the segment.
608 bytemuck::cast_slice(unsafe { &seg.as_ref()[..buffer_size] })
609 }
610 None => {
611 // Nothing has been allocated yet.
612 &[]
613 }
614 }
615 }
616
617 /// Get the SHM buffer as a mutable reference.
618 ///
619 /// # Safety
620 ///
621 /// `finish_wait()` must be called before this function is.
622 #[inline]
623 unsafe fn as_mut(&mut self) -> &mut [u32] {
624 match self.seg.as_mut() {
625 Some((seg, _)) => {
626 let buffer_size = seg.buffer_size();
627
628 // SAFETY: No other code should be able to access the segment.
629 bytemuck::cast_slice_mut(unsafe { &mut seg.as_mut()[..buffer_size] })
630 }
631 None => {
632 // Nothing has been allocated yet.
633 &mut []
634 }
635 }
636 }
637
638 /// Associate an SHM segment with the server.
639 fn associate(
640 &mut self,
641 conn: &impl Connection,
642 seg: ShmSegment,
643 ) -> Result<(), PushBufferError> {
644 // Register the guard.
645 let new_id = conn.generate_id()?;
646 conn.shm_attach_fd(new_id, seg.as_fd().try_clone_to_owned().unwrap(), true)?
647 .ignore_error();
648
649 // Take out the old one and detach it.
650 if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {
651 // Wait for the old segment to finish processing.
652 self.finish_wait(conn)?;
653
654 conn.shm_detach(old_id)?.ignore_error();
655
656 // Drop the old segment.
657 drop(old_seg);
658 }
659
660 Ok(())
661 }
662
663 /// Begin waiting for the SHM processing to finish.
664 fn begin_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {
665 let cookie = c.get_input_focus()?.sequence_number();
666 let old_cookie = self.done_processing.replace(cookie);
667 debug_assert!(old_cookie.is_none());
668 Ok(())
669 }
670
671 /// Wait for the SHM processing to finish.
672 fn finish_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {
673 if let Some(done_processing) = self.done_processing.take() {
674 // Cast to a cookie and wait on it.
675 let cookie = Cookie::<_, xproto::GetInputFocusReply>::new(c, done_processing);
676 cookie.reply()?;
677 }
678
679 Ok(())
680 }
681}
682
683struct ShmSegment {
684 id: File,
685 ptr: NonNull<i8>,
686 size: usize,
687 buffer_size: usize,
688}
689
690// SAFETY: We respect Rust's mutability rules for the inner allocation.
691unsafe impl Send for ShmSegment {}
692
693impl ShmSegment {
694 /// Create a new `ShmSegment` with the given size.
695 fn new(size: usize, buffer_size: usize) -> io::Result<Self> {
696 assert!(size >= buffer_size);
697
698 // Create a shared memory segment.
699 let id = File::from(create_shm_id()?);
700
701 // Set its length.
702 id.set_len(size as u64)?;
703
704 // Map the shared memory to our file descriptor space.
705 let ptr = unsafe {
706 let ptr = mm::mmap(
707 null_mut(),
708 size,
709 mm::ProtFlags::READ | mm::ProtFlags::WRITE,
710 mm::MapFlags::SHARED,
711 &id,
712 0,
713 )?;
714
715 match NonNull::new(ptr.cast()) {
716 Some(ptr) => ptr,
717 None => {
718 return Err(io::Error::new(
719 io::ErrorKind::Other,
720 "unexpected null when mapping SHM segment",
721 ));
722 }
723 }
724 };
725
726 Ok(Self {
727 id,
728 ptr,
729 size,
730 buffer_size,
731 })
732 }
733
734 /// Get this shared memory segment as a reference.
735 ///
736 /// # Safety
737 ///
738 /// One must ensure that no other processes are writing to this memory.
739 unsafe fn as_ref(&self) -> &[i8] {
740 unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.size) }
741 }
742
743 /// Get this shared memory segment as a mutable reference.
744 ///
745 /// # Safety
746 ///
747 /// One must ensure that no other processes are reading from or writing to this memory.
748 unsafe fn as_mut(&mut self) -> &mut [i8] {
749 unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }
750 }
751
752 /// Set the size of the buffer for this shared memory segment.
753 fn set_buffer_size(&mut self, buffer_size: usize) {
754 assert!(self.size >= buffer_size);
755 self.buffer_size = buffer_size
756 }
757
758 /// Get the size of the buffer for this shared memory segment.
759 fn buffer_size(&self) -> usize {
760 self.buffer_size
761 }
762
763 /// Get the size of this shared memory segment.
764 fn size(&self) -> usize {
765 self.size
766 }
767}
768
769impl AsFd for ShmSegment {
770 fn as_fd(&self) -> BorrowedFd<'_> {
771 self.id.as_fd()
772 }
773}
774
775impl Drop for ShmSegment {
776 fn drop(&mut self) {
777 unsafe {
778 // Unmap the shared memory segment.
779 mm::munmap(self.ptr.as_ptr().cast(), self.size).ok();
780 }
781 }
782}
783
784impl<D: ?Sized> Drop for X11DisplayImpl<D> {
785 fn drop(&mut self) {
786 // Make sure that the x11rb connection is dropped before its source is.
787 self.connection = None;
788 }
789}
790
791impl<D: ?Sized, W: ?Sized> Drop for X11Impl<D, W> {
792 fn drop(&mut self) {
793 // If we used SHM, make sure it's detached from the server.
794 if let Buffer::Shm(mut shm: ShmBuffer) = mem::replace(&mut self.buffer, src:Buffer::Wire(Vec::new())) {
795 // If we were in the middle of processing a buffer, wait for it to finish.
796 shm.finish_wait(self.display.connection()).ok();
797
798 if let Some((segment: ShmSegment, seg_id: u32)) = shm.seg.take() {
799 if let Ok(token: VoidCookie<'_, XCBConnection>) = self.display.connection().shm_detach(shmseg:seg_id) {
800 token.ignore_error();
801 }
802
803 // Drop the segment.
804 drop(segment);
805 }
806 }
807
808 // Close the graphics context that we created.
809 if let Ok(token: VoidCookie<'_, XCBConnection>) = self.display.connection().free_gc(self.gc) {
810 token.ignore_error();
811 }
812 }
813}
814
815/// Create a shared memory identifier.
816fn create_shm_id() -> io::Result<OwnedFd> {
817 use posix_shm::{Mode, ShmOFlags};
818
819 let mut rng = fastrand::Rng::new();
820 let mut name = String::with_capacity(23);
821
822 // Only try four times; the chances of a collision on this space is astronomically low, so if
823 // we miss four times in a row we're probably under attack.
824 for i in 0..4 {
825 name.clear();
826 name.push_str("softbuffer-x11-");
827 name.extend(std::iter::repeat_with(|| rng.alphanumeric()).take(7));
828
829 // Try to create the shared memory segment.
830 match posix_shm::shm_open(
831 &name,
832 ShmOFlags::RDWR | ShmOFlags::CREATE | ShmOFlags::EXCL,
833 Mode::RWXU,
834 ) {
835 Ok(id) => {
836 posix_shm::shm_unlink(&name).ok();
837 return Ok(id);
838 }
839
840 Err(rustix::io::Errno::EXIST) => {
841 log::warn!("x11: SHM ID collision at {} on try number {}", name, i);
842 }
843
844 Err(e) => return Err(e.into()),
845 };
846 }
847
848 Err(io::Error::new(
849 io::ErrorKind::Other,
850 "failed to generate a non-existent SHM name",
851 ))
852}
853
854/// Test to see if SHM is available.
855fn is_shm_available(c: &impl Connection) -> bool {
856 // Create a small SHM segment.
857 let seg = match ShmSegment::new(0x1000, 0x1000) {
858 Ok(seg) => seg,
859 Err(_) => return false,
860 };
861
862 // Attach and detach it.
863 let seg_id = match c.generate_id() {
864 Ok(id) => id,
865 Err(_) => return false,
866 };
867
868 let (attach, detach) = {
869 let attach = c.shm_attach_fd(seg_id, seg.as_fd().try_clone_to_owned().unwrap(), false);
870 let detach = c.shm_detach(seg_id);
871
872 match (attach, detach) {
873 (Ok(attach), Ok(detach)) => (attach, detach),
874 _ => return false,
875 }
876 };
877
878 // Check the replies.
879 matches!((attach.check(), detach.check()), (Ok(()), Ok(())))
880}
881
882/// Collect all visuals that use softbuffer's pixel format
883fn supported_visuals(c: &impl Connection) -> HashSet<Visualid> {
884 // Check that depth 24 uses 32 bits per pixels
885 // HACK(notgull): Also support depth 32 for transparent visuals.
886 // Otherwise winit users get weird errors.
887 if !c
888 .setup()
889 .pixmap_formats
890 .iter()
891 .any(|f| (f.depth == 24 || f.depth == 32) && f.bits_per_pixel == 32)
892 {
893 log::warn!("X11 server does not have a depth 24/32 format with 32 bits per pixel");
894 return HashSet::new();
895 }
896
897 // How does the server represent red, green, blue components of a pixel?
898 #[cfg(target_endian = "little")]
899 let own_byte_order = ImageOrder::LSB_FIRST;
900 #[cfg(target_endian = "big")]
901 let own_byte_order = ImageOrder::MSB_FIRST;
902 let expected_masks = if c.setup().image_byte_order == own_byte_order {
903 (0xff0000, 0xff00, 0xff)
904 } else {
905 // This is the byte-swapped version of our wished-for format
906 (0xff00, 0xff0000, 0xff000000)
907 };
908
909 c.setup()
910 .roots
911 .iter()
912 .flat_map(|screen| {
913 screen
914 .allowed_depths
915 .iter()
916 .filter(|depth| depth.depth == 24 || depth.depth == 32)
917 .flat_map(|depth| {
918 depth
919 .visuals
920 .iter()
921 .filter(|visual| {
922 // Ignore grayscale or indexes / color palette visuals
923 visual.class == VisualClass::TRUE_COLOR
924 || visual.class == VisualClass::DIRECT_COLOR
925 })
926 .filter(|visual| {
927 // Colors must be laid out as softbuffer expects
928 expected_masks == (visual.red_mask, visual.green_mask, visual.blue_mask)
929 })
930 .map(|visual| visual.visual_id)
931 })
932 })
933 .collect()
934}
935
936/// An error that can occur when pushing a buffer to the window.
937#[derive(Debug)]
938enum PushBufferError {
939 /// We encountered an X11 error.
940 X11(ReplyError),
941
942 /// We exhausted the XID space.
943 XidExhausted,
944
945 /// A system error occurred while creating the shared memory segment.
946 System(io::Error),
947}
948
949impl fmt::Display for PushBufferError {
950 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
951 match self {
952 Self::X11(e: &ReplyError) => write!(f, "X11 error: {}", e),
953 Self::XidExhausted => write!(f, "XID space exhausted"),
954 Self::System(e: &Error) => write!(f, "System error: {}", e),
955 }
956 }
957}
958
959impl std::error::Error for PushBufferError {}
960
961impl From<ConnectionError> for PushBufferError {
962 fn from(e: ConnectionError) -> Self {
963 Self::X11(ReplyError::ConnectionError(e))
964 }
965}
966
967impl From<ReplyError> for PushBufferError {
968 fn from(e: ReplyError) -> Self {
969 Self::X11(e)
970 }
971}
972
973impl From<ReplyOrIdError> for PushBufferError {
974 fn from(e: ReplyOrIdError) -> Self {
975 match e {
976 ReplyOrIdError::ConnectionError(e: ConnectionError) => Self::X11(ReplyError::ConnectionError(e)),
977 ReplyOrIdError::X11Error(e: X11Error) => Self::X11(ReplyError::X11Error(e)),
978 ReplyOrIdError::IdsExhausted => Self::XidExhausted,
979 }
980 }
981}
982
983impl From<io::Error> for PushBufferError {
984 fn from(e: io::Error) -> Self {
985 Self::System(e)
986 }
987}
988
989/// Convenient wrapper to cast errors into PushBufferError.
990trait PushResultExt<T, E> {
991 fn push_err(self) -> Result<T, PushBufferError>;
992}
993
994impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {
995 fn push_err(self) -> Result<T, PushBufferError> {
996 self.map_err(op:Into::into)
997 }
998}
999
1000/// Get the length that a slice needs to be to hold a buffer of the given dimensions.
1001#[inline(always)]
1002fn total_len(width: u16, height: u16) -> usize {
1003 let width: usize = width.into();
1004 let height: usize = height.into();
1005
1006 widthOption
1007 .checked_mul(height)
1008 .and_then(|len: usize| len.checked_mul(4))
1009 .unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))
1010}
1011