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