shikane 1.0.1

dynamic output configuration tool focusing on accuracy and determinism
Documentation
mod wl_store;

use std::collections::VecDeque;
use std::fmt::Display;
use std::hash::Hash;

use serde::{Deserialize, Serialize};
use snafu::{prelude::*, Location};
use wayland_client::backend::WaylandError;

use crate::profile::{AdaptiveSyncState, PhysicalSize, Position, Transform};
use crate::variant::ValidVariant;

pub use self::wl_store::{ForeignId, WlStore};

#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct WlGenericId(usize);

pub trait WlBackend {
    fn apply(&mut self, variant: &ValidVariant) -> Result<(), WlConfigurationError>;
    fn test(&mut self, variant: &ValidVariant) -> Result<(), WlConfigurationError>;

    fn drain_event_queue(&mut self) -> VecDeque<WlBackendEvent>;
    fn export_heads(&self) -> Option<VecDeque<WlHead>>;
    fn flush(&self) -> Result<(), WaylandError>;
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WlHead {
    base: WlBaseHead,
    current_mode: Option<WlMode>,
    modes: VecDeque<WlMode>,
    pub(crate) id: WlGenericId,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct WlMode {
    base: WlBaseMode,
    pub(crate) id: WlGenericId,
}

#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct WlBaseHead {
    pub name: String,
    pub description: String,
    pub size: PhysicalSize,
    pub enabled: bool,
    pub position: Position,
    pub transform: Option<Transform>,
    pub scale: f64,
    pub make: String,
    pub model: String,
    pub serial_number: String,
    pub adaptive_sync: Option<AdaptiveSyncState>,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WlBaseMode {
    pub width: i32,
    pub height: i32,
    pub refresh: i32,
    pub preferred: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WlBackendEvent {
    AtomicChangeDone,
    NeededResourceFinished,
    Succeeded,
    Failed,
    Cancelled,
}

#[derive(Debug, Snafu)]
#[snafu(context(suffix(Ctx)))]
#[snafu(visibility(pub(crate)))]
pub enum WlConfigurationError {
    #[snafu(display(
        "[{location}] Incompatible number of heads supplied: got {got}, have {have}"
    ))]
    HeadCountMismatch {
        location: Location,
        got: usize,
        have: usize,
    },
    #[snafu(display("[{location}] Cannot configure a dead head, head name: {head_name}"))]
    DeadHead {
        location: Location,
        head_name: String,
    },
    #[snafu(display("[{location}] Cannot configure a dead mode, mode: {mode}"))]
    DeadMode {
        location: Location,
        mode: WlBaseMode,
    },
    #[snafu(display("[{location}] Cannot configure an unknown head, head: {head:?}"))]
    UnknownHead {
        location: Location,
        head: Box<WlBaseHead>,
    },
    #[snafu(display("[{location}] Cannot configure an unknown mode, mode: {mode}"))]
    UnknownMode {
        location: Location,
        mode: WlBaseMode,
    },
}

impl WlHead {
    pub fn name(&self) -> &str {
        &self.base.name
    }
    pub fn description(&self) -> &str {
        &self.base.description
    }
    pub fn size(&self) -> PhysicalSize {
        self.base.size
    }
    pub fn modes(&self) -> &VecDeque<WlMode> {
        &self.modes
    }
    pub fn enabled(&self) -> bool {
        self.base.enabled
    }
    pub fn current_mode(&self) -> &Option<WlMode> {
        &self.current_mode
    }
    pub fn position(&self) -> Position {
        self.base.position
    }
    pub fn transform(&self) -> Option<Transform> {
        self.base.transform
    }
    pub fn scale(&self) -> f64 {
        self.base.scale
    }
    pub fn make(&self) -> &str {
        &self.base.make
    }
    pub fn model(&self) -> &str {
        &self.base.model
    }
    pub fn serial_number(&self) -> &str {
        &self.base.serial_number
    }
    pub fn adaptive_sync(&self) -> Option<AdaptiveSyncState> {
        self.base.adaptive_sync
    }
    pub fn wl_base_head(&self) -> &WlBaseHead {
        &self.base
    }
}

impl WlMode {
    pub fn width(&self) -> i32 {
        self.base.width
    }
    pub fn height(&self) -> i32 {
        self.base.height
    }
    pub fn refresh(&self) -> i32 {
        self.base.refresh
    }
    pub fn preferred(&self) -> bool {
        self.base.preferred
    }
    pub fn wl_base_mode(&self) -> WlBaseMode {
        self.base
    }
}

impl Display for WlBaseMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}x{}@{}mHz", self.width, self.height, self.refresh)
    }
}

impl Display for WlBackendEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let text = match self {
            WlBackendEvent::AtomicChangeDone => "AtomicChangeDone",
            WlBackendEvent::NeededResourceFinished => "NeededResourceFinished",
            WlBackendEvent::Succeeded => "Succeeded",
            WlBackendEvent::Failed => "Failed",
            WlBackendEvent::Cancelled => "Cancelled",
        };
        write!(f, "{text}")
    }
}

#[derive(Debug)]
pub struct LessEqWlHead<'a>(pub &'a WlHead);

impl<'a> PartialEq for LessEqWlHead<'a> {
    fn eq(&self, other: &Self) -> bool {
        let (a, b) = (self.0, other.0);
        a.id == b.id
            && a.serial_number() == b.serial_number()
            && a.model() == b.model()
            && a.make() == b.make()
            && a.description() == b.description()
            && a.name() == b.name()
    }
}

impl<'a> Eq for LessEqWlHead<'a> {}

impl<'a> Hash for LessEqWlHead<'a> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.id.hash(state);
        self.0.serial_number().hash(state);
        self.0.model().hash(state);
        self.0.make().hash(state);
        self.0.description().hash(state);
        self.0.name().hash(state);
    }
}