| 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 | |