| 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 | |