pinenote-service 1.0.1

Management dervice for Pine64's PineNote device
Documentation
use std::time::Duration;

use pinenote_service::types::rockchip_ebc::{DitherMode, DriverMode, Hint as CoreHint};
use tokio::sync::{mpsc, oneshot};
use zbus::{fdo, interface, object_server::SignalEmitter};

use crate::{
    dbus,
    ebc::{self, OffScreenError},
};

pub struct Ebc1 {
    ebc_tx: ebc::CommandSender,
}

impl Ebc1 {
    pub fn new(ebc_tx: mpsc::Sender<ebc::Command>) -> Self {
        Self {
            ebc_tx: ebc_tx.into(),
        }
    }
}

#[interface(name = "org.pinenote.Ebc1")]
impl Ebc1 {
    async fn global_refresh(&self) -> fdo::Result<()> {
        self.ebc_tx
            .send(ebc::Command::GlobalRefresh)
            .await
            .map_err(dbus::internal_error)
    }

    async fn dump_framebuffers(&self, directory: String) -> fdo::Result<()> {
        self.ebc_tx
            .send(ebc::Command::FbDumpToDir(directory))
            .await
            .map_err(dbus::internal_error)
    }

    async fn cycle_driver_mode(
        &self,
        #[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
    ) -> fdo::Result<()> {
        let (tx, reply) = oneshot::channel::<DriverMode>();

        let driver_mode = self
            .ebc_tx
            .with_reply(ebc::Property::DriverMode(tx), reply)
            .await
            .map_err(dbus::internal_error)?;

        let new_mode = driver_mode.cycle_next();
        if new_mode != driver_mode {
            self.set_driver_mode(new_mode).await?;
            self.driver_mode_changed(&emitter).await?;
        }
        Ok(())
    }

    async fn cycle_dither_mode(
        &self,
        #[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
    ) -> fdo::Result<()> {
        let (tx, reply) = oneshot::channel::<DitherMode>();

        let dither_mode = self
            .ebc_tx
            .with_reply(ebc::Property::DitherMode(tx), reply)
            .await
            .map_err(dbus::internal_error)?;

        let new_mode = dither_mode.cycle_next();
        if new_mode != dither_mode {
            self.set_dither_mode(new_mode).await?;
            self.dither_mode_changed(&emitter).await?;
        }

        Ok(())
    }

    async fn set_off_screen(
        &self,
        path: String,
        #[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
    ) -> fdo::Result<()> {
        let (tx, rx) = oneshot::channel::<Result<(), OffScreenError>>();

        let res = self
            .ebc_tx
            .with_reply(ebc::Command::OffScreen(path.clone(), tx), rx)
            .await
            .map_err(dbus::internal_error)?;

        if let Err(e) = res {
            match e {
                OffScreenError::LoadFailed => Err(fdo::Error::FileNotFound(path))?,
                OffScreenError::DecodeFailed => Err(fdo::Error::Failed(format!(
                    "Failed to load '{path}': Bad format"
                )))?,
                OffScreenError::UploadFailed => {
                    self.off_screen_override_changed(&emitter).await?;
                    Err(fdo::Error::Failed(
                        "Could not upload image to driver".into(),
                    ))?;
                }
            }
        } else {
            self.off_screen_override_changed(&emitter).await?
        }

        Ok(())
    }

    #[zbus(property)]
    async fn off_screen_override(&self) -> fdo::Result<String> {
        let (tx, rx) = oneshot::channel::<String>();

        self.ebc_tx
            .with_reply(ebc::Property::OffScreenOverride(tx), rx)
            .await
            .map_err(dbus::internal_error)
    }

    #[zbus(property)]
    async fn off_screen_disable(&self) -> fdo::Result<bool> {
        let (tx, rx) = oneshot::channel::<bool>();

        self.ebc_tx
            .with_reply(ebc::Property::OffScreenDisable(tx), rx)
            .await
            .map_err(dbus::internal_error)
    }

    #[zbus(property)]
    async fn set_off_screen_disable(&self, disable: bool) -> Result<(), zbus::Error> {
        self.ebc_tx
            .send(ebc::Property::SetOffScreenDisable(disable))
            .await
            .map_err(dbus::internal_error)
            .map_err(zbus::Error::from)
    }

    #[zbus(property)]
    async fn default_hint(&self) -> fdo::Result<super::Hint> {
        let (tx, rx) = oneshot::channel::<CoreHint>();

        self.ebc_tx
            .with_reply(ebc::Property::DefaultHint(tx), rx)
            .await
            .map_err(dbus::internal_error)
            .map(|ch| ch.into())
    }

    #[zbus(property)]
    async fn set_default_hint(
        &self,
        hint: super::Hint,
        #[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
    ) -> Result<(), zbus::Error> {
        let hint: CoreHint = hint.into();

        self.ebc_tx
            .send(ebc::Property::SetDefaultHint(hint))
            .await
            .map_err(dbus::internal_error)
            .map_err(zbus::Error::from)?;

        self.default_hint_hr_changed(&emitter).await?;

        Ok(())
    }

    #[zbus(property)]
    async fn default_hint_hr(&self) -> fdo::Result<String> {
        let (tx, rx) = oneshot::channel::<CoreHint>();

        let hint = self
            .ebc_tx
            .with_reply(ebc::Property::DefaultHint(tx), rx)
            .await
            .map_err(dbus::internal_error)?;

        Ok(format!("{hint}"))
    }

    #[zbus(property)]
    async fn set_default_hint_hr(
        &self,
        hint: String,
        #[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
    ) -> Result<(), zbus::Error> {
        let Ok(hint) = CoreHint::try_from_human_readable(hint.as_str()) else {
            return Err(fdo::Error::InvalidArgs("Invalid format".into()).into());
        };

        self.ebc_tx
            .send(ebc::Property::SetDefaultHint(hint))
            .await
            .map_err(dbus::internal_error)
            .map_err(zbus::Error::from)?;

        self.default_hint_changed(&emitter).await?;

        Ok(())
    }

    #[zbus(property)]
    async fn driver_mode(&self) -> fdo::Result<DriverMode> {
        let (tx, reply) = oneshot::channel::<DriverMode>();

        self.ebc_tx
            .with_reply(ebc::Property::DriverMode(tx), reply)
            .await
            .map_err(dbus::internal_error)
    }

    #[zbus(property)]
    async fn set_driver_mode(&self, driver_mode: DriverMode) -> Result<(), zbus::Error> {
        use DriverMode::*;
        if driver_mode == ZeroWaveform {
            Err(fdo::Error::InvalidArgs("Value not supported".into()))?
        }

        let current = self.driver_mode().await?;

        match current {
            Normal | Fast => (),
            _ => Err(fdo::Error::Failed("Refusing to change mode".into()))?,
        };

        self.ebc_tx
            .send(ebc::Property::SetDriverMode(driver_mode))
            .await
            .map_err(dbus::internal_error)?;

        // Dirty hack to let some time for the driver to update. Ideally we'd
        // want an event from the Core, but that's not implemented atm.
        let _ = tokio::time::timeout(Duration::from_secs(5), async {
            while self.driver_mode().await != Ok(driver_mode) {
                tokio::time::sleep(Duration::from_millis(100)).await;
            }
        })
        .await;

        Ok(())
    }

    #[zbus(property)]
    async fn dither_mode(&self) -> fdo::Result<DitherMode> {
        let (tx, reply) = oneshot::channel::<DitherMode>();

        self.ebc_tx
            .with_reply(ebc::Property::DitherMode(tx), reply)
            .await
            .map_err(dbus::internal_error)
    }

    #[zbus(property)]
    async fn set_dither_mode(&self, dither_mode: DitherMode) -> Result<(), zbus::Error> {
        self.ebc_tx
            .send(ebc::Property::SetDitherMode(dither_mode))
            .await
            .map_err(dbus::internal_error)
            .map_err(zbus::Error::from)
    }

    #[zbus(property)]
    async fn redraw_delay(&self) -> fdo::Result<u16> {
        let (tx, reply) = oneshot::channel::<u16>();

        self.ebc_tx
            .with_reply(ebc::Property::RedrawDelay(tx), reply)
            .await
            .map_err(dbus::internal_error)
    }

    #[zbus(property)]
    async fn set_redraw_delay(&self, redraw_delay: u16) -> Result<(), zbus::Error> {
        self.ebc_tx
            .send(ebc::Property::SetRedrawDelay(redraw_delay))
            .await
            .map_err(dbus::internal_error)
            .map_err(zbus::Error::from)
    }
}