jay-compositor 1.15.0

The Jay compositor
use crate::dbus::DbusError;
use crate::dbus::DbusSocket;
use crate::dbus::FALSE;
use crate::dbus::SignalHandler;
use crate::env::XDG_SESSION_ID;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::major_minor::MajorMinor;
use crate::utils::major_minor::major_minor;
use crate::wire_dbus::org;
use crate::wire_dbus::org::freedesktop::login1::seat::SwitchToReply;
use crate::wire_dbus::org::freedesktop::login1::session::PauseDevice;
use crate::wire_dbus::org::freedesktop::login1::session::ResumeDevice;
use crate::wire_dbus::org::freedesktop::login1::session::TakeDeviceReply;
use std::rc::Rc;
use thiserror::Error;
use uapi::c;

const LOGIND_NAME: &str = "org.freedesktop.login1";
const MANAGER_PATH: &str = "/org/freedesktop/login1";

#[derive(Debug, Error)]
pub enum LogindError {
    #[error("XDG_SESSION_ID is not set")]
    XdgSessionId,
    #[error("Could not retrieve the session dbus path")]
    GetSession(DbusError),
    #[error("Could not retrieve the session's seat name")]
    GetSeatName(DbusError),
    #[error(transparent)]
    TakeControl(DbusError),
}

pub struct Session {
    socket: Rc<DbusSocket>,
    seat: String,
    session_path: String,
}

impl Session {
    pub async fn get(socket: &Rc<DbusSocket>) -> Result<Self, LogindError> {
        let Some(session_id) = *XDG_SESSION_ID else {
            return Err(LogindError::XdgSessionId);
        };
        let session_path = {
            let session = socket
                .call_async(
                    LOGIND_NAME,
                    MANAGER_PATH,
                    org::freedesktop::login1::manager::GetSession {
                        session_id: session_id.into(),
                    },
                )
                .await;
            match session {
                Ok(s) => s.get().object_path.to_string(),
                Err(e) => return Err(LogindError::GetSession(e)),
            }
        };
        let seat = {
            let seat = socket
                .get_async::<org::freedesktop::login1::session::Seat>(LOGIND_NAME, &session_path)
                .await;
            match seat {
                Ok(s) => s.get().1.0.to_string(),
                Err(e) => return Err(LogindError::GetSeatName(e)),
            }
        };
        Ok(Self {
            socket: socket.clone(),
            seat,
            session_path,
        })
    }

    pub async fn take_control(&self) -> Result<(), LogindError> {
        let res = self
            .socket
            .call_async(
                LOGIND_NAME,
                &self.session_path,
                org::freedesktop::login1::session::TakeControl { force: FALSE },
            )
            .await;
        if let Err(e) = res {
            return Err(LogindError::TakeControl(e));
        }
        self.socket.call(
            LOGIND_NAME,
            &self.session_path,
            org::freedesktop::login1::session::SetType {
                ty: "wayland".into(),
            },
            |res| {
                if let Err(e) = res {
                    log::warn!("Could not change session type to wayland: {}", ErrorFmt(e));
                }
            },
        );
        Ok(())
    }

    pub fn get_device<F>(&self, dev: c::dev_t, f: F)
    where
        F: FnOnce(Result<&TakeDeviceReply, DbusError>) + 'static,
    {
        let MajorMinor { major, minor } = major_minor(dev);
        self.socket.call(
            LOGIND_NAME,
            &self.session_path,
            org::freedesktop::login1::session::TakeDevice {
                major: major as _,
                minor: minor as _,
            },
            f,
        );
    }

    pub fn on_pause<F>(&self, f: F) -> Result<SignalHandler, DbusError>
    where
        F: for<'b> Fn(PauseDevice<'b>) + 'static,
    {
        self.socket.handle_signal::<PauseDevice<'_>, _>(
            Some(LOGIND_NAME),
            Some(&self.session_path),
            f,
        )
    }

    pub fn on_resume<F>(&self, f: F) -> Result<SignalHandler, DbusError>
    where
        F: Fn(ResumeDevice) + 'static,
    {
        self.socket
            .handle_signal::<ResumeDevice, _>(Some(LOGIND_NAME), Some(&self.session_path), f)
    }

    pub fn device_paused(&self, major: u32, minor: u32) {
        self.socket.call_noreply(
            LOGIND_NAME,
            &self.session_path,
            org::freedesktop::login1::session::PauseDeviceComplete { major, minor },
        );
    }

    pub fn switch_to<F>(&self, vtnr: u32, f: F)
    where
        F: FnOnce(Result<&SwitchToReply, DbusError>) + 'static,
    {
        self.socket.call(
            LOGIND_NAME,
            &self.seat,
            org::freedesktop::login1::seat::SwitchTo { vtnr },
            f,
        );
    }
}