smithay_client_toolkit/shm/
raw.rs

1//! A raw shared memory pool handler.
2//!
3//! This is intended as a safe building block for higher level shared memory pool abstractions and is not
4//! encouraged for most library users.
5
6use rustix::{
7    io::Errno,
8    shm::{Mode, ShmOFlags},
9};
10use std::{
11    fs::File,
12    io,
13    os::unix::prelude::{AsFd, OwnedFd},
14    sync::Arc,
15    time::{SystemTime, UNIX_EPOCH},
16};
17
18use memmap2::MmapMut;
19use wayland_client::{
20    backend::ObjectData,
21    protocol::{wl_buffer, wl_shm, wl_shm_pool},
22    Dispatch, Proxy, QueueHandle, WEnum,
23};
24
25use crate::globals::ProvidesBoundGlobal;
26
27use super::CreatePoolError;
28
29/// A raw handler for file backed shared memory pools.
30///
31/// This type of pool will create the SHM memory pool and provide a way to resize the pool.
32///
33/// This pool does not release buffers. If you need this, use one of the higher level pools.
34#[derive(Debug)]
35pub struct RawPool {
36    pool: wl_shm_pool::WlShmPool,
37    len: usize,
38    mem_file: File,
39    mmap: MmapMut,
40}
41
42impl RawPool {
43    pub fn new(
44        len: usize,
45        shm: &impl ProvidesBoundGlobal<wl_shm::WlShm, 1>,
46    ) -> Result<RawPool, CreatePoolError> {
47        let shm = shm.bound_global()?;
48        let shm_fd = RawPool::create_shm_fd()?;
49        let mem_file = File::from(shm_fd);
50        mem_file.set_len(len as u64)?;
51
52        let pool = shm
53            .send_constructor(
54                wl_shm::Request::CreatePool { fd: mem_file.as_fd(), size: len as i32 },
55                Arc::new(ShmPoolData),
56            )
57            .unwrap_or_else(|_| Proxy::inert(shm.backend().clone()));
58        let mmap = unsafe { MmapMut::map_mut(&mem_file)? };
59
60        Ok(RawPool { pool, len, mem_file, mmap })
61    }
62
63    /// Resizes the memory pool, notifying the server the pool has changed in size.
64    ///
65    /// The wl_shm protocol only allows the pool to be made bigger. If the new size is smaller than the
66    /// current size of the pool, this function will do nothing.
67    pub fn resize(&mut self, size: usize) -> io::Result<()> {
68        if size > self.len {
69            self.len = size;
70            self.mem_file.set_len(size as u64)?;
71            self.pool.resize(size as i32);
72            self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?;
73        }
74
75        Ok(())
76    }
77
78    /// Returns a reference to the underlying shared memory file using the memmap2 crate.
79    pub fn mmap(&mut self) -> &mut MmapMut {
80        &mut self.mmap
81    }
82
83    /// Returns the size of the mempool
84    #[allow(clippy::len_without_is_empty)]
85    pub fn len(&self) -> usize {
86        self.len
87    }
88
89    /// Create a new buffer to this pool.
90    ///
91    /// ## Parameters
92    /// - `offset`: the offset (in bytes) from the beginning of the pool at which this buffer starts.
93    /// - `width` and `height`: the width and height of the buffer in pixels.
94    /// - `stride`: distance (in bytes) between the beginning of a row and the next one.
95    /// - `format`: the encoding format of the pixels.
96    ///
97    /// The encoding format of the pixels must be supported by the compositor or else a protocol error is
98    /// risen. You can ensure the format is supported by listening to [`Shm::formats`](crate::shm::Shm::formats).
99    ///
100    /// Note this function only creates the wl_buffer object, you will need to write to the pixels using the
101    /// [`io::Write`] implementation or [`RawPool::mmap`].
102    #[allow(clippy::too_many_arguments)]
103    pub fn create_buffer<D, U>(
104        &mut self,
105        offset: i32,
106        width: i32,
107        height: i32,
108        stride: i32,
109        format: wl_shm::Format,
110        udata: U,
111        qh: &QueueHandle<D>,
112    ) -> wl_buffer::WlBuffer
113    where
114        D: Dispatch<wl_buffer::WlBuffer, U> + 'static,
115        U: Send + Sync + 'static,
116    {
117        self.pool.create_buffer(offset, width, height, stride, format, qh, udata)
118    }
119
120    /// Create a new buffer to this pool.
121    ///
122    /// This is identical to [Self::create_buffer], but allows using a custom [ObjectData]
123    /// implementation instead of relying on the [Dispatch] interface.
124    #[allow(clippy::too_many_arguments)]
125    pub fn create_buffer_raw(
126        &mut self,
127        offset: i32,
128        width: i32,
129        height: i32,
130        stride: i32,
131        format: wl_shm::Format,
132        data: Arc<dyn ObjectData + 'static>,
133    ) -> wl_buffer::WlBuffer {
134        self.pool
135            .send_constructor(
136                wl_shm_pool::Request::CreateBuffer {
137                    offset,
138                    width,
139                    height,
140                    stride,
141                    format: WEnum::Value(format),
142                },
143                data,
144            )
145            .unwrap_or_else(|_| Proxy::inert(self.pool.backend().clone()))
146    }
147
148    /// Returns the pool object used to communicate with the server.
149    pub fn pool(&self) -> &wl_shm_pool::WlShmPool {
150        &self.pool
151    }
152}
153
154impl io::Write for RawPool {
155    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
156        io::Write::write(&mut self.mem_file, buf)
157    }
158
159    fn flush(&mut self) -> io::Result<()> {
160        io::Write::flush(&mut self.mem_file)
161    }
162}
163
164impl io::Seek for RawPool {
165    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
166        io::Seek::seek(&mut self.mem_file, pos)
167    }
168}
169
170impl RawPool {
171    fn create_shm_fd() -> io::Result<OwnedFd> {
172        #[cfg(target_os = "linux")]
173        {
174            match RawPool::create_memfd() {
175                Ok(fd) => return Ok(fd),
176
177                // Not supported, use fallback.
178                Err(Errno::NOSYS) => (),
179
180                Err(err) => return Err(Into::<io::Error>::into(err)),
181            };
182        }
183
184        let time = SystemTime::now();
185        let mut mem_file_handle = format!(
186            "/smithay-client-toolkit-{}",
187            time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
188        );
189
190        loop {
191            let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR;
192
193            let mode = Mode::RUSR | Mode::WUSR;
194
195            match rustix::shm::shm_open(mem_file_handle.as_str(), flags, mode) {
196                Ok(fd) => match rustix::shm::shm_unlink(mem_file_handle.as_str()) {
197                    Ok(_) => return Ok(fd),
198
199                    Err(errno) => {
200                        return Err(errno.into());
201                    }
202                },
203
204                Err(Errno::EXIST) => {
205                    // Change the handle if we happen to be duplicate.
206                    let time = SystemTime::now();
207
208                    mem_file_handle = format!(
209                        "/smithay-client-toolkit-{}",
210                        time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
211                    );
212
213                    continue;
214                }
215
216                Err(Errno::INTR) => continue,
217
218                Err(err) => return Err(err.into()),
219            }
220        }
221    }
222
223    #[cfg(target_os = "linux")]
224    fn create_memfd() -> rustix::io::Result<OwnedFd> {
225        use std::ffi::CStr;
226
227        use rustix::fs::{MemfdFlags, SealFlags};
228
229        loop {
230            let name = CStr::from_bytes_with_nul(b"smithay-client-toolkit\0").unwrap();
231            let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC;
232
233            match rustix::fs::memfd_create(name, flags) {
234                Ok(fd) => {
235                    // We only need to seal for the purposes of optimization, ignore the errors.
236                    let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL);
237                    return Ok(fd);
238                }
239
240                Err(Errno::INTR) => continue,
241
242                Err(err) => return Err(err),
243            }
244        }
245    }
246}
247
248impl Drop for RawPool {
249    fn drop(&mut self) {
250        self.pool.destroy();
251    }
252}
253
254#[derive(Debug)]
255struct ShmPoolData;
256
257impl ObjectData for ShmPoolData {
258    fn event(
259        self: Arc<Self>,
260        _: &wayland_client::backend::Backend,
261        _: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>,
262    ) -> Option<Arc<(dyn ObjectData + 'static)>> {
263        unreachable!("wl_shm_pool has no events")
264    }
265
266    fn destroyed(&self, _: wayland_client::backend::ObjectId) {}
267}