1 use std::{
2 cell::RefCell,
3 ffi::CStr,
4 fs::File,
5 io,
6 os::unix::io::{FromRawFd, RawFd},
7 rc::Rc,
8 time::SystemTime,
9 time::UNIX_EPOCH,
10 };
11
12 #[cfg(target_os = "linux")]
13 use nix::sys::memfd;
14 use nix::{
15 errno::Errno,
16 fcntl,
17 sys::{mman, stat},
18 unistd,
19 };
20
21 use memmap2::MmapMut;
22
23 use wayland_client::{
24 protocol::{wl_buffer, wl_shm, wl_shm_pool},
25 Attached, Main,
26 };
27
28 /// A Double memory pool, for convenient double-buffering
29 ///
30 /// This type wraps two internal memory pool, and can be
31 /// use for conveniently implementing double-buffering in your
32 /// apps.
33 ///
34 /// DoubleMemPool requires a implementation that is called when
35 /// one of the two internal memory pools becomes free after None
36 /// was returned from the `pool()` method.
37 pub struct DoubleMemPool {
38 pool1: MemPool,
39 pool2: MemPool,
40 free: Rc<RefCell<bool>>,
41 }
42
43 impl DoubleMemPool {
44 /// Create a double memory pool
new<F>(shm: Attached<wl_shm::WlShm>, callback: F) -> io::Result<DoubleMemPool> where F: FnMut(wayland_client::DispatchData) + 'static,45 pub fn new<F>(shm: Attached<wl_shm::WlShm>, callback: F) -> io::Result<DoubleMemPool>
46 where
47 F: FnMut(wayland_client::DispatchData) + 'static,
48 {
49 let free = Rc::new(RefCell::new(true));
50 let callback = Rc::new(RefCell::new(callback));
51 let my_free = free.clone();
52 let my_callback = callback.clone();
53 let pool1 = MemPool::new(shm.clone(), move |ddata| {
54 let signal = {
55 let mut my_free = my_free.borrow_mut();
56 if !*my_free {
57 *my_free = true;
58 true
59 } else {
60 false
61 }
62 };
63 if signal {
64 (&mut *my_callback.borrow_mut())(ddata);
65 }
66 })?;
67 let my_free = free.clone();
68 let pool2 = MemPool::new(shm, move |ddata| {
69 let signal = {
70 let mut my_free = my_free.borrow_mut();
71 if !*my_free {
72 *my_free = true;
73 true
74 } else {
75 false
76 }
77 };
78 if signal {
79 (&mut *callback.borrow_mut())(ddata);
80 }
81 })?;
82 Ok(DoubleMemPool { pool1, pool2, free })
83 }
84
85 /// This method checks both its internal memory pools and returns
86 /// one if that pool does not contain any buffers that are still in use
87 /// by the server. If both the memory pools contain buffers that are currently
88 /// in use by the server None will be returned.
pool(&mut self) -> Option<&mut MemPool>89 pub fn pool(&mut self) -> Option<&mut MemPool> {
90 if !self.pool1.is_used() {
91 Some(&mut self.pool1)
92 } else if !self.pool2.is_used() {
93 Some(&mut self.pool2)
94 } else {
95 *self.free.borrow_mut() = false;
96 None
97 }
98 }
99 }
100
101 /// A wrapper handling an SHM memory pool backed by a shared memory file
102 ///
103 /// This wrapper handles for you the creation of the shared memory file and its synchronization
104 /// with the protocol.
105 ///
106 /// Mempool internally tracks the release of the buffers by the compositor. As such, creating a buffer
107 /// that is not commited to a surface (and then never released by the server) would cause the Mempool
108 /// to be stuck believing it is still in use.
109 ///
110 /// Mempool will also handle the destruction of buffers and as such the `destroy()` method should not
111 /// be used on buffers created from Mempool.
112 ///
113 /// Overwriting the contents of the memory pool before it is completely freed may cause graphical
114 /// glitches due to the possible corruption of data while the compositor is reading it.
115 ///
116 /// Mempool requires a callback that will be called when the pool becomes free, this
117 /// happens when all the pools buffers are released by the server.
118 pub struct MemPool {
119 file: File,
120 len: usize,
121 pool: Main<wl_shm_pool::WlShmPool>,
122 buffer_count: Rc<RefCell<u32>>,
123 mmap: MmapMut,
124 callback: Rc<RefCell<dyn FnMut(wayland_client::DispatchData)>>,
125 }
126
127 impl MemPool {
128 /// Create a new memory pool associated with given shm
new<F>(shm: Attached<wl_shm::WlShm>, callback: F) -> io::Result<MemPool> where F: FnMut(wayland_client::DispatchData) + 'static,129 pub fn new<F>(shm: Attached<wl_shm::WlShm>, callback: F) -> io::Result<MemPool>
130 where
131 F: FnMut(wayland_client::DispatchData) + 'static,
132 {
133 let mem_fd = create_shm_fd()?;
134 let mem_file = unsafe { File::from_raw_fd(mem_fd) };
135 mem_file.set_len(128)?;
136
137 let pool = shm.create_pool(mem_fd, 128);
138
139 let mmap = unsafe { MmapMut::map_mut(&mem_file).unwrap() };
140
141 Ok(MemPool {
142 file: mem_file,
143 len: 128,
144 pool,
145 buffer_count: Rc::new(RefCell::new(0)),
146 mmap,
147 callback: Rc::new(RefCell::new(callback)),
148 })
149 }
150
151 /// Resize the memory pool
152 ///
153 /// This affect the size as it is seen by the wayland server. Even
154 /// if you extend the temporary file size by writing to it, you need to
155 /// call this method otherwise the server won't see the new size.
156 ///
157 /// Memory pools can only be extented, as such this method will do nothing
158 /// if the requested new size is smaller than the current size.
159 ///
160 /// This method allows you to ensure the underlying pool is large enough to
161 /// hold what you want to write to it.
resize(&mut self, newsize: usize) -> io::Result<()>162 pub fn resize(&mut self, newsize: usize) -> io::Result<()> {
163 if newsize > self.len {
164 self.file.set_len(newsize as u64)?;
165 self.pool.resize(newsize as i32);
166 self.len = newsize;
167 self.mmap = unsafe { MmapMut::map_mut(&self.file).unwrap() };
168 }
169 Ok(())
170 }
171
172 /// Create a new buffer to this pool
173 ///
174 /// The parameters are:
175 ///
176 /// - `offset`: the offset (in bytes) from the beginning of the pool at which this
177 /// buffer starts
178 /// - `width`: the width of this buffer (in pixels)
179 /// - `height`: the height of this buffer (in pixels)
180 /// - `stride`: distance (in bytes) between the beginning of a row and the next one
181 /// - `format`: the encoding format of the pixels. Using a format that was not
182 /// advertised to the `wl_shm` global by the server is a protocol error and will
183 /// terminate your connection
buffer( &self, offset: i32, width: i32, height: i32, stride: i32, format: wl_shm::Format, ) -> wl_buffer::WlBuffer184 pub fn buffer(
185 &self,
186 offset: i32,
187 width: i32,
188 height: i32,
189 stride: i32,
190 format: wl_shm::Format,
191 ) -> wl_buffer::WlBuffer {
192 *self.buffer_count.borrow_mut() += 1;
193 let my_buffer_count = self.buffer_count.clone();
194 let my_callback = self.callback.clone();
195 let buffer = self.pool.create_buffer(offset, width, height, stride, format);
196 buffer.quick_assign(move |buffer, event, dispatch_data| match event {
197 wl_buffer::Event::Release => {
198 buffer.destroy();
199 let new_count = {
200 // borrow the buffer_count for as short as possible, in case
201 // the user wants to create a new buffer from the callback
202 let mut my_buffer_count = my_buffer_count.borrow_mut();
203 *my_buffer_count -= 1;
204 *my_buffer_count
205 };
206 if new_count == 0 {
207 (&mut *my_callback.borrow_mut())(dispatch_data);
208 }
209 }
210 _ => unreachable!(),
211 });
212 (*buffer).clone().detach()
213 }
214
215 /// Uses the memmap2 crate to map the underlying shared memory file
mmap(&mut self) -> &mut MmapMut216 pub fn mmap(&mut self) -> &mut MmapMut {
217 &mut self.mmap
218 }
219
220 /// Returns true if the pool contains buffers that are currently in use by the server
is_used(&self) -> bool221 pub fn is_used(&self) -> bool {
222 *self.buffer_count.borrow() != 0
223 }
224 }
225
226 impl Drop for MemPool {
drop(&mut self)227 fn drop(&mut self) {
228 self.pool.destroy();
229 }
230 }
231
232 impl io::Write for MemPool {
write(&mut self, buf: &[u8]) -> io::Result<usize>233 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
234 io::Write::write(&mut self.file, buf)
235 }
flush(&mut self) -> io::Result<()>236 fn flush(&mut self) -> io::Result<()> {
237 io::Write::flush(&mut self.file)
238 }
239 }
240
241 impl io::Seek for MemPool {
seek(&mut self, pos: io::SeekFrom) -> io::Result<u64>242 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
243 io::Seek::seek(&mut self.file, pos)
244 }
245 }
246
create_shm_fd() -> io::Result<RawFd>247 fn create_shm_fd() -> io::Result<RawFd> {
248 // Only try memfd on linux
249 #[cfg(target_os = "linux")]
250 loop {
251 match memfd::memfd_create(
252 CStr::from_bytes_with_nul(b"smithay-client-toolkit\0").unwrap(),
253 memfd::MemFdCreateFlag::MFD_CLOEXEC,
254 ) {
255 Ok(fd) => return Ok(fd),
256 Err(nix::Error::Sys(Errno::EINTR)) => continue,
257 Err(nix::Error::Sys(Errno::ENOSYS)) => break,
258 Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
259 Err(err) => unreachable!(err),
260 }
261 }
262
263 // Fallback to using shm_open
264 let sys_time = SystemTime::now();
265 let mut mem_file_handle = format!(
266 "/smithay-client-toolkit-{}",
267 sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
268 );
269 loop {
270 match mman::shm_open(
271 mem_file_handle.as_str(),
272 fcntl::OFlag::O_CREAT
273 | fcntl::OFlag::O_EXCL
274 | fcntl::OFlag::O_RDWR
275 | fcntl::OFlag::O_CLOEXEC,
276 stat::Mode::S_IRUSR | stat::Mode::S_IWUSR,
277 ) {
278 Ok(fd) => match mman::shm_unlink(mem_file_handle.as_str()) {
279 Ok(_) => return Ok(fd),
280 Err(nix::Error::Sys(errno)) => match unistd::close(fd) {
281 Ok(_) => return Err(io::Error::from(errno)),
282 Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
283 Err(err) => panic!(err),
284 },
285 Err(err) => panic!(err),
286 },
287 Err(nix::Error::Sys(Errno::EEXIST)) => {
288 // If a file with that handle exists then change the handle
289 mem_file_handle = format!(
290 "/smithay-client-toolkit-{}",
291 sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
292 );
293 continue;
294 }
295 Err(nix::Error::Sys(Errno::EINTR)) => continue,
296 Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
297 Err(err) => unreachable!(err),
298 }
299 }
300 }
301
302 impl<E> crate::environment::Environment<E>
303 where
304 E: crate::environment::GlobalHandler<wl_shm::WlShm>,
305 {
306 /// Create a simple memory pool
307 ///
308 /// This memory pool track the usage of the buffers created from it,
309 /// and invokes your callback when the compositor has finished using
310 /// all of them.
create_simple_pool<F>(&self, callback: F) -> io::Result<MemPool> where F: FnMut(wayland_client::DispatchData) + 'static,311 pub fn create_simple_pool<F>(&self, callback: F) -> io::Result<MemPool>
312 where
313 F: FnMut(wayland_client::DispatchData) + 'static,
314 {
315 MemPool::new(self.require_global::<wl_shm::WlShm>(), callback)
316 }
317
318 /// Create a double memory pool
319 ///
320 /// This can be used for double-buffered drawing. The memory pool
321 /// is backed by two different SHM segments, which are used in alternance.
322 ///
323 /// The provided callback is triggered when one of the pools becomes unused again
324 /// after you tried to draw while both where in use.
create_double_pool<F>(&self, callback: F) -> io::Result<DoubleMemPool> where F: FnMut(wayland_client::DispatchData) + 'static,325 pub fn create_double_pool<F>(&self, callback: F) -> io::Result<DoubleMemPool>
326 where
327 F: FnMut(wayland_client::DispatchData) + 'static,
328 {
329 DoubleMemPool::new(self.require_global::<wl_shm::WlShm>(), callback)
330 }
331 }
332