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