ddc_hi/
lib.rs

1#![deny(missing_docs)]
2#![doc(html_root_url = "http://docs.rs/ddc-hi-rs/0.4.1")]
3
4//! High level DDC/CI monitor controls.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use ddc_hi::{Ddc, Display};
10//!
11//! for mut display in Display::enumerate() {
12//!     display.update_capabilities().unwrap();
13//!     println!("{:?} {}: {:?} {:?}",
14//!         display.info.backend, display.info.id,
15//!         display.info.manufacturer_id, display.info.model_name
16//!     );
17//!     if let Some(feature) = display.info.mccs_database.get(0xdf) {
18//!         let value = display.handle.get_vcp_feature(feature.code).unwrap();
19//!         println!("{}: {:?}", feature.name.as_ref().unwrap(), value);
20//!     }
21//! }
22//! ```
23
24use std::{io, fmt, str};
25use std::iter::FromIterator;
26use anyhow::{Error, Context};
27use ddc::Edid;
28use log::{warn, trace};
29
30pub use ddc::{Ddc, DdcTable, DdcHost, FeatureCode, VcpValue, VcpValueType, TimingMessage};
31
32/// Identifying information about an attached display.
33///
34/// Not all information will be available, particularly on backends like
35/// WinAPI that do not support EDID.
36//#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[derive(Clone, Debug)]
38pub struct DisplayInfo {
39	/// Identifies the backend or driver used to communicate with the display.
40	pub backend: Backend,
41	/// A unique identifier for the display, format is specific to the backend.
42	pub id: String,
43	/// A three-character identifier of the manufacturer of the display.
44	pub manufacturer_id: Option<String>,
45	/// A number that identifies the product model.
46	pub model_id: Option<u16>,
47	/// The version and revision of the product.
48	pub version: Option<(u8, u8)>,
49	/// Serial number of the device
50	pub serial: Option<u32>,
51	/// Year the display was manufactured.
52	pub manufacture_year: Option<u8>,
53	/// Week the display was manufactured.
54	pub manufacture_week: Option<u8>,
55	/// The model name of the display.
56	pub model_name: Option<String>,
57	/// Human-readable serial number of the device.
58	pub serial_number: Option<String>,
59	/// Raw EDID data provided by the display.
60	pub edid_data: Option<Vec<u8>>,
61	/// MCCS VCP version code.
62	pub mccs_version: Option<mccs::Version>,
63	/// MCCS VCP feature information.
64	pub mccs_database: mccs_db::Database,
65}
66
67impl fmt::Display for DisplayInfo {
68	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69		write!(f, "{}:{}", self.backend, self.id)?;
70
71		if let Some(s) = &self.manufacturer_id {
72			write!(f, " {}", s)?;
73		}
74
75		if let Some(s) = &self.model_name {
76			write!(f, " {}", s)?;
77		} else if let Some(s) = &self.model_id {
78			write!(f, " {}", s)?;
79		}
80
81		Ok(())
82	}
83}
84
85impl DisplayInfo {
86	/// Create an empty `DisplayInfo`.
87	pub fn new(backend: Backend, id: String) -> Self {
88		DisplayInfo {
89			backend,
90			id,
91			manufacturer_id: None,
92			model_id: None,
93			version: None,
94			serial: None,
95			manufacture_year: None,
96			manufacture_week: None,
97			model_name: None,
98			serial_number: None,
99			edid_data: None,
100			mccs_version: None,
101			mccs_database: Default::default(),
102		}
103	}
104
105	/// Creates a new `DisplayInfo` from unparsed EDID data.
106	///
107	/// May fail to parse the EDID data.
108	pub fn from_edid(backend: Backend, id: String, edid_data: Vec<u8>) -> io::Result<Self> {
109		trace!("DisplayInfo::from_edid({:?}, {})", backend, id);
110
111		let edid = edid::parse(&edid_data).to_result()
112			.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
113
114		let mut model_name = None;
115		let mut serial_number = None;
116
117		for desc in edid.descriptors {
118			match desc {
119				edid::Descriptor::SerialNumber(serial) => serial_number = Some(serial),
120				edid::Descriptor::ProductName(model) => model_name = Some(model),
121				_ => (),
122			}
123		}
124
125		Ok(DisplayInfo {
126			backend,
127			id,
128			edid_data: Some(edid_data),
129			manufacturer_id: Some(String::from_iter(edid.header.vendor.iter())),
130			model_id: Some(edid.header.product),
131			serial: Some(edid.header.serial),
132			version: Some((edid.header.version, edid.header.revision)),
133			manufacture_year: Some(edid.header.year),
134			manufacture_week: Some(edid.header.week),
135			model_name,
136			serial_number,
137			mccs_version: None,
138			mccs_database: Default::default(),
139		})
140	}
141
142	/// Create a new `DisplayInfo` from parsed capabilities.
143	pub fn from_capabilities(backend: Backend, id: String, caps: &mccs::Capabilities) -> Self {
144		trace!("DisplayInfo::from_capabilities({:?}, {})", backend, id);
145
146		let edid = caps.edid.clone().map(|edid|
147			Self::from_edid(backend, id.clone(), edid)
148		).transpose();
149
150		let mut res = DisplayInfo {
151			backend,
152			id,
153			model_name: caps.model.clone(),
154			mccs_version: caps.mccs_version.clone(),
155			edid_data: caps.edid.clone(),
156			// TODO: VDIF
157			serial_number: None,
158			manufacturer_id: None,
159			model_id: None,
160			serial: None,
161			version: None,
162			manufacture_year: None,
163			manufacture_week: None,
164			mccs_database: Default::default(),
165		};
166
167		if let Some(ver) = res.mccs_version.as_ref() {
168			res.mccs_database = mccs_db::Database::from_version(ver);
169			res.mccs_database.apply_capabilities(caps);
170		}
171
172		match edid {
173			Ok(Some(edid)) => {
174				// TODO: should this be edid.update_from(&res) instead?
175				res.update_from(&edid);
176			},
177			Ok(None) => (),
178			Err(e) => {
179				warn!("Failed to parse edid from caps of {}: {}", res, e);
180			},
181		}
182
183		res
184	}
185
186	/// Merge in any missing information from another `DisplayInfo`
187	pub fn update_from(&mut self, info: &DisplayInfo) {
188		if self.manufacturer_id.is_none() {
189			self.manufacturer_id = info.manufacturer_id.clone()
190		}
191
192		if self.model_id.is_none() {
193			self.model_id = info.model_id.clone()
194		}
195
196		if self.version.is_none() {
197			self.version = info.version.clone()
198		}
199
200		if self.serial.is_none() {
201			self.serial = info.serial.clone()
202		}
203
204		if self.manufacture_year.is_none() {
205			self.manufacture_year  = info.manufacture_year.clone()
206		}
207
208		if self.manufacture_week.is_none() {
209			self.manufacture_week = info.manufacture_week.clone()
210		}
211
212		if self.model_name.is_none() {
213			self.model_name = info.model_name.clone()
214		}
215
216		if self.serial_number.is_none() {
217			self.serial_number = info.serial_number.clone()
218		}
219
220		if self.edid_data.is_none() {
221			self.edid_data = info.edid_data.clone()
222		}
223
224		if self.mccs_version.is_none() {
225			self.mccs_version = info.mccs_version.clone()
226		}
227
228		if self.mccs_database.get(0xdf).is_none() {
229			if info.mccs_version.is_some() {
230				self.mccs_version = info.mccs_version.clone()
231			}
232			self.mccs_database = info.mccs_database.clone()
233		}
234	}
235
236	/// Populate information from a DDC connection.
237	///
238	/// This will read the VCP Version (`0xdf`) and fill in the `mccs_database`.
239	/// This data will be incomplete compared to being filled in from a capability
240	/// string.
241	pub fn update_from_ddc<D: Ddc>(&mut self, ddc: &mut D) -> Result<(), D::Error> {
242		if self.mccs_version.is_none() {
243			trace!("DisplayInfo::update_from_ddc");
244
245			let version = ddc.get_vcp_feature(0xdf)?;
246			let version = mccs::Version::new(version.sh, version.sl);
247			if version != mccs::Version::default() {
248				self.mccs_version = Some(version);
249				self.mccs_database = mccs_db::Database::from_version(&version);
250			}
251		}
252
253		Ok(())
254	}
255}
256
257/// A query to filter out matching displays.
258///
259/// Most comparisons must match the full string.
260pub enum Query {
261	/// Matches any display
262	Any,
263	/// Matches a display on the given backend
264	Backend(Backend),
265	/// Matches a display with the specified ID
266	Id(String),
267	/// Matches a display with the specified manufacturer
268	ManufacturerId(String),
269	/// Matches a display with the specified model name
270	ModelName(String),
271	/// Matches a display with the specified serial number
272	SerialNumber(String),
273	/// At least one of the queries must match
274	Or(Vec<Query>),
275	/// All of the queries must match
276	And(Vec<Query>),
277}
278
279impl Query {
280	/// Queries whether the provided display info is a match.
281	pub fn matches(&self, info: &DisplayInfo) -> bool {
282		match *self {
283			Query::Any => true,
284			Query::Backend(backend) => info.backend == backend,
285			Query::Id(ref id) => &info.id == id,
286			Query::ManufacturerId(ref id) => info.manufacturer_id.as_ref() == Some(id),
287			Query::ModelName(ref model) => info.model_name.as_ref() == Some(model),
288			Query::SerialNumber(ref serial) => info.serial_number.as_ref() == Some(serial),
289			Query::Or(ref query) => query.iter().any(|q| q.matches(info)),
290			Query::And(ref query) => query.iter().all(|q| q.matches(info)),
291		}
292	}
293}
294
295/// Identifies the backend driver used to communicate with a display.
296#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
297pub enum Backend {
298	/// Linux i2c-dev driver
299	I2cDevice,
300	/// Windows Monitor Configuration API
301	WinApi,
302	/// NVIDIA NVAPI driver
303	Nvapi,
304	/// MacOS APIs
305	MacOS,
306}
307
308impl fmt::Display for Backend {
309	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310		write!(f, "{}", match *self {
311			Backend::I2cDevice => "i2c-dev",
312			Backend::WinApi => "winapi",
313			Backend::Nvapi => "nvapi",
314			Backend::MacOS => "macos",
315		})
316	}
317}
318
319impl str::FromStr for Backend {
320	type Err = ();
321
322	fn from_str(s: &str) -> Result<Self, Self::Err> {
323		Ok(match s {
324			"i2c-dev" => Backend::I2cDevice,
325			"winapi" => Backend::WinApi,
326			"nvapi" => Backend::Nvapi,
327			"macos" => Backend::MacOS,
328			_ => return Err(()),
329		})
330	}
331}
332
333impl Backend {
334	/// Enumerate the possible backends.
335	///
336	/// Backends not supported for the current platform will be excluded.
337	pub fn values() -> &'static [Backend] {
338		&[
339			#[cfg(feature = "has-ddc-i2c")]
340			Backend::I2cDevice,
341			#[cfg(feature = "has-ddc-winapi")]
342			Backend::WinApi,
343			#[cfg(feature = "has-nvapi")]
344			Backend::Nvapi,
345			#[cfg(feature = "has-ddc-macos")]
346			Backend::MacOS,
347		]
348	}
349}
350
351/// An active handle to a connected display.
352pub struct Display {
353	/// The inner communication handle used for DDC commands.
354	pub handle: Handle,
355	/// Information about the connected display.
356	pub info: DisplayInfo,
357	filled_caps: bool,
358}
359
360impl Display {
361	/// Create a new display from the specified handle.
362	pub fn new(handle: Handle, info: DisplayInfo) -> Self {
363		Display {
364			handle,
365			info,
366			filled_caps: false,
367		}
368	}
369
370	/// Enumerate all detected displays.
371	pub fn enumerate() -> Vec<Self> {
372		let mut displays = Vec::new();
373
374		#[cfg(feature = "has-ddc-i2c")]
375		{
376			use std::os::unix::fs::MetadataExt;
377
378			if let Ok(devs) = ddc_i2c::I2cDeviceEnumerator::new() {
379				displays.extend(devs
380					.map(|mut ddc| -> Result<_, Error> {
381						let id = ddc.inner_ref().inner_ref().metadata().map(|meta| meta.rdev()).unwrap_or(Default::default());
382						let mut edid = vec![0u8; 0x100];
383						ddc.read_edid(0, &mut edid)
384							.with_context(|| format!("Failed to read EDID for i2c-{}", id))?;
385						let info = DisplayInfo::from_edid(Backend::I2cDevice, id.to_string(), edid)
386							.with_context(|| format!("Failed to parse EDID for i2c-{}", id))?;
387						Ok(Display::new(
388							Handle::I2cDevice(ddc),
389							info,
390						))
391					}).filter_map(|d| match d {
392						Ok(v) => Some(v),
393						Err(e) => {
394							warn!("Failed to enumerate a display: {}", e);
395							None
396						},
397					})
398				)
399			}
400		}
401
402		#[cfg(feature = "has-ddc-winapi")]
403		{
404			if let Ok(devs) = ddc_winapi::Monitor::enumerate() {
405				displays.extend(devs.into_iter()
406					.map(|ddc| {
407						let info = DisplayInfo::new(Backend::WinApi, ddc.description());
408						Display::new(
409							Handle::WinApi(ddc),
410							info,
411						)
412					})
413				)
414			}
415		}
416
417		#[cfg(feature = "has-ddc-macos")]
418		{
419			if let Ok(devs) = ddc_macos::Monitor::enumerate() {
420				displays.extend(devs.into_iter()
421					.map(|ddc| {
422						let info = ddc
423							.edid()
424							.and_then( |edid| {
425								DisplayInfo::from_edid(Backend::MacOS, ddc.description(), edid).ok()
426							})
427							.unwrap_or(DisplayInfo::new(Backend::MacOS, ddc.description()));
428						Display::new(Handle::MacOS(ddc), info)
429					})
430				)
431			}
432		}
433
434		#[cfg(feature = "has-nvapi")]
435		{
436			use std::rc::Rc;
437
438			if let Ok(_) = nvapi::initialize() {
439				if let Ok(gpus) = nvapi::PhysicalGpu::enumerate() {
440					for gpu in gpus {
441						let gpu = Rc::new(gpu);
442						let id_prefix = gpu.short_name().unwrap_or("NVAPI".into());
443						if let Ok(ids) = gpu.display_ids_connected(nvapi::ConnectedIdsFlags::empty()) {
444							for id in ids {
445								let mut i2c = nvapi::I2c::new(gpu.clone(), id.display_id); // TODO: it says mask, is it actually `1<<display_id` instead?
446								i2c.set_port(None, true); // TODO: port=Some(1) instead? docs seem to indicate it's not optional, but the one example I can find keeps it unset so...
447
448								// hack around broken nvidia drivers, the register argument doesn't seem to work at all so write the edid eeprom offset here first
449								i2c.set_address(0x50);
450								let _ = i2c.nvapi_write(&[], &[0]);
451
452								let mut ddc = ddc_i2c::I2cDdc::new(i2c);
453
454								let idstr = format!("{}/{}:{:?}", id_prefix, id.display_id, id.connector);
455								let mut edid = vec![0u8; 0x80]; // 0x100
456								let res = ddc.read_edid(0, &mut edid)
457									.context("Failed to read EDID")
458									.and_then(|_| DisplayInfo::from_edid(Backend::Nvapi, idstr, edid)
459										.context("Failed to parse EDID")
460									).map(|info| Display::new(
461										Handle::Nvapi(ddc),
462										info,
463									));
464								match res {
465									Ok(ddc) =>
466										displays.push(ddc),
467									Err(e) =>
468										warn!("Failed to enumerate NVAPI display {}/{}:{:?}: {}", id_prefix, id.display_id, id.connector, e),
469								}
470							}
471						}
472					}
473				}
474			}
475		}
476
477		displays
478	}
479
480	/// Updates the display info with data retrieved from the device's
481	/// reported capabilities.
482	pub fn update_capabilities(&mut self) -> Result<(), Error> {
483		if !self.filled_caps {
484			let (backend, id) = (self.info.backend, self.info.id.clone());
485			let caps = self.handle.capabilities()?;
486			let info = DisplayInfo::from_capabilities(
487				backend, id,
488				&caps,
489			);
490			if info.mccs_version.is_some() {
491				self.info.mccs_database = Default::default();
492			}
493			self.info.update_from(&info);
494		}
495
496		Ok(())
497	}
498
499	/// Update some display info.
500	pub fn update_from_ddc(&mut self) -> Result<(), Error> {
501		self.info.update_from_ddc(&mut self.handle)
502	}
503}
504
505/// A handle allowing communication with a display
506pub enum Handle {
507	#[doc(hidden)]
508	#[cfg(feature = "has-ddc-i2c")]
509	I2cDevice(ddc_i2c::I2cDeviceDdc),
510	#[doc(hidden)]
511	#[cfg(feature = "has-ddc-winapi")]
512	WinApi(ddc_winapi::Monitor),
513	#[doc(hidden)]
514	#[cfg(feature = "has-ddc-macos")]
515	MacOS(ddc_macos::Monitor),
516	#[doc(hidden)]
517	#[cfg(feature = "has-nvapi")]
518	Nvapi(ddc_i2c::I2cDdc<nvapi::I2c<::std::rc::Rc<nvapi::PhysicalGpu>>>),
519}
520
521impl Handle {
522	/// Request and parse the display's capabilities string.
523	pub fn capabilities(&mut self) -> Result<mccs::Capabilities, Error> {
524		mccs_caps::parse_capabilities(
525			&self.capabilities_string().context("Failed to read capabilities string")?
526		).context("Failed to parse MCCS capabilities")
527	}
528}
529
530impl ddc::DdcHost for Handle {
531	type Error = Error;
532
533	fn sleep(&mut self) {
534		match *self {
535			#[cfg(feature = "has-ddc-i2c")]
536			Handle::I2cDevice(ref mut i2c) => i2c.sleep(),
537			#[cfg(feature = "has-ddc-winapi")]
538			Handle::WinApi(ref mut monitor) => monitor.sleep(),
539			#[cfg(feature = "has-ddc-macos")]
540			Handle::MacOS(ref mut monitor) => monitor.sleep(),
541			#[cfg(feature = "has-nvapi")]
542			Handle::Nvapi(ref mut i2c) => i2c.sleep(),
543		}
544	}
545}
546
547impl Ddc for Handle {
548	fn capabilities_string(&mut self) -> Result<Vec<u8>, Self::Error> {
549		match *self {
550			#[cfg(feature = "has-ddc-i2c")]
551			Handle::I2cDevice(ref mut i2c) => i2c.capabilities_string().map_err(From::from),
552			#[cfg(feature = "has-ddc-winapi")]
553			Handle::WinApi(ref mut monitor) => monitor.capabilities_string().map_err(From::from),
554			#[cfg(feature = "has-ddc-macos")]
555			Handle::MacOS(ref mut monitor) => monitor.capabilities_string().map_err(From::from),
556			#[cfg(feature = "has-nvapi")]
557			Handle::Nvapi(ref mut i2c) => i2c.capabilities_string().map_err(From::from),
558		}
559	}
560
561	fn get_vcp_feature(&mut self, code: FeatureCode) -> Result<VcpValue, Self::Error> {
562		match *self {
563			#[cfg(feature = "has-ddc-i2c")]
564			Handle::I2cDevice(ref mut i2c) => i2c.get_vcp_feature(code).map_err(From::from),
565			#[cfg(feature = "has-ddc-winapi")]
566			Handle::WinApi(ref mut monitor) => monitor.get_vcp_feature(code).map_err(From::from),
567			#[cfg(feature = "has-ddc-macos")]
568			Handle::MacOS(ref mut monitor) => monitor.get_vcp_feature(code).map_err(From::from),
569			#[cfg(feature = "has-nvapi")]
570			Handle::Nvapi(ref mut i2c) => i2c.get_vcp_feature(code).map_err(From::from),
571		}
572	}
573
574	fn set_vcp_feature(&mut self, code: FeatureCode, value: u16) -> Result<(), Self::Error> {
575		match *self {
576			#[cfg(feature = "has-ddc-i2c")]
577			Handle::I2cDevice(ref mut i2c) => i2c.set_vcp_feature(code, value).map_err(From::from),
578			#[cfg(feature = "has-ddc-winapi")]
579			Handle::WinApi(ref mut monitor) => monitor.set_vcp_feature(code, value).map_err(From::from),
580			#[cfg(feature = "has-ddc-macos")]
581			Handle::MacOS(ref mut monitor) => monitor.set_vcp_feature(code, value).map_err(From::from),
582			#[cfg(feature = "has-nvapi")]
583			Handle::Nvapi(ref mut i2c) => i2c.set_vcp_feature(code, value).map_err(From::from),
584		}
585	}
586
587	fn save_current_settings(&mut self) -> Result<(), Self::Error> {
588		match *self {
589			#[cfg(feature = "has-ddc-i2c")]
590			Handle::I2cDevice(ref mut i2c) => i2c.save_current_settings().map_err(From::from),
591			#[cfg(feature = "has-ddc-winapi")]
592			Handle::WinApi(ref mut monitor) => monitor.save_current_settings().map_err(From::from),
593			#[cfg(feature = "has-ddc-macos")]
594			Handle::MacOS(ref mut monitor) => monitor.save_current_settings().map_err(From::from),
595			#[cfg(feature = "has-nvapi")]
596			Handle::Nvapi(ref mut i2c) => i2c.save_current_settings().map_err(From::from),
597		}
598	}
599
600	fn get_timing_report(&mut self) -> Result<TimingMessage, Self::Error> {
601		match *self {
602			#[cfg(feature = "has-ddc-i2c")]
603			Handle::I2cDevice(ref mut i2c) => i2c.get_timing_report().map_err(From::from),
604			#[cfg(feature = "has-ddc-winapi")]
605			Handle::WinApi(ref mut monitor) => monitor.get_timing_report().map_err(From::from),
606			#[cfg(feature = "has-ddc-macos")]
607			Handle::MacOS(ref mut monitor) => monitor.get_timing_report().map_err(From::from),
608			#[cfg(feature = "has-nvapi")]
609			Handle::Nvapi(ref mut i2c) => i2c.get_timing_report().map_err(From::from),
610		}
611	}
612}
613
614impl DdcTable for Handle {
615	fn table_read(&mut self, code: FeatureCode) -> Result<Vec<u8>, Self::Error> {
616		match *self {
617			#[cfg(feature = "has-ddc-i2c")]
618			Handle::I2cDevice(ref mut i2c) => i2c.table_read(code).map_err(From::from),
619			#[cfg(feature = "has-ddc-macos")]
620			Handle::MacOS(ref mut i2c) => i2c.table_read(code).map_err(From::from),
621			#[cfg(feature = "has-ddc-winapi")]
622			Handle::WinApi(_) =>
623				Err(io::Error::new(io::ErrorKind::Other, "winapi does not support DDC tables").into()),
624			#[cfg(feature = "has-nvapi")]
625			Handle::Nvapi(ref mut i2c) => i2c.table_read(code).map_err(From::from),
626		}
627	}
628
629	fn table_write(&mut self, code: FeatureCode, offset: u16, value: &[u8]) -> Result<(), Self::Error> {
630		match *self {
631			#[cfg(feature = "has-ddc-i2c")]
632			Handle::I2cDevice(ref mut i2c) => i2c.table_write(code, offset, value).map_err(From::from),
633			#[cfg(feature = "has-ddc-macos")]
634			Handle::MacOS(ref mut i2c) => i2c.table_write(code, offset, value).map_err(From::from),
635			#[cfg(feature = "has-ddc-winapi")]
636			Handle::WinApi(_) =>
637				Err(io::Error::new(io::ErrorKind::Other, "winapi does not support DDC tables").into()),
638			#[cfg(feature = "has-nvapi")]
639			Handle::Nvapi(ref mut i2c) => i2c.table_write(code, offset, value).map_err(From::from),
640		}
641	}
642}