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_current_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: 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(|d| Monitor::new(d))
55            .collect()
56    }
57
58    fn is_dry_run() -> bool {
59        unsafe { return 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 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 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    pub fn current_input_source(&mut self) -> anyhow::Result<InputSourceRaw> {
118        let feature_code: FeatureCode = self.feature_code(INPUT_SELECT);
119        Ok(self.ddc_hi_display.handle.get_vcp_feature(feature_code)?.sl)
120    }
121
122    /// Set the current input source.
123    pub fn set_current_input_source(&mut self, value: InputSourceRaw) -> anyhow::Result<()> {
124        info!(
125            "InputSource({self}) = {value}{mode}",
126            value = InputSource::str_from_raw(value),
127            mode = if Self::is_dry_run() { " (dry-run)" } else { "" }
128        );
129        if Self::is_dry_run() {
130            return Ok(());
131        }
132        let feature_code: FeatureCode = self.feature_code(INPUT_SELECT);
133        self.ddc_hi_display
134            .handle
135            .set_vcp_feature(feature_code, value as u16)
136            .inspect(|_| self.needs_sleep = true)
137    }
138
139    /// Get all input sources.
140    /// Requires to call [`Monitor::update_capabilities()`] beforehand.
141    pub fn input_sources(&mut self) -> Option<Vec<InputSourceRaw>> {
142        if let Some(feature) = self.feature_descriptor(INPUT_SELECT) {
143            trace!("INPUT_SELECT({self}) = {feature:?}");
144            if let mccs_db::ValueType::NonContinuous { values, .. } = &feature.ty {
145                return Some(values.keys().cloned().collect());
146            }
147        }
148        None
149    }
150
151    /// Sleep if any previous DDC commands need time to be executed.
152    /// See also [`ddc_hi::DdcHost::sleep()`].
153    pub fn sleep_if_needed(&mut self) {
154        if self.needs_sleep {
155            debug!("sleep({self})");
156            let start_time = Instant::now();
157            self.needs_sleep = false;
158            self.ddc_hi_display.handle.sleep();
159            debug!("sleep({self}) elapsed {:?}", start_time.elapsed());
160        }
161    }
162
163    /// Get a multi-line descriptive string.
164    pub fn to_long_string(&mut self) -> String {
165        let mut lines = Vec::new();
166        lines.push(self.to_string());
167        let input_source = self.current_input_source();
168        lines.push(format!(
169            "Input Source: {}",
170            match input_source {
171                Ok(value) => InputSource::str_from_raw(value),
172                Err(e) => e.to_string(),
173            }
174        ));
175        if let Some(input_sources) = self.input_sources() {
176            lines.push(format!(
177                "Input Sources: {}",
178                input_sources
179                    .iter()
180                    .map(|value| InputSource::str_from_raw(*value))
181                    .collect::<Vec<_>>()
182                    .join(", ")
183            ));
184        }
185        if let Some(model) = &self.ddc_hi_display.info.model_name {
186            lines.push(format!("Model: {}", model));
187        }
188        lines.push(format!("Backend: {}", self.ddc_hi_display.info.backend));
189        return lines.join("\n    ");
190    }
191}