jay-compositor 1.15.0

The Jay compositor
use crate::cli::ScreenshotFormat;
use crate::cli::screenshot::buf_to_bytes;
use crate::client::Client;
use crate::cmm::cmm_eotf::Eotf;
use crate::globals::GlobalBase;
use crate::globals::GlobalName;
use crate::globals::Singleton;
use crate::it::test_error::TestError;
use crate::it::test_error::TestResult;
use crate::it::test_ifs::test_buffer::TestBuffer;
use crate::it::test_ifs::test_keyboard::TestKeyboard;
use crate::it::test_ifs::test_pointer::TestPointer;
use crate::it::test_ifs::test_registry::TestRegistry;
use crate::it::test_ifs::test_screenshot::TestJayScreenshot;
use crate::it::test_ifs::test_seat::TestSeat;
use crate::it::test_ifs::test_session::TestSession;
use crate::it::test_ifs::test_surface::TestSurface;
use crate::it::test_ifs::test_toplevel_session::TestToplevelSession;
use crate::it::test_ifs::test_viewport::TestViewport;
use crate::it::test_ifs::test_xdg_surface::TestXdgSurface;
use crate::it::test_utils::test_surface_ext::TestSurfaceExt;
use crate::it::test_utils::test_window::TestWindow;
use crate::it::testrun::TestRun;
use crate::object::Interface;
use crate::theme::Color;
use crate::video::dmabuf::DmaBuf;
use crate::wire::ObjectId;
use crate::wire::WlSeat;
use crate::wire::WlSurfaceId;
use std::cell::Cell;
use std::ops::Deref;
use std::rc::Rc;
use uapi::OwnedFd;

pub struct TestClient {
    pub run: Rc<TestRun>,
    pub _socket: OwnedFd,
    pub client: Rc<Client>,
}

pub struct DefaultSeat {
    pub seat: Rc<TestSeat>,
    pub kb: Rc<TestKeyboard>,
    pub pointer: Rc<TestPointer>,
}

impl Deref for TestClient {
    type Target = Rc<Client>;

    fn deref(&self) -> &Self::Target {
        &self.client
    }
}

impl TestClient {
    #[expect(unused)]
    async fn save_screenshot(&self, name: &str, include_cursor: bool) -> Result<(), TestError> {
        let qoi = self.client.screenshot_qoi(include_cursor).await?;
        let path = format!("{}/screenshot_{}.qoi", self.run.out_dir, name);
        std::fs::write(path, qoi)?;
        Ok(())
    }

    pub async fn compare_screenshot(
        &self,
        name: &str,
        include_cursor: bool,
    ) -> Result<(), TestError> {
        let actual = self.client.screenshot_qoi(include_cursor).await?;
        let expected_path = format!("{}/screenshot_{}.qoi", self.run.in_dir, name);
        let expected = std::fs::read(expected_path)?;
        if actual != expected {
            let actual_out_path = format!("{}/screenshot_{}_actual.qoi", self.run.out_dir, name);
            let expected_out_path =
                format!("{}/screenshot_{}_expected.qoi", self.run.out_dir, name);
            let _ = std::fs::write(actual_out_path, actual);
            let _ = std::fs::write(expected_out_path, expected);
            bail!("Screenshots differ");
        }
        Ok(())
    }
}

pub trait TestClientExt {
    async fn sync(self: &Rc<Self>);
    fn bind<T: From<ObjectId>>(self: &Rc<Self>, singleton: Singleton) -> T;
    fn bind_global<T: From<ObjectId>>(
        self: &Rc<Self>,
        name: GlobalName,
        interface: Interface,
        version: u32,
    ) -> T;
    fn create_single_pixel_buffer(self: &Rc<Self>, color: Color) -> Rc<TestBuffer>;
    async fn get_default_seat(self: &Rc<Self>) -> TestResult<DefaultSeat>;
    async fn screenshot_qoi(self: &Rc<Self>, include_cursor: bool) -> Result<Vec<u8>, TestError>;
    async fn create_surface(self: &Rc<Self>) -> TestResult<Rc<TestSurface>>;
    fn get_viewport(self: &Rc<Self>, surface: &TestSurface) -> Rc<TestViewport>;
    async fn create_surface_ext(self: &Rc<Self>) -> Result<TestSurfaceExt, TestError>;
    fn create_xdg_surface(self: &Rc<Self>, surface: WlSurfaceId) -> Rc<TestXdgSurface>;
    async fn create_window_no_commit(self: &Rc<Self>) -> Result<Rc<TestWindow>, TestError>;
    async fn create_window(self: &Rc<Self>) -> Result<Rc<TestWindow>, TestError>;
    async fn restore_window(
        self: &Rc<Self>,
        session: &TestSession,
        name: &str,
    ) -> Result<(Rc<TestWindow>, Rc<TestToplevelSession>), TestError>;
    async fn take_screenshot(
        self: &Rc<Self>,
        include_cursor: bool,
    ) -> Result<(Rc<DmaBuf>, Option<Rc<OwnedFd>>), TestError>;
    fn new_registry(self: &Rc<Self>) -> Rc<TestRegistry>;
}

impl TestClientExt for Client {
    async fn sync(self: &Rc<Self>) {
        self.state.idle().await;
    }

    fn bind<T: From<ObjectId>>(self: &Rc<Self>, singleton: Singleton) -> T {
        let info = self.state.globals.singletons[singleton];
        self.bind_global(info.name, singleton.interface(), info.version)
    }

    fn bind_global<T: From<ObjectId>>(
        self: &Rc<Self>,
        name: GlobalName,
        interface: Interface,
        version: u32,
    ) -> T {
        let registry = self.get_synthetic_registry();
        self.send_wl_registry_bind(registry, name.raw(), interface.name(), version)
            .into()
    }

    fn create_single_pixel_buffer(self: &Rc<Self>, color: Color) -> Rc<TestBuffer> {
        let map = |c: f32| (c as f64 * u32::MAX as f64) as u32;
        let [r, g, b, a] = color.to_array(Eotf::Gamma22);
        let id = self.send_wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(
            map(r),
            map(g),
            map(b),
            map(a),
        );
        let buffer = Rc::new(TestBuffer {
            id,
            released: Cell::new(true),
        });
        self.set_synthetic_event_handler(id, &buffer);
        buffer
    }

    async fn get_default_seat(self: &Rc<Self>) -> TestResult<DefaultSeat> {
        let seat = 'get_seat: {
            for seat in self.state.globals.seats.lock().values() {
                if seat.seat_name() == "default" {
                    break 'get_seat seat.clone();
                }
            }
            bail!("Default seat not found");
        };
        let id = self.bind_global(seat.name(), WlSeat, 9);
        let tseat = Rc::new(TestSeat {
            id,
            client: self.clone(),
            caps: Cell::new(0),
            name: Default::default(),
        });
        self.set_synthetic_event_handler(id, &tseat);
        let pointer = tseat.get_pointer();
        let tkb = tseat.get_keyboard();
        self.sync().await;
        Ok(DefaultSeat {
            seat: tseat,
            kb: tkb,
            pointer,
        })
    }

    async fn screenshot_qoi(self: &Rc<Self>, include_cursor: bool) -> Result<Vec<u8>, TestError> {
        let (dmabuf, dev) = self.take_screenshot(include_cursor).await?;
        let qoi = buf_to_bytes(
            &self.state.eventfd_cache,
            dev.as_ref(),
            &dmabuf,
            ScreenshotFormat::Qoi,
            false,
        )?;
        Ok(qoi)
    }

    async fn create_surface(self: &Rc<Self>) -> TestResult<Rc<TestSurface>> {
        let id = self.send_wl_compositor_create_surface();
        self.sync().await;
        let surface = Rc::new(TestSurface {
            id,
            client: self.clone(),
            server: self.lookup(id)?,
            preferred_buffer_scale: Rc::new(Default::default()),
            preferred_buffer_transform: Rc::new(Default::default()),
        });
        self.set_synthetic_event_handler(id, &surface);
        Ok(surface)
    }

    fn get_viewport(self: &Rc<Self>, surface: &TestSurface) -> Rc<TestViewport> {
        let id = self.send_wp_viewporter_get_viewport(surface.id);
        Rc::new(TestViewport {
            id,
            client: self.clone(),
        })
    }

    async fn create_surface_ext(self: &Rc<Self>) -> Result<TestSurfaceExt, TestError> {
        let surface = self.create_surface().await?;
        let viewport = self.get_viewport(&surface);
        Ok(TestSurfaceExt {
            client: self.clone(),
            surface,
            viewport,
            color: Cell::new(Color::SOLID_BLACK),
        })
    }

    fn create_xdg_surface(self: &Rc<Self>, surface: WlSurfaceId) -> Rc<TestXdgSurface> {
        let id = self.send_xdg_wm_base_get_xdg_surface(surface);
        let xdg = Rc::new(TestXdgSurface {
            id,
            client: self.clone(),
            last_serial: Default::default(),
        });
        self.set_synthetic_event_handler(id, &xdg);
        xdg
    }

    async fn create_window_no_commit(self: &Rc<Self>) -> Result<Rc<TestWindow>, TestError> {
        let surface = self.create_surface_ext().await?;
        let xdg = self.create_xdg_surface(surface.surface.id);
        let tl = xdg.create_toplevel().await?;
        Ok(Rc::new(TestWindow { surface, xdg, tl }))
    }

    async fn create_window(self: &Rc<Self>) -> Result<Rc<TestWindow>, TestError> {
        let win = self.create_window_no_commit().await?;
        win.surface.surface.commit();
        self.sync().await;
        Ok(win)
    }

    async fn restore_window(
        self: &Rc<Self>,
        session: &TestSession,
        name: &str,
    ) -> Result<(Rc<TestWindow>, Rc<TestToplevelSession>), TestError> {
        let win = self.create_window_no_commit().await?;
        let session = session.restore_toplevel(&win, name);
        win.surface.surface.commit();
        win.tl.core.configured().await;
        Ok((win, session))
    }

    async fn take_screenshot(
        self: &Rc<Self>,
        include_cursor: bool,
    ) -> Result<(Rc<DmaBuf>, Option<Rc<OwnedFd>>), TestError> {
        self.sync().await;
        let id = self.send_jay_compositor_take_screenshot2(include_cursor);
        let js = Rc::new(TestJayScreenshot {
            state: self.state.clone(),
            drm_dev: Default::default(),
            planes: Default::default(),
            result: Default::default(),
        });
        self.set_synthetic_event_handler(id, &js);
        self.sync().await;
        match js.result.take() {
            Some(Ok(res)) => Ok((res, js.drm_dev.take())),
            Some(Err(res)) => bail!("Compositor could not take a screenshot: {}", res),
            None => bail!("Compositor did not send a screenshot"),
        }
    }

    fn new_registry(self: &Rc<Self>) -> Rc<TestRegistry> {
        let id = self.send_wl_display_get_registry();
        let slf = Rc::new(TestRegistry {
            id,
            client: self.clone(),
            globals: Default::default(),
            seats: Default::default(),
        });
        self.set_synthetic_event_handler(id, &slf);
        slf
    }
}