//! Alokacja pamięci współdzielonej i wystawienie jej jako `wl_shm`. //! //! Kluczowa zasada: **deskryptor pliku i mapowanie muszą żyć razem** z obiektami //! `wl_shm_pool`/`wl_buffer`. Kompozytor czyta bowiem z pamięci, do której fd //! wskazuje — jeśli plik zostałby zamknięty, bufory przestałyby być poprawne. //! Dlatego [`ShmBuffer`] trzyma `File`, `MmapMut`, `WlShmPool` i `WlBuffer` w jednej //! strukturze, której czas życia pilnuje kompilator. use std::fmt; use std::fs::File; use std::os::fd::AsFd; use memmap2::MmapMut; use wayland_client::protocol::wl_buffer::WlBuffer; use wayland_client::protocol::wl_shm::{Format, WlShm}; use wayland_client::protocol::wl_shm_pool::WlShmPool; use wayland_client::{Dispatch, QueueHandle}; /// Błędy alokacji bufora SHM. #[derive(Debug)] pub enum ShmError { /// Błąd operacji wejścia/wyjścia na pliku tymczasowym. Io(std::io::Error), /// Żądany rozmiar jest niepoprawny (zero lub ujemny). ZeroSize, /// Rozmiar przekracza zakres `i32` wymagany przez protokół. TooLarge(usize), } impl fmt::Display for ShmError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ShmError::Io(err) => write!(f, "błąd I/O pamięci współdzielonej: {err}"), ShmError::ZeroSize => write!(f, "rozmiar bufora SHM musi być dodatni"), ShmError::TooLarge(size) => { write!( f, "rozmiar bufora SHM ({size} B) przekracza zakres protokołu" ) } } } } impl std::error::Error for ShmError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { ShmError::Io(err) => Some(err), _ => None, } } } impl From for ShmError { fn from(value: std::io::Error) -> Self { ShmError::Io(value) } } /// Bufor pamięci współdzielonej gotowy do wystawienia jako `wl_buffer`. pub struct ShmBuffer { /// Plik tymczasowy będący „nośnikiem" pamięci. MUSI żyć tak długo jak bufory. pub file: File, /// Mapowanie pliku do przestrzeni adresowej procesu — tu renderuje Cairo. pub mmap: MmapMut, /// Szerokość bufora w pikselach. pub width: i32, /// Wysokość bufora w pikselach. pub height: i32, /// Liczba bajtów na wiersz. pub stride: i32, /// Łączny rozmiar bufora w bajtach. pub size: usize, /// Format piksela zgłoszony kompozytorowi. pub format: Format, /// Obiekt `wl_shm_pool` (trzymany, aby móc go później zniszczyć/zresize'ować). pub pool: WlShmPool, /// Obiekt `wl_buffer`, który podłączamy do powierzchni. pub buffer: WlBuffer, } impl ShmBuffer { /// Tworzy plik w pamięci, mapuje go i wystawia jako `wl_buffer` w formacie ARGB32. /// /// API-NOTE: używamy `tempfile::tempfile()`, bo jest przenośne i nie wymaga /// dodatkowej zależności. Dla pełnej zgodności ze specyfikacją `wl_shm` warto /// rozważyć `memfd_create` + pieczęć `F_SEAL_SHRINK` (chroni pamięć przed /// przycięciem przez kompozytor). Wersja z `tempfile` jest poprawna funkcjonalnie. pub fn new( shm: &WlShm, qh: &QueueHandle, width: i32, height: i32, ) -> Result where State: Dispatch + Dispatch + 'static, { if width <= 0 || height <= 0 { return Err(ShmError::ZeroSize); } let format = Format::Argb8888; // ARGB8888 to zawsze 4 bajty na piksel. let stride = width.checked_mul(4).ok_or(ShmError::TooLarge(usize::MAX))?; let size = (stride as usize) .checked_mul(height as usize) .ok_or(ShmError::TooLarge(usize::MAX))?; if size > i32::MAX as usize { return Err(ShmError::TooLarge(size)); } // 1. Nośnik pamięci. `tempfile()` tworzy anonimowy plik w tmpfs. let file = tempfile::tempfile()?; file.set_len(size as u64)?; // 2. Mapowanie do zapisu. // SAFETY: plik ma ustawioną długość `size`; mapujemy go prywatnie na wyłączność // tego procesu (kompozytor dostaje ten sam plik przez fd, mapuje go u siebie). let mmap = unsafe { MmapMut::map_mut(&file)? }; // 3. Wystawienie puli i bufora dla kompozytora. let pool = shm.create_pool(file.as_fd(), size as i32, qh, ()); let buffer = pool.create_buffer(0, width, height, stride, format, qh, ()); Ok(Self { file, mmap, width, height, stride, size, format, pool, buffer, }) } /// Czy bieżący bufor pasuje do zadanego rozmiaru (pozwala uniknąć realokacji). pub fn matches(&self, width: i32, height: i32) -> bool { self.width == width && self.height == height } } impl fmt::Debug for ShmBuffer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ShmBuffer") .field("width", &self.width) .field("height", &self.height) .field("stride", &self.stride) .field("size", &self.size) .field("format", &self.format) .finish_non_exhaustive() } }