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.
56use 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};
1718use memmap2::MmapMut;
19use wayland_client::{
20 backend::ObjectData,
21 protocol::{wl_buffer, wl_shm, wl_shm_pool},
22 Dispatch, Proxy, QueueHandle, WEnum,
23};
2425use crate::globals::ProvidesBoundGlobal;
2627use super::CreatePoolError;
2829/// 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}
4142impl RawPool {
43pub fn new(
44 len: usize,
45 shm: &impl ProvidesBoundGlobal<wl_shm::WlShm, 1>,
46 ) -> Result<RawPool, CreatePoolError> {
47let shm = shm.bound_global()?;
48let shm_fd = RawPool::create_shm_fd()?;
49let mem_file = File::from(shm_fd);
50 mem_file.set_len(len as u64)?;
5152let 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()));
58let mmap = unsafe { MmapMut::map_mut(&mem_file)? };
5960Ok(RawPool { pool, len, mem_file, mmap })
61 }
6263/// 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.
67pub fn resize(&mut self, size: usize) -> io::Result<()> {
68if size > self.len {
69self.len = size;
70self.mem_file.set_len(size as u64)?;
71self.pool.resize(size as i32);
72self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?;
73 }
7475Ok(())
76 }
7778/// Returns a reference to the underlying shared memory file using the memmap2 crate.
79pub fn mmap(&mut self) -> &mut MmapMut {
80&mut self.mmap
81 }
8283/// Returns the size of the mempool
84#[allow(clippy::len_without_is_empty)]
85pub fn len(&self) -> usize {
86self.len
87 }
8889/// 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)]
103pub 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
113where
114D: Dispatch<wl_buffer::WlBuffer, U> + 'static,
115 U: Send + Sync + 'static,
116 {
117self.pool.create_buffer(offset, width, height, stride, format, qh, udata)
118 }
119120/// 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)]
125pub 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 {
134self.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 }
147148/// Returns the pool object used to communicate with the server.
149pub fn pool(&self) -> &wl_shm_pool::WlShmPool {
150&self.pool
151 }
152}
153154impl io::Write for RawPool {
155fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
156 io::Write::write(&mut self.mem_file, buf)
157 }
158159fn flush(&mut self) -> io::Result<()> {
160 io::Write::flush(&mut self.mem_file)
161 }
162}
163164impl io::Seek for RawPool {
165fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
166 io::Seek::seek(&mut self.mem_file, pos)
167 }
168}
169170impl RawPool {
171fn create_shm_fd() -> io::Result<OwnedFd> {
172#[cfg(target_os = "linux")]
173{
174match RawPool::create_memfd() {
175Ok(fd) => return Ok(fd),
176177// Not supported, use fallback.
178Err(Errno::NOSYS) => (),
179180Err(err) => return Err(Into::<io::Error>::into(err)),
181 };
182 }
183184let time = SystemTime::now();
185let mut mem_file_handle = format!(
186"/smithay-client-toolkit-{}",
187 time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
188 );
189190loop {
191let flags = ShmOFlags::CREATE | ShmOFlags::EXCL | ShmOFlags::RDWR;
192193let mode = Mode::RUSR | Mode::WUSR;
194195match rustix::shm::shm_open(mem_file_handle.as_str(), flags, mode) {
196Ok(fd) => match rustix::shm::shm_unlink(mem_file_handle.as_str()) {
197Ok(_) => return Ok(fd),
198199Err(errno) => {
200return Err(errno.into());
201 }
202 },
203204Err(Errno::EXIST) => {
205// Change the handle if we happen to be duplicate.
206let time = SystemTime::now();
207208 mem_file_handle = format!(
209"/smithay-client-toolkit-{}",
210 time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
211 );
212213continue;
214 }
215216Err(Errno::INTR) => continue,
217218Err(err) => return Err(err.into()),
219 }
220 }
221 }
222223#[cfg(target_os = "linux")]
224fn create_memfd() -> rustix::io::Result<OwnedFd> {
225use std::ffi::CStr;
226227use rustix::fs::{MemfdFlags, SealFlags};
228229loop {
230let name = CStr::from_bytes_with_nul(b"smithay-client-toolkit\0").unwrap();
231let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC;
232233match rustix::fs::memfd_create(name, flags) {
234Ok(fd) => {
235// We only need to seal for the purposes of optimization, ignore the errors.
236let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL);
237return Ok(fd);
238 }
239240Err(Errno::INTR) => continue,
241242Err(err) => return Err(err),
243 }
244 }
245 }
246}
247248impl Drop for RawPool {
249fn drop(&mut self) {
250self.pool.destroy();
251 }
252}
253254#[derive(Debug)]
255struct ShmPoolData;
256257impl ObjectData for ShmPoolData {
258fn event(
259self: Arc<Self>,
260_: &wayland_client::backend::Backend,
261_: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>,
262 ) -> Option<Arc<(dyn ObjectData + 'static)>> {
263unreachable!("wl_shm_pool has no events")
264 }
265266fn destroyed(&self, _: wayland_client::backend::ObjectId) {}
267}