GUHXMDKNAOLJDBQ73FNQJDUHZ344S7P4FLFT2XCWGCKTCNYEZWKAC WVWOWY6SLDNBCWKFZ7HZ2C5VW2STMIT77MLQRTHAE4KS3XG7SSCQC 3E7ON5LK5KELT7NX7O2DRSVSQQGVW3HU3LFTQGGCJ5PIRZDWD56QC KLGRQAFUQPJP75NVLS5MZZ3CY4TGNVGB4K2YHQSLYVGAPKCLBLUAC SDFC64IQZWAAG36UV4GVSAER5S3FISBQEE2NVGI4G6ODD6CCGBIAC R6SN37XV5NNVRRB7WYOW6BL4DPA4GAKPOFSX2OT7BR4Y36X532BQC NOSXMSCMJV75LYKAIRD4FG4LFPYQ4CI5SIKGQC7MEYUEAB7X7NWAC 2NFFH775XBXGXBJYGTGUAUH45T5ZI6FFJXHFGUQQS324XEAFKT7AC NBT2QCONKZ2E5I7HIQJUNQAUNJA6RPYYTUVZQCT6Z6ABU42A277AC I3F42XELTBNGNFJGKEDBRYU75HHQX2CHMWPEIEMXAGU2FFHQVHKAC FJVNPRZD2CQHHDZMFTPW57J3QR3HOF7B5ONBDTPGRBGNJOXU36WAC 3MJHVTOTVPO6KYWXVKALETNOZBHQ3XSI3SDUWIPHGWP5ZITQL4KAC YR6S4ANJ34M45KU6BU5RRJWI5AW5SSFPCDLH4KUO55DICUK5LQ4QC XTYFIHM6F5QCIGESYIWC5PLHKKA3JE5XZAXTPILYZTSGVMOU7BCAC T2MYIZ6BIXHBKWZ3EJIYV65STSDAVKZHAPLTFXF27YBQIJCI7U7AC PTQ26KY65NDVZ35EG66SARWRGGYKDJ2BT3EVK4RJFS2J5AQUX6OQC B66IOHMWFZINTWC57Q5OD4YPBVQNBN3YUM3ZKX4MTDKHYGH5JHFAC XKAEHFMZP4PDXPJ2GZTRAJL7SCHJAH7EXRG2TWEBOFVOFUFJQ6RAC CTPHEJ2S7MMSZTNVO4LFEYHMMTKYWB5PC54RMVJHMATN5TY45GPQC 576YRB7XSKS6Q265TEJQS7HUGMKSGHGDRGVP6WBZS7XMK6RVJRNQC HKVKPIIWK6AB4DWCS6SB47RYEXTD4QAQIGDFNQGA3TDJEIAXBZKQC 5S34ANJMEKYNSGROMFRDP22UA2FK2ENSGOBB4MPSHS7PG2BW6DLAC VG3XTH77PHA277MDSNVYEMI76SAPF2C42JU4EVSE6AEEVOQSLAHQC DZSYKTTKQ6DUGPMCPS7FQRSE5FO4FEHY2L3UWB73A7Y7Y6GDHWCAC FVAECMP3SBASNUQGUTHGZTMXLLAGW2FK6MID7P6YP5LRD7ZHADYAC T5T2PZSJVBHIPHG7VRXZSH36WGSTR4I44BOI335X4F637E7GUWHAC CVXA6LIYFYIZEVPTI335A332EXRVJXF2SC4B5F2NFYXZXYVDFBKQC 4HTJHGHFPLGVXGAEKLBNPMTGWVVMYIL67JOTWB2DREGO6L3ENYMQC AFZQUL4VKEEDRSA5CV6YXN7PMX6PQ2PMLECTG53S4HZKTNCAIMXQC pub fn new(event_loop: &ActiveEventLoop) -> Result<Self, Box<dyn Error>> {let window = event_loop.create_window(Window::default_attributes().with_inner_size(PhysicalSize::new(WIDTH as f64, HEIGHT as f64)).with_title("Kakoune Client"),)?;let st = SurfaceTexture::new(WIDTH as u32,HEIGHT as u32,// We need to use unsafe here because otherwise we can't have self-referencial structsunsafe { &*(&window as *const Window) },);let pixels = Pixels::new(WIDTH as u32, HEIGHT as u32, st)?;
pub fn new() -> Result<Self, Box<dyn Error>> {
let bidx = (j + i * self.size.0) * 4;buf[bidx..][0..4].copy_from_slice(&color);
let bidx = j + i * self.size.0;let pixel = [color[3], color[0], color[1], color[2]];buf[bidx] = u32::from_be_bytes(pixel);
self.render_state = Renderer::new(event_loop).ok();
self.render_state = Renderer::new().ok();let window = event_loop.create_window(Window::default_attributes().with_inner_size(PhysicalSize::new(WIDTH as f64, HEIGHT as f64)).with_title("Kakoune Client"),).unwrap();let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let mut surface = softbuffer::Surface::new(&context, window).unwrap();surface.resize(NonZero::new(WIDTH).unwrap(), NonZero::new(HEIGHT).unwrap()).unwrap();self.pixels = Some((context, surface));
if let Some(render) = self.render_state.as_mut() {render.pixels.resize_buffer(size.width, size.height).unwrap();render.pixels.resize_surface(size.width, size.height)
if let Some(render) = self.render_state.as_mut()&& let Some((_, surface)) = self.pixels.as_mut(){surface.resize(NonZero::new(size.width).unwrap(),NonZero::new(size.height).unwrap(),)
// Not needed on all platforms#![allow(dead_code)]use std::cmp;use std::fmt;use std::num::NonZeroU32;use std::ops;use crate::Rect;use crate::SoftBufferError;/// Takes a mutable reference to a container and a function deriving a/// reference into it, and stores both, making it possible to get back the/// reference to the container once the other reference is no longer needed.////// This should be consistent with stacked borrow rules, and miri seems to/// accept it at least in simple cases.pub struct BorrowStack<'a, T: 'a + ?Sized, U: 'a + ?Sized> {container: *mut T,member: *mut U,_phantom: std::marker::PhantomData<&'a mut T>,}unsafe impl<'a, T: 'a + Send + ?Sized, U: 'a + Send + ?Sized> Send for BorrowStack<'a, T, U> {}impl<'a, T: 'a + ?Sized, U: 'a + ?Sized> BorrowStack<'a, T, U> {pub fn new<F>(container: &'a mut T, f: F) -> Result<Self, SoftBufferError>whereF: for<'b> FnOnce(&'b mut T) -> Result<&'b mut U, SoftBufferError>,{let container = container as *mut T;let member = f(unsafe { &mut *container })? as *mut U;Ok(Self {container,member,_phantom: std::marker::PhantomData,})}pub fn member(&self) -> &U {unsafe { &*self.member }}pub fn member_mut(&mut self) -> &mut U {unsafe { &mut *self.member }}pub fn into_container(self) -> &'a mut T {// SAFETY: Since we consume self and no longer reference member, this// mutable reference is unique.unsafe { &mut *self.container }}}impl<'a, T: 'a + ?Sized, U: 'a + ?Sized + fmt::Debug> fmt::Debug for BorrowStack<'a, T, U> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {f.debug_struct("BorrowStack").field("member", &self.member()).finish_non_exhaustive()}}/// Calculates the smallest `Rect` necessary to represent all damaged `Rect`s.pub(crate) fn union_damage(damage: &[Rect]) -> Option<Rect> {struct Region {left: u32,top: u32,bottom: u32,right: u32,}let region = damage.iter().map(|rect| Region {left: rect.x,top: rect.y,right: rect.x + rect.width.get(),bottom: rect.y + rect.height.get(),}).reduce(|mut prev, next| {prev.left = cmp::min(prev.left, next.left);prev.top = cmp::min(prev.top, next.top);prev.right = cmp::max(prev.right, next.right);prev.bottom = cmp::max(prev.bottom, next.bottom);prev})?;Some(Rect {x: region.left,y: region.top,width: NonZeroU32::new(region.right - region.left).expect("`right` must always be bigger then `left`"),height: NonZeroU32::new(region.bottom - region.top).expect("`bottom` must always be bigger then `top`"),})}/// A wrapper around a `Vec` of pixels that doesn't print the whole buffer on `Debug`.#[derive(PartialEq, Eq, Hash, Clone)]pub(crate) struct PixelBuffer(pub Vec<u32>);impl fmt::Debug for PixelBuffer {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {f.debug_struct("PixelBuffer").finish_non_exhaustive()}}impl ops::Deref for PixelBuffer {type Target = Vec<u32>;fn deref(&self) -> &Self::Target {&self.0}}impl ops::DerefMut for PixelBuffer {fn deref_mut(&mut self) -> &mut Self::Target {&mut self.0}}#[cfg(test)]mod tests {use super::*;#[test]fn test_borrowstack_slice_int() {fn f(mut stack: BorrowStack<[u32], u32>) {assert_eq!(*stack.member(), 3);*stack.member_mut() = 42;assert_eq!(stack.into_container(), &[1, 2, 42, 4, 5]);}let mut v = vec![1, 2, 3, 4, 5];f(BorrowStack::new(v.as_mut(), |v: &mut [u32]| Ok(&mut v[2])).unwrap());assert_eq!(&v, &[1, 2, 42, 4, 5]);}}
#![doc = include_str!("../README.md")]#![allow(clippy::needless_doctest_main)]#![deny(unsafe_op_in_unsafe_fn)]#![warn(missing_docs)]#![cfg_attr(docsrs, feature(doc_cfg))]extern crate core;mod backend_dispatch;use backend_dispatch::*;mod backend_interface;use backend_interface::*;mod backends;mod error;mod util;use std::cell::Cell;use std::marker::PhantomData;use std::num::NonZeroU32;use std::ops;use std::sync::Arc;use error::InitError;pub use error::SoftBufferError;use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};#[cfg(target_family = "wasm")]pub use backends::web::SurfaceExtWeb;/// An instance of this struct contains the platform-specific data that must be managed in order to/// write to a window on that platform.#[derive(Clone, Debug)]pub struct Context<D> {/// The inner static dispatch object.context_impl: ContextDispatch<D>,/// This is Send+Sync IFF D is Send+Sync._marker: PhantomData<Arc<D>>,}impl<D: HasDisplayHandle> Context<D> {/// Creates a new instance of this struct, using the provided display.pub fn new(display: D) -> Result<Self, SoftBufferError> {match ContextDispatch::new(display) {Ok(context_impl) => Ok(Self {context_impl,_marker: PhantomData,}),Err(InitError::Unsupported(display)) => {let raw = display.display_handle()?.as_raw();Err(SoftBufferError::UnsupportedDisplayPlatform {human_readable_display_platform_name: display_handle_type_name(&raw),display_handle: raw,})}Err(InitError::Failure(f)) => Err(f),}}}/// A rectangular region of the buffer coordinate space.#[derive(Clone, Copy, Debug)]pub struct Rect {/// x coordinate of top left cornerpub x: u32,/// y coordinate of top left cornerpub y: u32,/// widthpub width: NonZeroU32,/// heightpub height: NonZeroU32,}/// A surface for drawing to a window with software buffers.#[derive(Debug)]pub struct Surface<D, W> {/// This is boxed so that `Surface` is the same size on every platform.surface_impl: Box<SurfaceDispatch<D, W>>,_marker: PhantomData<Cell<()>>,}impl<D: HasDisplayHandle, W: HasWindowHandle> Surface<D, W> {/// Creates a new surface for the context for the provided window.pub fn new(context: &Context<D>, window: W) -> Result<Self, SoftBufferError> {match SurfaceDispatch::new(window, &context.context_impl) {Ok(surface_dispatch) => Ok(Self {surface_impl: Box::new(surface_dispatch),_marker: PhantomData,}),Err(InitError::Unsupported(window)) => {let raw = window.window_handle()?.as_raw();Err(SoftBufferError::UnsupportedWindowPlatform {human_readable_window_platform_name: window_handle_type_name(&raw),human_readable_display_platform_name: context.context_impl.variant_name(),window_handle: raw,})}Err(InitError::Failure(f)) => Err(f),}}/// Get a reference to the underlying window handle.pub fn window(&self) -> &W {self.surface_impl.window()}/// Set the size of the buffer that will be returned by [`Surface::buffer_mut`].////// If the size of the buffer does not match the size of the window, the buffer is drawn/// in the upper-left corner of the window. It is recommended in most production use cases/// to have the buffer fill the entire window. Use your windowing library to find the size/// of the window.pub fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {self.surface_impl.resize(width, height)}/// Copies the window contents into a buffer.////// ## Platform Dependent Behavior////// - On X11, the window must be visible./// - On AppKit, UIKit, Redox and Wayland, this function is unimplemented./// - On Web, this will fail if the content was supplied by/// a different origin depending on the sites CORS rules.pub fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {self.surface_impl.fetch()}/// Return a [`Buffer`] that the next frame should be rendered into. The size must/// be set with [`Surface::resize`] first. The initial contents of the buffer may be zeroed, or/// may contain a previous frame. Call [`Buffer::age`] to determine this.////// ## Platform Dependent Behavior////// - On DRM/KMS, there is no reliable and sound way to wait for the page flip to happen from within/// `softbuffer`. Therefore it is the responsibility of the user to wait for the page flip before/// sending another frame.pub fn buffer_mut(&mut self) -> Result<Buffer<'_, D, W>, SoftBufferError> {Ok(Buffer {buffer_impl: self.surface_impl.buffer_mut()?,_marker: PhantomData,})}}impl<D: HasDisplayHandle, W: HasWindowHandle> AsRef<W> for Surface<D, W> {#[inline]fn as_ref(&self) -> &W {self.window()}}impl<D: HasDisplayHandle, W: HasWindowHandle> HasWindowHandle for Surface<D, W> {#[inline]fn window_handle(&self,) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {self.window().window_handle()}}/// A buffer that can be written to by the CPU and presented to the window.////// This derefs to a `[u32]`, which depending on the backend may be a mapping into shared memory/// accessible to the display server, so presentation doesn't require any (client-side) copying.////// This trusts the display server not to mutate the buffer, which could otherwise be unsound.////// # Data representation////// The format of the buffer is as follows. There is one `u32` in the buffer for each pixel in/// the area to draw. The first entry is the upper-left most pixel. The second is one to the right/// etc. (Row-major top to bottom left to right one `u32` per pixel). Within each `u32` the highest/// order 8 bits are to be set to 0. The next highest order 8 bits are the red channel, then the/// green channel, and then the blue channel in the lowest-order 8 bits. See the examples for/// one way to build this format using bitwise operations.////// --------////// Pixel format (`u32`):////// 00000000RRRRRRRRGGGGGGGGBBBBBBBB////// 0: Bit is 0/// R: Red channel/// G: Green channel/// B: Blue channel////// # Platform dependent behavior/// No-copy presentation is currently supported on:/// - Wayland/// - X, when XShm is available/// - Win32/// - Orbital, when buffer size matches window size////// Currently [`Buffer::present`] must block copying image data on:/// - Web/// - AppKit/// - UIKit////// Buffer copies an channel swizzling happen on:/// - Android#[derive(Debug)]pub struct Buffer<'a, D, W> {buffer_impl: BufferDispatch<'a, D, W>,_marker: PhantomData<(Arc<D>, Cell<()>)>,}impl<D: HasDisplayHandle, W: HasWindowHandle> Buffer<'_, D, W> {/// The amount of pixels wide the buffer is.pub fn width(&self) -> NonZeroU32 {let width = self.buffer_impl.width();debug_assert_eq!(width.get() as usize * self.buffer_impl.height().get() as usize,self.len(),"buffer must be sized correctly");width}/// The amount of pixels tall the buffer is.pub fn height(&self) -> NonZeroU32 {let height = self.buffer_impl.height();debug_assert_eq!(height.get() as usize * self.buffer_impl.width().get() as usize,self.len(),"buffer must be sized correctly");height}/// `age` is the number of frames ago this buffer was last presented. So if the value is/// `1`, it is the same as the last frame, and if it is `2`, it is the same as the frame/// before that (for backends using double buffering). If the value is `0`, it is a new/// buffer that has unspecified contents.////// This can be used to update only a portion of the buffer.pub fn age(&self) -> u8 {self.buffer_impl.age()}/// Presents buffer to the window.////// # Platform dependent behavior////// ## Wayland////// On Wayland, calling this function may send requests to the underlying `wl_surface`. The/// graphics context may issue `wl_surface.attach`, `wl_surface.damage`, `wl_surface.damage_buffer`/// and `wl_surface.commit` requests when presenting the buffer.////// If the caller wishes to synchronize other surface/window changes, such requests must be sent to the/// Wayland compositor before calling this function.pub fn present(self) -> Result<(), SoftBufferError> {self.buffer_impl.present()}/// Presents buffer to the window, with damage regions.////// # Platform dependent behavior////// Supported on:/// - Wayland/// - X, when XShm is available/// - Win32/// - Web////// Otherwise this is equivalent to [`Self::present`].pub fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {self.buffer_impl.present_with_damage(damage)}}impl<D: HasDisplayHandle, W: HasWindowHandle> ops::Deref for Buffer<'_, D, W> {type Target = [u32];#[inline]fn deref(&self) -> &[u32] {self.buffer_impl.pixels()}}impl<D: HasDisplayHandle, W: HasWindowHandle> ops::DerefMut for Buffer<'_, D, W> {#[inline]fn deref_mut(&mut self) -> &mut [u32] {self.buffer_impl.pixels_mut()}}/// There is no display handle.#[derive(Debug)]#[allow(dead_code)]pub struct NoDisplayHandle(core::convert::Infallible);impl HasDisplayHandle for NoDisplayHandle {fn display_handle(&self,) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {match self.0 {}}}/// There is no window handle.#[derive(Debug)]pub struct NoWindowHandle(());impl HasWindowHandle for NoWindowHandle {fn window_handle(&self,) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {Err(raw_window_handle::HandleError::NotSupported)}}fn window_handle_type_name(handle: &RawWindowHandle) -> &'static str {match handle {RawWindowHandle::Xlib(_) => "Xlib",RawWindowHandle::Win32(_) => "Win32",RawWindowHandle::WinRt(_) => "WinRt",RawWindowHandle::Web(_) => "Web",RawWindowHandle::Wayland(_) => "Wayland",RawWindowHandle::AndroidNdk(_) => "AndroidNdk",RawWindowHandle::AppKit(_) => "AppKit",RawWindowHandle::Orbital(_) => "Orbital",RawWindowHandle::UiKit(_) => "UiKit",RawWindowHandle::Xcb(_) => "XCB",RawWindowHandle::Drm(_) => "DRM",RawWindowHandle::Gbm(_) => "GBM",RawWindowHandle::Haiku(_) => "Haiku",_ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point}}fn display_handle_type_name(handle: &RawDisplayHandle) -> &'static str {match handle {RawDisplayHandle::Xlib(_) => "Xlib",RawDisplayHandle::Web(_) => "Web",RawDisplayHandle::Wayland(_) => "Wayland",RawDisplayHandle::AppKit(_) => "AppKit",RawDisplayHandle::Orbital(_) => "Orbital",RawDisplayHandle::UiKit(_) => "UiKit",RawDisplayHandle::Xcb(_) => "XCB",RawDisplayHandle::Drm(_) => "DRM",RawDisplayHandle::Gbm(_) => "GBM",RawDisplayHandle::Haiku(_) => "Haiku",RawDisplayHandle::Windows(_) => "Windows",RawDisplayHandle::Android(_) => "Android",_ => "Unknown Name", //don't completely fail to compile if there is a new raw window handle type that's added at some point}}#[cfg(not(target_family = "wasm"))]fn __assert_send() {fn is_send<T: Send>() {}fn is_sync<T: Sync>() {}is_send::<Context<()>>();is_sync::<Context<()>>();is_send::<Surface<(), ()>>();is_send::<Buffer<'static, (), ()>>();/// ```compile_fail/// use softbuffer::Surface;////// fn __is_sync<T: Sync>() {}/// __is_sync::<Surface<(), ()>>();/// ```fn __surface_not_sync() {}/// ```compile_fail/// use softbuffer::Buffer;////// fn __is_sync<T: Sync>() {}/// __is_sync::<Buffer<'static, (), ()>>();/// ```fn __buffer_not_sync() {}}
use raw_window_handle::{HandleError, RawDisplayHandle, RawWindowHandle};use std::error::Error;use std::fmt;use std::num::NonZeroU32;#[derive(Debug)]#[non_exhaustive]/// A sum type of all of the errors that can occur during the operation of this crate.pub enum SoftBufferError {/// A [`raw-window-handle`] error occurred.////// [`raw-window-handle`]: raw_window_handleRawWindowHandle(HandleError),/// The [`RawDisplayHandle`] passed into [`Context::new`] is not supported by this crate.////// [`RawDisplayHandle`]: raw_window_handle::RawDisplayHandle/// [`Context::new`]: crate::Context::newUnsupportedDisplayPlatform {/// The platform name of the display that was passed into [`Context::new`].////// This is a human-readable string that describes the platform of the display that was/// passed into [`Context::new`]. The value is not guaranteed to be stable and this/// exists for debugging purposes only.////// [`Context::new`]: crate::Context::newhuman_readable_display_platform_name: &'static str,/// The [`RawDisplayHandle`] that was passed into [`Context::new`].////// [`RawDisplayHandle`]: raw_window_handle::RawDisplayHandle/// [`Context::new`]: crate::Context::newdisplay_handle: RawDisplayHandle,},/// The [`RawWindowHandle`] passed into [`Surface::new`] is not supported by this crate.////// [`RawWindowHandle`]: raw_window_handle::RawWindowHandle/// [`Surface::new`]: crate::Surface::newUnsupportedWindowPlatform {/// The platform name of the window that was passed into [`Surface::new`].////// This is a human-readable string that describes the platform of the window that was/// passed into [`Surface::new`]. The value is not guaranteed to be stable and this/// exists for debugging purposes only.////// [`Surface::new`]: crate::Surface::newhuman_readable_window_platform_name: &'static str,/// The platform name of the display used by the [`Context`].////// It is possible for a window to be created on a different type of display than the/// display that was passed into [`Context::new`]. This is a human-readable string that/// describes the platform of the display that was passed into [`Context::new`]. The value/// is not guaranteed to be stable and this exists for debugging purposes only.////// [`Context`]: crate::Context/// [`Context::new`]: crate::Context::newhuman_readable_display_platform_name: &'static str,/// The [`RawWindowHandle`] that was passed into [`Surface::new`].////// [`RawWindowHandle`]: raw_window_handle::RawWindowHandle/// [`Surface::new`]: crate::Surface::newwindow_handle: RawWindowHandle,},/// The [`RawWindowHandle`] passed into [`Surface::new`] is missing necessary fields.////// [`RawWindowHandle`]: raw_window_handle::RawWindowHandle/// [`Surface::new`]: crate::Surface::newIncompleteWindowHandle,/// The [`RawDisplayHandle`] passed into [`Context::new`] is missing necessary fields.////// [`RawDisplayHandle`]: raw_window_handle::RawDisplayHandle/// [`Context::new`]: crate::Context::newIncompleteDisplayHandle,/// The provided size is outside of the range supported by the backend.SizeOutOfRange {/// The width that was out of range.width: NonZeroU32,/// The height that was out of range.height: NonZeroU32,},/// The provided damage rect is outside of the range supported by the backend.DamageOutOfRange {/// The damage rect that was out of range.rect: crate::Rect,},/// A platform-specific backend error occurred.////// The first field provides a human-readable description of the error. The second field/// provides the actual error that occurred. Note that the second field is, under the hood,/// a private wrapper around the actual error, preventing the user from downcasting to the/// actual error type.PlatformError(Option<String>, Option<Box<dyn Error>>),/// This function is unimplemented on this platform.Unimplemented,}impl fmt::Display for SoftBufferError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {Self::RawWindowHandle(err) => fmt::Display::fmt(err, f),Self::UnsupportedDisplayPlatform {human_readable_display_platform_name,display_handle,} => write!(f,"The provided display returned an unsupported platform: {}.\nDisplay handle: {:?}",human_readable_display_platform_name, display_handle),Self::UnsupportedWindowPlatform {human_readable_window_platform_name,human_readable_display_platform_name,window_handle,} => write!(f,"The provided window returned an unsupported platform: {}, {}.\nWindow handle: {:?}",human_readable_window_platform_name, human_readable_display_platform_name, window_handle),Self::IncompleteWindowHandle => write!(f, "The provided window handle is null."),Self::IncompleteDisplayHandle => write!(f, "The provided display handle is null."),Self::SizeOutOfRange { width, height } => write!(f,"Surface size {width}x{height} out of range for backend.",),Self::PlatformError(msg, None) => write!(f, "Platform error: {msg:?}"),Self::PlatformError(msg, Some(err)) => write!(f, "Platform error: {msg:?}: {err}"),Self::DamageOutOfRange { rect } => write!(f,"Damage rect {}x{} at ({}, {}) out of range for backend.",rect.width, rect.height, rect.x, rect.y),Self::Unimplemented => write!(f, "This function is unimplemented on this platform."),}}}impl std::error::Error for SoftBufferError {fn source(&self) -> Option<&(dyn Error + 'static)> {match self {Self::RawWindowHandle(err) => Some(err),Self::PlatformError(_, err) => err.as_deref(),_ => None,}}}impl From<HandleError> for SoftBufferError {fn from(err: HandleError) -> Self {Self::RawWindowHandle(err)}}/// Simple unit error type used to bubble up rejected platforms.pub(crate) enum InitError<D> {/// Failed to initialize.Failure(SoftBufferError),/// Cannot initialize this handle on this platform.Unsupported(D),}impl<T> From<SoftBufferError> for InitError<T> {fn from(err: SoftBufferError) -> Self {Self::Failure(err)}}impl<T> From<HandleError> for InitError<T> {fn from(err: HandleError) -> Self {Self::Failure(err.into())}}/// Convenient wrapper to cast errors into SoftBufferError.#[allow(dead_code)]pub(crate) trait SwResultExt<T> {fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError>;}impl<T, E: std::error::Error + 'static> SwResultExt<T> for Result<T, E> {fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError> {self.map_err(|e| {SoftBufferError::PlatformError(Some(msg.into()), Some(Box::new(LibraryError(e))))})}}impl<T> SwResultExt<T> for Option<T> {fn swbuf_err(self, msg: impl Into<String>) -> Result<T, SoftBufferError> {self.ok_or_else(|| SoftBufferError::PlatformError(Some(msg.into()), None))}}/// A wrapper around a library error.////// This prevents `x11-dl` and `x11rb` from becoming public dependencies, since users cannot downcast/// to this type.#[allow(dead_code)]struct LibraryError<E>(E);impl<E: fmt::Debug> fmt::Debug for LibraryError<E> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {fmt::Debug::fmt(&self.0, f)}}impl<E: fmt::Display> fmt::Display for LibraryError<E> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {fmt::Display::fmt(&self.0, f)}}impl<E: fmt::Debug + fmt::Display> std::error::Error for LibraryError<E> {}
//! Implementation of software buffering for X11.//!//! This module converts the input buffer into an XImage and then sends it over the wire to be//! drawn by the X server. The SHM extension is used if available.#![allow(clippy::uninlined_format_args)]use crate::backend_interface::*;use crate::error::{InitError, SwResultExt};use crate::{util, Rect, SoftBufferError};use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, XcbDisplayHandle,XcbWindowHandle,};use rustix::{fd::{AsFd, BorrowedFd, OwnedFd},mm, shm as posix_shm,};use std::{collections::HashSet,fmt,fs::File,io, mem,num::{NonZeroU16, NonZeroU32},ptr::{null_mut, NonNull},slice,sync::Arc,};use as_raw_xcb_connection::AsRawXcbConnection;use x11rb::connection::{Connection, SequenceNumber};use x11rb::cookie::Cookie;use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};use x11rb::protocol::shm::{self, ConnectionExt as _};use x11rb::protocol::xproto::{self, ConnectionExt as _, ImageOrder, VisualClass, Visualid};use x11rb::xcb_ffi::XCBConnection;#[derive(Debug)]pub struct X11DisplayImpl<D: ?Sized> {/// The handle to the XCB connection.connection: Option<XCBConnection>,/// SHM extension is available.is_shm_available: bool,/// All visuals using softbuffer's pixel representationsupported_visuals: HashSet<Visualid>,/// The generic display where the `connection` field comes from.////// Without `&mut`, the underlying connection cannot be closed without other unsafe behavior./// With `&mut`, the connection can be dropped without us knowing about it. Therefore, we/// cannot provide `&mut` access to this field._display: D,}impl<D: HasDisplayHandle + ?Sized> ContextInterface<D> for Arc<X11DisplayImpl<D>> {/// Create a new `X11DisplayImpl`.fn new(display: D) -> Result<Self, InitError<D>>whereD: Sized,{// Get the underlying libxcb handle.let raw = display.display_handle()?.as_raw();let xcb_handle = match raw {RawDisplayHandle::Xcb(xcb_handle) => xcb_handle,RawDisplayHandle::Xlib(xlib) => {// Convert to an XCB handle.let connection = xlib.display.map(|display| {// Get the underlying XCB connection.// SAFETY: The user has asserted that the display handle is valid.unsafe {let display = tiny_xlib::Display::from_ptr(display.as_ptr());NonNull::new_unchecked(display.as_raw_xcb_connection()).cast()}});// Construct the equivalent XCB display and window handles.XcbDisplayHandle::new(connection, xlib.screen)}_ => return Err(InitError::Unsupported(display)),};// Validate the display handle to ensure we can use it.let connection = match xcb_handle.connection {Some(connection) => {// Wrap the display handle in an x11rb connection.// SAFETY: We don't own the connection, so don't drop it. We also assert that the connection is valid.let result =unsafe { XCBConnection::from_raw_xcb_connection(connection.as_ptr(), false) };result.swbuf_err("Failed to wrap XCB connection")?}None => {// The user didn't provide an XCB connection, so create our own.tracing::info!("no XCB connection provided by the user, so spawning our own");XCBConnection::connect(None).swbuf_err("Failed to spawn XCB connection")?.0}};let is_shm_available = is_shm_available(&connection);if !is_shm_available {tracing::warn!("SHM extension is not available. Performance may be poor.");}let supported_visuals = supported_visuals(&connection);Ok(Arc::new(X11DisplayImpl {connection: Some(connection),is_shm_available,supported_visuals,_display: display,}))}}impl<D: ?Sized> X11DisplayImpl<D> {fn connection(&self) -> &XCBConnection {self.connection.as_ref().expect("X11DisplayImpl::connection() called after X11DisplayImpl::drop()")}}/// The handle to an X11 drawing context.#[derive(Debug)]pub struct X11Impl<D: ?Sized, W: ?Sized> {/// X display this window belongs to.display: Arc<X11DisplayImpl<D>>,/// The window to draw to.window: xproto::Window,/// The graphics context to use when drawing.gc: xproto::Gcontext,/// The depth (bits per pixel) of the drawing context.depth: u8,/// The visual ID of the drawing context.visual_id: u32,/// The buffer we draw to.buffer: Buffer,/// Buffer has been presented.buffer_presented: bool,/// The current buffer width/height.size: Option<(NonZeroU16, NonZeroU16)>,/// Keep the window alive.window_handle: W,}/// The buffer that is being drawn to.#[derive(Debug)]enum Buffer {/// A buffer implemented using shared memory to prevent unnecessary copying.Shm(ShmBuffer),/// A normal buffer that we send over the wire.Wire(util::PixelBuffer),}#[derive(Debug)]struct ShmBuffer {/// The shared memory segment, paired with its ID.seg: Option<(ShmSegment, shm::Seg)>,/// A cookie indicating that the shared memory segment is ready to be used.////// We can't soundly read from or write to the SHM segment until the X server is done processing the/// `shm::PutImage` request. However, the X server handles requests in order, which means that, if/// we send a very small request after the `shm::PutImage` request, then the X server will have to/// process that request before it can process the `shm::PutImage` request. Therefore, we can use/// the reply to that small request to determine when the `shm::PutImage` request is done.////// In this case, we use `GetInputFocus` since it is a very small request.////// We store the sequence number instead of the `Cookie` since we cannot hold a self-referential/// reference to the `connection` field.done_processing: Option<SequenceNumber>,}impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> SurfaceInterface<D, W> for X11Impl<D, W> {type Context = Arc<X11DisplayImpl<D>>;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;/// Create a new `X11Impl` from a `HasWindowHandle`.fn new(window_src: W, display: &Arc<X11DisplayImpl<D>>) -> Result<Self, InitError<W>> {// Get the underlying raw window handle.let raw = window_src.window_handle()?.as_raw();let window_handle = match raw {RawWindowHandle::Xcb(xcb) => xcb,RawWindowHandle::Xlib(xlib) => {let window = NonZeroU32::new(xlib.window as u32).ok_or(SoftBufferError::IncompleteWindowHandle)?;let mut xcb_window_handle = XcbWindowHandle::new(window);xcb_window_handle.visual_id = NonZeroU32::new(xlib.visual_id as u32);xcb_window_handle}_ => {return Err(InitError::Unsupported(window_src));}};tracing::trace!("new: window_handle={:X}", window_handle.window);let window = window_handle.window.get();// Run in parallel: start getting the window depth and (if necessary) visual.let display2 = display.clone();let tokens = {let geometry_token = display2.connection().get_geometry(window).swbuf_err("Failed to send geometry request")?;let window_attrs_token = if window_handle.visual_id.is_none() {Some(display2.connection().get_window_attributes(window).swbuf_err("Failed to send window attributes request")?,)} else {None};(geometry_token, window_attrs_token)};// Create a new graphics context to draw to.let gc = display.connection().generate_id().swbuf_err("Failed to generate GC ID")?;display.connection().create_gc(gc,window,&xproto::CreateGCAux::new().graphics_exposures(0),).swbuf_err("Failed to send GC creation request")?.check().swbuf_err("Failed to create GC")?;// Finish getting the depth of the window.let (geometry_reply, visual_id) = {let (geometry_token, window_attrs_token) = tokens;let geometry_reply = geometry_token.reply().swbuf_err("Failed to get geometry reply")?;let visual_id = match window_attrs_token {None => window_handle.visual_id.unwrap().get(),Some(window_attrs) => {window_attrs.reply().swbuf_err("Failed to get window attributes reply")?.visual}};(geometry_reply, visual_id)};if !display.supported_visuals.contains(&visual_id) {return Err(SoftBufferError::PlatformError(Some(format!("Visual 0x{visual_id:x} does not use softbuffer's pixel format and is unsupported")),None,).into());}// See if SHM is available.let buffer = if display.is_shm_available {// SHM is available.Buffer::Shm(ShmBuffer {seg: None,done_processing: None,})} else {// SHM is not available.Buffer::Wire(util::PixelBuffer(Vec::new()))};Ok(Self {display: display.clone(),window,gc,depth: geometry_reply.depth,visual_id,buffer,buffer_presented: false,size: None,window_handle: window_src,})}#[inline]fn window(&self) -> &W {&self.window_handle}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {tracing::trace!("resize: window={:X}, size={}x{}",self.window,width,height);// Width and height should fit in u16.let width: NonZeroU16 = width.try_into().or(Err(SoftBufferError::SizeOutOfRange { width, height }))?;let height: NonZeroU16 = height.try_into().or(Err(SoftBufferError::SizeOutOfRange {width: width.into(),height,}))?;if self.size != Some((width, height)) {self.buffer_presented = false;self.buffer.resize(self.display.connection(), width.get(), height.get()).swbuf_err("Failed to resize X11 buffer")?;// We successfully resized the buffer.self.size = Some((width, height));}Ok(())}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {tracing::trace!("buffer_mut: window={:X}", self.window);// Finish waiting on the previous `shm::PutImage` request, if any.self.buffer.finish_wait(self.display.connection())?;// We can now safely call `buffer_mut` on the buffer.Ok(BufferImpl(self))}fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {tracing::trace!("fetch: window={:X}", self.window);let (width, height) = self.size.expect("Must set size of surface before calling `fetch()`");// TODO: Is it worth it to do SHM here? Probably not.let reply = self.display.connection().get_image(xproto::ImageFormat::Z_PIXMAP,self.window,0,0,width.get(),height.get(),u32::MAX,).swbuf_err("Failed to send image fetching request")?.reply().swbuf_err("Failed to fetch image from window")?;if reply.depth == self.depth && reply.visual == self.visual_id {let mut out = vec![0u32; reply.data.len() / 4];bytemuck::cast_slice_mut::<u32, u8>(&mut out).copy_from_slice(&reply.data);Ok(out)} else {Err(SoftBufferError::PlatformError(Some("Mismatch between reply and window data".into()),None,))}}}#[derive(Debug)]pub struct BufferImpl<'a, D: ?Sized, W: ?Sized>(&'a mut X11Impl<D, W>);impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle + ?Sized> BufferInterfacefor BufferImpl<'_, D, W>{fn width(&self) -> NonZeroU32 {self.0.size.unwrap().0.into()}fn height(&self) -> NonZeroU32 {self.0.size.unwrap().1.into()}#[inline]fn pixels(&self) -> &[u32] {// SAFETY: We called `finish_wait` on the buffer, so it is safe to call `buffer()`.unsafe { self.0.buffer.buffer() }}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {// SAFETY: We called `finish_wait` on the buffer, so it is safe to call `buffer_mut`.unsafe { self.0.buffer.buffer_mut() }}fn age(&self) -> u8 {if self.0.buffer_presented {1} else {0}}/// Push the buffer to the window.fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {let imp = self.0;let (surface_width, surface_height) = imp.size.expect("Must set size of surface before calling `present_with_damage()`");tracing::trace!("present: window={:X}", imp.window);match imp.buffer {Buffer::Wire(ref wire) => {// This is a suboptimal strategy, raise a stink in the debug logs.tracing::debug!("Falling back to non-SHM method for window drawing.");imp.display.connection().put_image(xproto::ImageFormat::Z_PIXMAP,imp.window,imp.gc,surface_width.get(),surface_height.get(),0,0,0,imp.depth,bytemuck::cast_slice(wire),).map(|c| c.ignore_error()).push_err().swbuf_err("Failed to draw image to window")?;}Buffer::Shm(ref mut shm) => {// If the X server is still processing the last image, wait for it to finish.// SAFETY: We know that we called finish_wait() before this.// Put the image into the window.if let Some((_, segment_id)) = shm.seg {damage.iter().try_for_each(|rect| {let (src_x, src_y, dst_x, dst_y, width, height) = (|| {Some((u16::try_from(rect.x).ok()?,u16::try_from(rect.y).ok()?,i16::try_from(rect.x).ok()?,i16::try_from(rect.y).ok()?,u16::try_from(rect.width.get()).ok()?,u16::try_from(rect.height.get()).ok()?,))})().ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;imp.display.connection().shm_put_image(imp.window,imp.gc,surface_width.get(),surface_height.get(),src_x,src_y,width,height,dst_x,dst_y,imp.depth,xproto::ImageFormat::Z_PIXMAP.into(),false,segment_id,0,).push_err().map(|c| c.ignore_error()).swbuf_err("Failed to draw image to window")}).and_then(|()| {// Send a short request to act as a notification for when the X server is done processing the image.shm.begin_wait(imp.display.connection()).swbuf_err("Failed to draw image to window")})?;}}}imp.buffer_presented = true;Ok(())}fn present(self) -> Result<(), SoftBufferError> {let (width, height) = self.0.size.expect("Must set size of surface before calling `present()`");self.present_with_damage(&[Rect {x: 0,y: 0,width: width.into(),height: height.into(),}])}}impl Buffer {/// Resize the buffer to the given size.fn resize(&mut self,conn: &impl Connection,width: u16,height: u16,) -> Result<(), PushBufferError> {match self {Buffer::Shm(ref mut shm) => shm.alloc_segment(conn, total_len(width, height)),Buffer::Wire(wire) => {wire.resize(total_len(width, height) / 4, 0);Ok(())}}}/// Finish waiting for an ongoing `shm::PutImage` request, if there is one.fn finish_wait(&mut self, conn: &impl Connection) -> Result<(), SoftBufferError> {if let Buffer::Shm(ref mut shm) = self {shm.finish_wait(conn).swbuf_err("Failed to wait for X11 buffer")?;}Ok(())}/// Get a reference to the buffer.////// # Safety////// `finish_wait()` must be called in between `shm::PutImage` requests and this function.#[inline]unsafe fn buffer(&self) -> &[u32] {match self {Buffer::Shm(ref shm) => unsafe { shm.as_ref() },Buffer::Wire(wire) => wire,}}/// Get a mutable reference to the buffer.////// # Safety////// `finish_wait()` must be called in between `shm::PutImage` requests and this function.#[inline]unsafe fn buffer_mut(&mut self) -> &mut [u32] {match self {Buffer::Shm(ref mut shm) => unsafe { shm.as_mut() },Buffer::Wire(wire) => wire,}}}impl ShmBuffer {/// Allocate a new `ShmSegment` of the given size.fn alloc_segment(&mut self,conn: &impl Connection,buffer_size: usize,) -> Result<(), PushBufferError> {// Round the size up to the next power of two to prevent frequent reallocations.let size = buffer_size.next_power_of_two();// Get the size of the segment currently in use.let needs_realloc = match self.seg {Some((ref seg, _)) => seg.size() < size,None => true,};// Reallocate if necessary.if needs_realloc {let new_seg = ShmSegment::new(size, buffer_size)?;self.associate(conn, new_seg)?;} else if let Some((ref mut seg, _)) = self.seg {seg.set_buffer_size(buffer_size);}Ok(())}/// Get the SHM buffer as a reference.////// # Safety////// `finish_wait()` must be called before this function is.#[inline]unsafe fn as_ref(&self) -> &[u32] {match self.seg.as_ref() {Some((seg, _)) => {let buffer_size = seg.buffer_size();// SAFETY: No other code should be able to access the segment.bytemuck::cast_slice(unsafe { &seg.as_ref()[..buffer_size] })}None => {// Nothing has been allocated yet.&[]}}}/// Get the SHM buffer as a mutable reference.////// # Safety////// `finish_wait()` must be called before this function is.#[inline]unsafe fn as_mut(&mut self) -> &mut [u32] {match self.seg.as_mut() {Some((seg, _)) => {let buffer_size = seg.buffer_size();// SAFETY: No other code should be able to access the segment.bytemuck::cast_slice_mut(unsafe { &mut seg.as_mut()[..buffer_size] })}None => {// Nothing has been allocated yet.&mut []}}}/// Associate an SHM segment with the server.fn associate(&mut self,conn: &impl Connection,seg: ShmSegment,) -> Result<(), PushBufferError> {// Register the guard.let new_id = conn.generate_id()?;conn.shm_attach_fd(new_id, seg.as_fd().try_clone_to_owned().unwrap(), true)?.ignore_error();// Take out the old one and detach it.if let Some((old_seg, old_id)) = self.seg.replace((seg, new_id)) {// Wait for the old segment to finish processing.self.finish_wait(conn)?;conn.shm_detach(old_id)?.ignore_error();// Drop the old segment.drop(old_seg);}Ok(())}/// Begin waiting for the SHM processing to finish.fn begin_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {let cookie = c.get_input_focus()?.sequence_number();let old_cookie = self.done_processing.replace(cookie);debug_assert!(old_cookie.is_none());Ok(())}/// Wait for the SHM processing to finish.fn finish_wait(&mut self, c: &impl Connection) -> Result<(), PushBufferError> {if let Some(done_processing) = self.done_processing.take() {// Cast to a cookie and wait on it.let cookie = Cookie::<_, xproto::GetInputFocusReply>::new(c, done_processing);cookie.reply()?;}Ok(())}}#[derive(Debug)]struct ShmSegment {id: File,ptr: NonNull<i8>,size: usize,buffer_size: usize,}// SAFETY: We respect Rust's mutability rules for the inner allocation.unsafe impl Send for ShmSegment {}impl ShmSegment {/// Create a new `ShmSegment` with the given size.fn new(size: usize, buffer_size: usize) -> io::Result<Self> {assert!(size >= buffer_size);// Create a shared memory segment.let id = File::from(create_shm_id()?);// Set its length.id.set_len(size as u64)?;// Map the shared memory to our file descriptor space.let ptr = NonNull::new(unsafe {mm::mmap(null_mut(),size,mm::ProtFlags::READ | mm::ProtFlags::WRITE,mm::MapFlags::SHARED,&id,0,)?}).ok_or(io::Error::new(io::ErrorKind::Other,"unexpected null when mapping SHM segment",))?.cast();Ok(Self {id,ptr,size,buffer_size,})}/// Get this shared memory segment as a reference.////// # Safety////// One must ensure that no other processes are writing to this memory.unsafe fn as_ref(&self) -> &[i8] {unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.size) }}/// Get this shared memory segment as a mutable reference.////// # Safety////// One must ensure that no other processes are reading from or writing to this memory.unsafe fn as_mut(&mut self) -> &mut [i8] {unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) }}/// Set the size of the buffer for this shared memory segment.fn set_buffer_size(&mut self, buffer_size: usize) {assert!(self.size >= buffer_size);self.buffer_size = buffer_size}/// Get the size of the buffer for this shared memory segment.fn buffer_size(&self) -> usize {self.buffer_size}/// Get the size of this shared memory segment.fn size(&self) -> usize {self.size}}impl AsFd for ShmSegment {fn as_fd(&self) -> BorrowedFd<'_> {self.id.as_fd()}}impl Drop for ShmSegment {fn drop(&mut self) {unsafe {// Unmap the shared memory segment.mm::munmap(self.ptr.as_ptr().cast(), self.size).ok();}}}impl<D: ?Sized> Drop for X11DisplayImpl<D> {fn drop(&mut self) {// Make sure that the x11rb connection is dropped before its source is.self.connection = None;}}impl<D: ?Sized, W: ?Sized> Drop for X11Impl<D, W> {fn drop(&mut self) {// If we used SHM, make sure it's detached from the server.if let Buffer::Shm(mut shm) = mem::replace(&mut self.buffer,Buffer::Wire(util::PixelBuffer(Vec::new())),) {// If we were in the middle of processing a buffer, wait for it to finish.shm.finish_wait(self.display.connection()).ok();if let Some((segment, seg_id)) = shm.seg.take() {if let Ok(token) = self.display.connection().shm_detach(seg_id) {token.ignore_error();}// Drop the segment.drop(segment);}}// Close the graphics context that we created.if let Ok(token) = self.display.connection().free_gc(self.gc) {token.ignore_error();}}}/// Create a shared memory identifier.fn create_shm_id() -> io::Result<OwnedFd> {use posix_shm::{Mode, OFlags};let mut rng = fastrand::Rng::new();let mut name = String::with_capacity(23);// Only try four times; the chances of a collision on this space is astronomically low, so if// we miss four times in a row we're probably under attack.for i in 0..4 {name.clear();name.push_str("softbuffer-x11-");name.extend(std::iter::repeat_with(|| rng.alphanumeric()).take(7));// Try to create the shared memory segment.match posix_shm::open(&name,OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,Mode::RWXU,) {Ok(id) => {posix_shm::unlink(&name).ok();return Ok(id);}Err(rustix::io::Errno::EXIST) => {tracing::warn!("x11: SHM ID collision at {} on try number {}", name, i);}Err(e) => return Err(e.into()),};}Err(io::Error::new(io::ErrorKind::Other,"failed to generate a non-existent SHM name",))}/// Test to see if SHM is available.fn is_shm_available(c: &impl Connection) -> bool {// Create a small SHM segment.let seg = match ShmSegment::new(0x1000, 0x1000) {Ok(seg) => seg,Err(_) => return false,};// Attach and detach it.let seg_id = match c.generate_id() {Ok(id) => id,Err(_) => return false,};let (attach, detach) = {let attach = c.shm_attach_fd(seg_id, seg.as_fd().try_clone_to_owned().unwrap(), false);let detach = c.shm_detach(seg_id);match (attach, detach) {(Ok(attach), Ok(detach)) => (attach, detach),_ => return false,}};// Check the replies.matches!((attach.check(), detach.check()), (Ok(()), Ok(())))}/// Collect all visuals that use softbuffer's pixel formatfn supported_visuals(c: &impl Connection) -> HashSet<Visualid> {// Check that depth 24 uses 32 bits per pixels// HACK(notgull): Also support depth 32 for transparent visuals.// Otherwise winit users get weird errors.if !c.setup().pixmap_formats.iter().any(|f| (f.depth == 24 || f.depth == 32) && f.bits_per_pixel == 32){tracing::warn!("X11 server does not have a depth 24/32 format with 32 bits per pixel");return HashSet::new();}// How does the server represent red, green, blue components of a pixel?#[cfg(target_endian = "little")]let own_byte_order = ImageOrder::LSB_FIRST;#[cfg(target_endian = "big")]let own_byte_order = ImageOrder::MSB_FIRST;let expected_masks = if c.setup().image_byte_order == own_byte_order {(0xff0000, 0xff00, 0xff)} else {// This is the byte-swapped version of our wished-for format(0xff00, 0xff0000, 0xff000000)};c.setup().roots.iter().flat_map(|screen| {screen.allowed_depths.iter().filter(|depth| depth.depth == 24 || depth.depth == 32).flat_map(|depth| {depth.visuals.iter().filter(|visual| {// Ignore grayscale or indexes / color palette visualsvisual.class == VisualClass::TRUE_COLOR|| visual.class == VisualClass::DIRECT_COLOR}).filter(|visual| {// Colors must be laid out as softbuffer expectsexpected_masks == (visual.red_mask, visual.green_mask, visual.blue_mask)}).map(|visual| visual.visual_id)})}).collect()}/// An error that can occur when pushing a buffer to the window.#[derive(Debug)]enum PushBufferError {/// We encountered an X11 error.X11(ReplyError),/// We exhausted the XID space.XidExhausted,/// A system error occurred while creating the shared memory segment.System(io::Error),}impl fmt::Display for PushBufferError {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {Self::X11(e) => write!(f, "X11 error: {}", e),Self::XidExhausted => write!(f, "XID space exhausted"),Self::System(e) => write!(f, "System error: {}", e),}}}impl std::error::Error for PushBufferError {}impl From<ConnectionError> for PushBufferError {fn from(e: ConnectionError) -> Self {Self::X11(ReplyError::ConnectionError(e))}}impl From<ReplyError> for PushBufferError {fn from(e: ReplyError) -> Self {Self::X11(e)}}impl From<ReplyOrIdError> for PushBufferError {fn from(e: ReplyOrIdError) -> Self {match e {ReplyOrIdError::ConnectionError(e) => Self::X11(ReplyError::ConnectionError(e)),ReplyOrIdError::X11Error(e) => Self::X11(ReplyError::X11Error(e)),ReplyOrIdError::IdsExhausted => Self::XidExhausted,}}}impl From<io::Error> for PushBufferError {fn from(e: io::Error) -> Self {Self::System(e)}}/// Convenient wrapper to cast errors into PushBufferError.trait PushResultExt<T, E> {fn push_err(self) -> Result<T, PushBufferError>;}impl<T, E: Into<PushBufferError>> PushResultExt<T, E> for Result<T, E> {fn push_err(self) -> Result<T, PushBufferError> {self.map_err(Into::into)}}/// Get the length that a slice needs to be to hold a buffer of the given dimensions.#[inline(always)]fn total_len(width: u16, height: u16) -> usize {let width: usize = width.into();let height: usize = height.into();width.checked_mul(height).and_then(|len| len.checked_mul(4)).unwrap_or_else(|| panic!("Dimensions are too large: ({} x {})", width, height))}
//! Implementation of software buffering for Windows.//!//! This module converts the input buffer into a bitmap and then stretches it to the window.use crate::backend_interface::*;use crate::{Rect, SoftBufferError};use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};use std::io;use std::marker::PhantomData;use std::mem;use std::num::{NonZeroI32, NonZeroU32};use std::ptr::{self, NonNull};use std::slice;use std::sync::{mpsc, Mutex, OnceLock};use std::thread;use windows_sys::Win32::Foundation::HWND;use windows_sys::Win32::Graphics::Gdi;const ZERO_QUAD: Gdi::RGBQUAD = Gdi::RGBQUAD {rgbBlue: 0,rgbGreen: 0,rgbRed: 0,rgbReserved: 0,};#[derive(Debug)]struct Buffer {dc: Gdi::HDC,bitmap: Gdi::HBITMAP,pixels: NonNull<u32>,width: NonZeroI32,height: NonZeroI32,presented: bool,}unsafe impl Send for Buffer {}impl Drop for Buffer {fn drop(&mut self) {unsafe {Gdi::DeleteObject(self.bitmap);}Allocator::get().deallocate(self.dc);}}impl Buffer {fn new(window_dc: Gdi::HDC, width: NonZeroI32, height: NonZeroI32) -> Self {let dc = Allocator::get().allocate(window_dc);assert!(!dc.is_null());// Create a new bitmap info struct.let bitmap_info = BitmapInfo {bmi_header: Gdi::BITMAPINFOHEADER {biSize: mem::size_of::<Gdi::BITMAPINFOHEADER>() as u32,biWidth: width.get(),biHeight: -height.get(),biPlanes: 1,biBitCount: 32,biCompression: Gdi::BI_BITFIELDS,biSizeImage: 0,biXPelsPerMeter: 0,biYPelsPerMeter: 0,biClrUsed: 0,biClrImportant: 0,},bmi_colors: [Gdi::RGBQUAD {rgbRed: 0xff,..ZERO_QUAD},Gdi::RGBQUAD {rgbGreen: 0xff,..ZERO_QUAD},Gdi::RGBQUAD {rgbBlue: 0xff,..ZERO_QUAD},],};// XXX alignment?// XXX better to use CreateFileMapping, and pass hSection?// XXX test return value?let mut pixels: *mut u32 = ptr::null_mut();let bitmap = unsafe {Gdi::CreateDIBSection(dc,&bitmap_info as *const BitmapInfo as *const _,Gdi::DIB_RGB_COLORS,&mut pixels as *mut *mut u32 as _,ptr::null_mut(),0,)};assert!(!bitmap.is_null());let pixels = NonNull::new(pixels).unwrap();unsafe {Gdi::SelectObject(dc, bitmap);}Self {dc,bitmap,width,height,pixels,presented: false,}}#[inline]fn pixels(&self) -> &[u32] {unsafe {slice::from_raw_parts(self.pixels.as_ptr(),i32::from(self.width) as usize * i32::from(self.height) as usize,)}}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {unsafe {slice::from_raw_parts_mut(self.pixels.as_ptr(),i32::from(self.width) as usize * i32::from(self.height) as usize,)}}}/// The handle to a window for software buffering.#[derive(Debug)]pub struct Win32Impl<D: ?Sized, W> {/// The window handle.window: OnlyUsedFromOrigin<HWND>,/// The device context for the window.dc: OnlyUsedFromOrigin<Gdi::HDC>,/// The buffer used to hold the image.buffer: Option<Buffer>,/// The handle for the window.////// This should be kept alive in order to keep `window` valid.handle: W,/// The display handle.////// We don't use this, but other code might._display: PhantomData<D>,}impl<D: ?Sized, W> Drop for Win32Impl<D, W> {fn drop(&mut self) {// Release our resources.Allocator::get().release(self.window.0, self.dc.0);}}/// The Win32-compatible bitmap information.#[repr(C)]struct BitmapInfo {bmi_header: Gdi::BITMAPINFOHEADER,bmi_colors: [Gdi::RGBQUAD; 3],}impl<D: HasDisplayHandle, W: HasWindowHandle> Win32Impl<D, W> {fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {let buffer = self.buffer.as_mut().unwrap();unsafe {for rect in damage.iter().copied() {let (x, y, width, height) = (|| {Some((i32::try_from(rect.x).ok()?,i32::try_from(rect.y).ok()?,i32::try_from(rect.width.get()).ok()?,i32::try_from(rect.height.get()).ok()?,))})().ok_or(SoftBufferError::DamageOutOfRange { rect })?;Gdi::BitBlt(self.dc.0,x,y,width,height,buffer.dc,x,y,Gdi::SRCCOPY,);}// Validate the window.Gdi::ValidateRect(self.window.0, ptr::null_mut());}buffer.presented = true;Ok(())}}impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for Win32Impl<D, W> {type Context = D;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;/// Create a new `Win32Impl` from a `Win32WindowHandle`.fn new(window: W, _display: &D) -> Result<Self, crate::error::InitError<W>> {let raw = window.window_handle()?.as_raw();let RawWindowHandle::Win32(handle) = raw else {return Err(crate::InitError::Unsupported(window));};// Get the handle to the device context.// SAFETY: We have confirmed that the window handle is valid.let hwnd = handle.hwnd.get() as HWND;let dc = Allocator::get().get_dc(hwnd);// GetDC returns null if there is a platform error.if dc.is_null() {return Err(SoftBufferError::PlatformError(Some("Device Context is null".into()),Some(Box::new(io::Error::last_os_error())),).into());}Ok(Self {dc: dc.into(),window: hwnd.into(),buffer: None,handle: window,_display: PhantomData,})}#[inline]fn window(&self) -> &W {&self.handle}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {let (width, height) = (|| {let width = NonZeroI32::try_from(width).ok()?;let height = NonZeroI32::try_from(height).ok()?;Some((width, height))})().ok_or(SoftBufferError::SizeOutOfRange { width, height })?;if let Some(buffer) = self.buffer.as_ref() {if buffer.width == width && buffer.height == height {return Ok(());}}self.buffer = Some(Buffer::new(self.dc.0, width, height));Ok(())}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {if self.buffer.is_none() {panic!("Must set size of surface before calling `buffer_mut()`");}Ok(BufferImpl(self))}/// Fetch the buffer from the window.fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {Err(SoftBufferError::Unimplemented)}}#[derive(Debug)]pub struct BufferImpl<'a, D, W>(&'a mut Win32Impl<D, W>);impl<D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'_, D, W> {fn width(&self) -> NonZeroU32 {self.0.buffer.as_ref().unwrap().width.try_into().unwrap()}fn height(&self) -> NonZeroU32 {self.0.buffer.as_ref().unwrap().height.try_into().unwrap()}#[inline]fn pixels(&self) -> &[u32] {self.0.buffer.as_ref().unwrap().pixels()}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {self.0.buffer.as_mut().unwrap().pixels_mut()}fn age(&self) -> u8 {match self.0.buffer.as_ref() {Some(buffer) if buffer.presented => 1,_ => 0,}}fn present(self) -> Result<(), SoftBufferError> {let imp = self.0;let buffer = imp.buffer.as_ref().unwrap();imp.present_with_damage(&[Rect {x: 0,y: 0,// We know width/height will be non-negativewidth: buffer.width.try_into().unwrap(),height: buffer.height.try_into().unwrap(),}])}fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {let imp = self.0;imp.present_with_damage(damage)}}/// Allocator for device contexts.////// Device contexts can only be allocated or freed on the thread that originated them./// So we spawn a thread specifically for allocating and freeing device contexts./// This is the interface to that thread.struct Allocator {/// The channel for sending commands.sender: Mutex<mpsc::Sender<Command>>,}impl Allocator {/// Get the global instance of the allocator.fn get() -> &'static Allocator {static ALLOCATOR: OnceLock<Allocator> = OnceLock::new();ALLOCATOR.get_or_init(|| {let (sender, receiver) = mpsc::channel::<Command>();// Create a thread responsible for DC handling.thread::Builder::new().name(concat!("softbuffer_", env!("CARGO_PKG_VERSION"), "_dc_allocator").into()).spawn(move || {while let Ok(command) = receiver.recv() {command.handle();}}).expect("failed to spawn the DC allocator thread");Allocator {sender: Mutex::new(sender),}})}/// Send a command to the allocator thread.fn send_command(&self, cmd: Command) {self.sender.lock().unwrap().send(cmd).unwrap();}/// Get the device context for a window.fn get_dc(&self, window: HWND) -> Gdi::HDC {let (callback, waiter) = mpsc::sync_channel(1);// Send command to the allocator.self.send_command(Command::GetDc { window, callback });// Wait for the response back.waiter.recv().unwrap()}/// Allocate a new device context.fn allocate(&self, dc: Gdi::HDC) -> Gdi::HDC {let (callback, waiter) = mpsc::sync_channel(1);// Send command to the allocator.self.send_command(Command::Allocate { dc, callback });// Wait for the response back.waiter.recv().unwrap()}/// Deallocate a device context.fn deallocate(&self, dc: Gdi::HDC) {self.send_command(Command::Deallocate(dc));}/// Release a device context.fn release(&self, owner: HWND, dc: Gdi::HDC) {self.send_command(Command::Release { dc, owner });}}/// Commands to be sent to the allocator.enum Command {/// Call `GetDc` to get the device context for the provided window.GetDc {/// The window to provide a device context for.window: HWND,/// Send back the device context.callback: mpsc::SyncSender<Gdi::HDC>,},/// Allocate a new device context using `GetCompatibleDc`.Allocate {/// The DC to be compatible with.dc: Gdi::HDC,/// Send back the device context.callback: mpsc::SyncSender<Gdi::HDC>,},/// Deallocate a device context.Deallocate(Gdi::HDC),/// Release a window-associated device context.Release {/// The device context to release.dc: Gdi::HDC,/// The window that owns this device context.owner: HWND,},}unsafe impl Send for Command {}impl Command {/// Handle this command.////// This should be called on the allocator thread.fn handle(self) {match self {Self::GetDc { window, callback } => {// Get the DC and send it back.let dc = unsafe { Gdi::GetDC(window) };callback.send(dc).ok();}Self::Allocate { dc, callback } => {// Allocate a DC and send it back.let dc = unsafe { Gdi::CreateCompatibleDC(dc) };callback.send(dc).ok();}Self::Deallocate(dc) => {// Deallocate this DC.unsafe {Gdi::DeleteDC(dc);}}Self::Release { dc, owner } => {// Release this DC.unsafe {Gdi::ReleaseDC(owner, dc);}}}}}#[derive(Debug)]struct OnlyUsedFromOrigin<T>(T);unsafe impl<T> Send for OnlyUsedFromOrigin<T> {}impl<T> From<T> for OnlyUsedFromOrigin<T> {fn from(t: T) -> Self {Self(t)}}
//! Implementation of software buffering for web targets.#![allow(clippy::uninlined_format_args)]use js_sys::Object;use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};use wasm_bindgen::{JsCast, JsValue};use web_sys::ImageData;use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};use crate::backend_interface::*;use crate::error::{InitError, SwResultExt};use crate::{util, NoDisplayHandle, NoWindowHandle, Rect, SoftBufferError};use std::marker::PhantomData;use std::num::NonZeroU32;/// Display implementation for the web platform.////// This just caches the document to prevent having to query it every time.#[derive(Clone, Debug)]pub struct WebDisplayImpl<D> {document: web_sys::Document,_display: D,}impl<D: HasDisplayHandle> ContextInterface<D> for WebDisplayImpl<D> {fn new(display: D) -> Result<Self, InitError<D>> {let raw = display.display_handle()?.as_raw();let RawDisplayHandle::Web(..) = raw else {return Err(InitError::Unsupported(display));};let document = web_sys::window().swbuf_err("`Window` is not present in this runtime")?.document().swbuf_err("`Document` is not present in this runtime")?;Ok(Self {document,_display: display,})}}#[derive(Debug)]pub struct WebImpl<D, W> {/// The handle and context to the canvas that we're drawing to.canvas: Canvas,/// The buffer that we're drawing to.buffer: util::PixelBuffer,/// Buffer has been presented.buffer_presented: bool,/// The current canvas width/height.size: Option<(NonZeroU32, NonZeroU32)>,/// The underlying window handle.window_handle: W,/// The underlying display handle._display: PhantomData<D>,}/// Holding canvas and context for [`HtmlCanvasElement`] or [`OffscreenCanvas`],/// since they have different types.#[derive(Debug)]enum Canvas {Canvas {canvas: HtmlCanvasElement,ctx: CanvasRenderingContext2d,},OffscreenCanvas {canvas: OffscreenCanvas,ctx: OffscreenCanvasRenderingContext2d,},}impl<D: HasDisplayHandle, W: HasWindowHandle> WebImpl<D, W> {fn from_canvas(canvas: HtmlCanvasElement, window: W) -> Result<Self, SoftBufferError> {let ctx = Self::resolve_ctx(canvas.get_context("2d").ok(), "CanvasRenderingContext2d")?;Ok(Self {canvas: Canvas::Canvas { canvas, ctx },buffer: util::PixelBuffer(Vec::new()),buffer_presented: false,size: None,window_handle: window,_display: PhantomData,})}fn from_offscreen_canvas(canvas: OffscreenCanvas, window: W) -> Result<Self, SoftBufferError> {let ctx = Self::resolve_ctx(canvas.get_context("2d").ok(),"OffscreenCanvasRenderingContext2d",)?;Ok(Self {canvas: Canvas::OffscreenCanvas { canvas, ctx },buffer: util::PixelBuffer(Vec::new()),buffer_presented: false,size: None,window_handle: window,_display: PhantomData,})}fn resolve_ctx<T: JsCast>(result: Option<Option<Object>>,name: &str,) -> Result<T, SoftBufferError> {let ctx = result.swbuf_err("Canvas already controlled using `OffscreenCanvas`")?.swbuf_err(format!("A canvas context other than `{name}` was already created"))?.dyn_into().unwrap_or_else(|_| panic!("`getContext(\"2d\") didn't return a `{name}`"));Ok(ctx)}fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {let (buffer_width, _buffer_height) = self.size.expect("Must set size of surface before calling `present_with_damage()`");let union_damage = if let Some(rect) = util::union_damage(damage) {rect} else {return Ok(());};// Create a bitmap from the buffer.let bitmap: Vec<_> = self.buffer.chunks_exact(buffer_width.get() as usize).skip(union_damage.y as usize).take(union_damage.height.get() as usize).flat_map(|row| {row.iter().skip(union_damage.x as usize).take(union_damage.width.get() as usize)}).copied().flat_map(|pixel| [(pixel >> 16) as u8, (pixel >> 8) as u8, pixel as u8, 255]).collect();debug_assert_eq!(bitmap.len() as u32,union_damage.width.get() * union_damage.height.get() * 4);#[cfg(target_feature = "atomics")]#[allow(non_local_definitions)]let result = {// When using atomics, the underlying memory becomes `SharedArrayBuffer`,// which can't be shared with `ImageData`.use js_sys::{Uint8Array, Uint8ClampedArray};use wasm_bindgen::prelude::wasm_bindgen;#[wasm_bindgen]extern "C" {#[wasm_bindgen(js_name = ImageData)]type ImageDataExt;#[wasm_bindgen(catch, constructor, js_class = ImageData)]fn new(array: Uint8ClampedArray, sw: u32) -> Result<ImageDataExt, JsValue>;}let array = Uint8Array::new_with_length(bitmap.len() as u32);array.copy_from(&bitmap);let array = Uint8ClampedArray::new(&array);ImageDataExt::new(array, union_damage.width.get()).map(JsValue::from).map(ImageData::unchecked_from_js)};#[cfg(not(target_feature = "atomics"))]let result = ImageData::new_with_u8_clamped_array(wasm_bindgen::Clamped(&bitmap),union_damage.width.get(),);// This should only throw an error if the buffer we pass's size is incorrect.let image_data = result.unwrap();for rect in damage {// This can only throw an error if `data` is detached, which is impossible.self.canvas.put_image_data(&image_data,union_damage.x.into(),union_damage.y.into(),(rect.x - union_damage.x).into(),(rect.y - union_damage.y).into(),rect.width.get().into(),rect.height.get().into(),).unwrap();}self.buffer_presented = true;Ok(())}}impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for WebImpl<D, W> {type Context = WebDisplayImpl<D>;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;fn new(window: W, display: &WebDisplayImpl<D>) -> Result<Self, InitError<W>> {let raw = window.window_handle()?.as_raw();let canvas: HtmlCanvasElement = match raw {RawWindowHandle::Web(handle) => {display.document.query_selector(&format!("canvas[data-raw-handle=\"{}\"]", handle.id))// `querySelector` only throws an error if the selector is invalid..unwrap().swbuf_err("No canvas found with the given id")?// We already made sure this was a canvas in `querySelector`..unchecked_into()}RawWindowHandle::WebCanvas(handle) => {let value: &JsValue = unsafe { handle.obj.cast().as_ref() };value.clone().unchecked_into()}RawWindowHandle::WebOffscreenCanvas(handle) => {let value: &JsValue = unsafe { handle.obj.cast().as_ref() };let canvas: OffscreenCanvas = value.clone().unchecked_into();return Self::from_offscreen_canvas(canvas, window).map_err(InitError::Failure);}_ => return Err(InitError::Unsupported(window)),};Self::from_canvas(canvas, window).map_err(InitError::Failure)}/// Get the inner window handle.#[inline]fn window(&self) -> &W {&self.window_handle}/// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`./// Resize the canvas to the given dimensions.fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {if self.size != Some((width, height)) {self.buffer_presented = false;self.buffer.resize(total_len(width.get(), height.get()), 0);self.canvas.set_width(width.get());self.canvas.set_height(height.get());self.size = Some((width, height));}Ok(())}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {Ok(BufferImpl { imp: self })}fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {let (width, height) = self.size.expect("Must set size of surface before calling `fetch()`");let image_data = self.canvas.get_image_data(0., 0., width.get().into(), height.get().into()).ok()// TODO: Can also error if width or height are 0..swbuf_err("`Canvas` contains pixels from a different origin")?;Ok(image_data.data().0.chunks_exact(4).map(|chunk| u32::from_be_bytes([0, chunk[0], chunk[1], chunk[2]])).collect())}}/// Extension methods for the Wasm target on [`Surface`](crate::Surface).pub trait SurfaceExtWeb: Sized {/// Creates a new instance of this struct, using the provided [`HtmlCanvasElement`].////// # Errors/// - If the canvas was already controlled by an `OffscreenCanvas`./// - If a another context then "2d" was already created for this canvas.fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError>;/// Creates a new instance of this struct, using the provided [`OffscreenCanvas`].////// # Errors/// If a another context then "2d" was already created for this canvas.fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result<Self, SoftBufferError>;}impl SurfaceExtWeb for crate::Surface<NoDisplayHandle, NoWindowHandle> {fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError> {let imple = crate::SurfaceDispatch::Web(WebImpl::from_canvas(canvas, NoWindowHandle(()))?);Ok(Self {surface_impl: Box::new(imple),_marker: PhantomData,})}fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result<Self, SoftBufferError> {let imple = crate::SurfaceDispatch::Web(WebImpl::from_offscreen_canvas(offscreen_canvas,NoWindowHandle(()),)?);Ok(Self {surface_impl: Box::new(imple),_marker: PhantomData,})}}impl Canvas {fn set_width(&self, width: u32) {match self {Self::Canvas { canvas, .. } => canvas.set_width(width),Self::OffscreenCanvas { canvas, .. } => canvas.set_width(width),}}fn set_height(&self, height: u32) {match self {Self::Canvas { canvas, .. } => canvas.set_height(height),Self::OffscreenCanvas { canvas, .. } => canvas.set_height(height),}}fn get_image_data(&self, sx: f64, sy: f64, sw: f64, sh: f64) -> Result<ImageData, JsValue> {match self {Canvas::Canvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh),Canvas::OffscreenCanvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh),}}// NOTE: suppress the lint because we mirror `CanvasRenderingContext2D.putImageData()`, and// this is just an internal API used by this module only, so it's not too relevant.#[allow(clippy::too_many_arguments)]fn put_image_data(&self,imagedata: &ImageData,dx: f64,dy: f64,dirty_x: f64,dirty_y: f64,width: f64,height: f64,) -> Result<(), JsValue> {match self {Self::Canvas { ctx, .. } => ctx.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(imagedata, dx, dy, dirty_x, dirty_y, width, height,),Self::OffscreenCanvas { ctx, .. } => ctx.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(imagedata, dx, dy, dirty_x, dirty_y, width, height,),}}}#[derive(Debug)]pub struct BufferImpl<'a, D, W> {imp: &'a mut WebImpl<D, W>,}impl<D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'_, D, W> {fn width(&self) -> NonZeroU32 {self.imp.size.expect("must set size of surface before calling `width()` on the buffer").0}fn height(&self) -> NonZeroU32 {self.imp.size.expect("must set size of surface before calling `height()` on the buffer").1}fn pixels(&self) -> &[u32] {&self.imp.buffer}fn pixels_mut(&mut self) -> &mut [u32] {&mut self.imp.buffer}fn age(&self) -> u8 {if self.imp.buffer_presented {1} else {0}}/// Push the buffer to the canvas.fn present(self) -> Result<(), SoftBufferError> {let (width, height) = self.imp.size.expect("Must set size of surface before calling `present()`");self.imp.present_with_damage(&[Rect {x: 0,y: 0,width,height,}])}fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {self.imp.present_with_damage(damage)}}#[inline(always)]fn total_len(width: u32, height: u32) -> usize {// Convert width and height to `usize`, then multiply.width.try_into().ok().and_then(|w: usize| height.try_into().ok().and_then(|h| w.checked_mul(h))).unwrap_or_else(|| {panic!("Overflow when calculating total length of buffer: {}x{}",width, height);})}
use crate::{backend_interface::*,error::{InitError, SwResultExt},util, Rect, SoftBufferError,};use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};use std::{num::{NonZeroI32, NonZeroU32},sync::{Arc, Mutex},};use wayland_client::{backend::{Backend, ObjectId},globals::{registry_queue_init, GlobalListContents},protocol::{wl_registry, wl_shm, wl_surface},Connection, Dispatch, EventQueue, Proxy, QueueHandle,};mod buffer;use buffer::WaylandBuffer;struct State;#[derive(Debug)]pub struct WaylandDisplayImpl<D: ?Sized> {conn: Option<Connection>,event_queue: Mutex<EventQueue<State>>,qh: QueueHandle<State>,shm: wl_shm::WlShm,/// The object that owns the display handle.////// This has to be dropped *after* the `conn` field, because the `conn` field implicitly borrows/// this._display: D,}impl<D: HasDisplayHandle + ?Sized> WaylandDisplayImpl<D> {fn conn(&self) -> &Connection {self.conn.as_ref().unwrap()}}impl<D: HasDisplayHandle + ?Sized> ContextInterface<D> for Arc<WaylandDisplayImpl<D>> {fn new(display: D) -> Result<Self, InitError<D>>whereD: Sized,{let raw = display.display_handle()?.as_raw();let RawDisplayHandle::Wayland(w) = raw else {return Err(InitError::Unsupported(display));};let backend = unsafe { Backend::from_foreign_display(w.display.as_ptr().cast()) };let conn = Connection::from_backend(backend);let (globals, event_queue) =registry_queue_init(&conn).swbuf_err("Failed to make round trip to server")?;let qh = event_queue.handle();let shm: wl_shm::WlShm = globals.bind(&qh, 1..=1, ()).swbuf_err("Failed to instantiate Wayland Shm")?;Ok(Arc::new(WaylandDisplayImpl {conn: Some(conn),event_queue: Mutex::new(event_queue),qh,shm,_display: display,}))}}impl<D: ?Sized> Drop for WaylandDisplayImpl<D> {fn drop(&mut self) {// Make sure the connection is dropped first.self.conn = None;}}#[derive(Debug)]pub struct WaylandImpl<D: ?Sized, W: ?Sized> {display: Arc<WaylandDisplayImpl<D>>,surface: Option<wl_surface::WlSurface>,buffers: Option<(WaylandBuffer, WaylandBuffer)>,size: Option<(NonZeroI32, NonZeroI32)>,/// The pointer to the window object.////// This has to be dropped *after* the `surface` field, because the `surface` field implicitly/// borrows this.window_handle: W,}impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> WaylandImpl<D, W> {fn surface(&self) -> &wl_surface::WlSurface {self.surface.as_ref().unwrap()}fn present_with_damage(&mut self, damage: &[Rect]) -> Result<(), SoftBufferError> {let _ = self.display.event_queue.lock().unwrap_or_else(|x| x.into_inner()).dispatch_pending(&mut State);if let Some((front, back)) = &mut self.buffers {// Swap front and back bufferstd::mem::swap(front, back);front.age = 1;if back.age != 0 {back.age += 1;}front.attach(self.surface.as_ref().unwrap());// Like Mesa's EGL/WSI implementation, we damage the whole buffer with `i32::MAX` if// the compositor doesn't support `damage_buffer`.// https://bugs.freedesktop.org/show_bug.cgi?id=78190if self.surface().version() < 4 {self.surface().damage(0, 0, i32::MAX, i32::MAX);} else {for rect in damage {// Introduced in version 4, it is an error to use this request in version 3 or lower.let (x, y, width, height) = (|| {Some((i32::try_from(rect.x).ok()?,i32::try_from(rect.y).ok()?,i32::try_from(rect.width.get()).ok()?,i32::try_from(rect.height.get()).ok()?,))})().ok_or(SoftBufferError::DamageOutOfRange { rect: *rect })?;self.surface().damage_buffer(x, y, width, height);}}self.surface().commit();}let _ = self.display.event_queue.lock().unwrap_or_else(|x| x.into_inner()).flush();Ok(())}}impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> SurfaceInterface<D, W>for WaylandImpl<D, W>{type Context = Arc<WaylandDisplayImpl<D>>;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;fn new(window: W, display: &Arc<WaylandDisplayImpl<D>>) -> Result<Self, InitError<W>> {// Get the raw Wayland window.let raw = window.window_handle()?.as_raw();let RawWindowHandle::Wayland(w) = raw else {return Err(InitError::Unsupported(window));};let surface_id = unsafe {ObjectId::from_ptr(wl_surface::WlSurface::interface(),w.surface.as_ptr().cast(),)}.swbuf_err("Failed to create proxy for surface ID.")?;let surface = wl_surface::WlSurface::from_id(display.conn(), surface_id).swbuf_err("Failed to create proxy for surface ID.")?;Ok(Self {display: display.clone(),surface: Some(surface),buffers: Default::default(),size: None,window_handle: window,})}#[inline]fn window(&self) -> &W {&self.window_handle}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {self.size = Some((|| {let width = NonZeroI32::try_from(width).ok()?;let height = NonZeroI32::try_from(height).ok()?;Some((width, height))})().ok_or(SoftBufferError::SizeOutOfRange { width, height })?,);Ok(())}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {let (width, height) = self.size.expect("Must set size of surface before calling `buffer_mut()`");if let Some((_front, back)) = &mut self.buffers {// Block if back buffer not released yetif !back.released() {let mut event_queue = self.display.event_queue.lock().unwrap_or_else(|x| x.into_inner());while !back.released() {event_queue.blocking_dispatch(&mut State).map_err(|err| {SoftBufferError::PlatformError(Some("Wayland dispatch failure".to_string()),Some(Box::new(err)),)})?;}}// Resize, if buffer isn't large enoughback.resize(width.get(), height.get());} else {// Allocate front and back bufferself.buffers = Some((WaylandBuffer::new(&self.display.shm,width.get(),height.get(),&self.display.qh,),WaylandBuffer::new(&self.display.shm,width.get(),height.get(),&self.display.qh,),));};let width = self.buffers.as_mut().unwrap().1.width;let height = self.buffers.as_mut().unwrap().1.height;let age = self.buffers.as_mut().unwrap().1.age;Ok(BufferImpl {stack: util::BorrowStack::new(self, |buffer| {Ok(unsafe { buffer.buffers.as_mut().unwrap().1.mapped_mut() })})?,width,height,age,})}}impl<D: ?Sized, W: ?Sized> Drop for WaylandImpl<D, W> {fn drop(&mut self) {// Make sure the surface is dropped first.self.surface = None;}}#[derive(Debug)]pub struct BufferImpl<'a, D: ?Sized, W> {stack: util::BorrowStack<'a, WaylandImpl<D, W>, [u32]>,width: i32,height: i32,age: u8,}impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> BufferInterface for BufferImpl<'_, D, W> {fn width(&self) -> NonZeroU32 {NonZeroU32::new(self.width as u32).unwrap()}fn height(&self) -> NonZeroU32 {NonZeroU32::new(self.height as usize as u32).unwrap()}#[inline]fn pixels(&self) -> &[u32] {self.stack.member()}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {self.stack.member_mut()}fn age(&self) -> u8 {self.age}fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {self.stack.into_container().present_with_damage(damage)}fn present(self) -> Result<(), SoftBufferError> {let imp = self.stack.into_container();let (width, height) = imp.size.expect("Must set size of surface before calling `present()`");imp.present_with_damage(&[Rect {x: 0,y: 0,// We know width/height will be non-negativewidth: width.try_into().unwrap(),height: height.try_into().unwrap(),}])}}impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {fn event(_: &mut State,_: &wl_registry::WlRegistry,_: wl_registry::Event,_: &GlobalListContents,_: &Connection,_: &QueueHandle<State>,) {// Ignore globals added after initialization}}impl Dispatch<wl_shm::WlShm, ()> for State {fn event(_: &mut State,_: &wl_shm::WlShm,_: wl_shm::Event,_: &(),_: &Connection,_: &QueueHandle<State>,) {}}
use memmap2::MmapMut;use std::{ffi::CStr,fs::File,os::unix::prelude::{AsFd, AsRawFd},slice,sync::{atomic::{AtomicBool, Ordering},Arc,},};use wayland_client::{protocol::{wl_buffer, wl_shm, wl_shm_pool, wl_surface},Connection, Dispatch, QueueHandle,};use super::State;#[cfg(any(target_os = "linux", target_os = "freebsd"))]fn create_memfile() -> File {use rustix::fs::{MemfdFlags, SealFlags};let name = unsafe { CStr::from_bytes_with_nul_unchecked("softbuffer\0".as_bytes()) };let fd = rustix::fs::memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING).expect("Failed to create memfd to store buffer.");rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL).expect("Failed to seal memfd.");File::from(fd)}#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]fn create_memfile() -> File {use rustix::{fs::Mode, io::Errno, shm::OFlags};use std::iter;// Use a cached RNG to avoid hammering the thread local.let mut rng = fastrand::Rng::new();for _ in 0..=4 {let mut name = String::from("softbuffer-");name.extend(iter::repeat_with(|| rng.alphanumeric()).take(7));name.push('\0');let name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) };// `CLOEXEC` is implied with `shm_open`let fd = rustix::shm::open(name,OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,Mode::RWXU,);if !matches!(fd, Err(Errno::EXIST)) {let fd = fd.expect("Failed to create POSIX shm to store buffer.");let _ = rustix::shm::unlink(name);return File::from(fd);}}panic!("Failed to generate non-existent shm name")}// Round size to use for pool for given dimensions, rounding up to power of 2fn get_pool_size(width: i32, height: i32) -> i32 {((width * height * 4) as u32).next_power_of_two() as i32}unsafe fn map_file(file: &File) -> MmapMut {unsafe { MmapMut::map_mut(file.as_raw_fd()).expect("Failed to map shared memory") }}#[derive(Debug)]pub(super) struct WaylandBuffer {qh: QueueHandle<State>,tempfile: File,map: MmapMut,pool: wl_shm_pool::WlShmPool,pool_size: i32,buffer: wl_buffer::WlBuffer,pub width: i32,pub height: i32,released: Arc<AtomicBool>,pub age: u8,}impl WaylandBuffer {pub fn new(shm: &wl_shm::WlShm, width: i32, height: i32, qh: &QueueHandle<State>) -> Self {// Calculate size to use for shm poollet pool_size = get_pool_size(width, height);// Create an `mmap` shared memorylet tempfile = create_memfile();let _ = tempfile.set_len(pool_size as u64);let map = unsafe { map_file(&tempfile) };// Create wayland shm pool and bufferlet pool = shm.create_pool(tempfile.as_fd(), pool_size, qh, ());let released = Arc::new(AtomicBool::new(true));let buffer = pool.create_buffer(0,width,height,width * 4,wl_shm::Format::Argb8888,qh,released.clone(),);Self {qh: qh.clone(),map,tempfile,pool,pool_size,buffer,width,height,released,age: 0,}}pub fn resize(&mut self, width: i32, height: i32) {// If size is the same, there's nothing to doif self.width != width || self.height != height {// Destroy old bufferself.buffer.destroy();// Grow pool, if neededlet size = ((width * height * 4) as u32).next_power_of_two() as i32;if size > self.pool_size {let _ = self.tempfile.set_len(size as u64);self.pool.resize(size);self.pool_size = size;self.map = unsafe { map_file(&self.tempfile) };}// Create buffer with correct sizeself.buffer = self.pool.create_buffer(0,width,height,width * 4,wl_shm::Format::Argb8888,&self.qh,self.released.clone(),);self.width = width;self.height = height;}}pub fn attach(&self, surface: &wl_surface::WlSurface) {self.released.store(false, Ordering::SeqCst);surface.attach(Some(&self.buffer), 0, 0);surface.set_opaque_region(None);}pub fn released(&self) -> bool {self.released.load(Ordering::SeqCst)}fn len(&self) -> usize {self.width as usize * self.height as usize}pub unsafe fn mapped_mut(&mut self) -> &mut [u32] {unsafe { slice::from_raw_parts_mut(self.map.as_mut_ptr() as *mut u32, self.len()) }}}impl Drop for WaylandBuffer {fn drop(&mut self) {self.buffer.destroy();self.pool.destroy();}}impl Dispatch<wl_shm_pool::WlShmPool, ()> for State {fn event(_: &mut State,_: &wl_shm_pool::WlShmPool,_: wl_shm_pool::Event,_: &(),_: &Connection,_: &QueueHandle<State>,) {}}impl Dispatch<wl_buffer::WlBuffer, Arc<AtomicBool>> for State {fn event(_: &mut State,_: &wl_buffer::WlBuffer,event: wl_buffer::Event,released: &Arc<AtomicBool>,_: &Connection,_: &QueueHandle<State>,) {if let wl_buffer::Event::Release = event {released.store(true, Ordering::SeqCst);}}}
use crate::error::InitError;use raw_window_handle::{HasDisplayHandle, HasWindowHandle, OrbitalWindowHandle, RawWindowHandle};use std::{cmp, marker::PhantomData, num::NonZeroU32, slice, str};use crate::backend_interface::*;use crate::{util, Rect, SoftBufferError};#[derive(Debug)]struct OrbitalMap {address: usize,size: usize,size_unaligned: usize,}impl OrbitalMap {unsafe fn new(fd: usize, size_unaligned: usize) -> syscall::Result<Self> {// Page align sizelet pages = (size_unaligned + syscall::PAGE_SIZE - 1) / syscall::PAGE_SIZE;let size = pages * syscall::PAGE_SIZE;// Map window bufferlet address = unsafe {syscall::fmap(fd,&syscall::Map {offset: 0,size,flags: syscall::PROT_READ | syscall::PROT_WRITE | syscall::MAP_SHARED,address: 0,},)?};Ok(Self {address,size,size_unaligned,})}unsafe fn data(&self) -> &[u32] {unsafe { slice::from_raw_parts(self.address as *const u32, self.size_unaligned / 4) }}unsafe fn data_mut(&mut self) -> &mut [u32] {unsafe { slice::from_raw_parts_mut(self.address as *mut u32, self.size_unaligned / 4) }}}impl Drop for OrbitalMap {fn drop(&mut self) {unsafe {// Unmap window buffer on dropsyscall::funmap(self.address, self.size).expect("failed to unmap orbital window");}}}#[derive(Debug)]pub struct OrbitalImpl<D, W> {handle: ThreadSafeWindowHandle,width: u32,height: u32,presented: bool,window_handle: W,_display: PhantomData<D>,}#[derive(Debug)]struct ThreadSafeWindowHandle(OrbitalWindowHandle);unsafe impl Send for ThreadSafeWindowHandle {}unsafe impl Sync for ThreadSafeWindowHandle {}impl<D: HasDisplayHandle, W: HasWindowHandle> OrbitalImpl<D, W> {fn window_fd(&self) -> usize {self.handle.0.window.as_ptr() as usize}// Read the current width and sizefn window_size(&self) -> (usize, usize) {let mut window_width = 0;let mut window_height = 0;let mut buf: [u8; 4096] = [0; 4096];let count = syscall::fpath(self.window_fd(), &mut buf).unwrap();let path = str::from_utf8(&buf[..count]).unwrap();// orbital:/x/y/w/h/tlet mut parts = path.split('/').skip(3);if let Some(w) = parts.next() {window_width = w.parse::<usize>().unwrap_or(0);}if let Some(h) = parts.next() {window_height = h.parse::<usize>().unwrap_or(0);}(window_width, window_height)}fn set_buffer(&self, buffer: &[u32], width_u32: u32, height_u32: u32) {// Read the current width and sizelet (window_width, window_height) = self.window_size();{// Map window bufferlet mut window_map =unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) }.expect("failed to map orbital window");// Window buffer is u32 color data in 0xAABBGGRR formatlet window_data = unsafe { window_map.data_mut() };// Copy each line, cropping to fitlet width = width_u32 as usize;let height = height_u32 as usize;let min_width = cmp::min(width, window_width);let min_height = cmp::min(height, window_height);for y in 0..min_height {let offset_buffer = y * width;let offset_data = y * window_width;window_data[offset_data..offset_data + min_width].copy_from_slice(&buffer[offset_buffer..offset_buffer + min_width]);}// Window buffer map is dropped here}// Tell orbital to show the latest window datasyscall::fsync(self.window_fd()).expect("failed to sync orbital window");}}impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for OrbitalImpl<D, W> {type Context = D;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;fn new(window: W, _display: &D) -> Result<Self, InitError<W>> {let raw = window.window_handle()?.as_raw();let RawWindowHandle::Orbital(handle) = raw else {return Err(InitError::Unsupported(window));};Ok(Self {handle: ThreadSafeWindowHandle(handle),width: 0,height: 0,presented: false,window_handle: window,_display: PhantomData,})}#[inline]fn window(&self) -> &W {&self.window_handle}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {let width = width.get();let height = height.get();if width != self.width || height != self.height {self.presented = false;self.width = width;self.height = height;}Ok(())}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {let (window_width, window_height) = self.window_size();let pixels = if self.width as usize == window_width && self.height as usize == window_height{Pixels::Mapping(unsafe { OrbitalMap::new(self.window_fd(), window_width * window_height * 4) }.expect("failed to map orbital window"),)} else {Pixels::Buffer(util::PixelBuffer(vec![0;self.width as usize* self.height as usize]))};Ok(BufferImpl { imp: self, pixels })}}#[derive(Debug)]enum Pixels {Mapping(OrbitalMap),Buffer(util::PixelBuffer),}#[derive(Debug)]pub struct BufferImpl<'a, D, W> {imp: &'a mut OrbitalImpl<D, W>,pixels: Pixels,}impl<D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'_, D, W> {fn width(&self) -> NonZeroU32 {NonZeroU32::new(self.imp.width as u32).unwrap()}fn height(&self) -> NonZeroU32 {NonZeroU32::new(self.imp.height as u32).unwrap()}#[inline]fn pixels(&self) -> &[u32] {match &self.pixels {Pixels::Mapping(mapping) => unsafe { mapping.data() },Pixels::Buffer(buffer) => buffer,}}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {match &mut self.pixels {Pixels::Mapping(mapping) => unsafe { mapping.data_mut() },Pixels::Buffer(buffer) => buffer,}}fn age(&self) -> u8 {match self.pixels {Pixels::Mapping(_) if self.imp.presented => 1,_ => 0,}}fn present(self) -> Result<(), SoftBufferError> {match self.pixels {Pixels::Mapping(mapping) => {drop(mapping);syscall::fsync(self.imp.window_fd()).expect("failed to sync orbital window");self.imp.presented = true;}Pixels::Buffer(buffer) => {self.imp.set_buffer(&buffer, self.imp.width, self.imp.height);}}Ok(())}fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {self.present()}}
use crate::{ContextInterface, InitError};use raw_window_handle::HasDisplayHandle;#[cfg(target_os = "android")]pub(crate) mod android;#[cfg(target_vendor = "apple")]pub(crate) mod cg;#[cfg(all(feature = "kms",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]pub(crate) mod kms;#[cfg(target_os = "redox")]pub(crate) mod orbital;#[cfg(all(feature = "wayland",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]pub(crate) mod wayland;#[cfg(target_family = "wasm")]pub(crate) mod web;#[cfg(target_os = "windows")]pub(crate) mod win32;#[cfg(all(feature = "x11",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]pub(crate) mod x11;impl<D: HasDisplayHandle> ContextInterface<D> for D {fn new(display: D) -> Result<Self, InitError<D>> {Ok(display)}}
//! Backend for DRM/KMS for raw rendering directly to the screen.//!//! This strategy uses dumb buffers for rendering.use drm::buffer::{Buffer, DrmFourcc};use drm::control::dumbbuffer::{DumbBuffer, DumbMapping};use drm::control::{connector, crtc, framebuffer, plane, ClipRect, Device as CtrlDevice, PageFlipFlags,};use drm::Device;use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle};use std::collections::HashSet;use std::fmt;use std::marker::PhantomData;use std::num::NonZeroU32;use std::os::unix::io::{AsFd, BorrowedFd};use std::sync::Arc;use crate::backend_interface::*;use crate::error::{InitError, SoftBufferError, SwResultExt};#[derive(Debug)]pub(crate) struct KmsDisplayImpl<D: ?Sized> {/// The underlying raw device file descriptor.fd: BorrowedFd<'static>,/// Holds a reference to the display._display: D,}impl<D: ?Sized> AsFd for KmsDisplayImpl<D> {fn as_fd(&self) -> BorrowedFd<'_> {self.fd}}impl<D: ?Sized> Device for KmsDisplayImpl<D> {}impl<D: ?Sized> CtrlDevice for KmsDisplayImpl<D> {}impl<D: HasDisplayHandle + ?Sized> ContextInterface<D> for Arc<KmsDisplayImpl<D>> {fn new(display: D) -> Result<Self, InitError<D>>whereD: Sized,{let RawDisplayHandle::Drm(drm) = display.display_handle()?.as_raw() else {return Err(InitError::Unsupported(display));};if drm.fd == -1 {return Err(SoftBufferError::IncompleteDisplayHandle.into());}// SAFETY: Invariants guaranteed by the user.let fd = unsafe { BorrowedFd::borrow_raw(drm.fd) };Ok(Arc::new(KmsDisplayImpl {fd,_display: display,}))}}/// All the necessary types for the Drm/Kms backend.#[derive(Debug)]pub(crate) struct KmsImpl<D: ?Sized, W: ?Sized> {/// The display implementation.display: Arc<KmsDisplayImpl<D>>,/// The connectors to use.connectors: Vec<connector::Handle>,/// The CRTC to render to.crtc: crtc::Info,/// The dumb buffer we're using as a buffer.buffer: Option<Buffers>,/// Window handle that we are keeping around.window_handle: W,}#[derive(Debug)]struct Buffers {/// The involved set of buffers.buffers: [SharedBuffer; 2],/// Whether to use the first buffer or the second buffer as the front buffer.first_is_front: bool,}/// The buffer implementation.pub(crate) struct BufferImpl<'a, D: ?Sized, W: ?Sized> {/// The mapping of the dump buffer.mapping: DumbMapping<'a>,/// The framebuffer object of the current front buffer.front_fb: framebuffer::Handle,/// The CRTC handle.crtc_handle: crtc::Handle,/// This is used to change the front buffer.first_is_front: &'a mut bool,/// The current size.size: (NonZeroU32, NonZeroU32),/// The display implementation.display: &'a KmsDisplayImpl<D>,/// Age of the front buffer.front_age: &'a mut u8,/// Age of the back buffer.back_age: &'a mut u8,/// Window reference._window: PhantomData<&'a mut W>,}impl<D: ?Sized + fmt::Debug, W: ?Sized + fmt::Debug> fmt::Debug for BufferImpl<'_, D, W> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {// FIXME: Derive instead once `DumbMapping` impls `Debug`.f.debug_struct("BufferImpl").finish_non_exhaustive()}}/// The combined frame buffer and dumb buffer.#[derive(Debug)]struct SharedBuffer {/// The frame buffer.fb: framebuffer::Handle,/// The dumb buffer.db: DumbBuffer,/// The age of this buffer.age: u8,}impl<D: HasDisplayHandle + ?Sized, W: HasWindowHandle> SurfaceInterface<D, W> for KmsImpl<D, W> {type Context = Arc<KmsDisplayImpl<D>>;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;/// Create a new KMS backend.fn new(window: W, display: &Arc<KmsDisplayImpl<D>>) -> Result<Self, InitError<W>> {// Make sure that the window handle is valid.let RawWindowHandle::Drm(drm) = window.window_handle()?.as_raw() else {return Err(InitError::Unsupported(window));};let plane_handle =NonZeroU32::new(drm.plane).ok_or(SoftBufferError::IncompleteWindowHandle)?;let plane_handle = plane::Handle::from(plane_handle);let plane_info = display.get_plane(plane_handle).swbuf_err("failed to get plane info")?;let handles = display.resource_handles().swbuf_err("failed to get resource handles")?;// Use either the attached CRTC or the primary CRTC.let crtc = {let handle = match plane_info.crtc() {Some(crtc) => crtc,None => {tracing::warn!("no CRTC attached to plane, falling back to primary CRTC");handles.filter_crtcs(plane_info.possible_crtcs()).first().copied().swbuf_err("failed to find a primary CRTC")?}};// Get info about the CRTC.display.get_crtc(handle).swbuf_err("failed to get CRTC info")?};// Figure out all of the encoders that are attached to this CRTC.let encoders = handles.encoders.iter().flat_map(|handle| display.get_encoder(*handle)).filter(|encoder| encoder.crtc() == Some(crtc.handle())).map(|encoder| encoder.handle()).collect::<HashSet<_>>();// Get a list of every connector that the CRTC is connected to via encoders.let connectors = handles.connectors.iter().flat_map(|handle| display.get_connector(*handle, false)).filter(|connector| {connector.current_encoder().is_some_and(|encoder| encoders.contains(&encoder))}).map(|info| info.handle()).collect::<Vec<_>>();Ok(Self {crtc,connectors,display: display.clone(),buffer: None,window_handle: window,})}#[inline]fn window(&self) -> &W {&self.window_handle}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {// Don't resize if we don't have to.if let Some(buffer) = &self.buffer {let (buffer_width, buffer_height) = buffer.size();if buffer_width == width && buffer_height == height {return Ok(());}}// Create a new buffer set.let front_buffer = SharedBuffer::new(&self.display, width, height)?;let back_buffer = SharedBuffer::new(&self.display, width, height)?;self.buffer = Some(Buffers {first_is_front: true,buffers: [front_buffer, back_buffer],});Ok(())}/*fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {// TODO: Implement this!}*/fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {// Map the dumb buffer.let set = self.buffer.as_mut().expect("Must set size of surface before calling `buffer_mut()`");let size = set.size();let [first_buffer, second_buffer] = &mut set.buffers;let (front_buffer, back_buffer) = if set.first_is_front {(first_buffer, second_buffer)} else {(second_buffer, first_buffer)};let front_fb = front_buffer.fb;let front_age = &mut front_buffer.age;let back_age = &mut back_buffer.age;let mapping = self.display.map_dumb_buffer(&mut front_buffer.db).swbuf_err("failed to map dumb buffer")?;Ok(BufferImpl {mapping,size,first_is_front: &mut set.first_is_front,front_fb,crtc_handle: self.crtc.handle(),display: &self.display,front_age,back_age,_window: PhantomData,})}}impl<D: ?Sized, W: ?Sized> Drop for KmsImpl<D, W> {fn drop(&mut self) {// Map the CRTC to the information that was there before.self.display.set_crtc(self.crtc.handle(),self.crtc.framebuffer(),self.crtc.position(),&self.connectors,self.crtc.mode(),).ok();}}impl<D: ?Sized, W: ?Sized> BufferInterface for BufferImpl<'_, D, W> {fn width(&self) -> NonZeroU32 {self.size.0}fn height(&self) -> NonZeroU32 {self.size.1}#[inline]fn pixels(&self) -> &[u32] {bytemuck::cast_slice(self.mapping.as_ref())}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {bytemuck::cast_slice_mut(self.mapping.as_mut())}#[inline]fn age(&self) -> u8 {*self.front_age}#[inline]fn present_with_damage(self, damage: &[crate::Rect]) -> Result<(), SoftBufferError> {let rectangles = damage.iter().map(|&rect| {let err = || SoftBufferError::DamageOutOfRange { rect };Ok::<_, SoftBufferError>(ClipRect::new(rect.x.try_into().map_err(|_| err())?,rect.y.try_into().map_err(|_| err())?,rect.x.checked_add(rect.width.get()).and_then(|x| x.try_into().ok()).ok_or_else(err)?,rect.y.checked_add(rect.height.get()).and_then(|y| y.try_into().ok()).ok_or_else(err)?,))}).collect::<Result<Vec<_>, _>>()?;// Dirty the framebuffer with out damage rectangles.//// Some drivers don't support this, so we just ignore the `ENOSYS` error.// TODO: It would be nice to not have to heap-allocate the above rectangles if we know that// this is going to fail. Low hanging fruit PR: add a flag that's set to false if this// returns `ENOSYS` and check that before allocating the above and running this.match self.display.dirty_framebuffer(self.front_fb, &rectangles) {Ok(()) => {}Err(e) if e.raw_os_error() == Some(rustix::io::Errno::NOSYS.raw_os_error()) => {}Err(e) => {return Err(SoftBufferError::PlatformError(Some("failed to dirty framebuffer".into()),Some(e.into()),));}}// Swap the buffers.// TODO: Use atomic commits here!self.display.page_flip(self.crtc_handle, self.front_fb, PageFlipFlags::EVENT, None).swbuf_err("failed to page flip")?;// Flip the front and back buffers.*self.first_is_front = !*self.first_is_front;// Set the ages.*self.front_age = 1;if *self.back_age != 0 {*self.back_age += 1;}Ok(())}#[inline]fn present(self) -> Result<(), SoftBufferError> {let (width, height) = self.size;self.present_with_damage(&[crate::Rect {x: 0,y: 0,width,height,}])}}impl SharedBuffer {/// Create a new buffer set.pub(crate) fn new<D: ?Sized>(display: &KmsDisplayImpl<D>,width: NonZeroU32,height: NonZeroU32,) -> Result<Self, SoftBufferError> {let db = display.create_dumb_buffer((width.get(), height.get()), DrmFourcc::Argb8888, 32).swbuf_err("failed to create dumb buffer")?;let fb = display.add_framebuffer(&db, 24, 32).swbuf_err("failed to add framebuffer")?;Ok(SharedBuffer { fb, db, age: 0 })}/// Get the size of this buffer.pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) {let (width, height) = self.db.size();NonZeroU32::new(width).and_then(|width| NonZeroU32::new(height).map(|height| (width, height))).expect("buffer size is zero")}}impl Buffers {/// Get the size of this buffer.pub(crate) fn size(&self) -> (NonZeroU32, NonZeroU32) {self.buffers[0].size()}}
//! Softbuffer implementation using CoreGraphics.use crate::backend_interface::*;use crate::error::InitError;use crate::{util, Rect, SoftBufferError};use objc2::rc::Retained;use objc2::runtime::{AnyObject, Bool};use objc2::{define_class, msg_send, AllocAnyThread, DefinedClass, MainThreadMarker, Message};use objc2_core_foundation::{CFRetained, CGPoint};use objc2_core_graphics::{CGBitmapInfo, CGColorRenderingIntent, CGColorSpace, CGDataProvider, CGImage, CGImageAlphaInfo,CGImageByteOrderInfo, CGImageComponentInfo, CGImagePixelFormatInfo,};use objc2_foundation::{ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey,NSKeyValueObservingOptions, NSNumber, NSObject, NSObjectNSKeyValueObserverRegistration,NSString, NSValue,};use objc2_quartz_core::{kCAGravityTopLeft, CALayer, CATransaction};use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};use std::ffi::c_void;use std::marker::PhantomData;use std::mem::size_of;use std::num::NonZeroU32;use std::ops::Deref;use std::ptr::{self, slice_from_raw_parts_mut, NonNull};define_class!(#[unsafe(super(NSObject))]#[name = "SoftbufferObserver"]#[ivars = SendCALayer]#[derive(Debug)]struct Observer;/// NSKeyValueObservingimpl Observer {#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]fn observe_value(&self,key_path: Option<&NSString>,_object: Option<&AnyObject>,change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,_context: *mut c_void,) {self.update(key_path, change);}});impl Observer {fn new(layer: &CALayer) -> Retained<Self> {let this = Self::alloc().set_ivars(SendCALayer(layer.retain()));unsafe { msg_send![super(this), init] }}fn update(&self,key_path: Option<&NSString>,change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,) {let layer = self.ivars();let change =change.expect("requested a change dictionary in `addObserver`, but none was provided");let new = change.objectForKey(unsafe { NSKeyValueChangeNewKey }).expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");// NOTE: Setting these values usually causes a quarter second animation to occur, which is// undesirable.//// However, since we're setting them inside an observer, there already is a transaction// ongoing, and as such we don't need to wrap this in a `CATransaction` ourselves.if key_path == Some(ns_string!("contentsScale")) {let new = new.downcast::<NSNumber>().unwrap();let scale_factor = new.as_cgfloat();// Set the scale factor of the layer to match the root layer when it changes (e.g. if// moved to a different monitor, or monitor settings changed).layer.setContentsScale(scale_factor);} else if key_path == Some(ns_string!("bounds")) {let new = new.downcast::<NSValue>().unwrap();let bounds = new.get_rect().expect("new bounds value was not CGRect");// Set `bounds` and `position` so that the new layer is inside the superlayer.//// This differs from just setting the `bounds`, as it also takes into account any// translation that the superlayer may have that we'd want to preserve.layer.setFrame(bounds);} else {panic!("unknown observed keypath {key_path:?}");}}}#[derive(Debug)]pub struct CGImpl<D, W> {/// Our layer.layer: SendCALayer,/// The layer that our layer was created from.////// Can also be retrieved from `layer.superlayer()`.root_layer: SendCALayer,observer: Retained<Observer>,color_space: CFRetained<CGColorSpace>,/// The width of the underlying buffer.width: usize,/// The height of the underlying buffer.height: usize,window_handle: W,_display: PhantomData<D>,}impl<D, W> Drop for CGImpl<D, W> {fn drop(&mut self) {// SAFETY: Registered in `new`, must be removed before the observer is deallocated.unsafe {self.root_layer.removeObserver_forKeyPath(&self.observer, ns_string!("contentsScale"));self.root_layer.removeObserver_forKeyPath(&self.observer, ns_string!("bounds"));}}}impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for CGImpl<D, W> {type Context = D;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;fn new(window_src: W, _display: &D) -> Result<Self, InitError<W>> {// `NSView`/`UIView` can only be accessed from the main thread.let _mtm = MainThreadMarker::new().ok_or(SoftBufferError::PlatformError(Some("can only access Core Graphics handles from the main thread".to_string()),None,))?;let root_layer = match window_src.window_handle()?.as_raw() {RawWindowHandle::AppKit(handle) => {// SAFETY: The pointer came from `WindowHandle`, which ensures that the// `AppKitWindowHandle` contains a valid pointer to an `NSView`.//// We use `NSObject` here to avoid importing `objc2-app-kit`.let view: &NSObject = unsafe { handle.ns_view.cast().as_ref() };// Force the view to become layer backedlet _: () = unsafe { msg_send![view, setWantsLayer: Bool::YES] };// SAFETY: `-[NSView layer]` returns an optional `CALayer`let layer: Option<Retained<CALayer>> = unsafe { msg_send![view, layer] };layer.expect("failed making the view layer-backed")}RawWindowHandle::UiKit(handle) => {// SAFETY: The pointer came from `WindowHandle`, which ensures that the// `UiKitWindowHandle` contains a valid pointer to an `UIView`.//// We use `NSObject` here to avoid importing `objc2-ui-kit`.let view: &NSObject = unsafe { handle.ui_view.cast().as_ref() };// SAFETY: `-[UIView layer]` returns `CALayer`let layer: Retained<CALayer> = unsafe { msg_send![view, layer] };layer}_ => return Err(InitError::Unsupported(window_src)),};// Add a sublayer, to avoid interfering with the root layer, since setting the contents of// e.g. a view-controlled layer is brittle.let layer = CALayer::new();root_layer.addSublayer(&layer);// Set the anchor point and geometry. Softbuffer's uses a coordinate system with the origin// in the top-left corner.//// NOTE: This doesn't really matter unless we start modifying the `position` of our layer// ourselves, but it's nice to have in place.layer.setAnchorPoint(CGPoint::new(0.0, 0.0));layer.setGeometryFlipped(true);// Do not use auto-resizing mask.//// This is done to work around a bug in macOS 14 and above, where views using auto layout// may end up setting fractional values as the bounds, and that in turn doesn't propagate// properly through the auto-resizing mask and with contents gravity.//// Instead, we keep the bounds of the layer in sync with the root layer using an observer,// see below.//// layer.setAutoresizingMask(kCALayerHeightSizable | kCALayerWidthSizable);let observer = Observer::new(&layer);// Observe changes to the root layer's bounds and scale factor, and apply them to our layer.//// The previous implementation updated the scale factor inside `resize`, but this works// poorly with transactions, and is generally inefficient. Instead, we update the scale// factor only when needed because the super layer's scale factor changed.//// Note that inherent in this is an explicit design decision: We control the `bounds` and// `contentsScale` of the layer directly, and instead let the `resize` call that the user// controls only be the size of the underlying buffer.//// SAFETY: Observer deregistered in `Drop` before the observer object is deallocated.unsafe {root_layer.addObserver_forKeyPath_options_context(&observer,ns_string!("contentsScale"),NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial,ptr::null_mut(),);root_layer.addObserver_forKeyPath_options_context(&observer,ns_string!("bounds"),NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial,ptr::null_mut(),);}// Set the content so that it is placed in the top-left corner if it does not have the same// size as the surface itself.//// TODO(madsmtm): Consider changing this to `kCAGravityResize` to stretch the content if// resized to something that doesn't fit, see #177.layer.setContentsGravity(unsafe { kCAGravityTopLeft });// Initialize color space here, to reduce work later on.let color_space = CGColorSpace::new_device_rgb().unwrap();// Grab initial width and height from the layer (whose properties have just been initialized// by the observer using `NSKeyValueObservingOptionInitial`).let size = layer.bounds().size;let scale_factor = layer.contentsScale();let width = (size.width * scale_factor) as usize;let height = (size.height * scale_factor) as usize;Ok(Self {layer: SendCALayer(layer),root_layer: SendCALayer(root_layer),observer,color_space,width,height,_display: PhantomData,window_handle: window_src,})}#[inline]fn window(&self) -> &W {&self.window_handle}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {self.width = width.get() as usize;self.height = height.get() as usize;Ok(())}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {Ok(BufferImpl {buffer: util::PixelBuffer(vec![0; self.width * self.height]),imp: self,})}}#[derive(Debug)]pub struct BufferImpl<'a, D, W> {imp: &'a mut CGImpl<D, W>,buffer: util::PixelBuffer,}impl<D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'_, D, W> {fn width(&self) -> NonZeroU32 {NonZeroU32::new(self.imp.width as u32).unwrap()}fn height(&self) -> NonZeroU32 {NonZeroU32::new(self.imp.height as u32).unwrap()}#[inline]fn pixels(&self) -> &[u32] {&self.buffer}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {&mut self.buffer}fn age(&self) -> u8 {0}fn present(self) -> Result<(), SoftBufferError> {unsafe extern "C-unwind" fn release(_info: *mut c_void,data: NonNull<c_void>,size: usize,) {let data = data.cast::<u32>();let slice = slice_from_raw_parts_mut(data.as_ptr(), size / size_of::<u32>());// SAFETY: This is the same slice that we passed to `Box::into_raw` below.drop(unsafe { Box::from_raw(slice) })}let data_provider = {let len = self.buffer.len() * size_of::<u32>();let buffer: *mut [u32] = Box::into_raw(self.buffer.0.into_boxed_slice());// Convert slice pointer to thin pointer.let data_ptr = buffer.cast::<c_void>();// SAFETY: The data pointer and length are valid.// The info pointer can safely be NULL, we don't use it in the `release` callback.unsafe {CGDataProvider::with_data(ptr::null_mut(), data_ptr, len, Some(release)).unwrap()}};// `CGBitmapInfo` consists of a combination of `CGImageAlphaInfo`, `CGImageComponentInfo`// `CGImageByteOrderInfo` and `CGImagePixelFormatInfo` (see e.g. `CGBitmapInfoMake`).//// TODO: Use `CGBitmapInfo::new` once the next version of objc2-core-graphics is released.let bitmap_info = CGBitmapInfo(CGImageAlphaInfo::NoneSkipFirst.0| CGImageComponentInfo::Integer.0| CGImageByteOrderInfo::Order32Little.0| CGImagePixelFormatInfo::Packed.0,);let image = unsafe {CGImage::new(self.imp.width,self.imp.height,8,32,self.imp.width * 4,Some(&self.imp.color_space),bitmap_info,Some(&data_provider),ptr::null(),false,CGColorRenderingIntent::RenderingIntentDefault,)}.unwrap();// The CALayer has a default action associated with a change in the layer contents, causing// a quarter second fade transition to happen every time a new buffer is applied. This can// be avoided by wrapping the operation in a transaction and disabling all actions.CATransaction::begin();CATransaction::setDisableActions(true);// SAFETY: The contents is `CGImage`, which is a valid class for `contents`.unsafe { self.imp.layer.setContents(Some(image.as_ref())) };CATransaction::commit();Ok(())}fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {self.present()}}#[derive(Debug)]struct SendCALayer(Retained<CALayer>);// SAFETY: CALayer is dubiously thread safe, like most things in Core Animation.// But since we make sure to do our changes within a CATransaction, it is// _probably_ fine for us to use CALayer from different threads.//// See also:// https://developer.apple.com/documentation/quartzcore/catransaction/1448267-lock?language=objc// https://stackoverflow.com/questions/76250226/how-to-render-content-of-calayer-on-a-background-threadunsafe impl Send for SendCALayer {}// SAFETY: Same as above.unsafe impl Sync for SendCALayer {}impl Deref for SendCALayer {type Target = CALayer;fn deref(&self) -> &Self::Target {&self.0}}
//! Implementation of software buffering for Android.use std::marker::PhantomData;use std::num::{NonZeroI32, NonZeroU32};use ndk::{hardware_buffer_format::HardwareBufferFormat,native_window::{NativeWindow, NativeWindowBufferLockGuard},};#[cfg(doc)]use raw_window_handle::AndroidNdkWindowHandle;use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};use crate::error::InitError;use crate::{util, BufferInterface, Rect, SoftBufferError, SurfaceInterface};/// The handle to a window for software buffering.#[derive(Debug)]pub struct AndroidImpl<D, W> {native_window: NativeWindow,window: W,_display: PhantomData<D>,}impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for AndroidImpl<D, W> {type Context = D;type Buffer<'a>= BufferImpl<'a, D, W>whereSelf: 'a;/// Create a new [`AndroidImpl`] from an [`AndroidNdkWindowHandle`].fn new(window: W, _display: &Self::Context) -> Result<Self, InitError<W>> {let raw = window.window_handle()?.as_raw();let RawWindowHandle::AndroidNdk(a) = raw else {return Err(InitError::Unsupported(window));};// Acquire a new owned reference to the window, that will be freed on drop.// SAFETY: We have confirmed that the window handle is valid.let native_window = unsafe { NativeWindow::clone_from_ptr(a.a_native_window.cast()) };Ok(Self {native_window,_display: PhantomData,window,})}#[inline]fn window(&self) -> &W {&self.window}/// Also changes the pixel format to [`HardwareBufferFormat::R8G8B8A8_UNORM`].fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {let (width, height) = (|| {let width = NonZeroI32::try_from(width).ok()?;let height = NonZeroI32::try_from(height).ok()?;Some((width, height))})().ok_or(SoftBufferError::SizeOutOfRange { width, height })?;self.native_window.set_buffers_geometry(width.into(),height.into(),// Default is typically R5G6B5 16bpp, switch to 32bppSome(HardwareBufferFormat::R8G8B8X8_UNORM),).map_err(|err| {SoftBufferError::PlatformError(Some("Failed to set buffer geometry on ANativeWindow".to_owned()),Some(Box::new(err)),)})}fn buffer_mut(&mut self) -> Result<BufferImpl<'_, D, W>, SoftBufferError> {let native_window_buffer = self.native_window.lock(None).map_err(|err| {SoftBufferError::PlatformError(Some("Failed to lock ANativeWindow".to_owned()),Some(Box::new(err)),)})?;if !matches!(native_window_buffer.format(),// These are the only formats we supportHardwareBufferFormat::R8G8B8A8_UNORM | HardwareBufferFormat::R8G8B8X8_UNORM) {return Err(SoftBufferError::PlatformError(Some(format!("Unexpected buffer format {:?}, please call \.resize() first to change it to RGBx8888",native_window_buffer.format())),None,));}let buffer = vec![0; native_window_buffer.width() * native_window_buffer.height()];Ok(BufferImpl {native_window_buffer,buffer: util::PixelBuffer(buffer),marker: PhantomData,})}/// Fetch the buffer from the window.fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {Err(SoftBufferError::Unimplemented)}}#[derive(Debug)]pub struct BufferImpl<'a, D: ?Sized, W> {native_window_buffer: NativeWindowBufferLockGuard<'a>,buffer: util::PixelBuffer,marker: PhantomData<(&'a D, &'a W)>,}// TODO: Move to NativeWindowBufferLockGuard?unsafe impl<'a, D, W> Send for BufferImpl<'a, D, W> {}impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferImpl<'a, D, W> {fn width(&self) -> NonZeroU32 {NonZeroU32::new(self.native_window_buffer.width() as u32).unwrap()}fn height(&self) -> NonZeroU32 {NonZeroU32::new(self.native_window_buffer.height() as u32).unwrap()}#[inline]fn pixels(&self) -> &[u32] {&self.buffer}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {&mut self.buffer}#[inline]fn age(&self) -> u8 {0}// TODO: This function is pretty slow this wayfn present(mut self) -> Result<(), SoftBufferError> {let input_lines = self.buffer.chunks(self.native_window_buffer.width());for (output, input) in self.native_window_buffer.lines()// Unreachable as we checked before that this is a valid, mappable format.unwrap().zip(input_lines){// .lines() removed the strideassert_eq!(output.len(), input.len() * 4);for (i, pixel) in input.iter().enumerate() {// Swizzle colors from BGR(A) to RGB(A)let [b, g, r, a] = pixel.to_le_bytes();output[i * 4].write(r);output[i * 4 + 1].write(g);output[i * 4 + 2].write(b);output[i * 4 + 3].write(a);}}Ok(())}fn present_with_damage(self, _damage: &[Rect]) -> Result<(), SoftBufferError> {// TODO: Android requires the damage rect _at lock time_// Since we're faking the backing buffer _anyway_, we could even fake the surface lock// and lock it here (if it doesn't influence timings).self.present()}}
//! Interface implemented by backendsuse crate::{InitError, Rect, SoftBufferError};use raw_window_handle::{HasDisplayHandle, HasWindowHandle};use std::num::NonZeroU32;pub(crate) trait ContextInterface<D: HasDisplayHandle + ?Sized> {fn new(display: D) -> Result<Self, InitError<D>>whereD: Sized,Self: Sized;}pub(crate) trait SurfaceInterface<D: HasDisplayHandle + ?Sized, W: HasWindowHandle + ?Sized> {type Context: ContextInterface<D>;type Buffer<'a>: BufferInterfacewhereSelf: 'a;fn new(window: W, context: &Self::Context) -> Result<Self, InitError<W>>whereW: Sized,Self: Sized;/// Get the inner window handle.fn window(&self) -> &W;/// Resize the internal buffer to the given width and height.fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError>;/// Get a mutable reference to the buffer.fn buffer_mut(&mut self) -> Result<Self::Buffer<'_>, SoftBufferError>;/// Fetch the buffer from the window.fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {Err(SoftBufferError::Unimplemented)}}pub(crate) trait BufferInterface {fn width(&self) -> NonZeroU32;fn height(&self) -> NonZeroU32;fn pixels(&self) -> &[u32];fn pixels_mut(&mut self) -> &mut [u32];fn age(&self) -> u8;fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError>;fn present(self) -> Result<(), SoftBufferError>;}
//! Implements `buffer_interface::*` traits for enums dispatching to backendsuse crate::{backend_interface::*, backends, InitError, Rect, SoftBufferError};use raw_window_handle::{HasDisplayHandle, HasWindowHandle};use std::fmt;use std::num::NonZeroU32;/// A macro for creating the enum used to statically dispatch to the platform-specific implementation.macro_rules! make_dispatch {(<$dgen: ident, $wgen: ident> =>$($(#[$attr:meta])*$name: ident($context_inner: ty, $surface_inner: ty, $buffer_inner: ty),)*) => {#[derive(Clone)]pub(crate) enum ContextDispatch<$dgen> {$($(#[$attr])*$name($context_inner),)*}impl<D: HasDisplayHandle> ContextDispatch<D> {pub fn variant_name(&self) -> &'static str {match self {$($(#[$attr])*Self::$name(_) => stringify!($name),)*}}}impl<D: HasDisplayHandle> ContextInterface<D> for ContextDispatch<D> {fn new(mut display: D) -> Result<Self, InitError<D>>whereD: Sized,{$($(#[$attr])*match <$context_inner as ContextInterface<D>>::new(display) {Ok(x) => {return Ok(Self::$name(x));}Err(InitError::Unsupported(d)) => display = d,Err(InitError::Failure(f)) => return Err(InitError::Failure(f)),})*Err(InitError::Unsupported(display))}}impl<D: fmt::Debug> fmt::Debug for ContextDispatch<D> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {$($(#[$attr])*Self::$name(inner) => inner.fmt(f),)*}}}#[allow(clippy::large_enum_variant)] // it's boxed anywayspub(crate) enum SurfaceDispatch<$dgen, $wgen> {$($(#[$attr])*$name($surface_inner),)*}impl<D: HasDisplayHandle, W: HasWindowHandle> SurfaceInterface<D, W> for SurfaceDispatch<D, W> {type Context = ContextDispatch<D>;type Buffer<'a> = BufferDispatch<'a, D, W> where Self: 'a;fn new(window: W, display: &Self::Context) -> Result<Self, InitError<W>>whereW: Sized,Self: Sized {match display {$($(#[$attr])*ContextDispatch::$name(inner) => Ok(Self::$name(<$surface_inner>::new(window, inner)?)),)*}}fn window(&self) -> &W {match self {$($(#[$attr])*Self::$name(inner) => inner.window(),)*}}fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) -> Result<(), SoftBufferError> {match self {$($(#[$attr])*Self::$name(inner) => inner.resize(width, height),)*}}fn buffer_mut(&mut self) -> Result<BufferDispatch<'_, D, W>, SoftBufferError> {match self {$($(#[$attr])*Self::$name(inner) => Ok(BufferDispatch::$name(inner.buffer_mut()?)),)*}}fn fetch(&mut self) -> Result<Vec<u32>, SoftBufferError> {match self {$($(#[$attr])*Self::$name(inner) => inner.fetch(),)*}}}impl<D: fmt::Debug, W: fmt::Debug> fmt::Debug for SurfaceDispatch<D, W> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {$($(#[$attr])*Self::$name(inner) => inner.fmt(f),)*}}}pub(crate) enum BufferDispatch<'a, $dgen, $wgen> {$($(#[$attr])*$name($buffer_inner),)*}impl<'a, D: HasDisplayHandle, W: HasWindowHandle> BufferInterface for BufferDispatch<'a, D, W> {#[inline]fn width(&self) -> NonZeroU32 {match self {$($(#[$attr])*Self::$name(inner) => inner.width(),)*}}#[inline]fn height(&self) -> NonZeroU32 {match self {$($(#[$attr])*Self::$name(inner) => inner.height(),)*}}#[inline]fn pixels(&self) -> &[u32] {match self {$($(#[$attr])*Self::$name(inner) => inner.pixels(),)*}}#[inline]fn pixels_mut(&mut self) -> &mut [u32] {match self {$($(#[$attr])*Self::$name(inner) => inner.pixels_mut(),)*}}fn age(&self) -> u8 {match self {$($(#[$attr])*Self::$name(inner) => inner.age(),)*}}fn present(self) -> Result<(), SoftBufferError> {match self {$($(#[$attr])*Self::$name(inner) => inner.present(),)*}}fn present_with_damage(self, damage: &[Rect]) -> Result<(), SoftBufferError> {match self {$($(#[$attr])*Self::$name(inner) => inner.present_with_damage(damage),)*}}}impl<D: fmt::Debug, W: fmt::Debug> fmt::Debug for BufferDispatch<'_, D, W> {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {match self {$($(#[$attr])*Self::$name(inner) => inner.fmt(f),)*}}}};}// XXX empty enum with generic bound is invalid?make_dispatch! {<D, W> =>#[cfg(target_os = "android")]Android(D, backends::android::AndroidImpl<D, W>, backends::android::BufferImpl<'a, D, W>),#[cfg(all(feature = "x11",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]X11(std::sync::Arc<backends::x11::X11DisplayImpl<D>>, backends::x11::X11Impl<D, W>, backends::x11::BufferImpl<'a, D, W>),#[cfg(all(feature = "wayland",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]Wayland(std::sync::Arc<backends::wayland::WaylandDisplayImpl<D>>, backends::wayland::WaylandImpl<D, W>, backends::wayland::BufferImpl<'a, D, W>),#[cfg(all(feature = "kms",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]Kms(std::sync::Arc<backends::kms::KmsDisplayImpl<D>>, backends::kms::KmsImpl<D, W>, backends::kms::BufferImpl<'a, D, W>),#[cfg(target_os = "windows")]Win32(D, backends::win32::Win32Impl<D, W>, backends::win32::BufferImpl<'a, D, W>),#[cfg(target_vendor = "apple")]CoreGraphics(D, backends::cg::CGImpl<D, W>, backends::cg::BufferImpl<'a, D, W>),#[cfg(target_family = "wasm")]Web(backends::web::WebDisplayImpl<D>, backends::web::WebImpl<D, W>, backends::web::BufferImpl<'a, D, W>),#[cfg(target_os = "redox")]Orbital(D, backends::orbital::OrbitalImpl<D, W>, backends::orbital::BufferImpl<'a, D, W>),}
fn main() {cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");}
[package]name = "run-wasm"version = "0.1.0"edition = "2021"[dependencies]cargo-run-wasm = "0.4.0"
use std::num::NonZeroU32;use winit::event::{KeyEvent, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop};use winit::keyboard::{Key, NamedKey};#[path = "utils/winit_app.rs"]mod winit_app;fn main() {let event_loop = EventLoop::new().unwrap();let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let app = winit_app::WinitAppBuilder::with_init(|elwt| winit_app::make_window(elwt, |w| w),move |_elwt, window| {let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap();// Intentionally set the size of the surface to something different than the size of the window.surface.resize(NonZeroU32::new(256).unwrap(), NonZeroU32::new(128).unwrap()).unwrap();surface},).with_event_handler(|window, surface, window_id, event, elwt| {elwt.set_control_flow(ControlFlow::Wait);if window_id != window.id() {return;}match event {WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};let mut buffer = surface.buffer_mut().unwrap();let width = buffer.width().get();for y in 0..buffer.height().get() {for x in 0..width {let red = x % 255;let green = y % 255;let blue = (x * y) % 255;let color = blue | (green << 8) | (red << 16);buffer[(y * width + x) as usize] = color;}}buffer.present().unwrap();}WindowEvent::CloseRequested| WindowEvent::KeyboardInput {event:KeyEvent {logical_key: Key::Named(NamedKey::Escape),..},..} => {elwt.exit();}_ => {}}});winit_app::run_app(event_loop, app);}
#![cfg(target_os = "android")]use winit::event_loop::EventLoop;pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid};#[path = "winit_multithread.rs"]mod desktop_example;/// Run with `cargo apk r --example winit_android`#[no_mangle]fn android_main(app: AndroidApp) {let mut builder = EventLoop::builder();// Install the Android event loop extension if necessary.builder.with_android_app(app);desktop_example::ex::entry(builder.build().unwrap())}
//! `Surface` implements `Send`. This makes sure that multithreading can work here.#[cfg(not(target_family = "wasm"))]#[path = "utils/winit_app.rs"]mod winit_app;#[cfg(not(target_family = "wasm"))]pub mod ex {use std::num::NonZeroU32;use std::sync::{mpsc, Arc, Mutex};use winit::event::{KeyEvent, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop, OwnedDisplayHandle};use winit::keyboard::{Key, NamedKey};use winit::window::Window;use super::winit_app;type Surface = softbuffer::Surface<OwnedDisplayHandle, Arc<Window>>;fn render_thread(do_render: mpsc::Receiver<(Arc<Mutex<Surface>>, NonZeroU32, NonZeroU32)>,done: mpsc::Sender<()>,) {loop {println!("waiting for render...");let Ok((surface, width, height)) = do_render.recv() else {println!("main thread destroyed");break;};// Perform the rendering.let mut surface = surface.lock().unwrap();println!("resizing...");surface.resize(width, height).unwrap();let mut buffer = surface.buffer_mut().unwrap();for y in 0..buffer.height().get() {for x in 0..buffer.width().get() {let red = x % 255;let green = y % 255;let blue = (x * y) % 255;let index = y * buffer.width().get() + x;buffer[index as usize] = blue | (green << 8) | (red << 16);}}println!("presenting...");buffer.present().unwrap();// We're done, tell the main thread to keep going.done.send(()).ok();}}pub fn entry(event_loop: EventLoop<()>) {let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let app = winit_app::WinitAppBuilder::with_init(|elwt| {let attributes = Window::default_attributes();#[cfg(target_family = "wasm")]let attributes =winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true);let window = Arc::new(elwt.create_window(attributes).unwrap());// Spawn a thread to handle rendering for this specific surface. The channels will// be closed and the thread will be stopped whenever this surface (the returned// context below) is dropped, so that it can all be recreated again (on Android)// when a new surface is created.let (start_render, do_render) = mpsc::channel();let (render_done, finish_render) = mpsc::channel();println!("starting thread...");std::thread::spawn(move || render_thread(do_render, render_done));(window, start_render, finish_render)},move |_elwt, (window, _start_render, _finish_render)| {println!("making surface...");Arc::new(Mutex::new(softbuffer::Surface::new(&context, window.clone()).unwrap(),))},).with_event_handler(|state, surface, window_id, event, elwt| {let (window, start_render, finish_render) = state;elwt.set_control_flow(ControlFlow::Wait);if window_id != window.id() {return;}match event {WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};let size = window.inner_size();println!("got size: {size:?}");if let (Some(width), Some(height)) =(NonZeroU32::new(size.width), NonZeroU32::new(size.height)){// Start the render and then finish it.start_render.send((surface.clone(), width, height)).unwrap();finish_render.recv().unwrap();}}WindowEvent::CloseRequested| WindowEvent::KeyboardInput {event:KeyEvent {logical_key: Key::Named(NamedKey::Escape),..},..} => {elwt.exit();}_ => {}}});winit_app::run_app(event_loop, app);}}#[cfg(target_family = "wasm")]mod ex {use winit::event_loop::EventLoop;pub(crate) fn entry(_event_loop: EventLoop<()>) {eprintln!("winit_multithreaded doesn't work on WASM");}}#[cfg(not(target_os = "android"))]fn main() {use winit::event_loop::EventLoop;ex::entry(EventLoop::new().unwrap())}
#![cfg(target_os = "android")]use winit::event_loop::EventLoop;pub use winit::platform::android::{activity::AndroidApp, EventLoopBuilderExtAndroid};#[path = "winit.rs"]mod desktop_example;/// Run with `cargo apk r --example winit_android`#[no_mangle]fn android_main(app: AndroidApp) {let mut builder = EventLoop::builder();// Install the Android event loop extension if necessary.builder.with_android_app(app);desktop_example::entry(builder.build().unwrap())}
use std::num::NonZeroU32;use winit::event::{KeyEvent, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop};use winit::keyboard::{Key, NamedKey};#[path = "utils/winit_app.rs"]mod winit_app;#[cfg(not(target_os = "android"))]fn main() {entry(EventLoop::new().unwrap())}pub(crate) fn entry(event_loop: EventLoop<()>) {let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let app = winit_app::WinitAppBuilder::with_init(|elwt| winit_app::make_window(elwt, |w| w),move |_elwt, window| softbuffer::Surface::new(&context, window.clone()).unwrap(),).with_event_handler(|window, surface, window_id, event, elwt| {elwt.set_control_flow(ControlFlow::Wait);if window_id != window.id() {return;}match event {WindowEvent::Resized(size) => {let Some(surface) = surface else {eprintln!("Resized fired before Resumed or after Suspended");return;};if let (Some(width), Some(height)) =(NonZeroU32::new(size.width), NonZeroU32::new(size.height)){surface.resize(width, height).unwrap();}}WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};let mut buffer = surface.buffer_mut().unwrap();for y in 0..buffer.height().get() {for x in 0..buffer.width().get() {let red = x % 255;let green = y % 255;let blue = (x * y) % 255;let index = y * buffer.width().get() + x;buffer[index as usize] = blue | (green << 8) | (red << 16);}}buffer.present().unwrap();}WindowEvent::CloseRequested| WindowEvent::KeyboardInput {event:KeyEvent {logical_key: Key::Named(NamedKey::Escape),..},..} => {elwt.exit();}_ => {}}});winit_app::run_app(event_loop, app);}
/// Common boilerplate for setting up a winit application.use std::marker::PhantomData;use std::rc::Rc;use winit::application::ApplicationHandler;use winit::event::WindowEvent;use winit::event_loop::{ActiveEventLoop, EventLoop};use winit::window::{Window, WindowAttributes, WindowId};/// Run a Winit application.#[allow(unused_mut)]pub(crate) fn run_app(event_loop: EventLoop<()>, mut app: impl ApplicationHandler<()> + 'static) {#[cfg(not(target_family = "wasm"))]event_loop.run_app(&mut app).unwrap();#[cfg(target_family = "wasm")]winit::platform::web::EventLoopExtWebSys::spawn_app(event_loop, app);}/// Create a window from a set of window attributes.#[allow(dead_code)]pub(crate) fn make_window(elwt: &ActiveEventLoop,f: impl FnOnce(WindowAttributes) -> WindowAttributes,) -> Rc<Window> {let attributes = f(WindowAttributes::default());#[cfg(target_family = "wasm")]let attributes = winit::platform::web::WindowAttributesExtWebSys::with_append(attributes, true);let window = elwt.create_window(attributes);Rc::new(window.unwrap())}/// Easily constructable winit application.pub(crate) struct WinitApp<T, S, Init, InitSurface, Handler, AboutToWaitHandler> {/// Closure to initialize `state`.init: Init,/// Closure to initialize `surface_state`.init_surface: InitSurface,/// Closure to run on window events.event: Handler,/// Closure to run on about_to_wait events.about_to_wait: AboutToWaitHandler,/// Contained state.state: Option<T>,/// Contained surface state.surface_state: Option<S>,}/// Builder that makes it so we don't have to name `T`.pub(crate) struct WinitAppBuilder<T, S, Init, InitSurface> {/// Closure to initialize `state`.init: Init,/// Closure to initialize `surface_state`.init_surface: InitSurface,/// Eat the type parameter._marker: PhantomData<(Option<T>, Option<S>)>,}impl<T, S, Init, InitSurface> WinitAppBuilder<T, S, Init, InitSurface>whereInit: FnMut(&ActiveEventLoop) -> T,InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S,{/// Create with an "init" closure.pub(crate) fn with_init(init: Init, init_surface: InitSurface) -> Self {Self {init,init_surface,_marker: PhantomData,}}/// Build a new application.#[allow(clippy::type_complexity)]pub(crate) fn with_event_handler<F>(self,handler: F,) -> WinitApp<T, S, Init, InitSurface, F, impl FnMut(&mut T, Option<&mut S>, &ActiveEventLoop)>whereF: FnMut(&mut T, Option<&mut S>, WindowId, WindowEvent, &ActiveEventLoop),{WinitApp::new(self.init, self.init_surface, handler, |_, _, _| {})}}impl<T, S, Init, InitSurface, Handler, AboutToWaitHandler>WinitApp<T, S, Init, InitSurface, Handler, AboutToWaitHandler>whereInit: FnMut(&ActiveEventLoop) -> T,InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S,Handler: FnMut(&mut T, Option<&mut S>, WindowId, WindowEvent, &ActiveEventLoop),AboutToWaitHandler: FnMut(&mut T, Option<&mut S>, &ActiveEventLoop),{/// Create a new application.pub(crate) fn new(init: Init,init_surface: InitSurface,event: Handler,about_to_wait: AboutToWaitHandler,) -> Self {Self {init,init_surface,event,about_to_wait,state: None,surface_state: None,}}/// Build a new application.#[allow(dead_code)]pub(crate) fn with_about_to_wait_handler<F>(self,about_to_wait: F,) -> WinitApp<T, S, Init, InitSurface, Handler, F>whereF: FnMut(&mut T, Option<&mut S>, &ActiveEventLoop),{WinitApp::new(self.init, self.init_surface, self.event, about_to_wait)}}impl<T, S, Init, InitSurface, Handler, AboutToWaitHandler> ApplicationHandlerfor WinitApp<T, S, Init, InitSurface, Handler, AboutToWaitHandler>whereInit: FnMut(&ActiveEventLoop) -> T,InitSurface: FnMut(&ActiveEventLoop, &mut T) -> S,Handler: FnMut(&mut T, Option<&mut S>, WindowId, WindowEvent, &ActiveEventLoop),AboutToWaitHandler: FnMut(&mut T, Option<&mut S>, &ActiveEventLoop),{fn resumed(&mut self, el: &ActiveEventLoop) {debug_assert!(self.state.is_none());let mut state = (self.init)(el);self.surface_state = Some((self.init_surface)(el, &mut state));self.state = Some(state);}fn suspended(&mut self, _event_loop: &ActiveEventLoop) {let surface_state = self.surface_state.take();debug_assert!(surface_state.is_some());drop(surface_state);}fn window_event(&mut self,event_loop: &ActiveEventLoop,window_id: WindowId,event: WindowEvent,) {let state = self.state.as_mut().unwrap();let surface_state = self.surface_state.as_mut();(self.event)(state, surface_state, window_id, event, event_loop);}fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {if let Some(state) = self.state.as_mut() {let surface_state = self.surface_state.as_mut();(self.about_to_wait)(state, surface_state, event_loop);}}}
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};use softbuffer::Buffer;use std::num::NonZeroU32;use winit::event::{ElementState, KeyEvent, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop};use winit::keyboard::{Key, NamedKey};#[path = "utils/winit_app.rs"]mod winit_app;fn redraw(buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>, flag: bool) {let width = buffer.width().get();let height = buffer.height().get();for y in 0..height {for x in 0..width {let value = if flag && x >= 100 && x < width - 100 && y >= 100 && y < height - 100 {0x00ffffff} else {let red = (x & 0xff) ^ (y & 0xff);let green = (x & 0x7f) ^ (y & 0x7f);let blue = (x & 0x3f) ^ (y & 0x3f);blue | (green << 8) | (red << 16)};buffer[(y * width + x) as usize] = value;}}}fn main() {let event_loop = EventLoop::new().unwrap();let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let app = winit_app::WinitAppBuilder::with_init(|elwt| {let window = winit_app::make_window(elwt, |w| {w.with_title("Press space to show/hide a rectangle")});let flag = false;(window, flag)},move |_elwt, (window, _flag)| softbuffer::Surface::new(&context, window.clone()).unwrap(),).with_event_handler(|state, surface, window_id, event, elwt| {let (window, flag) = state;elwt.set_control_flow(ControlFlow::Wait);if window_id != window.id() {return;}match event {WindowEvent::Resized(size) => {let Some(surface) = surface else {eprintln!("Resized fired before Resumed or after Suspended");return;};if let (Some(width), Some(height)) =(NonZeroU32::new(size.width), NonZeroU32::new(size.height)){// Resize surfacesurface.resize(width, height).unwrap();}}WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};// Draw something in the windowlet mut buffer = surface.buffer_mut().unwrap();redraw(&mut buffer, *flag);buffer.present().unwrap();}WindowEvent::CloseRequested| WindowEvent::KeyboardInput {event:KeyEvent {logical_key: Key::Named(NamedKey::Escape),..},..} => {elwt.exit();}WindowEvent::KeyboardInput {event:KeyEvent {state: ElementState::Pressed,logical_key: Key::Named(NamedKey::Space),..},..} => {// Flip the rectangle flag and request a redraw to show the changed image*flag = !*flag;window.request_redraw();}_ => {}}});winit_app::run_app(event_loop, app);}
//! Example of using `softbuffer` with `libxcb`.#[cfg(all(feature = "x11",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]mod example {use raw_window_handle::{DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle, XcbDisplayHandle,XcbWindowHandle,};use std::{env, num::NonZeroU32, ptr::NonNull};use x11rb::{connection::Connection,protocol::{xproto::{self, ConnectionExt as _},Event,},xcb_ffi::XCBConnection,};const RED: u32 = 255 << 16;pub(crate) fn run() {// Create a new XCB connectionlet (conn, screen) = XCBConnection::connect(None).expect("Failed to connect to X server");// x11rb doesn't use raw-window-handle yet, so just create our own.let display_handle = XcbDisplayHandle::new(if env::var_os("SOFTBUFFER_NO_DISPLAY").is_some() {None} else {NonNull::new(conn.get_raw_xcb_connection() as *mut _)},screen as _,);// Create a new window.let mut width = 640u16;let mut height = 480u16;let window = conn.generate_id().unwrap();let screen = &conn.setup().roots[screen];let (root_visual, root_parent) = (screen.root_visual, screen.root);conn.create_window(x11rb::COPY_FROM_PARENT as _,window,root_parent,0,0,width,height,0,xproto::WindowClass::COPY_FROM_PARENT,root_visual,&xproto::CreateWindowAux::new().background_pixel(screen.white_pixel).event_mask(xproto::EventMask::EXPOSURE | xproto::EventMask::STRUCTURE_NOTIFY),).unwrap().check().unwrap();let mut window_handle = XcbWindowHandle::new(NonZeroU32::new(window).unwrap());window_handle.visual_id = NonZeroU32::new(root_visual);// Create a new softbuffer context.// SAFETY: The display and window handles outlive the context.let display_handle =unsafe { DisplayHandle::borrow_raw(RawDisplayHandle::Xcb(display_handle)) };let window_handle =unsafe { WindowHandle::borrow_raw(RawWindowHandle::Xcb(window_handle)) };let context = softbuffer::Context::new(display_handle).unwrap();let mut surface = softbuffer::Surface::new(&context, window_handle).unwrap();// Register an atom for closing the window.let wm_protocols_atom = conn.intern_atom(false, "WM_PROTOCOLS".as_bytes()).unwrap().reply().unwrap().atom;let delete_window_atom = conn.intern_atom(false, "WM_DELETE_WINDOW".as_bytes()).unwrap().reply().unwrap().atom;conn.change_property(xproto::PropMode::REPLACE as _,window,wm_protocols_atom,xproto::AtomEnum::ATOM,32,1,&delete_window_atom.to_ne_bytes(),).unwrap().check().unwrap();// Map the window to the screen.conn.map_window(window).unwrap().check().unwrap();// Pump events.loop {let event = conn.wait_for_event().unwrap();match event {Event::Expose(_) => {// Draw a width x height red rectangle.surface.resize(NonZeroU32::new(width.into()).unwrap(),NonZeroU32::new(height.into()).unwrap(),).unwrap();let mut buffer = surface.buffer_mut().unwrap();buffer.fill(RED);buffer.present().unwrap();}Event::ConfigureNotify(configure_notify) => {width = configure_notify.width;height = configure_notify.height;}Event::ClientMessage(cm) => {if cm.data.as_data32()[0] == delete_window_atom {break;}}_ => {}}}// Delete the context and drop the window.drop(context);conn.destroy_window(window).unwrap().check().unwrap();}}#[cfg(all(feature = "x11",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]fn main() {example::run();}#[cfg(not(all(feature = "x11",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows")))))]fn main() {eprintln!("This example requires the `x11` feature to be enabled on a supported platform.");}
use image::GenericImageView;use std::num::NonZeroU32;use winit::event::{KeyEvent, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop};use winit::keyboard::{Key, NamedKey};#[path = "utils/winit_app.rs"]mod winit_app;fn main() {//see fruit.jpg.license for the license of fruit.jpglet fruit = image::load_from_memory(include_bytes!("fruit.jpg")).unwrap();let (width, height) = (fruit.width(), fruit.height());let event_loop = EventLoop::new().unwrap();let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let app = winit_app::WinitAppBuilder::with_init(move |elwt| {winit_app::make_window(elwt, |w| {w.with_inner_size(winit::dpi::PhysicalSize::new(width, height))})},move |_elwt, window| {let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap();// Intentionally only set the size of the surface once, at creation.// This is needed if the window chooses to ignore the size we passed in above, and for the// platforms softbuffer supports that don't yet extract the size from the window.surface.resize(NonZeroU32::new(width).unwrap(),NonZeroU32::new(height).unwrap(),).unwrap();surface},).with_event_handler(move |window, surface, window_id, event, elwt| {elwt.set_control_flow(ControlFlow::Wait);if window_id != window.id() {return;}match event {WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};let mut buffer = surface.buffer_mut().unwrap();let width = fruit.width();for (x, y, pixel) in fruit.pixels() {let red = pixel.0[0] as u32;let green = pixel.0[1] as u32;let blue = pixel.0[2] as u32;let color = blue | (green << 8) | (red << 16);buffer[(y * width + x) as usize] = color;}buffer.present().unwrap();}WindowEvent::CloseRequested| WindowEvent::KeyboardInput {event:KeyEvent {logical_key: Key::Named(NamedKey::Escape),..},..} => {elwt.exit();}_ => {}}});winit_app::run_app(event_loop, app);}
fruit.jpg is licensed under the Creative Commons Attribution 3.0 Unported license and was put on Wikimedia by user AtomcibreSource: https://commons.wikimedia.org/wiki/File:Culinary_fruits_front_view.jpgLicense: https://creativecommons.org/licenses/by/3.0/deed.en
//! Example of using softbuffer with drm-rs.#[cfg(all(feature = "kms",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows"))))]mod imple {use drm::control::{connector, Device as CtrlDevice, Event, ModeTypeFlags, PlaneType};use drm::Device;use raw_window_handle::{DisplayHandle, DrmDisplayHandle, DrmWindowHandle, WindowHandle};use softbuffer::{Context, Surface};use std::num::NonZeroU32;use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};use std::path::Path;use std::time::{Duration, Instant};pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {// Open a new device.let device = Card::find()?;// Create the softbuffer context.let context = unsafe {Context::new(DisplayHandle::borrow_raw({let handle = DrmDisplayHandle::new(device.as_fd().as_raw_fd());handle.into()}))}?;// Get the DRM handles.let handles = device.resource_handles()?;// Get the list of connectors and CRTCs.let connectors = handles.connectors().iter().map(|&con| device.get_connector(con, true)).collect::<Result<Vec<_>, _>>()?;let crtcs = handles.crtcs().iter().map(|&crtc| device.get_crtc(crtc)).collect::<Result<Vec<_>, _>>()?;// Find a connected crtc.let con = connectors.iter().find(|con| con.state() == connector::State::Connected).ok_or("No connected connectors")?;// Get the first CRTC.let crtc = crtcs.first().ok_or("No CRTCs")?;// Find a mode to use.let mode = con.modes().iter().find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)).or_else(|| con.modes().first()).ok_or("No modes")?;// Look for a primary plane compatible with our CRTC.let planes = device.plane_handles()?;let planes = planes.iter().filter(|&&plane| {device.get_plane(plane).is_ok_and(|plane| {let crtcs = handles.filter_crtcs(plane.possible_crtcs());crtcs.contains(&crtc.handle())})}).collect::<Vec<_>>();// Find the first primary plane or take the first one period.let plane = planes.iter().find(|&&&plane| {if let Ok(props) = device.get_properties(plane) {let (ids, vals) = props.as_props_and_values();for (&id, &val) in ids.iter().zip(vals.iter()) {if let Ok(info) = device.get_property(id) {if info.name().to_str() == Ok("type") {return val == PlaneType::Primary as u32 as u64;}}}}false}).or(planes.first()).ok_or("No planes")?;// Create the surface on top of this plane.// Note: This requires root on DRM/KMS.let mut surface = unsafe {Surface::new(&context,WindowHandle::borrow_raw({let handle = DrmWindowHandle::new((**plane).into());handle.into()}),)}?;// Resize the surface.let (width, height) = mode.size();surface.resize(NonZeroU32::new(width as u32).unwrap(),NonZeroU32::new(height as u32).unwrap(),)?;// Start drawing to it.let start = Instant::now();let mut tick = 0;while Instant::now().duration_since(start) < Duration::from_secs(2) {tick += 1;println!("Drawing tick {tick}");// Start drawing.let mut buffer = surface.buffer_mut()?;draw_to_buffer(&mut buffer, tick);buffer.present()?;// Wait for the page flip to happen.rustix::event::poll(&mut [rustix::event::PollFd::new(&device,rustix::event::PollFlags::IN,)],None,)?;// Receive the events.let events = device.receive_events()?;println!("Got some events...");for event in events {match event {Event::PageFlip(_) => {println!("Page flip event.");}Event::Vblank(_) => {println!("Vblank event.");}_ => {println!("Unknown event.");}}}}Ok(())}fn draw_to_buffer(buf: &mut [u32], tick: usize) {let scale = colorous::SINEBOW;let mut i = (tick as f64) / 20.0;while i > 1.0 {i -= 1.0;}let color = scale.eval_continuous(i);let pixel = ((color.r as u32) << 16) | ((color.g as u32) << 8) | (color.b as u32);buf.fill(pixel);}struct Card(std::fs::File);impl Card {fn find() -> Result<Card, Box<dyn std::error::Error>> {for i in 0..10 {let path = format!("/dev/dri/card{i}");// Card enumeration may not start at zero, allow failures while openinglet Ok(device) = Card::open(path) else {continue;};// Only use it if it has connectors.let Ok(handles) = device.resource_handles() else {continue;};if handles.connectors.iter().filter_map(|c| device.get_connector(*c, false).ok()).any(|c| c.state() == connector::State::Connected){return Ok(device);}}Err("No DRM device found".into())}fn open(path: impl AsRef<Path>) -> Result<Card, Box<dyn std::error::Error>> {let file = std::fs::OpenOptions::new().read(true).write(true).open(path)?;Ok(Card(file))}}impl AsFd for Card {fn as_fd(&self) -> BorrowedFd<'_> {self.0.as_fd()}}impl Device for Card {}impl CtrlDevice for Card {}}#[cfg(not(all(feature = "kms",not(any(target_os = "android",target_vendor = "apple",target_os = "redox",target_family = "wasm",target_os = "windows")))))]mod imple {pub(super) fn entry() -> Result<(), Box<dyn std::error::Error>> {eprintln!("This example requires the `kms` feature.");Ok(())}}fn main() -> Result<(), Box<dyn std::error::Error>> {imple::entry()}
#[cfg(not(target_family = "wasm"))]use rayon::prelude::*;use std::f64::consts::PI;use std::num::NonZeroU32;use web_time::Instant;use winit::event::{KeyEvent, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop};use winit::keyboard::{Key, NamedKey};#[path = "utils/winit_app.rs"]mod winit_app;fn main() {let event_loop = EventLoop::new().unwrap();let start = Instant::now();let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let app = winit_app::WinitAppBuilder::with_init(|event_loop| {let window = winit_app::make_window(event_loop, |w| w);let old_size = (0, 0);let frames = pre_render_frames(0, 0);(window, old_size, frames)},move |_elwft, (window, _old_size, _frames)| {softbuffer::Surface::new(&context, window.clone()).unwrap()},).with_event_handler(move |state, surface, window_id, event, elwt| {let (window, old_size, frames) = state;elwt.set_control_flow(ControlFlow::Poll);if window_id != window.id() {return;}match event {WindowEvent::Resized(size) => {let Some(surface) = surface else {eprintln!("Resized fired before Resumed or after Suspended");return;};if let (Some(width), Some(height)) =(NonZeroU32::new(size.width), NonZeroU32::new(size.height)){surface.resize(width, height).unwrap();}}WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};let elapsed = start.elapsed().as_secs_f64() % 1.0;let mut buffer = surface.buffer_mut().unwrap();let size = (buffer.width().get(), buffer.height().get());if size != *old_size {*old_size = size;*frames = pre_render_frames(size.0, size.1);}let frame = &frames[((elapsed * 60.0).round() as usize).clamp(0, 59)];buffer.copy_from_slice(frame);buffer.present().unwrap();}WindowEvent::CloseRequested| WindowEvent::KeyboardInput {event:KeyEvent {logical_key: Key::Named(NamedKey::Escape),..},..} => {elwt.exit();}_ => {}}}).with_about_to_wait_handler(|state, _, _| {let (window, _, _) = state;window.request_redraw();});winit_app::run_app(event_loop, app);}fn pre_render_frames(width: u32, height: u32) -> Vec<Vec<u32>> {let render = |frame_id| {let elapsed = ((frame_id as f64) / (60.0)) * 2.0 * PI;let coords = (0..height).flat_map(|x| (0..width).map(move |y| (x, y)));coords.map(|(x, y)| {let y = (y as f64) / (height as f64);let x = (x as f64) / (width as f64);let red =((((y + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255);let green =((((x + elapsed).sin() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255);let blue =((((y - elapsed).cos() * 0.5 + 0.5) * 255.0).round() as u32).clamp(0, 255);blue | (green << 8) | (red << 16)}).collect::<Vec<_>>()};#[cfg(target_family = "wasm")]return (0..60).map(render).collect();#[cfg(not(target_family = "wasm"))](0..60).into_par_iter().map(render).collect()}
#![allow(deprecated)] // TODOuse criterion::{criterion_group, criterion_main, Criterion};fn buffer_mut(c: &mut Criterion) {#[cfg(target_family = "wasm")]{// Do nothing.let _ = c;}#[cfg(not(target_family = "wasm"))]{use criterion::black_box;use softbuffer::{Context, Surface};use std::num::NonZeroU32;use winit::event_loop::ControlFlow;use winit::platform::run_on_demand::EventLoopExtRunOnDemand;let mut evl = winit::event_loop::EventLoop::new().unwrap();let context = Context::new(evl.owned_display_handle()).unwrap();let window = evl.create_window(winit::window::Window::default_attributes().with_visible(false)).unwrap();evl.run_on_demand(move |ev, elwt| {elwt.set_control_flow(ControlFlow::Poll);if let winit::event::Event::AboutToWait = ev {elwt.exit();let mut surface = Surface::new(&context, &window).unwrap();let size = window.inner_size();surface.resize(NonZeroU32::new(size.width).unwrap(),NonZeroU32::new(size.height).unwrap(),).unwrap();c.bench_function("buffer_mut()", |b| {b.iter(|| {for _ in 0..500 {black_box(surface.buffer_mut().unwrap());}});});c.bench_function("pixels_mut()", |b| {let mut buffer = surface.buffer_mut().unwrap();b.iter(|| {for _ in 0..500 {let x: &mut [u32] = &mut buffer;black_box(x);}});});}}).unwrap();}}criterion_group!(benches, buffer_mut);criterion_main!(benches);
# SoftbufferEnables software rendering via drawing an image straight to a window.Softbuffer integrates with the [`raw-window-handle`](https://crates.io/crates/raw-window-handle) crateto allow writing pixels to a window in a cross-platform way while using the very high quality dedicated window managementlibraries that are available in the Rust ecosystem.## Alternatives[minifb](https://crates.io/crates/minifb) also allows putting a 2D buffer/image on a window in a platform-independent way.Minifb's approach to doing window management itself, however, is problematic code duplication. We already have very high qualitylibraries for this in the Rust ecosystem (such as [winit](https://crates.io/crates/winit)), and minifb's implementationof window management is not ideal. For example, it occasionally segfaults and is missing key features such as settinga window icon on some platforms. While adding these features to minifb would be possible, it makes more sense to usethe standard window handling systems instead.What about [pixels](https://crates.io/crates/pixels)? Pixels accomplishes a very similar goal to Softbuffer,however there are two key differences. Pixels provides some capacity for GPU-accelerated post-processing of what isdisplayed, while Softbuffer does not. Due to not having this post-processing, Softbuffer does not rely on the GPU orhardware accelerated graphics stack in any way, and is thus more portable to installations that do not have access tohardware acceleration (e.g. VMs, older computers, computers with misconfigured drivers). Softbuffer should be used overpixels when its GPU-accelerated post-processing effects are not needed.## License & CreditsThis library is dual-licensed under MIT or Apache-2.0, just like minifb and rust. Significant portions of code were takenfrom the minifb library to do platform-specific work.## Platform support:Some, but not all, platforms supported in [raw-window-handle](https://crates.io/crates/raw-window-handle) are supportedby Softbuffer. Pull requests are welcome to add new platforms! **Nonetheless, all major desktop platforms that winit useson desktop are supported.**For now, the priority for new platforms is:1. to have at least one platform on each OS working (e.g. one of Win32 or WinRT, or one of Xlib, Xcb, and Wayland) and2. for that one platform on each OS to be the one that winit uses.(PRs will be accepted for any platform, even if it does not follow the above priority.)| Platform |||-----------|--||Android NDK|✅|| AppKit |✅|| Orbital |✅|| UIKit |✅|| Wayland |✅|| Web |✅|| Win32 |✅|| WinRT |❌|| XCB |✅|| Xlib |✅|✅: Present\❔: Immature\❌: Absent## WebAssemblyTo run an example with the web backend: `cargo run-wasm --example winit`## AndroidTo run the Android-specific example on an Android phone: `cargo apk r --example winit_android` or `cargo apk r --example winit_multithread_android`.## Example```rust,no_runuse std::num::NonZeroU32;use std::rc::Rc;use winit::event::{Event, WindowEvent};use winit::event_loop::{ControlFlow, EventLoop};use winit::window::Window;#[path = "../examples/utils/winit_app.rs"]mod winit_app;fn main() {let event_loop = EventLoop::new().unwrap();let context = softbuffer::Context::new(event_loop.owned_display_handle()).unwrap();let mut app = winit_app::WinitAppBuilder::with_init(|elwt| {let window = elwt.create_window(Window::default_attributes());Rc::new(window.unwrap())},|_elwt, window| softbuffer::Surface::new(&context, window.clone()).unwrap(),).with_event_handler(|window, surface, window_id, event, elwt| {elwt.set_control_flow(ControlFlow::Wait);if window_id != window.id() {return;}match event {WindowEvent::RedrawRequested => {let Some(surface) = surface else {eprintln!("RedrawRequested fired before Resumed or after Suspended");return;};let size = window.inner_size();surface.resize(NonZeroU32::new(size.width).unwrap(),NonZeroU32::new(size.height).unwrap(),).unwrap();let mut buffer = surface.buffer_mut().unwrap();for index in 0..(buffer.width().get() * buffer.height().get()) {let y = index / buffer.width().get();let x = index % buffer.width().get();let red = x % 255;let green = y % 255;let blue = (x * y) % 255;buffer[index as usize] = blue | (green << 8) | (red << 16);}buffer.present().unwrap();}WindowEvent::CloseRequested => {elwt.exit();}_ => {}}});event_loop.run_app(&mut app).unwrap();}```## MSRV PolicyThis crate's Minimum Supported Rust Version (MSRV) is **1.71**. Changes tothe MSRV will be accompanied by a minor version bump.As a **tentative** policy, the upper bound of the MSRV is given by the followingformula:```textmin(sid, stable - 3)```Where `sid` is the current version of `rustc` provided by [Debian Sid], and`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.[Debian Sid]: https://packages.debian.org/sid/rustcOrbital is not covered by this MSRV policy, as it requires a Rust nightlytoolchain to compile.All crates in the [`rust-windowing`] organizations have thesame MSRV policy.[`rust-windowing`]: https://github.com/rust-windowing## ChangelogSee the [changelog](CHANGELOG.md) for a list of this package's versions and the changes made in each version.
Copyright 2022 Kirill ChibisovPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rights touse, copy, modify, merge, publish, distribute, sublicense, and/or sell copiesof the Software, and to permit persons to whom the Software is furnished to doso, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALLTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.
Apache LicenseVersion 2.0, January 2004http://www.apache.org/licenses/TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION1. Definitions."License" shall mean the terms and conditions for use, reproduction,and distribution as defined by Sections 1 through 9 of this document."Licensor" shall mean the copyright owner or entity authorized bythe copyright owner that is granting the License."Legal Entity" shall mean the union of the acting entity and allother entities that control, are controlled by, or are under commoncontrol with that entity. For the purposes of this definition,"control" means (i) the power, direct or indirect, to cause thedirection or management of such entity, whether by contract orotherwise, or (ii) ownership of fifty percent (50%) or more of theoutstanding shares, or (iii) beneficial ownership of such entity."You" (or "Your") shall mean an individual or Legal Entityexercising permissions granted by this License."Source" form shall mean the preferred form for making modifications,including but not limited to software source code, documentationsource, and configuration files."Object" form shall mean any form resulting from mechanicaltransformation or translation of a Source form, including butnot limited to compiled object code, generated documentation,and conversions to other media types."Work" shall mean the work of authorship, whether in Source orObject form, made available under the License, as indicated by acopyright notice that is included in or attached to the work(an example is provided in the Appendix below)."Derivative Works" shall mean any work, whether in Source or Objectform, that is based on (or derived from) the Work and for which theeditorial revisions, annotations, elaborations, or other modificationsrepresent, as a whole, an original work of authorship. For the purposesof this License, Derivative Works shall not include works that remainseparable from, or merely link (or bind by name) to the interfaces of,the Work and Derivative Works thereof."Contribution" shall mean any work of authorship, includingthe original version of the Work and any modifications or additionsto that Work or Derivative Works thereof, that is intentionallysubmitted to Licensor for inclusion in the Work by the copyright owneror by an individual or Legal Entity authorized to submit on behalf ofthe copyright owner. For the purposes of this definition, "submitted"means any form of electronic, verbal, or written communication sentto the Licensor or its representatives, including but not limited tocommunication on electronic mailing lists, source code control systems,and issue tracking systems that are managed by, or on behalf of, theLicensor for the purpose of discussing and improving the Work, butexcluding communication that is conspicuously marked or otherwisedesignated in writing by the copyright owner as "Not a Contribution.""Contributor" shall mean Licensor and any individual or Legal Entityon behalf of whom a Contribution has been received by Licensor andsubsequently incorporated within the Work.2. Grant of Copyright License. Subject to the terms and conditions ofthis License, each Contributor hereby grants to You a perpetual,worldwide, non-exclusive, no-charge, royalty-free, irrevocablecopyright license to reproduce, prepare Derivative Works of,publicly display, publicly perform, sublicense, and distribute theWork and such Derivative Works in Source or Object form.3. Grant of Patent License. Subject to the terms and conditions ofthis License, each Contributor hereby grants to You a perpetual,worldwide, non-exclusive, no-charge, royalty-free, irrevocable(except as stated in this section) patent license to make, have made,use, offer to sell, sell, import, and otherwise transfer the Work,where such license applies only to those patent claims licensableby such Contributor that are necessarily infringed by theirContribution(s) alone or by combination of their Contribution(s)with the Work to which such Contribution(s) was submitted. If Youinstitute patent litigation against any entity (including across-claim or counterclaim in a lawsuit) alleging that the Workor a Contribution incorporated within the Work constitutes director contributory patent infringement, then any patent licensesgranted to You under this License for that Work shall terminateas of the date such litigation is filed.4. Redistribution. You may reproduce and distribute copies of theWork or Derivative Works thereof in any medium, with or withoutmodifications, and in Source or Object form, provided that Youmeet the following conditions:(a) You must give any other recipients of the Work orDerivative Works a copy of this License; and(b) You must cause any modified files to carry prominent noticesstating that You changed the files; and(c) You must retain, in the Source form of any Derivative Worksthat You distribute, all copyright, patent, trademark, andattribution notices from the Source form of the Work,excluding those notices that do not pertain to any part ofthe Derivative Works; and(d) If the Work includes a "NOTICE" text file as part of itsdistribution, then any Derivative Works that You distribute mustinclude a readable copy of the attribution notices containedwithin such NOTICE file, excluding those notices that do notpertain to any part of the Derivative Works, in at least oneof the following places: within a NOTICE text file distributedas part of the Derivative Works; within the Source form ordocumentation, if provided along with the Derivative Works; or,within a display generated by the Derivative Works, if andwherever such third-party notices normally appear. The contentsof the NOTICE file are for informational purposes only anddo not modify the License. You may add Your own attributionnotices within Derivative Works that You distribute, alongsideor as an addendum to the NOTICE text from the Work, providedthat such additional attribution notices cannot be construedas modifying the License.You may add Your own copyright statement to Your modifications andmay provide additional or different license terms and conditionsfor use, reproduction, or distribution of Your modifications, orfor any such Derivative Works as a whole, provided Your use,reproduction, and distribution of the Work otherwise complies withthe conditions stated in this License.5. Submission of Contributions. Unless You explicitly state otherwise,any Contribution intentionally submitted for inclusion in the Workby You to the Licensor shall be under the terms and conditions ofthis License, without any additional terms or conditions.Notwithstanding the above, nothing herein shall supersede or modifythe terms of any separate license agreement you may have executedwith Licensor regarding such Contributions.6. Trademarks. This License does not grant permission to use the tradenames, trademarks, service marks, or product names of the Licensor,except as required for reasonable and customary use in describing theorigin of the Work and reproducing the content of the NOTICE file.7. Disclaimer of Warranty. Unless required by applicable law oragreed to in writing, Licensor provides the Work (and eachContributor provides its Contributions) on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied, including, without limitation, any warranties or conditionsof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR APARTICULAR PURPOSE. You are solely responsible for determining theappropriateness of using or redistributing the Work and assume anyrisks associated with Your exercise of permissions under this License.8. Limitation of Liability. In no event and under no legal theory,whether in tort (including negligence), contract, or otherwise,unless required by applicable law (such as deliberate and grosslynegligent acts) or agreed to in writing, shall any Contributor beliable to You for damages, including any direct, indirect, special,incidental, or consequential damages of any character arising as aresult of this License or out of the use or inability to use theWork (including but not limited to damages for loss of goodwill,work stoppage, computer failure or malfunction, or any and allother commercial damages or losses), even if such Contributorhas been advised of the possibility of such damages.9. Accepting Warranty or Additional Liability. While redistributingthe Work or Derivative Works thereof, You may choose to offer,and charge a fee for, acceptance of support, warranty, indemnity,or other liability obligations and/or rights consistent with thisLicense. However, in accepting such obligations, You may act onlyon Your own behalf and on Your sole responsibility, not on behalfof any other Contributor, and only if You agree to indemnify,defend, and hold each Contributor harmless for any liabilityincurred by, or claims asserted against, such Contributor by reasonof your accepting any such warranty or additional liability.END OF TERMS AND CONDITIONSAPPENDIX: How to apply the Apache License to your work.To apply the Apache License to your work, attach the followingboilerplate notice, with the fields enclosed by brackets "{}"replaced with your own identifying information. (Don't includethe brackets!) The text should be enclosed in the appropriatecomment syntax for the file format. We also recommend that afile or class name and description of purpose be included on thesame "printed page" as the copyright notice for easieridentification within third-party archives.Copyright 2022 Kirill ChibisovLicensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.
[package]name = "softbuffer"version = "0.4.8"edition = "2021"license = "MIT OR Apache-2.0"description = "Cross-platform software buffer"documentation = "https://docs.rs/softbuffer"readme = "README.md"repository = "https://github.com/rust-windowing/softbuffer"keywords = ["framebuffer", "windowing"]categories = ["game-development", "graphics", "gui", "multimedia", "rendering"]exclude = ["examples"]rust-version = "1.71.0"[[bench]]name = "buffer_mut"harness = false[features]default = ["kms", "x11", "x11-dlopen", "wayland", "wayland-dlopen"]kms = ["bytemuck", "drm", "rustix"]wayland = ["wayland-backend","wayland-client","wayland-sys","memmap2","rustix","fastrand",]wayland-dlopen = ["wayland-sys/dlopen"]x11 = ["as-raw-xcb-connection","bytemuck","fastrand","rustix","tiny-xlib","x11rb",]x11-dlopen = ["tiny-xlib/dlopen", "x11rb/dl-libxcb"][dependencies]raw_window_handle = { package = "raw-window-handle", version = "0.6", features = ["std",] }tracing = { version = "0.1.41", default-features = false }[target.'cfg(target_os = "android")'.dependencies]bytemuck = "1.12.3"ndk = "0.9.0"[target.'cfg(not(any(target_os = "android", target_vendor = "apple", target_os = "redox", target_family = "wasm", target_os = "windows")))'.dependencies]as-raw-xcb-connection = { version = "1.0.0", optional = true }bytemuck = { version = "1.12.3", optional = true }drm = { version = "0.14.1", default-features = false, optional = true }fastrand = { version = "2.0.0", optional = true }memmap2 = { version = "0.9.0", optional = true }rustix = { version = "1.0.1", features = ["fs","mm","shm","std",], default-features = false, optional = true }tiny-xlib = { version = "0.2.1", optional = true }wayland-backend = { version = "0.3.0", features = ["client_system",], optional = true }wayland-client = { version = "0.31.0", optional = true }wayland-sys = { version = "0.31.0", optional = true }x11rb = { version = "0.13.0", features = ["allow-unsafe-code","shm",], optional = true }[target.'cfg(target_os = "windows")'.dependencies.windows-sys]version = "0.61.2"features = ["Win32_Graphics_Gdi","Win32_UI_Shell","Win32_UI_WindowsAndMessaging","Win32_Foundation",][target.'cfg(target_vendor = "apple")'.dependencies]objc2-core-graphics = { version = "0.3.2", default-features = false, features = ["std","objc2","CGColorSpace","CGDataProvider","CGImage",] }objc2 = "0.6.0"objc2-core-foundation = { version = "0.3.2", default-features = false, features = ["std","CFCGTypes",] }objc2-foundation = { version = "0.3.2", default-features = false, features = ["std","objc2-core-foundation","NSDictionary","NSGeometry","NSKeyValueObserving","NSString","NSThread","NSValue",] }objc2-quartz-core = { version = "0.3.2", default-features = false, features = ["std","objc2-core-foundation","CALayer","CATransaction",] }[target.'cfg(target_family = "wasm")'.dependencies]js-sys = "0.3.63"wasm-bindgen = "0.2.86"[target.'cfg(target_family = "wasm")'.dependencies.web-sys]version = "0.3.55"features = ["CanvasRenderingContext2d","Document","Element","HtmlCanvasElement","ImageData","OffscreenCanvas","OffscreenCanvasRenderingContext2d","Window",][target.'cfg(target_os = "redox")'.dependencies]redox_syscall = "0.7"[dev-dependencies]colorous = "1.0.12"criterion = { version = "0.8.1", default-features = false, features = ["cargo_bench_support",] }web-time = "1.0.0"winit = "0.30.0"[target.'cfg(target_os = "android")'.dev-dependencies]winit = { version = "0.30.0", features = ["android-native-activity"] }android-activity = "0.6"[dev-dependencies.image]version = "0.25.0"# Disable rayon on webdefault-features = falsefeatures = ["jpeg"][target.'cfg(not(target_family = "wasm"))'.dev-dependencies]# Turn rayon back on everywhere else; creating the separate entry resets the features to default.rayon = "1.5.1"[target.'cfg(target_family = "wasm")'.dev-dependencies]wasm-bindgen-test = "0.3"[target.'cfg(not(any(target_os = "android", target_vendor = "apple", target_os = "redox", target_family = "wasm", target_os = "windows")))'.dev-dependencies]rustix = { version = "1.0.1", features = ["event"] }[workspace]members = ["run-wasm"][[example]]# Run with `cargo apk r --example winit_android`name = "winit_android"crate-type = ["cdylib"][[example]]# Run with `cargo apk r --example winit_multithread_android`name = "winit_multithread_android"crate-type = ["cdylib"][package.metadata.docs.rs]all-features = truerustdoc-args = ["--cfg", "docsrs"]default-target = "x86_64-unknown-linux-gnu"targets = ["x86_64-pc-windows-msvc","x86_64-apple-darwin","x86_64-unknown-linux-gnu","wasm32-unknown-unknown",]
# Unreleased# 0.4.7- Fix documentation building on `docs.rs`.# 0.4.7- Added support for Android using the `ndk` crate.- Added support for `wasm64-*` targets.- Improved examples.- Added `Buffer::width()` and `Buffer::height()` getters.- `Context` now implements `Clone`.- `Context`, `Surface` and `Buffer` now implement `Debug`.- Bump MSRV to Rust 1.71.- Replace `log` with `tracing`.- Remove `cfg_aliases` dependency.- Update to `objc2` 0.6, `objc2-*` 0.3, `drm` 0.14, `rustix` 1.0 and `windows-sys` 0.61.# 0.4.6- Added support for iOS, tvOS, watchOS and visionOS (UIKit).- Redo the way surfaces work on macOS to work directly with layers, which willallow initializing directly from a `CALayer` in the future.- Update to `windows-sys` 0.59.0 and `core-graphics` v0.24.0.- Add an MSRV policy.# 0.4.5- Make the `wayland-sys` dependency optional. (#223)- Allow for transparent visuals on X11. This technically doesn't work, butotherwise `winit` users might get crashes. (#226)# 0.4.4- Make `Context` `Send`+`Sync` and `Surface` `Send`. (#217)# 0.4.3- Use objc2 as the backend for the CoreGraphics implementation. (#210)- Update drm-rs to version v0.12.0. (#209)- Bump MSRV to 1.70.0 to be in line with Winit.# 0.4.2- Add the ability to get the underlying window handle. (#193)- Rework the backend to use a trait-based interface. (#196)- On Orbital, fix window resize. (#200)- Fix `bytes()` for KMS/DRM implementation. (#203)# 0.4.1- On MacOS, Fix double-free of `NSWindow`. (#180)- Update `drm` to 0.11 (#178)* Fixes build on architectures where drm-rs did not have generated bindings.- Update x11rb to v0.13 (#183)- On Web, add support for more `RawWindowHandle` variants. (#188)- On Wayland, fix buffer age. (#191)# 0.4.0- **Breaking:** Port to use `raw-window-handle` v0.6.(#132)- Enable creating X11 displays without an existing connection. (#171)# 0.3.3- Fix a bug in the new shared memory model in X11. (#170)# 0.3.2* Document that `present_with_damage` is supported on web platforms. (#152)* Replace our usage of `nix` with `rustix`. This enables this crate to run without `libc`. (#164)* Use POSIX shared memory instead of Sys-V shared memory for the X11 backend. (#165)* Bump version for the following dependencies:* `memmap2` (#156)* `redox_syscall` (#161)* `drm` (#163)# 0.3.1* On X11, fix the length of the returned buffer when using the wire-transferred buffer.* On Web, fix incorrect starting coordinates when handling buffer damage.* On Redox, use `MAP_SHARED`; fixing behavior with latest Orbital.* Error instead of segfault on macOS if size isn't set.* Add `OffscreenCanvas` support in web backend.* Add DRM/KMS backend, for running on tty without X/Wayland.* Make `fetch` error on Windows, where it wasn't working correctly.* Implement `Error` trait for `SoftBufferError`.* Dependency updates.# 0.3.0- On MacOS, the contents scale is updated when set_buffer() is called, to adapt when the window is on a new screen (#68).- **Breaking:** Split the `GraphicsContext` type into `Context` and `Surface` (#64).- On Web, cache the document in the `Context` type (#66).- **Breaking:** Introduce a new "owned buffer" for no-copy presentation (#65).- Enable support for multi-threaded WASM (#77).- Fix buffer resizing on X11 (#69).- Add a set of functions for handling buffer damage (#99).- Add a `fetch()` function for getting the window contents (#104).- Bump MSRV to 1.64 (#81).# 0.2.1- Bump `windows-sys` to 0.48# 0.2.0- Add support for Redox/Orbital.- Add support for BSD distributions.- Ported Windows backend from `winapi` to `windows-sys`.- **Breaking:** Take a reference to a window instead of owning the window.- Add a `from_raw` function for directly using raw handles.- Improvements for Wayland support.- Support for HiDPI on macOS.- **Breaking:** Add feature flags for `x11` and `wayland` backends.- Use static dispatch instead of dynamic dispatch for the backends.- Add `libxcb` support to the X11 backend.- Use X11 MIT-SHM extension, if available.# 0.1.1- Added WASM support (Thanks to [Liamolucko](https://github.com/Liamolucko)!)- CALayer is now used for Mac OS backend, which is more flexible about what happens in the windowing library (Thanks to [lunixbochs](https://github.com/lunixbochs)!)# 0.1.0Initial published version with support for Linux (X11 and Wayland), Mac OS (but buggy), and Windows.
/targetCargo.lock/.idea/.vscode
name: Releasepermissions:contents: writeon:push:tags:- v[0-9]+.*jobs:create-release:if: github.repository_owner == 'rust-windowing'runs-on: ubuntu-lateststeps:- uses: actions/checkout@v6- uses: taiki-e/create-gh-release-action@v1with:changelog: CHANGELOG.mdbranch: masterenv:GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}- name: Publish to crates.iorun: cargo publish --token ${{ secrets.CRATES_IO_API_TOKEN }}
name: CIon:pull_request:push:branches: [master]jobs:fmt:name: Tidy Coderuns-on: ubuntu-lateststeps:- uses: actions/checkout@v6- uses: hecrj/setup-rust-action@v2with:rust-version: stablecomponents: rustfmt- name: Check Formattingrun: cargo +stable fmt --all -- --check- uses: taiki-e/install-action@v2with:tool: typos-cli- name: Check Formattingrun: cargo fmt --all -- --check- name: Run Typosrun: typosmsrv:name: MSRVneeds: fmtruns-on: ubuntu-lateststrategy:fail-fast: falsematrix:include:# Android- target: aarch64-linux-android# CoreGraphics- target: aarch64-apple-darwin- target: x86_64-apple-ios# Orbital (doesn't follow MSRV)# - target: x86_64-unknown-redox# Wayland, KMS/DRM, X11- target: i686-unknown-linux-gnu- target: x86_64-unknown-linux-gnu- target: x86_64-unknown-linux-gnufeatures: "x11,x11-dlopen"- target: x86_64-unknown-linux-gnufeatures: "wayland,wayland-dlopen"- target: x86_64-unknown-linux-gnufeatures: "kms"- target: x86_64-unknown-freebsd- target: x86_64-unknown-netbsdfeatures: "x11,x11-dlopen,wayland,wayland-dlopen"# Web- target: wasm32-unknown-unknown# Win32- target: x86_64-pc-windows-msvc- target: x86_64-pc-windows-gnusteps:- uses: actions/checkout@v6- uses: hecrj/setup-rust-action@v2with:rust-version: '1.71.1'targets: ${{ matrix.target }}- name: Use minimal dependency versions# By downgrading all our dependency versions, we ensure that our minimum# version bounds are actually adequate (i.e. users can build `softbuffer`# with minimal versions themselves) and opt-out of any unexpected MSRV# bumps in semver-compatible releases of downstream crates.## RUSTC_BOOTSTRAP=1 is kind of a hack, but it's cumbersome and slow to# install the nightly toolchain.run: RUSTC_BOOTSTRAP=1 cargo -Zminimal-versions generate-lockfile- name: Check that crate compilesrun: cargo check --verbose --target ${{ matrix.target }} ${{ matrix.features && '--no-default-features --features' }} ${{ matrix.features }}tests:name: Testsneeds: fmtstrategy:fail-fast: falsematrix:rust_version: [stable, nightly]platform:- { target: x86_64-pc-windows-msvc, os: windows-latest, }- { target: i686-pc-windows-msvc, os: windows-latest, }# TODO: wait for https://github.com/microsoft/windows-rs/issues/2614#issuecomment-1684152597# to be resolved before re-enabling these# - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }# - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen" }- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "kms" }- { target: x86_64-unknown-redox, os: ubuntu-latest, }- { target: x86_64-unknown-freebsd, os: ubuntu-latest, }- { target: x86_64-unknown-netbsd, os: ubuntu-latest, options: --no-default-features, features: "x11,x11-dlopen,wayland,wayland-dlopen" }- { target: aarch64-apple-darwin, os: macos-latest, }- { target: wasm32-unknown-unknown, os: ubuntu-latest, }include:- rust_version: nightlyplatform: { target: wasm32-unknown-unknown, os: ubuntu-latest, options: "-Zbuild-std=panic_abort,std", rustflags: "-Ctarget-feature=+atomics,+bulk-memory" }# Mac Catalyst is only Tier 2 since Rust 1.81- rust_version: 'nightly'platform: { target: aarch64-apple-ios-macabi, os: macos-latest }env:RUST_BACKTRACE: 1CARGO_INCREMENTAL: 0RUSTFLAGS: "-C debuginfo=0 --deny warnings ${{ matrix.platform.rustflags }}"# Disable doc tests on Rust 1.83, since some path handling regressed there.# This has been fixed in Rust 1.84 beta.# https://github.com/rust-lang/rust/issues/132203OPTIONS: ${{ matrix.platform.options }} ${{ matrix.rust_version == 'stable' && '--lib' || '' }}FEATURES: ${{ format(',{0}', matrix.platform.features ) }}CMD: ${{ matrix.platform.cmd }}RUSTDOCFLAGS: -Dwarningsruns-on: ${{ matrix.platform.os }}steps:- uses: actions/checkout@v6- uses: taiki-e/install-action@v2if: matrix.platform.target == 'wasm32-unknown-unknown'with:tool: wasm-bindgen-cli- uses: hecrj/setup-rust-action@v2with:rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}targets: ${{ matrix.platform.target }}components: clippy, rust-src- name: Install libwaylandif: (matrix.platform.os == 'ubuntu-latest')run: sudo apt-get update && sudo apt-get install libwayland-dev- name: Install GCC Multilibif: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')run: sudo apt-get install gcc-multilib- name: Build crateshell: bashrun: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES- name: Build testsshell: bashif: >!((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) &&!contains(matrix.platform.target, 'redox') &&!contains(matrix.platform.target, 'freebsd') &&!contains(matrix.platform.target, 'netbsd')run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES- name: Run testsshell: bashif: >!((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) &&!contains(matrix.platform.target, 'redox') &&!contains(matrix.platform.target, 'freebsd') &&!contains(matrix.platform.target, 'netbsd') &&!contains(matrix.platform.target, 'linux')run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES# TODO: We should also be using Wayland for testing here.- name: Run tests using Xvfbshell: bashif: >!((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) &&!contains(matrix.platform.target, 'redox') &&!contains(matrix.platform.target, 'freebsd') &&!contains(matrix.platform.target, 'netbsd') &&contains(matrix.platform.target, 'linux') &&!contains(matrix.platform.options, '--no-default-features') &&!contains(matrix.platform.features, 'wayland')run: xvfb-run cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES- name: Lint with clippyshell: bashif: >(matrix.rust_version == 'stable') &&!contains(matrix.platform.options, '--no-default-features') &&!((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) &&!contains(matrix.platform.target, 'redox') &&!contains(matrix.platform.target, 'freebsd') &&!contains(matrix.platform.target, 'netbsd')run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings- name: Lint with rustdocshell: bashif: >(matrix.rust_version == 'stable') &&!contains(matrix.platform.options, '--no-default-features') &&!((matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')) &&!contains(matrix.platform.target, 'redox') &&!contains(matrix.platform.target, 'freebsd') &&!contains(matrix.platform.target, 'netbsd')run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
version: 2updates:- package-ecosystem: cargodirectory: /schedule:interval: weekly- package-ecosystem: github-actionsdirectory: /schedule:interval: weekly
# Android/src/android.rs @MarijnS95# Apple platforms/src/cg.rs @madsmtm# DRM/KMS (no maintainer)/src/kms.rs# Redox/src/orbital.rs @jackpot51# Wayland/src/wayland @ids1024# Web/src/web.rs @daxpedda# Windows/src/win32.rs @notgull# X11/src/x11.rs @notgull
[alias]run-wasm = ["run", "--release", "--package", "run-wasm", "--"][target.wasm32-unknown-unknown]runner = "wasm-bindgen-test-runner"
softbuffer = { path = "softbuffer" }
name = "android_system_properties"version = "0.1.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"dependencies = ["libc",][[package]]
name = "ash"version = "0.37.3+1.3.251"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"dependencies = ["libloading 0.7.4",][[package]]
[[package]]name = "autocfg"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"[[package]]name = "bit-set"version = "0.5.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"dependencies = ["bit-vec",]
name = "codespan-reporting"version = "0.11.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"dependencies = ["termcolor","unicode-width",][[package]]name = "com"version = "0.6.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6"dependencies = ["com_macros",][[package]]name = "com_macros"version = "0.6.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5"dependencies = ["com_macros_support","proc-macro2","syn 1.0.109",][[package]]name = "com_macros_support"version = "0.6.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c"dependencies = ["proc-macro2","quote","syn 1.0.109",][[package]]
name = "drm-ffi"version = "0.9.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b"dependencies = ["drm-sys","rustix 0.38.44",][[package]]name = "drm-fourcc"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"[[package]]name = "drm-sys"version = "0.8.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c"dependencies = ["libc","linux-raw-sys 0.6.5",][[package]]
][[package]]name = "gl_generator"version = "0.14.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"dependencies = ["khronos_api","log","xml-rs",][[package]]name = "glow"version = "0.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"dependencies = ["js-sys","slotmap","wasm-bindgen","web-sys",][[package]]name = "glutin_wgl_sys"version = "0.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"dependencies = ["gl_generator",][[package]]name = "gpu-alloc"version = "0.6.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"dependencies = ["bitflags 2.10.0","gpu-alloc-types",][[package]]name = "gpu-alloc-types"version = "0.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"dependencies = ["bitflags 2.10.0",][[package]]name = "gpu-allocator"version = "0.25.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884"dependencies = ["log","presser","thiserror","winapi","windows",][[package]]name = "gpu-descriptor"version = "0.2.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"dependencies = ["bitflags 2.10.0","gpu-descriptor-types","hashbrown 0.14.5",
version = "0.14.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"dependencies = ["ahash","allocator-api2",][[package]]name = "hashbrown"
name = "hassle-rs"version = "0.11.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890"dependencies = ["bitflags 2.10.0","com","libc","libloading 0.8.9","thiserror","widestring","winapi",][[package]]
name = "khronos-egl"version = "6.0.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"dependencies = ["libc","libloading 0.8.9","pkg-config",][[package]]name = "khronos_api"version = "3.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"[[package]]
name = "metal"version = "0.27.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25"dependencies = ["bitflags 2.10.0","block","core-graphics-types","foreign-types 0.5.0","log","objc","paste",][[package]]
][[package]]name = "naga"version = "0.19.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843"dependencies = ["bit-set","bitflags 2.10.0","codespan-reporting","hexf-parse","indexmap","log","num-traits","rustc-hash","spirv","termcolor","thiserror","unicode-xid",
name = "objc"version = "0.2.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"dependencies = ["malloc_buf","objc_exception",][[package]]
"objc2","objc2-foundation",
"objc2 0.5.2","objc2-foundation 0.2.2",][[package]]name = "objc2-core-foundation"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"dependencies = ["bitflags 2.10.0","dispatch2","objc2 0.6.3",][[package]]name = "objc2-core-graphics"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"dependencies = ["bitflags 2.10.0","dispatch2","objc2 0.6.3","objc2-core-foundation","objc2-io-surface",
"objc2",
"objc2 0.5.2",][[package]]name = "objc2-foundation"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"dependencies = ["bitflags 2.10.0","objc2 0.6.3","objc2-core-foundation",
name = "objc2-quartz-core"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"dependencies = ["bitflags 2.10.0","objc2 0.6.3","objc2-core-foundation","objc2-foundation 0.3.2",][[package]]
name = "objc_exception"version = "0.1.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"dependencies = ["cc",][[package]]
][[package]]name = "parking_lot"version = "0.12.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"dependencies = ["lock_api","parking_lot_core",][[package]]name = "parking_lot_core"version = "0.9.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"dependencies = ["cfg-if","libc","redox_syscall 0.5.18","smallvec","windows-link",
name = "pixels"version = "0.15.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "518d43cd70c5381d4c7bd4bf47ee344beee99b58b0587adcb198cc713ff0dfb5"dependencies = ["bytemuck","pollster","raw-window-handle","thiserror","ultraviolet","wgpu",][[package]]
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"[[package]]name = "rustc-hash"version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"dependencies = ["bitflags 2.10.0",]
name = "safe_arch"version = "0.7.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"dependencies = ["bytemuck",][[package]]
"bitflags 2.10.0",
"as-raw-xcb-connection","bytemuck","drm","fastrand","js-sys","memmap2","ndk","objc2 0.6.3","objc2-core-foundation","objc2-core-graphics","objc2-foundation 0.3.2","objc2-quartz-core 0.3.2","raw-window-handle","redox_syscall 0.7.0","rustix 1.1.2","tiny-xlib","tracing","wasm-bindgen","wayland-backend","wayland-client","wayland-sys","web-sys","windows-sys 0.61.2","x11rb",
version = "1.0.109"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"dependencies = ["proc-macro2","quote","unicode-ident",][[package]]name = "syn"
name = "tiny-xlib"version = "0.2.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e"dependencies = ["as-raw-xcb-connection","ctor-lite","libloading","pkg-config","tracing",][[package]]
name = "unicode-width"version = "0.1.14"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"[[package]]name = "unicode-xid"version = "0.2.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"[[package]]
name = "wgpu"version = "0.19.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01"dependencies = ["arrayvec","cfg-if","cfg_aliases 0.1.1","js-sys","log","naga","parking_lot","profiling","raw-window-handle","smallvec","static_assertions","wasm-bindgen","wasm-bindgen-futures","web-sys","wgpu-core","wgpu-hal","wgpu-types",][[package]]name = "wgpu-core"version = "0.19.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a"dependencies = ["arrayvec","bit-vec","bitflags 2.10.0","cfg_aliases 0.1.1","codespan-reporting","indexmap","log","naga","once_cell","parking_lot","profiling","raw-window-handle","rustc-hash","smallvec","thiserror","web-sys","wgpu-hal","wgpu-types",][[package]]name = "wgpu-hal"version = "0.19.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703"dependencies = ["android_system_properties","arrayvec","ash","bit-set","bitflags 2.10.0","block","cfg_aliases 0.1.1","core-graphics-types","d3d12","glow","glutin_wgl_sys","gpu-alloc","gpu-allocator","gpu-descriptor","hassle-rs","js-sys","khronos-egl","libc","libloading 0.8.9","log","metal","naga","ndk-sys 0.5.0+25.2.9519653","objc","once_cell","parking_lot","profiling","range-alloc","raw-window-handle","renderdoc-sys","rustc-hash","smallvec","thiserror","wasm-bindgen","web-sys","wgpu-types","winapi",][[package]]name = "wgpu-types"version = "0.19.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805"dependencies = ["bitflags 2.10.0","js-sys","web-sys",][[package]]name = "wide"version = "0.7.33"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03"dependencies = ["bytemuck","safe_arch",][[package]]name = "widestring"version = "1.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"[[package]]
name = "windows"version = "0.52.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"dependencies = ["windows-core","windows-targets 0.52.6",][[package]]name = "windows-core"version = "0.52.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"dependencies = ["windows-targets 0.52.6",][[package]]