jay-compositor 1.15.0

The Jay compositor
use crate::backend::ButtonState;
use crate::backend::KeyState;
use crate::backend::ScrollAxis;
use crate::ei::ei_client::EiClient;
use crate::ei::ei_client::EiClientError;
use crate::ei::ei_ifs::ei_seat::EiSeat;
use crate::ei::ei_ifs::ei_touchscreen::TouchChange;
use crate::ei::ei_object::EiObject;
use crate::ei::ei_object::EiVersion;
use crate::fixed::Fixed;
use crate::ifs::wl_seat::CursorPositionType;
use crate::ifs::wl_seat::PX_PER_SCROLL;
use crate::leaks::Tracker;
use crate::rect::Rect;
use crate::scale::Scale;
use crate::utils::copyhashmap::CopyHashMap;
use crate::utils::syncqueue::SyncQueue;
use crate::wire_ei::EiDeviceId;
use crate::wire_ei::ei_device::ClientFrame;
use crate::wire_ei::ei_device::ClientStartEmulating;
use crate::wire_ei::ei_device::ClientStopEmulating;
use crate::wire_ei::ei_device::Destroyed;
use crate::wire_ei::ei_device::DeviceType;
use crate::wire_ei::ei_device::Dimensions;
use crate::wire_ei::ei_device::Done;
use crate::wire_ei::ei_device::EiDeviceRequestHandler;
use crate::wire_ei::ei_device::Interface;
use crate::wire_ei::ei_device::Name;
use crate::wire_ei::ei_device::Paused;
use crate::wire_ei::ei_device::Region;
use crate::wire_ei::ei_device::RegionMappingId;
use crate::wire_ei::ei_device::Release;
use crate::wire_ei::ei_device::Resumed;
use crate::wire_ei::ei_device::ServerFrame;
use crate::wire_ei::ei_device::ServerStartEmulating;
use crate::wire_ei::ei_device::ServerStopEmulating;
use CursorPositionType::Motion;
use linearize::LinearizeExt;
use std::cell::Cell;
use std::rc::Rc;
use thiserror::Error;

pub const EI_DEVICE_TYPE_VIRTUAL: u32 = 1;
#[expect(unused)]
pub const EI_DEVICE_TYPE_PHYSICAL: u32 = 2;

pub const REGION_MAPPING_ID_SINCE: EiVersion = EiVersion(2);

pub struct EiDevice {
    pub id: EiDeviceId,
    pub client: Rc<EiClient>,
    pub tracker: Tracker<Self>,
    pub version: EiVersion,
    pub seat: Rc<EiSeat>,

    pub button_changes: SyncQueue<(u32, ButtonState)>,
    pub touch_changes: CopyHashMap<u32, TouchChange>,
    pub scroll_px: [Cell<Option<f32>>; 2],
    pub scroll_v120: [Cell<Option<i32>>; 2],
    pub scroll_stop: [Cell<Option<bool>>; 2],
    pub absolute_motion: Cell<Option<(f32, f32)>>,
    pub relative_motion: Cell<Option<(f32, f32)>>,
    pub key_changes: SyncQueue<(u32, KeyState)>,
}

pub trait EiDeviceInterface: EiObject {
    fn new(device: &Rc<EiDevice>, version: EiVersion) -> Rc<Self>;

    fn destroy(&self) -> Result<(), EiClientError>;

    fn send_destroyed(&self, serial: u32);
}

impl EiDevice {
    pub fn send_interface<T>(&self, interface: &T)
    where
        T: EiDeviceInterface,
    {
        self.client.event(Interface {
            self_id: self.id,
            object: interface.id(),
            interface_name: interface.interface().name(),
            version: interface.version().0,
        });
    }

    pub fn send_device_type(&self, ty: u32) {
        self.client.event(DeviceType {
            self_id: self.id,
            device_type: ty,
        });
    }

    pub fn send_resumed(&self, serial: u32) {
        self.client.event(Resumed {
            self_id: self.id,
            serial,
        });
    }

    pub fn send_start_emulating(&self, serial: u32, sequence: u32) {
        self.client.event(ServerStartEmulating {
            self_id: self.id,
            serial,
            sequence,
        });
    }

    #[expect(unused)]
    pub fn send_stop_emulating(&self, serial: u32) {
        self.client.event(ServerStopEmulating {
            self_id: self.id,
            serial,
        });
    }

    #[expect(unused)]
    pub fn send_dimensions(&self, width: u32, height: u32) {
        self.client.event(Dimensions {
            self_id: self.id,
            width,
            height,
        });
    }

    #[expect(unused)]
    pub fn send_name(&self, name: &str) {
        self.client.event(Name {
            self_id: self.id,
            name,
        });
    }

    pub fn send_region(&self, rect: Rect, scale: Scale) {
        self.client.event(Region {
            self_id: self.id,
            offset_x: rect.x1() as u32,
            offset_y: rect.y1() as u32,
            width: rect.width() as u32,
            hight: rect.height() as u32,
            scale: (1.0 / scale.to_f64()) as f32,
        });
    }

    pub fn send_region_mapping_id(&self, mapping_id: &str) {
        if self.version >= REGION_MAPPING_ID_SINCE {
            self.client.event(RegionMappingId {
                self_id: self.id,
                mapping_id,
            });
        }
    }

    #[expect(unused)]
    pub fn send_paused(&self, serial: u32) {
        self.client.event(Paused {
            self_id: self.id,
            serial,
        });
    }

    pub fn send_done(&self) {
        self.client.event(Done { self_id: self.id });
    }

    pub fn send_frame(&self, serial: u32, timestamp: u64) {
        self.client.event(ServerFrame {
            self_id: self.id,
            serial,
            timestamp,
        });
    }

    fn send_destroyed(&self, serial: u32) {
        self.client.event(Destroyed {
            self_id: self.id,
            serial,
        });
    }

    pub fn destroy(&self) -> Result<(), EiClientError> {
        macro_rules! destroy_interface {
            ($name:ident) => {
                if let Some(interface) = self.seat.$name.take() {
                    interface.destroy()?;
                }
            };
        }
        destroy_interface!(pointer);
        destroy_interface!(pointer_absolute);
        destroy_interface!(scroll);
        destroy_interface!(button);
        destroy_interface!(keyboard);
        destroy_interface!(touchscreen);
        self.send_destroyed(self.client.serial());
        self.client.remove_obj(self)?;
        Ok(())
    }
}

impl EiDeviceRequestHandler for EiDevice {
    type Error = EiDeviceError;

    fn release(&self, _req: Release, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.destroy()?;
        Ok(())
    }

    fn client_start_emulating(
        &self,
        _req: ClientStartEmulating,
        _slf: &Rc<Self>,
    ) -> Result<(), Self::Error> {
        Ok(())
    }

    fn client_stop_emulating(
        &self,
        _req: ClientStopEmulating,
        _slf: &Rc<Self>,
    ) -> Result<(), Self::Error> {
        Ok(())
    }

    fn client_frame(&self, req: ClientFrame, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        let seat = &self.seat.seat;
        let time = req.timestamp;
        while let Some((button, pressed)) = self.button_changes.pop() {
            seat.button_event(time, button, pressed);
        }
        while let Some((button, pressed)) = self.key_changes.pop() {
            let phy = seat.get_physical_keyboard(self.seat.keyboard_id, None);
            phy.phy_state.update(time, seat, button, pressed);
        }
        if let Some((x, y)) = self.relative_motion.take() {
            let x = Fixed::from_f32(x);
            let y = Fixed::from_f32(y);
            seat.motion_event(time, x, y, x, y);
        }
        if let Some((x, y)) = self.absolute_motion.take() {
            let x = Fixed::from_f32(x);
            let y = Fixed::from_f32(y);
            seat.motion_event_abs(time, x, y, Motion);
        }
        {
            let mut need_frame = false;
            for axis in ScrollAxis::variants() {
                let idx = axis as usize;
                if let Some(v120) = self.scroll_v120[idx].take() {
                    need_frame = true;
                    seat.axis_120(v120, axis, false);
                }
                if let Some(px) = self.scroll_px[idx].take() {
                    need_frame = true;
                    seat.axis_px(Fixed::from_f32(px), axis, false);
                }
                if self.scroll_stop[idx].take() == Some(true) {
                    need_frame = true;
                    seat.axis_stop(axis);
                }
            }
            if need_frame {
                seat.axis_frame(PX_PER_SCROLL, time);
            }
        }
        if self.touch_changes.is_not_empty() {
            for (touch_id, change) in self.touch_changes.lock().drain() {
                let id = touch_id as i32;
                match change {
                    TouchChange::Down(x, y) => {
                        let x = Fixed::from_f32(x);
                        let y = Fixed::from_f32(y);
                        seat.touch_down_at(time, id, x, y);
                    }
                    TouchChange::Motion(x, y) => {
                        let x = Fixed::from_f32(x);
                        let y = Fixed::from_f32(y);
                        seat.touch_motion_at(time, id, x, y);
                    }
                    TouchChange::Up => seat.touch_up(time, id),
                    TouchChange::Cancel => seat.touch_cancel(time, id),
                }
            }
            seat.touch_frame(time);
        }
        Ok(())
    }
}

ei_object_base! {
    self = EiDevice;
    version = self.version;
}

impl EiObject for EiDevice {}

#[derive(Debug, Error)]
pub enum EiDeviceError {
    #[error(transparent)]
    EiClientError(Box<EiClientError>),
}
efrom!(EiDeviceError, EiClientError);