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