ddc_winapi/
lib.rs

1#![deny(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/ddc-winapi/0.2.2/")]
3
4//! Implementation of DDC/CI traits on Windows.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! extern crate ddc;
10//!
11//! # fn main() {
12//! use ddc::Ddc;
13//! use ddc_winapi::Monitor;
14//!
15//! for mut ddc in Monitor::enumerate().unwrap() {
16//!     let mccs_version = ddc.get_vcp_feature(0xdf).unwrap();
17//!     println!("MCCS version: {:04x}", mccs_version.maximum());
18//! }
19//! # }
20//! ```
21
22extern crate winapi;
23extern crate ddc;
24extern crate widestring;
25
26use std::{io, ptr, mem, fmt};
27use std::borrow::Cow;
28use winapi::um::physicalmonitorenumerationapi::*;
29use winapi::um::lowlevelmonitorconfigurationapi::*;
30use winapi::shared::windef::{HMONITOR, HDC, LPRECT};
31use winapi::shared::minwindef::{LPARAM, BYTE, DWORD, BOOL, TRUE};
32use winapi::um::winnt::HANDLE;
33use widestring::{WideCStr, WideStr};
34use ddc::{Ddc, DdcHost, FeatureCode, VcpValue, TimingMessage};
35
36// TODO: good luck getting EDID: https://social.msdn.microsoft.com/Forums/vstudio/en-US/efc46c70-7479-4d59-822b-600cb4852c4b/how-to-locate-the-edid-data-folderkey-in-the-registry-which-belongs-to-a-specific-physicalmonitor?forum=wdk
37
38/// A handle to an attached monitor that allows the use of DDC/CI operations.
39pub struct Monitor {
40    monitor: PHYSICAL_MONITOR,
41}
42
43impl Monitor {
44    /// Create a new monitor from the specified handle.
45    pub unsafe fn new(monitor: PHYSICAL_MONITOR) -> Self {
46        Monitor {
47            monitor: monitor,
48        }
49    }
50
51    /// Enumerate all connected physical monitors.
52    pub fn enumerate() -> io::Result<Vec<Self>> {
53        enumerate_monitors().and_then(|mon|
54            mon.into_iter().map(|mon|
55                get_physical_monitors_from_hmonitor(mon).map(|mon|
56                    mon.into_iter().map(|mon| unsafe { Monitor::new(mon) })
57                )
58            ).collect::<io::Result<Vec<_>>>()
59        ).map(|v| v.into_iter().flat_map(|mon| mon).collect())
60    }
61
62    /// Physical monitor description string.
63    pub fn description(&self) -> String {
64        let str_ptr = ptr::addr_of!(self.monitor.szPhysicalMonitorDescription);
65        let desc = match (str_ptr as usize) & (mem::align_of::<u16>() - 1) {
66            0 => Cow::Borrowed(unsafe { &*str_ptr }),
67            _ => Cow::Owned(self.monitor.szPhysicalMonitorDescription),
68        };
69        match WideCStr::from_slice_truncate(&desc[..]) {
70            Ok(cstr) => cstr.to_string_lossy(),
71            Err(_) => WideStr::from_slice(&desc[..]).to_string_lossy(),
72        }
73    }
74
75    /// Physical monitor winapi handle.
76    pub fn handle(&self) -> HANDLE {
77        self.monitor.hPhysicalMonitor
78    }
79
80    /// Retrieves a monitor's horizontal and vertical synchronization frequencies.
81    pub fn winapi_get_timing_report(&self) -> io::Result<MC_TIMING_REPORT> {
82        unsafe {
83            let mut report = mem::zeroed();
84            if GetTimingReport(self.handle(), &mut report) != TRUE {
85                Err(io::Error::last_os_error())
86            } else {
87                Ok(report)
88            }
89        }
90    }
91
92    /// Sets the value of a Virtual Control Panel (VCP) code for a monitor.
93    pub fn winapi_set_vcp_feature(&self, code: BYTE, value: DWORD) -> io::Result<()> {
94        unsafe {
95            if SetVCPFeature(self.handle(), code, value) != TRUE {
96                Err(io::Error::last_os_error())
97            } else {
98                Ok(())
99            }
100        }
101    }
102
103    /// Saves the current monitor settings to the display's nonvolatile storage.
104    pub fn winapi_save_current_settings(&self) -> io::Result<()> {
105        unsafe {
106            if SaveCurrentSettings(self.handle()) != TRUE {
107                Err(io::Error::last_os_error())
108            } else {
109                Ok(())
110            }
111        }
112    }
113
114    /// Retrieves the current value, maximum value, and code type of a Virtual
115    /// Control Panel (VCP) code for a monitor.
116    ///
117    /// Returns `(vcp_type, current_value, max_value)`
118    pub fn winapi_get_vcp_feature_and_vcp_feature_reply(&self, code: BYTE) -> io::Result<(MC_VCP_CODE_TYPE, DWORD, DWORD)> {
119        unsafe {
120            let mut ty = 0;
121            let mut current = 0;
122            let mut max = 0;
123            if GetVCPFeatureAndVCPFeatureReply(self.handle(), code, &mut ty, &mut current, &mut max) != TRUE {
124                Err(io::Error::last_os_error())
125            } else {
126                Ok((ty, current, max))
127            }
128        }
129    }
130
131    /// Retrieves the length of the buffer to pass to
132    /// `winapi_capabilities_request_and_capabilities_reply`.
133    pub fn winapi_get_capabilities_string_length(&self) -> io::Result<DWORD> {
134        unsafe {
135            let mut len = 0;
136            if GetCapabilitiesStringLength(self.handle(), &mut len) != TRUE {
137                Err(io::Error::last_os_error())
138            } else {
139                Ok(len)
140            }
141        }
142    }
143
144    /// Retrieves a string describing a monitor's capabilities.
145    ///
146    /// This string is always ASCII and includes a terminating null character.
147    pub fn winapi_capabilities_request_and_capabilities_reply(&self, string: &mut [u8]) -> io::Result<()> {
148        unsafe {
149            if CapabilitiesRequestAndCapabilitiesReply(self.handle(), string.as_mut_ptr() as *mut _, string.len() as _) != TRUE {
150                Err(io::Error::last_os_error())
151            } else {
152                Ok(())
153            }
154        }
155    }
156}
157
158impl DdcHost for Monitor {
159    type Error = io::Error;
160}
161
162impl Ddc for Monitor {
163    fn capabilities_string(&mut self) -> Result<Vec<u8>, Self::Error> {
164        let mut str = vec![0u8; self.winapi_get_capabilities_string_length()? as usize];
165        self.winapi_capabilities_request_and_capabilities_reply(&mut str)
166            .map(|_| {
167                let len = str.len();
168                if len > 0 {
169                    str.truncate(len - 1); // remove trailing null byte
170                }
171                str
172            })
173    }
174
175    fn get_vcp_feature(&mut self, code: FeatureCode) -> Result<VcpValue, Self::Error> {
176        self.winapi_get_vcp_feature_and_vcp_feature_reply(code)
177            .map(|(ty, cur, max)| VcpValue {
178                ty: match ty {
179                    MC_SET_PARAMETER => 0,
180                    MC_MOMENTARY => 1,
181                    _ => 0, // shouldn't be reachable?
182                },
183                mh: (max >> 8) as _,
184                ml: max as _,
185                sh: (cur >> 8) as _,
186                sl: cur as _,
187            })
188    }
189
190    fn set_vcp_feature(&mut self, code: FeatureCode, value: u16) -> Result<(), Self::Error> {
191        self.winapi_set_vcp_feature(code, value as _)
192    }
193
194    fn save_current_settings(&mut self) -> Result<(), Self::Error> {
195        self.winapi_save_current_settings()
196    }
197
198    fn get_timing_report(&mut self) -> Result<TimingMessage, Self::Error> {
199        self.winapi_get_timing_report()
200            .map(|timing| TimingMessage {
201                timing_status: timing.bTimingStatusByte,
202                horizontal_frequency: timing.dwHorizontalFrequencyInHZ as _,
203                vertical_frequency: timing.dwVerticalFrequencyInHZ as _,
204            })
205    }
206}
207
208impl Drop for Monitor {
209    fn drop(&mut self) {
210        unsafe {
211            DestroyPhysicalMonitor(self.handle());
212        }
213    }
214}
215
216impl fmt::Debug for Monitor {
217    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218        f.debug_struct("Monitor")
219            .field("handle", &self.handle())
220            .field("description", &self.description())
221            .finish()
222    }
223}
224
225/// WinAPI `GetPhysicalMonitorsFromHMONITOR`
226pub fn get_physical_monitors_from_hmonitor(monitor: HMONITOR) -> io::Result<Vec<PHYSICAL_MONITOR>> {
227    unsafe {
228        let mut len = 0;
229        if GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, &mut len) != TRUE {
230            return Err(io::Error::last_os_error())
231        }
232
233        let mut monitors = vec![mem::zeroed::<PHYSICAL_MONITOR>(); len as usize];
234        if GetPhysicalMonitorsFromHMONITOR(monitor, len, monitors.as_mut_ptr()) != TRUE {
235            Err(io::Error::last_os_error())
236        } else {
237            Ok(monitors)
238        }
239    }
240}
241
242/// Enumerates all `HMONITOR`s using the `EnumDisplayMonitors` WinAPI call.
243pub fn enumerate_monitors() -> io::Result<Vec<HMONITOR>> {
244    unsafe extern "system" fn callback(monitor: HMONITOR, _hdc_monitor: HDC, _lprc: LPRECT, userdata: LPARAM) -> BOOL {
245        let monitors: &mut Vec<HMONITOR> = &mut *(userdata as *mut Vec<HMONITOR>);
246        monitors.push(monitor);
247        TRUE
248    }
249
250    let mut monitors = Vec::<HMONITOR>::new();
251    if unsafe {
252        let userdata = ptr::addr_of_mut!(monitors);
253        winapi::um::winuser::EnumDisplayMonitors(ptr::null_mut(), ptr::null(), Some(callback), userdata as _)
254    } != TRUE {
255        Err(io::Error::last_os_error())
256    } else {
257        Ok(monitors)
258    }
259}