🔒 Repository is read-only – file editing is disabled.

luna/de/selene/src/shm.rs main

414 lines Raw ← Back
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
//! `wl_shm` — współdzielona pamięć klienta (bufory ARGB8888 → RGBA).

use std::ffi::c_void;
use std::os::fd::OwnedFd;
use std::sync::{Arc, Mutex};

use wayland_server::{
    protocol::{wl_buffer, wl_shm, wl_shm_pool},
    Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};

use crate::state::State;
use crate::sync::LockExt;

/// Górny limit pojedynczego obrazu (RGBA) — chroni przed OOM wymuszonym przez klienta.
/// 256 MiB pokrywa 8K na kilku monitorach, a odcina absurdalne żądania.
const MAX_IMAGE_BYTES: usize = 256 * 1024 * 1024;

/// Zmapowany region puli. Odświeżany przy `wl_shm_pool.resize`.
struct Mapping {
    ptr: *mut c_void,
    size: usize,
}

// SAFETY: mapa jest tylko do odczytu; dostęp do `ptr` serializuje `Mutex`
// (i tak dzieje się to w wątku protokołu).
unsafe impl Send for Mapping {}

/// Dane puli pamięci (`wl_shm_pool`): deskryptor + zmapowany region.
///
/// Klient może tworzyć pulę małą i **rozszerzać** ją (`wl_shm_pool.resize`) —
/// dlatego mapa i rozmiar żyją za `Mutex` i są powiększane na żądanie.
pub struct ShmPoolData {
    pub fd: OwnedFd,
    map: Mutex<Mapping>,
}

impl ShmPoolData {
    /// Mapuje początkowy zakres puli (tylko odczyt). Błąd mapowania = pusta pula.
    pub fn new(fd: OwnedFd, declared_size: usize) -> Self {
        // Faktyczny rozmiar obiektu shm: klient może zadeklarować więcej, niż istnieje,
        // a odczyt poza obiekt kończy się SIGBUS (nie do złapania). Przycinamy rozmiar.
        let real = real_size(&fd);
        let size = declared_size.min(real);
        let ptr = map_region(&fd, size);
        if debug() {
            eprintln!("selene/shm: create_pool declared={declared_size} real={real} used={size}");
        }
        Self {
            fd,
            map: Mutex::new(Mapping { ptr, size }),
        }
    }

    /// Powiększa mapę po `wl_shm_pool.resize` (klient wcześniej powiększył `fd`).
    ///
    /// Rozmiar bywa deklarowany większy niż realny obiekt — przycinamy do realnego,
    /// żeby nie czytać poza obiekt (SIGBUS).
    pub fn resize(&self, declared_size: usize) {
        let real = real_size(&self.fd);
        let size = declared_size.min(real);
        let mut map = self.map.lock_or_recover();
        if size <= map.size {
            return; // tylko rośnie
        }
        if !map.ptr.is_null() {
            // SAFETY: odmapowujemy poprzedni zakres.
            unsafe {
                let _ = rustix::mm::munmap(map.ptr, map.size);
            }
        }
        map.ptr = map_region(&self.fd, size);
        map.size = size;
        if debug() {
            eprintln!("selene/shm: pool resize -> used={size}");
        }
    }
}

impl Drop for ShmPoolData {
    fn drop(&mut self) {
        let map = self.map.get_mut().unwrap_or_else(|e| e.into_inner());
        if map.ptr.is_null() {
            return;
        }
        // SAFETY: odmapowujemy ten sam zakres, który zmapowaliśmy.
        unsafe {
            let _ = rustix::mm::munmap(map.ptr, map.size);
        }
    }
}

/// Realny rozmiar obiektu shm (bez deklaracji klienta).
fn real_size(fd: &OwnedFd) -> usize {
    rustix::fs::fstat(fd)
        .map(|st| st.st_size.max(0) as usize)
        .unwrap_or(0)
}

/// Mapuje `size` bajtów pliku `fd` (tylko odczyt). `null` przy niepowodzeniu.
fn map_region(fd: &OwnedFd, size: usize) -> *mut c_void {
    if size == 0 {
        return std::ptr::null_mut();
    }
    // SAFETY: `fd` żyje w strukturze, mapujemy tylko do odczytu.
    unsafe {
        rustix::mm::mmap(
            std::ptr::null_mut::<c_void>(),
            size,
            rustix::mm::ProtFlags::READ,
            rustix::mm::MapFlags::PRIVATE,
            fd,
            0,
        )
    }
    .unwrap_or(std::ptr::null_mut())
}

fn debug() -> bool {
    std::env::var("SELENE_STATS").is_ok()
}

/// Dane bufora (`wl_buffer`): odniesienie do puli + geometria.
pub struct BufferData {
    pub pool: Arc<ShmPoolData>,
    pub offset: usize,
    pub width: i32,
    pub height: i32,
    pub stride: i32,
    pub format: wl_shm::Format,
}

impl BufferData {
    /// Odczytuje piksele bufora jako premultiplied RGBA8 (swizzle BGRA→RGBA).
    ///
    /// Waliduje geometrię względem rozmiaru puli (arytmetyka bez przepełnień) i ogranicza
    /// rozmiar alokacji, żeby klient nie wywołał paniki ani OOM.
    pub fn to_rgba(&self) -> Option<Vec<u8>> {
        if self.width <= 0 || self.height <= 0 || self.stride <= 0 {
            return None;
        }
        let (w, h) = (self.width as usize, self.height as usize);
        let stride = self.stride as usize;
        // Wiersz musi pomieścić `w` pikseli po 4 bajty (inaczej odczyt wyszedłby poza wiersz).
        if stride < w.checked_mul(4)? {
            return None;
        }
        // Cały zakres danych musi mieścić się w puli — na sprawdzonej arytmetyce.
        let end = self.offset.checked_add(stride.checked_mul(h)?)?;
        // Limit alokacji chroniący przed OOM wymuszonym przez klienta.
        let out_len = w.checked_mul(h)?.checked_mul(4)?;
        if out_len > MAX_IMAGE_BYTES {
            return None;
        }

        // Trzymamy blokadę mapy przez cały odczyt (mapa może rosnąć w `resize`).
        let map = self.pool.map.lock_or_recover();
        if map.ptr.is_null() || end > map.size {
            return None;
        }
        let base = map.ptr as *const u8;
        let swizzle = matches!(
            self.format,
            wl_shm::Format::Argb8888 | wl_shm::Format::Xrgb8888
        );
        // `Abgr8888`/`Xbgr8888` mają w pamięci kolejność RGBA — dokładnie taką, jakiej
        // używa renderer, więc wystarczy `memcpy` (bez podmiany R↔B).
        let direct = matches!(
            self.format,
            wl_shm::Format::Abgr8888 | wl_shm::Format::Xbgr8888
        );
        if !swizzle && !direct {
            return None;
        }
        let force_alpha = matches!(
            self.format,
            wl_shm::Format::Xrgb8888 | wl_shm::Format::Xbgr8888
        );
        let mut out = vec![0u8; out_len];
        #[cfg(target_endian = "little")]
        {
            // Piksele przetwarzamy po cztery bajty naraz (u32). Podmiana R↔B to kilka
            // operacji bitowych — wcześniejszy pętla bajt-po-bajcie na buforze
            // 1920×1080 zajmowała kilka milisekund **w wątku protokołu**, opóźniając
            // obsługę wejścia (kliknięcia i ruch kursora).
            for y in 0..h {
                let row = self.offset + y * stride;
                let row_dst = &mut out[y * w * 4..(y + 1) * w * 4];
                let row_src = unsafe { std::slice::from_raw_parts(base.add(row), w * 4) };
                if direct {
                    row_dst.copy_from_slice(row_src);
                    if force_alpha {
                        for px in row_dst.chunks_exact_mut(4) {
                            px[3] = 255;
                        }
                    }
                    continue;
                }
                let dst32 =
                    unsafe { std::slice::from_raw_parts_mut(row_dst.as_mut_ptr() as *mut u32, w) };
                let src32 =
                    unsafe { std::slice::from_raw_parts(row_src.as_ptr() as *const u32, w) };
                for (d, &s) in dst32.iter_mut().zip(src32.iter()) {
                    let mut v =
                        (s & 0xFF00_FF00) | ((s & 0x00FF_0000) >> 16) | ((s & 0x0000_00FF) << 16);
                    if force_alpha {
                        v |= 0xFF00_0000;
                    }
                    *d = v;
                }
            }
        }
        #[cfg(not(target_endian = "little"))]
        {
            for y in 0..h {
                let row = self.offset + y * stride;
                let row_dst = &mut out[y * w * 4..(y + 1) * w * 4];
                let row_src = unsafe { std::slice::from_raw_parts(base.add(row), w * 4) };
                if direct {
                    row_dst.copy_from_slice(row_src);
                    if force_alpha {
                        for px in row_dst.chunks_exact_mut(4) {
                            px[3] = 255;
                        }
                    }
                    continue;
                }
                for (d, s) in row_dst.chunks_exact_mut(4).zip(row_src.chunks_exact(4)) {
                    d[0] = s[2];
                    d[1] = s[1];
                    d[2] = s[0];
                    d[3] = if force_alpha { 255 } else { s[3] };
                }
            }
        }
        Some(out)
    }
}

impl GlobalDispatch<wl_shm::WlShm, ()> for State {
    fn bind(
        _state: &mut Self,
        _handle: &DisplayHandle,
        _client: &Client,
        resource: New<wl_shm::WlShm>,
        _data: &(),
        data_init: &mut DataInit<'_, Self>,
    ) {
        let shm = data_init.init(resource, ());
        // Ogłaszamy **wszystkie** formaty, które realnie obsługujemy. Część toolkitów (Qt,
        // część aplikacji GTK/Electron) domyślnie tworzy bufor `Xrgb8888` i bez niego w ogóle
        // nie dochodzi do `commit`. `Abgr8888`/`Xbgr8888` mają w pamięci kolejność RGBA
        // (natywną dla naszego renderera), więc klient unika podmiany R↔B co klatkę.
        for format in [
            wl_shm::Format::Argb8888,
            wl_shm::Format::Xrgb8888,
            wl_shm::Format::Abgr8888,
            wl_shm::Format::Xbgr8888,
        ] {
            let _ = shm.send_event(wl_shm::Event::Format {
                format: wayland_server::WEnum::Value(format),
            });
        }
    }
}

impl Dispatch<wl_shm::WlShm, ()> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &wl_shm::WlShm,
        request: wl_shm::Request,
        _data: &(),
        _handle: &DisplayHandle,
        data_init: &mut DataInit<'_, Self>,
    ) {
        if let wl_shm::Request::CreatePool { id, fd, size } = request {
            data_init.init(id, Arc::new(ShmPoolData::new(fd, size.max(0) as usize)));
        }
    }
}

impl Dispatch<wl_shm_pool::WlShmPool, Arc<ShmPoolData>> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &wl_shm_pool::WlShmPool,
        request: wl_shm_pool::Request,
        data: &Arc<ShmPoolData>,
        _handle: &DisplayHandle,
        data_init: &mut DataInit<'_, Self>,
    ) {
        match request {
            wl_shm_pool::Request::CreateBuffer {
                id,
                offset,
                width,
                height,
                stride,
                format,
            } => {
                data_init.init(
                    id,
                    BufferData {
                        pool: Arc::clone(data),
                        offset: offset.max(0) as usize,
                        width,
                        height,
                        stride,
                        format: format.into_result().unwrap_or(wl_shm::Format::Argb8888),
                    },
                );
            }
            wl_shm_pool::Request::Resize { size } => {
                data.resize(size.max(0) as usize);
            }
            _ => {}
        }
    }
}

impl Dispatch<wl_buffer::WlBuffer, BufferData> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &wl_buffer::WlBuffer,
        _request: wl_buffer::Request,
        _data: &BufferData,
        _handle: &DisplayHandle,
        _data_init: &mut DataInit<'_, Self>,
    ) {
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    fn memfd(bytes: &[u8]) -> OwnedFd {
        let fd =
            rustix::fs::memfd_create("selene-shm-test", rustix::fs::MemfdFlags::CLOEXEC).unwrap();
        let mut file = std::fs::File::from(fd);
        file.write_all(bytes).unwrap();
        file.into()
    }

    fn pool(bytes: &[u8]) -> Arc<ShmPoolData> {
        Arc::new(ShmPoolData::new(memfd(bytes), bytes.len()))
    }

    fn buffer(pool: Arc<ShmPoolData>, w: i32, h: i32, stride: i32) -> BufferData {
        BufferData {
            pool,
            offset: 0,
            width: w,
            height: h,
            stride,
            format: wl_shm::Format::Argb8888,
        }
    }

    #[test]
    fn to_rgba_swizzles_bgra_to_rgba() {
        // 1x1: pamięć B,G,R,A = (10,20,30,40) → RGBA (30,20,10,40).
        let rgba = buffer(pool(&[10, 20, 30, 40]), 1, 1, 4).to_rgba().unwrap();
        assert_eq!(rgba, vec![30, 20, 10, 40]);
    }

    #[test]
    fn xrgb_forces_opaque_alpha() {
        let mut b = buffer(pool(&[1, 2, 3, 0]), 1, 1, 4);
        b.format = wl_shm::Format::Xrgb8888;
        assert_eq!(b.to_rgba().unwrap(), vec![3, 2, 1, 255]);
    }

    #[test]
    fn abgr_is_copied_without_swizzle() {
        let mut b = buffer(pool(&[10, 20, 30, 40]), 1, 1, 4);
        b.format = wl_shm::Format::Abgr8888;
        assert_eq!(b.to_rgba().unwrap(), vec![10, 20, 30, 40]);
    }

    #[test]
    fn xbgr_forces_opaque_alpha_without_swizzle() {
        let mut b = buffer(pool(&[1, 2, 3, 0]), 1, 1, 4);
        b.format = wl_shm::Format::Xbgr8888;
        assert_eq!(b.to_rgba().unwrap(), vec![1, 2, 3, 255]);
    }

    #[test]
    fn rejects_stride_smaller_than_row() {
        // stride 2 < width*4 → odrzucone, bez paniki/OOB.
        assert!(buffer(pool(&[0u8; 64]), 4, 1, 2).to_rgba().is_none());
    }

    #[test]
    fn rejects_buffer_outside_pool() {
        // 2x2 ARGB = 16 bajtów > pula 8 → odrzucone.
        assert!(buffer(pool(&[0u8; 8]), 2, 2, 8).to_rgba().is_none());
    }

    #[test]
    fn resize_extends_mapping() {
        // W pliku jest 8 B, ale deklarujemy na starcie 4 B (jak sctk: mała pula + resize).
        let pool = Arc::new(ShmPoolData::new(memfd(&[1, 2, 3, 4, 5, 6, 7, 8]), 4));
        // Bufor 2x1 (8 B) nie mieści się w zmapowanych 4 B.
        assert!(buffer(Arc::clone(&pool), 2, 1, 8).to_rgba().is_none());
        // Po `resize` mapa rośnie i bufor się mieści.
        pool.resize(8);
        let rgba = buffer(Arc::clone(&pool), 2, 1, 8).to_rgba().unwrap();
        assert_eq!(rgba, vec![3, 2, 1, 4, 7, 6, 5, 8]);
    }
}