Skip to main content

ddc_macos/
monitor.rs

1#![deny(missing_docs)]
2
3use crate::error::Error;
4use crate::iokit::CoreDisplay_DisplayCreateInfoDictionary;
5use crate::iokit::IoObject;
6use crate::{arm, intel};
7use core_foundation::base::{CFType, TCFType};
8use core_foundation::data::CFData;
9use core_foundation::dictionary::CFDictionary;
10use core_foundation::string::{CFString, CFStringRef};
11use core_graphics::base::CGError;
12use core_graphics::display::{CGDirectDisplayID, CGDisplay};
13use ddc::{
14    DdcCommand, DdcCommandMarker, DdcCommandRaw, DdcCommandRawMarker, DdcHost, Delay, ErrorCode, I2C_ADDRESS_DDC_CI,
15    SUB_ADDRESS_DDC_CI,
16};
17use std::time::Duration;
18use std::{fmt, iter};
19
20extern "C" {
21    // Not bound by core-graphics 0.24; declared here so we can include mirrored
22    // secondary displays. Links against the CoreGraphics framework already
23    // pulled in by the core-graphics crate.
24    fn CGGetOnlineDisplayList(max_displays: u32, online: *mut CGDirectDisplayID, count: *mut u32) -> CGError;
25}
26
27/// Display ids for every online display, including mirrored secondaries that
28/// `CGDisplay::active_displays()` omits. Those secondaries still have working
29/// DDC services, so they must be enumerated for DDC/CI to reach them.
30fn online_display_ids() -> Result<Vec<CGDirectDisplayID>, Error> {
31    let mut count: u32 = 0;
32    let err: CGError = unsafe { CGGetOnlineDisplayList(0, std::ptr::null_mut(), &mut count) };
33    if err != 0 {
34        return Err(Error::from(err));
35    }
36    let mut ids = vec![0 as CGDirectDisplayID; count as usize];
37    let err: CGError = unsafe { CGGetOnlineDisplayList(count, ids.as_mut_ptr(), &mut count) };
38    if err != 0 {
39        return Err(Error::from(err));
40    }
41    ids.truncate(count as usize);
42    Ok(ids)
43}
44
45/// DDC access method for a monitor
46#[derive(Debug)]
47enum MonitorService {
48    Intel(IoObject),
49    Arm(arm::IOAVService),
50}
51
52/// A handle to an attached monitor that allows the use of DDC/CI operations.
53#[derive(Debug)]
54pub struct Monitor {
55    monitor: CGDisplay,
56    service: MonitorService,
57    i2c_address: u16,
58    delay: Delay,
59}
60
61impl fmt::Display for Monitor {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", self.description())
64    }
65}
66
67impl Monitor {
68    /// Create a new monitor from the specified handle.
69    fn new(monitor: CGDisplay, service: MonitorService, i2c_address: u16) -> Self {
70        Monitor {
71            monitor,
72            service,
73            i2c_address,
74            delay: Default::default(),
75        }
76    }
77
78    /// Enumerate all connected physical monitors returning [Vec<Monitor>]
79    pub fn enumerate() -> Result<Vec<Self>, Error> {
80        let monitors = online_display_ids()?
81            .into_iter()
82            .filter_map(|display_id| {
83                let display = CGDisplay::new(display_id);
84                return if let Some(service) = intel::get_io_framebuffer_port(display) {
85                    Some(Self::new(display, MonitorService::Intel(service), I2C_ADDRESS_DDC_CI))
86                } else if let Ok((service, i2c_address)) = arm::get_display_av_service(display) {
87                    Some(Self::new(display, MonitorService::Arm(service), i2c_address))
88                } else {
89                    None
90                };
91            })
92            .collect();
93        Ok(monitors)
94    }
95
96    /// Physical monitor description string. If it cannot get the product's name it will use
97    /// the vendor number and model number to form a description
98    pub fn description(&self) -> String {
99        self.product_name().unwrap_or(format!(
100            "{:04x}:{:04x}",
101            self.monitor.vendor_number(),
102            self.monitor.model_number()
103        ))
104    }
105
106    /// Serial number for this [Monitor]
107    pub fn serial_number(&self) -> Option<String> {
108        let serial = self.monitor.serial_number();
109        match serial {
110            0 => None,
111            _ => Some(format!("{}", serial)),
112        }
113    }
114
115    /// Product name for this [Monitor], if available
116    pub fn product_name(&self) -> Option<String> {
117        let info: CFDictionary<CFString, CFType> =
118            unsafe { CFDictionary::wrap_under_create_rule(CoreDisplay_DisplayCreateInfoDictionary(self.monitor.id)) };
119
120        let display_product_name_key = CFString::from_static_string("DisplayProductName");
121        let display_product_names_dict = info.find(&display_product_name_key)?.downcast::<CFDictionary>()?;
122        let (_, localized_product_names) = display_product_names_dict.get_keys_and_values();
123        localized_product_names
124            .first()
125            .map(|name| unsafe { CFString::wrap_under_get_rule(*name as CFStringRef) }.to_string())
126    }
127
128    /// Returns Extended display identification data (EDID) for this [Monitor] as raw bytes data
129    pub fn edid(&self) -> Option<Vec<u8>> {
130        let info: CFDictionary<CFString, CFType> =
131            unsafe { CFDictionary::wrap_under_create_rule(CoreDisplay_DisplayCreateInfoDictionary(self.monitor.id)) };
132        let display_product_name_key = CFString::from_static_string("IODisplayEDIDOriginal");
133        let edid_data = info.find(&display_product_name_key)?.downcast::<CFData>()?;
134        Some(edid_data.bytes().into())
135    }
136
137    /// CoreGraphics display handle for this monitor
138    pub fn handle(&self) -> CGDisplay {
139        self.monitor
140    }
141
142    fn encode_command<'a>(&self, data: &[u8], packet: &'a mut [u8]) -> &'a [u8] {
143        packet[0] = SUB_ADDRESS_DDC_CI;
144        packet[1] = 0x80 | data.len() as u8;
145        packet[2..2 + data.len()].copy_from_slice(data);
146        packet[2 + data.len()] =
147            Self::checksum(iter::once((self.i2c_address as u8) << 1).chain(packet[..2 + data.len()].iter().cloned()));
148        &packet[..3 + data.len()]
149    }
150
151    fn decode_response<'a>(&self, response: &'a mut [u8]) -> Result<&'a mut [u8], crate::error::Error> {
152        if response.is_empty() {
153            return Ok(response);
154        };
155        let len = (response[1] & 0x7f) as usize;
156        if len + 2 >= response.len() {
157            return Err(Error::Ddc(ErrorCode::InvalidLength));
158        }
159        let checksum = Self::checksum(
160            iter::once(((self.i2c_address << 1) | 1) as u8)
161                .chain(iter::once(SUB_ADDRESS_DDC_CI))
162                .chain(response[1..2 + len].iter().cloned()),
163        );
164        if response[2 + len] != checksum {
165            return Err(Error::Ddc(ErrorCode::InvalidChecksum));
166        }
167        Ok(&mut response[2..2 + len])
168    }
169}
170
171impl DdcHost for Monitor {
172    type Error = Error;
173
174    fn sleep(&mut self) {
175        self.delay.sleep()
176    }
177}
178
179impl DdcCommandRaw for Monitor {
180    fn execute_raw<'a>(
181        &mut self,
182        data: &[u8],
183        out: &'a mut [u8],
184        response_delay: Duration,
185    ) -> Result<&'a mut [u8], Self::Error> {
186        assert!(data.len() <= 36);
187        let mut packet = [0u8; 36 + 3];
188        let packet = self.encode_command(data, &mut packet);
189        let response = match &self.service {
190            MonitorService::Intel(service) => intel::execute(service, self.i2c_address, packet, out, response_delay),
191            MonitorService::Arm(service) => arm::execute(service, self.i2c_address, packet, out, response_delay),
192        }?;
193        self.decode_response(response)
194    }
195}
196
197impl DdcCommandMarker for Monitor {}
198
199impl DdcCommandRawMarker for Monitor {
200    fn set_sleep_delay(&mut self, delay: Delay) {
201        self.delay = delay;
202    }
203}