From 7d2505804954b391a06d186d2a8fbc328de46ddd Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Tue, 25 Jul 2023 20:20:40 +0200 Subject: [PATCH 1/7] Update edition, fix clippies, add an example --- Cargo.toml | 2 +- examples/list.rs | 21 +++++++++++++++++++++ src/iokit/wrappers.rs | 8 ++++---- src/lib.rs | 2 ++ src/monitor.rs | 7 +++---- 5 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 examples/list.rs diff --git a/Cargo.toml b/Cargo.toml index 1db72e9..f731df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ name = "ddc-macos" version = "0.2.0" authors = ["Haim Gelfenbeyn "] -edition = "2018" description = "DDC/CI monitor control on MacOS" documentation = "http://haimgel.github.io/ddc-macos-rs/ddc_macos" readme = "README.md" @@ -10,6 +9,7 @@ repository = "https://github.com/haimgel/ddc-macos-rs" license = "MIT" keywords = ["ddc", "mccs", "vcp", "vesa", "macos"] categories = ["hardware-support", "os::macos-apis"] +edition = "2021" [dependencies] core-foundation = "^0.9.1" diff --git a/examples/list.rs b/examples/list.rs new file mode 100644 index 0000000..3d31db6 --- /dev/null +++ b/examples/list.rs @@ -0,0 +1,21 @@ +extern crate ddc; +extern crate ddc_macos; + +use ddc::Ddc; +use ddc_macos::Monitor; + +fn main() { + let monitors = Monitor::enumerate() + .expect("Could not enumerate external monitors"); + + if monitors.is_empty() { + println!("No external monitors found"); + } else { + for mut monitor in monitors { + let input = monitor.get_vcp_feature(0x60) + .expect("Could not get feature 0x60"); + println!("Current input: {:04x}", input.value()); + println!("Monitor description: {}", monitor.description()); + } + } +} \ No newline at end of file diff --git a/src/iokit/wrappers.rs b/src/iokit/wrappers.rs index 1b90205..75c0108 100644 --- a/src/iokit/wrappers.rs +++ b/src/iokit/wrappers.rs @@ -33,9 +33,9 @@ impl From for IoObject { } } -impl Into for &IoObject { - fn into(self) -> io_object_t { - self.0 +impl From<&IoObject> for io_object_t { + fn from(val: &IoObject) -> io_object_t { + val.0 } } @@ -66,7 +66,7 @@ impl IoIterator { unsafe { kern_try!(IOServiceGetMatchingServices(kIOMasterPortDefault, dict as _, &mut iter)); } - Ok(Self { 0: iter }) + Ok(Self(iter)) } } diff --git a/src/lib.rs b/src/lib.rs index 60dbc6c..b1b73c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![doc(html_root_url = "http://haimgel.github.io/ddc-macos-rs/")] + //! Implementation of DDC/CI traits on MacOS. //! //! # Example diff --git a/src/monitor.rs b/src/monitor.rs index e4a0534..13d7309 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,5 +1,4 @@ #![deny(missing_docs)] -#![doc(html_root_url = "http://haimgel.github.io/ddc-macos-rs/")] use crate::iokit::display::*; use crate::iokit::io2c_interface::*; @@ -85,12 +84,12 @@ impl Monitor { let displays = CGDisplay::active_displays() .map_err(Error::from)? .into_iter() - .map(|display_id| { + .filter_map(|display_id| { let display = CGDisplay::new(display_id); let frame_buffer = Self::get_io_framebuffer_port(display)?; Some(Self::new(display, frame_buffer)) - }) - .filter_map(|x| x) + }) + .collect(); Ok(displays) } From d17389a18279f6499862f506bd2faeaf1c68304b Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Tue, 25 Jul 2023 20:23:05 +0200 Subject: [PATCH 2/7] Add example to readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3d7bfc8..2c84a29 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,11 @@ `ddc-macos` implements the [`ddc`](https://crates.io/crates/ddc) traits for MacOS. +## Examples +You can list external monitors and their description using the provided example using: + +`cargo run --example list` + ## [Documentation][docs] See the [documentation][docs] for up to date information. From 321213b5720e45d62e1f7a26281b2de0405d7409 Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Fri, 28 Jul 2023 10:09:35 +0200 Subject: [PATCH 3/7] Handle inability to get input --- examples/list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/list.rs b/examples/list.rs index 3d31db6..1e6d2ad 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -12,9 +12,9 @@ fn main() { println!("No external monitors found"); } else { for mut monitor in monitors { - let input = monitor.get_vcp_feature(0x60) - .expect("Could not get feature 0x60"); - println!("Current input: {:04x}", input.value()); + if let Ok(input) = monitor.get_vcp_feature(0x60) { + println!("Current input: {:04x}", input.value()); + } println!("Monitor description: {}", monitor.description()); } } From 18f7dde254cad69059c1e238d0d39889a8b378fd Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Fri, 28 Jul 2023 12:29:04 +0200 Subject: [PATCH 4/7] Fix warnings --- src/monitor.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/monitor.rs b/src/monitor.rs index 13d7309..dddefc7 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -79,7 +79,7 @@ impl Monitor { } /// Enumerate all connected physical monitors. - pub fn enumerate() -> std::result::Result, Error> { + pub fn enumerate() -> Result, Error> { unsafe { let displays = CGDisplay::active_displays() .map_err(Error::from)? @@ -248,7 +248,7 @@ impl DdcHost for Monitor { } impl DdcCommand for Monitor { - fn execute(&mut self, command: C) -> std::result::Result<::Ok, Self::Error> { + fn execute(&mut self, command: C) -> Result<::Ok, Self::Error> { // Encode the command into request_data buffer // 36 bytes is an arbitrary number, larger than any I2C command length. // Cannot use [0u8; C::MAX_LEN] (associated constants do not work here) @@ -283,7 +283,7 @@ impl DdcCommand for Monitor { } if request.replyTransactionType == kIOI2CNoTransactionType { - ddc::CommandResult::decode(&[0u8; 0]).map_err(From::from) + CommandResult::decode(&[0u8; 0]).map_err(From::from) } else { let reply_length = (reply_data[1] & 0x7f) as usize; if reply_length + 2 >= reply_data.len() { @@ -298,7 +298,7 @@ impl DdcCommand for Monitor { if reply_data[2 + reply_length] != checksum { return Err(Error::Ddc(ErrorCode::InvalidChecksum)); } - ddc::CommandResult::decode(&reply_data[2..reply_length + 2]).map_err(From::from) + CommandResult::decode(&reply_data[2..reply_length + 2]).map_err(From::from) } } } From e490aca319e3bd4dd16ddda8f871130ed5396dab Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Fri, 28 Jul 2023 12:54:36 +0200 Subject: [PATCH 5/7] Removed some unsafes and improved example output --- Cargo.toml | 1 + examples/list.rs | 26 ++++++++++++++++++++++-- src/monitor.rs | 51 ++++++++++++++++++++++++------------------------ 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f731df0..9b1df0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ ddc = "^0.2.0" io-kit-sys = "0.1.0" mach = "^0.3.2" thiserror = "^1" +edid = "0.3.0" [badges] maintenance = { status = "actively-developed" } diff --git a/examples/list.rs b/examples/list.rs index 1e6d2ad..93c9fff 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -12,10 +12,32 @@ fn main() { println!("No external monitors found"); } else { for mut monitor in monitors { + println!("Monitor"); + println!("\tDescription: {}", monitor.description()); + if let Some(desc) = monitor.product_name() { + println!("\tProduct Name: {}", desc); + } + if let Some(number) = monitor.serial_number() { + println!("\tSerial Number: {}", number); + } if let Ok(input) = monitor.get_vcp_feature(0x60) { - println!("Current input: {:04x}", input.value()); + println!("\tCurrent input: {:04x}", input.value()); + } + + /* + match edid::parse(data: &[u8]) { + nom::IResult::Done(remaining, parsed) => { + assert_eq!(remaining.len(), 0); + assert_eq!(&parsed, expected); + }, + nom::IResult::Error(err) => { + panic!(format!("{}", err)); + }, + nom::IResult::Incomplete(_) => { + panic!("Incomplete"); + }, } - println!("Monitor description: {}", monitor.description()); + */ } } } \ No newline at end of file diff --git a/src/monitor.rs b/src/monitor.rs index dddefc7..6b274f6 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -78,39 +78,40 @@ impl Monitor { Monitor { monitor, frame_buffer } } - /// Enumerate all connected physical monitors. + /// Enumerate all connected physical monitors returning [Vec] pub fn enumerate() -> Result, Error> { - unsafe { - let displays = CGDisplay::active_displays() - .map_err(Error::from)? - .into_iter() - .filter_map(|display_id| { - let display = CGDisplay::new(display_id); - let frame_buffer = Self::get_io_framebuffer_port(display)?; - Some(Self::new(display, frame_buffer)) - }) - - .collect(); - Ok(displays) - } + let monitors = CGDisplay::active_displays() + .map_err(Error::from)? + .into_iter() + .filter_map(|display_id| { + let display = CGDisplay::new(display_id); + let frame_buffer = Self::get_io_framebuffer_port(display)?; + Some(Self::new(display, frame_buffer)) + }) + .collect(); + Ok(monitors) } - /// Physical monitor description string. + /// Physical monitor description string. If it cannot get the product's name it will use + /// the vendor number and model number to form a description pub fn description(&self) -> String { - let name = self.product_name().unwrap_or(format!( + self.product_name().unwrap_or(format!( "{:04x}:{:04x}", self.monitor.vendor_number(), self.monitor.model_number() - )); + )) + } + + /// Serial number for this [Monitor] + pub fn serial_number(&self) -> Option { let serial = self.monitor.serial_number(); - if serial != 0 { - format!("{} S/N {}", name, serial) - } else { - name + match serial { + 0 => None, + _ => Some(format!("{}", serial)) } } - /// Product name for this monitor. + /// Product name for this [Monitor], if available pub fn product_name(&self) -> Option { let info = Self::display_info_dict(&self.frame_buffer)?; let display_product_name_key = CFString::from_static_string("DisplayProductName"); @@ -121,7 +122,7 @@ impl Monitor { .map(|name| unsafe { CFString::wrap_under_get_rule(*name as CFStringRef) }.to_string()) } - /// Returns EDID for this display as raw bytes data + /// Returns Extended display identification data (EDID) for this [Monitor] as raw bytes data pub fn edid(&self) -> Option> { let info = Self::display_info_dict(&self.frame_buffer)?; let display_product_name_key = CFString::from_static_string("IODisplayEDIDOriginal"); @@ -141,7 +142,7 @@ impl Monitor { } } - /// Finds a framebuffer that matches display, returns a properly formatted *unique* display name + // Finds a framebuffer that matches display, returns a properly formatted *unique* display name fn framebuffer_port_matches_display(port: &IoObject, display: CGDisplay) -> Option<()> { let mut bus_count: IOItemCount = 0; unsafe { @@ -179,7 +180,7 @@ impl Monitor { } // Gets the framebuffer port - unsafe fn get_io_framebuffer_port(display: CGDisplay) -> Option { + fn get_io_framebuffer_port(display: CGDisplay) -> Option { if display.is_builtin() { return None; } From 6c952f7ec5e5dc7c25f642c72476d0aa1ff7044c Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Fri, 28 Jul 2023 15:42:21 +0200 Subject: [PATCH 6/7] Add sample EDID dumps --- tests/edid/dell.edid.bin | Bin 0 -> 384 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/edid/dell.edid.bin diff --git a/tests/edid/dell.edid.bin b/tests/edid/dell.edid.bin new file mode 100644 index 0000000000000000000000000000000000000000..aa1267c296f5b54f93412287f2fa783dd9867bf8 GIT binary patch literal 384 zcmZSh4+abZYo06&b~7?mRA6M;8mL@hoicHwuk8+1zJL%#4hBXv@VusA!G#5y0R{@9 z9GXdn3=C2X3=IDnl-B|5(kzh0(iq}^V%?3%Vs#8_!0q literal 0 HcmV?d00001 From 865e40cd5a4c4fbb833956bf0a72290afc66d995 Mon Sep 17 00:00:00 2001 From: Andrew Mackenzie Date: Fri, 28 Jul 2023 15:49:24 +0200 Subject: [PATCH 7/7] Try parsing EDID --- Cargo.toml | 19 +++++++++++-------- examples/list.rs | 20 +++++++------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b1df0d..9665238 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,17 @@ categories = ["hardware-support", "os::macos-apis"] edition = "2021" [dependencies] -core-foundation = "^0.9.1" -core-foundation-sys = "^0.8.1" -core-graphics = "^0.22.1" -ddc = "^0.2.0" -io-kit-sys = "0.1.0" -mach = "^0.3.2" -thiserror = "^1" -edid = "0.3.0" +core-foundation = "0.9" +core-foundation-sys = "0.8" +core-graphics = "0.23" +ddc = "0.2" +io-kit-sys = "0.3" +mach = "0.3" +thiserror = "1" + +[dev-dependencies] +edid-rs = "0.1" +nom = "*" [badges] maintenance = { status = "actively-developed" } diff --git a/examples/list.rs b/examples/list.rs index 93c9fff..1741728 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -24,20 +24,14 @@ fn main() { println!("\tCurrent input: {:04x}", input.value()); } - /* - match edid::parse(data: &[u8]) { - nom::IResult::Done(remaining, parsed) => { - assert_eq!(remaining.len(), 0); - assert_eq!(&parsed, expected); - }, - nom::IResult::Error(err) => { - panic!(format!("{}", err)); - }, - nom::IResult::Incomplete(_) => { - panic!("Incomplete"); - }, + if let Some(data) = monitor.edid() { + let mut cursor = std::io::Cursor::new(&data); + let mut reader = edid_rs::Reader::new(&mut cursor); + match edid_rs::EDID::parse(&mut reader) { + Ok(edid) => println!("\tEDID Info: {:?}", edid), + _ => println!("\tCould not parse provided EDID information"), + } } - */ } } } \ No newline at end of file