🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
//! 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<std::io::Error> 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<State>(
shm: &WlShm,
qh: &QueueHandle<State>,
width: i32,
height: i32,
) -> Result<Self, ShmError>
where
State: Dispatch<WlShmPool, ()> + Dispatch<WlBuffer, ()> + '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()
}
}