monitor_input/
monitor.rs

1use std::time::Instant;
2
3use super::*;
4use ddc_hi::{Ddc, DdcHost, FeatureCode};
5use log::*;
6
7/// VCP feature code for input select
8const INPUT_SELECT: FeatureCode = 0x60;
9
10static mut DRY_RUN: bool = false;
11
12/// Represents a display monitor.
13/// # Examples
14/// ```no_run
15/// # use monitor_input::{InputSource,Monitor};
16/// let mut monitors = Monitor::enumerate();
17/// monitors[0].set_input_source(InputSource::UsbC1.as_raw());
18/// ```
19pub struct Monitor {
20    ddc_hi_display: ddc_hi::Display,
21    is_capabilities_updated: bool,
22    needs_sleep: bool,
23}
24
25impl std::fmt::Display for Monitor {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        write!(f, "{}", self.ddc_hi_display.info.id)
28    }
29}
30
31impl std::fmt::Debug for Monitor {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.debug_struct("Monitor")
34            .field("info", &self.ddc_hi_display.info)
35            .finish()
36    }
37}
38
39impl Monitor {
40    /// Create an instance from [`ddc_hi::Display`].
41    pub fn new(ddc_hi_display: ddc_hi::Display) -> Self {
42        Monitor {
43            ddc_hi_display,
44            is_capabilities_updated: false,
45            needs_sleep: false,
46        }
47    }
48
49    /// Enumerate all display monitors.
50    /// See also [`ddc_hi::Display::enumerate()`].
51    pub fn enumerate() -> Vec<Self> {
52        ddc_hi::Display::enumerate()
53            .into_iter()
54            .map(Monitor::new)
55            .collect()
56    }
57
58    fn is_dry_run() -> bool {
59        unsafe { DRY_RUN }
60    }
61
62    /// Set the dry-run mode.
63    /// When in dry-run mode, functions that are supposed to make changes
64    /// don't actually make the changes.
65    pub fn set_dry_run(value: bool) {
66        unsafe { DRY_RUN = value }
67    }
68
69    /// Updates the display info with data retrieved from the device's
70    /// reported capabilities.
71    /// See also [`ddc_hi::Display::update_capabilities()`].
72    pub fn update_capabilities(&mut self) -> anyhow::Result<()> {
73        if self.is_capabilities_updated {
74            return Ok(());
75        }
76        self.is_capabilities_updated = true;
77        debug!("update_capabilities({self})");
78        let start_time = Instant::now();
79        let result = self
80            .ddc_hi_display
81            .update_capabilities()
82            .inspect_err(|e| warn!("{self}: Failed to update capabilities: {e}"));
83        debug!(
84            "update_capabilities({self}) elapsed: {:?}",
85            start_time.elapsed()
86        );
87        result
88    }
89
90    pub(crate) fn contains_backend(&self, backend: &str) -> bool {
91        self.ddc_hi_display
92            .info
93            .backend
94            .to_string()
95            .contains(backend)
96    }
97
98    pub(crate) fn contains(&self, name: &str) -> bool {
99        self.ddc_hi_display.info.id.contains(name)
100    }
101
102    fn feature_descriptor(&self, feature_code: FeatureCode) -> Option<&mccs_db::Descriptor> {
103        self.ddc_hi_display.info.mccs_database.get(feature_code)
104    }
105
106    fn feature_code(&self, feature_code: FeatureCode) -> FeatureCode {
107        // TODO: `mccs_database` is initialized by `display.update_capabilities()`
108        // which is quite slow, and it seems to work without this.
109        // See also https://github.com/mjkoo/monitor-switch/blob/master/src/main.rs.
110        if let Some(feature) = self.feature_descriptor(feature_code) {
111            return feature.code;
112        }
113        feature_code
114    }
115
116    /// Get the current input source.
117    /// # Examples
118    /// ```no_run
119    /// # use monitor_input::{InputSource,Monitor};
120    /// # fn print(monitor: &mut Monitor) -> anyhow::Result<()> {
121    /// print!("{}", InputSource::str_from_raw(monitor.input_source()?));
122    /// #   Ok(())
123    /// # }
124    /// ```
125    pub fn input_source(&mut self) -> anyhow::Result<InputSourceRaw> {
126        let feature_code: FeatureCode = self.feature_code(INPUT_SELECT);
127        Ok(self.ddc_hi_display.handle.get_vcp_feature(feature_code)?.sl)
128    }
129
130    /// Set the current input source.
131    /// # Examples
132    /// ```no_run
133    /// # use monitor_input::{InputSource,Monitor};
134    /// fn set_to_usbc1(monitor: &mut Monitor) -> anyhow::Result<()> {
135    ///   monitor.set_input_source(InputSource::UsbC1.as_raw())
136    /// }
137    /// ```
138    /// ```no_run
139    /// # use monitor_input::{InputSource,Monitor};
140    /// fn set_to(monitor: &mut Monitor, input_source_str: &str) -> anyhow::Result<()> {
141    ///   monitor.set_input_source(InputSource::raw_from_str(input_source_str)?)
142    /// }
143    /// ```
144    pub fn set_input_source(&mut self, value: InputSourceRaw) -> anyhow::Result<()> {
145        info!(
146            "InputSource({self}) = {value}{mode}",
147            value = InputSource::str_from_raw(value),
148            mode = if Self::is_dry_run() { " (dry-run)" } else { "" }
149        );
150        if Self::is_dry_run() {
151            return Ok(());
152        }
153        let feature_code: FeatureCode = self.feature_code(INPUT_SELECT);
154        self.ddc_hi_display
155            .handle
156            .set_vcp_feature(feature_code, value as u16)
157            .inspect(|_| self.needs_sleep = true)
158    }
159
160    /// Get all input sources.
161    /// Requires to call [`Monitor::update_capabilities()`] beforehand.
162    pub fn input_sources(&mut self) -> Option<Vec<InputSourceRaw>> {
163        if let Some(feature) = self.feature_descriptor(INPUT_SELECT) {
164            trace!("INPUT_SELECT({self}) = {feature:?}");
165            if let mccs_db::ValueType::NonContinuous { values, .. } = &feature.ty {
166                return Some(values.keys().cloned().collect());
167            }
168        }
169        None
170    }
171
172    /// Sleep if any previous DDC commands need time to be executed.
173    /// See also [`ddc_hi::DdcHost::sleep()`].
174    pub fn sleep_if_needed(&mut self) {
175        if self.needs_sleep {
176            debug!("sleep({self})");
177            let start_time = Instant::now();
178            self.needs_sleep = false;
179            self.ddc_hi_display.handle.sleep();
180            debug!("sleep({self}) elapsed {:?}", start_time.elapsed());
181        }
182    }
183
184    /// Get a multi-line descriptive string.
185    pub fn to_long_string(&mut self) -> String {
186        let mut lines = Vec::new();
187        lines.push(self.to_string());
188        let input_source = self.input_source();
189        lines.push(format!(
190            "Input Source: {}",
191            match input_source {
192                Ok(value) => InputSource::str_from_raw(value),
193                Err(e) => e.to_string(),
194            }
195        ));
196        if let Some(input_sources) = self.input_sources() {
197            lines.push(format!(
198                "Input Sources: {}",
199                input_sources
200                    .iter()
201                    .map(|value| InputSource::str_from_raw(*value))
202                    .collect::<Vec<_>>()
203                    .join(", ")
204            ));
205        }
206        if let Some(model) = &self.ddc_hi_display.info.model_name {
207            lines.push(format!("Model: {model}"));
208        }
209        lines.push(format!("Backend: {}", self.ddc_hi_display.info.backend));
210        lines.join("\n    ")
211    }
212}