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 | |
8 | use crate::error::SwResultExt; |
9 | use crate::{Rect, SoftBufferError}; |
10 | use raw_window_handle::{XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle}; |
11 | use rustix::{ |
12 | fd::{AsFd, BorrowedFd, OwnedFd}, |
13 | mm, shm as posix_shm, |
14 | }; |
15 | |
16 | use 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 | |
26 | use as_raw_xcb_connection::AsRawXcbConnection; |
27 | use x11rb::connection::{Connection, SequenceNumber}; |
28 | use x11rb::cookie::Cookie; |
29 | use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError}; |
30 | use x11rb::protocol::shm::{self, ConnectionExt as _}; |
31 | use x11rb::protocol::xproto::{self, ConnectionExt as _}; |
32 | use x11rb::xcb_ffi::XCBConnection; |
33 | |
34 | pub 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 | |
42 | impl 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. |
102 | pub 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. |
129 | enum 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 | |
137 | struct 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 | |
156 | impl 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 | |
356 | pub struct BufferImpl<'a>(&'a mut X11Impl); |
357 | |
358 | impl<'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 | |
484 | impl 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 | |
538 | impl 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 | |
652 | struct ShmSegment { |
653 | id: File, |
654 | ptr: NonNull<i8>, |
655 | size: usize, |
656 | buffer_size: usize, |
657 | } |
658 | |
659 | impl 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 | |
735 | impl AsFd for ShmSegment { |
736 | fn as_fd(&self) -> BorrowedFd<'_> { |
737 | self.id.as_fd() |
738 | } |
739 | } |
740 | |
741 | impl 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 | |
750 | impl 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. |
775 | fn 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. |
814 | fn 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)] |
843 | enum 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 | |
854 | impl 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 | |
864 | impl std::error::Error for PushBufferError {} |
865 | |
866 | impl From<ConnectionError> for PushBufferError { |
867 | fn from(e: ConnectionError) -> Self { |
868 | Self::X11(ReplyError::ConnectionError(e)) |
869 | } |
870 | } |
871 | |
872 | impl From<ReplyError> for PushBufferError { |
873 | fn from(e: ReplyError) -> Self { |
874 | Self::X11(e) |
875 | } |
876 | } |
877 | |
878 | impl 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 | |
888 | impl 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. |
895 | trait PushResultExt<T, E> { |
896 | fn push_err(self) -> Result<T, PushBufferError>; |
897 | } |
898 | |
899 | impl<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)] |
907 | fn 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 | |