softbuffer/
x11.rs

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