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