1#![deny(missing_docs)]
2#![doc(html_root_url = "http://docs.rs/ddc-hi-rs/0.4.1")]
3
4use 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#[derive(Clone, Debug)]
38pub struct DisplayInfo {
39 pub backend: Backend,
41 pub id: String,
43 pub manufacturer_id: Option<String>,
45 pub model_id: Option<u16>,
47 pub version: Option<(u8, u8)>,
49 pub serial: Option<u32>,
51 pub manufacture_year: Option<u8>,
53 pub manufacture_week: Option<u8>,
55 pub model_name: Option<String>,
57 pub serial_number: Option<String>,
59 pub edid_data: Option<Vec<u8>>,
61 pub mccs_version: Option<mccs::Version>,
63 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 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 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 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 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 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 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 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
257pub enum Query {
261 Any,
263 Backend(Backend),
265 Id(String),
267 ManufacturerId(String),
269 ModelName(String),
271 SerialNumber(String),
273 Or(Vec<Query>),
275 And(Vec<Query>),
277}
278
279impl Query {
280 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#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
297pub enum Backend {
298 I2cDevice,
300 WinApi,
302 Nvapi,
304 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 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
351pub struct Display {
353 pub handle: Handle,
355 pub info: DisplayInfo,
357 filled_caps: bool,
358}
359
360impl Display {
361 pub fn new(handle: Handle, info: DisplayInfo) -> Self {
363 Display {
364 handle,
365 info,
366 filled_caps: false,
367 }
368 }
369
370 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); i2c.set_port(None, true); 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]; 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 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 pub fn update_from_ddc(&mut self) -> Result<(), Error> {
501 self.info.update_from_ddc(&mut self.handle)
502 }
503}
504
505pub 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 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}