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