pub mod ddc;
pub mod mccs;
use ddc::{edid::Edid, Ddc, DdcError};
use mccs::{
capabilities::Capabilities,
features::{ContrastValue, InputSource, LuminanceValue, OsdLanguages},
};
use std::{fmt::Display, io};
use thiserror::Error;
#[cfg(target_os = "linux")]
use crate::ddc::linux::{LinuxDdcDevice, LinuxDdcDeviceEnumerator};
#[derive(Debug, Error)]
pub enum DisplayError {
#[error("the backend does not support the operation")]
UnsupportedOp,
#[error("failed to read edid: {0}")]
DdcError(#[from] DdcError),
#[error("data read failed: {0}")]
IoError(#[from] io::Error),
}
#[derive(Clone, Debug)]
pub struct MonitorInfo {
edid: Edid,
mccs_features: Option<Capabilities>,
}
impl MonitorInfo {
pub fn manufacture_year(&self) -> usize {
self.edid.header.year as usize + 1990
}
pub fn serial(&self) -> u32 {
self.edid.header.serial
}
pub fn capabilities(&self) -> Option<&Capabilities> {
self.mccs_features.as_ref()
}
}
pub struct MonitorDevice<D>
where
D: Ddc,
{
pub handle: Box<D>,
pub info: MonitorInfo,
}
impl<D> Display for MonitorDevice<D>
where
D: Ddc,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("Monitor ({})", self.handle.name()))
.field("serial", &self.info.serial())
.field("manufacture_year", &self.info.manufacture_year())
.finish()
}
}
impl<D> MonitorDevice<D>
where
D: Ddc,
{
pub fn new(mut handle: D) -> Result<Self, DisplayError> {
let edid = handle.read_edid()?;
Ok(MonitorDevice {
handle: Box::new(handle),
info: MonitorInfo {
edid,
mccs_features: None,
},
})
}
pub fn get_input_source(&mut self) -> Result<InputSource, DdcError> {
self.handle.get_vcp_feature()
}
pub fn set_input_source(&mut self, input_source: InputSource) -> Result<(), DdcError> {
self.handle.set_vcp_feature(input_source)
}
pub fn get_language(&mut self) -> Result<OsdLanguages, DdcError> {
self.handle.get_vcp_feature()
}
pub fn set_language(&mut self, language: OsdLanguages) -> Result<(), DdcError> {
self.handle.set_vcp_feature(language)
}
pub fn get_luminance(&mut self) -> Result<f64, DdcError> {
let luminance: LuminanceValue = self.handle.get_vcp_feature()?;
Ok((luminance.val as f64) / luminance.max as f64)
}
pub fn set_luminance(&mut self, lum: f64) -> Result<(), DdcError> {
assert!(lum >= 0. && lum <= 1.);
let mut luminance: LuminanceValue = self.handle.get_vcp_feature()?;
luminance.val = ((luminance.max as f64) * lum).round() as u16;
self.handle.set_vcp_feature(luminance)
}
pub fn get_contrast(&mut self) -> Result<f64, DdcError> {
let contrast: ContrastValue = self.handle.get_vcp_feature()?;
Ok((contrast.val as f64) / contrast.max as f64)
}
pub fn set_contrast(&mut self, lum: f64) -> Result<(), DdcError> {
assert!(lum >= 0. && lum <= 1.);
let mut contrast: ContrastValue = self.handle.get_vcp_feature()?;
contrast.val = ((contrast.max as f64) * lum).round() as u16;
self.handle.set_vcp_feature(contrast)
}
}
#[cfg(target_os = "linux")]
pub type Monitor = MonitorDevice<LinuxDdcDevice>;
impl Monitor {
#[cfg(target_os = "linux")]
pub fn enumerate() -> MonitorIterator<LinuxDdcDevice> {
MonitorIterator {
inner_iter: Box::new(LinuxDdcDeviceEnumerator::iter()),
}
}
}
pub struct MonitorIterator<D>
where
D: Ddc,
{
inner_iter: Box<dyn Iterator<Item = D>>,
}
impl<D> Iterator for MonitorIterator<D>
where
D: Ddc,
{
type Item = MonitorDevice<D>;
fn next(&mut self) -> Option<Self::Item> {
self.inner_iter
.next()
.and_then(|dev| MonitorDevice::new(dev).ok())
}
}