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 | |
6 | use rustix::{ |
7 | io::Errno, |
8 | shm::{Mode, ShmOFlags}, |
9 | }; |
10 | use std::{ |
11 | fs::File, |
12 | io, |
13 | os::unix::prelude::{AsFd, OwnedFd}, |
14 | sync::Arc, |
15 | time::{SystemTime, UNIX_EPOCH}, |
16 | }; |
17 | |
18 | use memmap2::MmapMut; |
19 | use wayland_client::{ |
20 | backend::ObjectData, |
21 | protocol::{wl_buffer, wl_shm, wl_shm_pool}, |
22 | Dispatch, Proxy, QueueHandle, WEnum, |
23 | }; |
24 | |
25 | use crate::globals::ProvidesBoundGlobal; |
26 | |
27 | use 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)] |
35 | pub struct RawPool { |
36 | pool: wl_shm_pool::WlShmPool, |
37 | len: usize, |
38 | mem_file: File, |
39 | mmap: MmapMut, |
40 | } |
41 | |
42 | impl 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 | |
154 | impl 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 | |
164 | impl 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 | |
170 | impl 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 | |
248 | impl Drop for RawPool { |
249 | fn drop(&mut self) { |
250 | self.pool.destroy(); |
251 | } |
252 | } |
253 | |
254 | #[derive (Debug)] |
255 | struct ShmPoolData; |
256 | |
257 | impl 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 | |