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::backend_interface::*; |
9 | use crate::error::{InitError, SwResultExt}; |
10 | use crate::{Rect, SoftBufferError}; |
11 | use raw_window_handle::{ |
12 | HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, |
13 | XcbWindowHandle, |
14 | }; |
15 | use rustix::{ |
16 | fd::{AsFd, BorrowedFd, OwnedFd}, |
17 | mm, shm as posix_shm, |
18 | }; |
19 | |
20 | use 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 | |
31 | use as_raw_xcb_connection::AsRawXcbConnection; |
32 | use x11rb::connection::{Connection, SequenceNumber}; |
33 | use x11rb::cookie::Cookie; |
34 | use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError}; |
35 | use x11rb::protocol::shm::{self, ConnectionExt as _}; |
36 | use x11rb::protocol::xproto::{self, ConnectionExt as _, ImageOrder, VisualClass, Visualid}; |
37 | use x11rb::xcb_ffi::XCBConnection; |
38 | |
39 | pub 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 | |
57 | impl<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 | |
119 | impl<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. |
128 | pub 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. |
158 | enum 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 | |
166 | struct 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 | |
185 | impl<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 | |
385 | pub struct BufferImpl<'a, D: ?Sized, W: ?Sized>(&'a mut X11Impl<D, W>); |
386 | |
387 | impl<'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 | |
515 | impl 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 | |
569 | impl 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 | |
683 | struct 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. |
691 | unsafe impl Send for ShmSegment {} |
692 | |
693 | impl 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 | |
769 | impl AsFd for ShmSegment { |
770 | fn as_fd(&self) -> BorrowedFd<'_> { |
771 | self.id.as_fd() |
772 | } |
773 | } |
774 | |
775 | impl 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 | |
784 | impl<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 | |
791 | impl<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. |
816 | fn 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. |
855 | fn 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 |
883 | fn 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)] |
938 | enum 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 | |
949 | impl 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 | |
959 | impl std::error::Error for PushBufferError {} |
960 | |
961 | impl From<ConnectionError> for PushBufferError { |
962 | fn from(e: ConnectionError) -> Self { |
963 | Self::X11(ReplyError::ConnectionError(e)) |
964 | } |
965 | } |
966 | |
967 | impl From<ReplyError> for PushBufferError { |
968 | fn from(e: ReplyError) -> Self { |
969 | Self::X11(e) |
970 | } |
971 | } |
972 | |
973 | impl 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 | |
983 | impl 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. |
990 | trait PushResultExt<T, E> { |
991 | fn push_err(self) -> Result<T, PushBufferError>; |
992 | } |
993 | |
994 | impl<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)] |
1002 | fn 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 | |