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