1use core::fmt;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum ConnectorKind {
21 Dsi,
23 Dpi,
25 Edp,
27 Lvds,
29 Hdmi,
31 DisplayPort,
33 Dvi,
35 Vga,
37 Composite,
39 Virtual,
41 Other,
43}
44
45impl ConnectorKind {
46 #[inline]
48 pub const fn is_internal_panel(self) -> bool {
49 matches!(
50 self,
51 ConnectorKind::Dsi | ConnectorKind::Dpi | ConnectorKind::Edp | ConnectorKind::Lvds
52 )
53 }
54
55 const fn rank(self) -> u8 {
61 match self {
62 ConnectorKind::Dsi | ConnectorKind::Dpi => 0,
63 ConnectorKind::Edp | ConnectorKind::Lvds => 1,
64 ConnectorKind::Hdmi | ConnectorKind::DisplayPort => 2,
65 ConnectorKind::Dvi => 3,
66 ConnectorKind::Vga => 4,
67 ConnectorKind::Composite => 5,
68 ConnectorKind::Other => 6,
69 ConnectorKind::Virtual => 7,
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub struct ModeInfo {
79 pub width: u16,
81 pub height: u16,
83 pub vrefresh: u32,
85 pub preferred: bool,
88}
89
90impl ModeInfo {
91 #[inline]
93 pub const fn area(&self) -> u64 {
94 self.width as u64 * self.height as u64
95 }
96}
97
98impl fmt::Display for ModeInfo {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 write!(f, "{}x{}@{}", self.width, self.height, self.vrefresh)
101 }
102}
103
104#[derive(Clone, Debug)]
106pub struct ConnectorInfo {
107 pub id: u32,
109 pub kind: ConnectorKind,
111 pub connected: bool,
113 pub modes: Vec<ModeInfo>,
115}
116
117impl ConnectorInfo {
118 #[inline]
120 pub fn is_usable(&self) -> bool {
121 self.connected && !self.modes.is_empty()
122 }
123
124 fn largest(&self) -> Option<usize> {
126 self.modes
127 .iter()
128 .enumerate()
129 .max_by_key(|(_, m)| (m.area(), m.vrefresh))
130 .map(|(i, _)| i)
131 }
132
133 fn preferred(&self) -> Option<usize> {
135 self.modes.iter().position(|m| m.preferred)
136 }
137}
138
139#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
141pub enum OutputPreference {
142 #[default]
144 Auto,
145 Kind(ConnectorKind),
147 Id(u32),
149}
150
151#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
153pub enum ModePreference {
154 #[default]
157 Preferred,
158 Exact {
160 width: u16,
162 height: u16,
164 },
165 ExactRefresh {
167 width: u16,
169 height: u16,
171 vrefresh: u32,
173 },
174 Largest,
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub struct Selection {
181 pub connector: usize,
183 pub mode: usize,
185 pub fell_back: bool,
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195#[non_exhaustive]
196pub enum SelectionError {
197 NoConnectors,
199
200 NothingConnected,
202
203 RequestedIdUnavailable(u32),
205
206 RequestedKindUnavailable(ConnectorKind),
208}
209
210impl core::fmt::Display for SelectionError {
211 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212 match self {
213 Self::NoConnectors => f.write_str("the device has no connectors"),
214 Self::NothingConnected => f.write_str("no connector has a display attached"),
215 Self::RequestedIdUnavailable(id) => {
216 write!(f, "connector {id} was requested but is not connected")
217 }
218 Self::RequestedKindUnavailable(kind) => write!(f, "no usable {kind:?} connector"),
219 }
220 }
221}
222
223impl core::error::Error for SelectionError {}
224
225pub fn select(
232 connectors: &[ConnectorInfo],
233 output: OutputPreference,
234 mode: ModePreference,
235) -> Result<Selection, SelectionError> {
236 if connectors.is_empty() {
237 return Err(SelectionError::NoConnectors);
238 }
239
240 let index = select_connector(connectors, output)?;
241 let (mode, fell_back) = select_mode(&connectors[index], mode);
242
243 Ok(Selection {
244 connector: index,
245 mode,
246 fell_back,
247 })
248}
249
250pub fn select_connector(
252 connectors: &[ConnectorInfo],
253 preference: OutputPreference,
254) -> Result<usize, SelectionError> {
255 let usable = |i: &usize| connectors[*i].is_usable();
256
257 match preference {
258 OutputPreference::Id(id) => (0..connectors.len())
259 .filter(usable)
260 .find(|&i| connectors[i].id == id)
261 .ok_or(SelectionError::RequestedIdUnavailable(id)),
262
263 OutputPreference::Kind(kind) => (0..connectors.len())
264 .filter(usable)
265 .find(|&i| connectors[i].kind == kind)
266 .ok_or(SelectionError::RequestedKindUnavailable(kind)),
267
268 OutputPreference::Auto => (0..connectors.len())
269 .filter(usable)
270 .min_by_key(|&i| {
272 let c = &connectors[i];
273 let area = c
274 .preferred()
275 .or_else(|| c.largest())
276 .map(|m| c.modes[m].area())
277 .unwrap_or(0);
278 (c.kind.rank(), core::cmp::Reverse(area), i)
279 })
280 .ok_or(SelectionError::NothingConnected),
281 }
282}
283
284pub fn select_mode(connector: &ConnectorInfo, preference: ModePreference) -> (usize, bool) {
286 let exact = |width: u16, height: u16, vrefresh: Option<u32>| {
287 connector
288 .modes
289 .iter()
290 .enumerate()
291 .filter(|(_, m)| {
294 m.width == width && m.height == height && vrefresh.is_none_or(|hz| m.vrefresh == hz)
295 })
296 .max_by_key(|(_, m)| m.vrefresh)
297 .map(|(i, _)| i)
298 };
299
300 let requested = match preference {
301 ModePreference::Preferred => connector.preferred(),
302 ModePreference::Exact { width, height } => exact(width, height, None),
303 ModePreference::ExactRefresh {
304 width,
305 height,
306 vrefresh,
307 } => exact(width, height, Some(vrefresh)).or_else(|| exact(width, height, None)),
308 ModePreference::Largest => connector.largest(),
309 };
310
311 match requested {
312 Some(i) => (i, false),
313 None => (
316 connector
317 .preferred()
318 .or_else(|| connector.largest())
319 .unwrap_or(0),
320 true,
321 ),
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 fn mode(width: u16, height: u16, vrefresh: u32, preferred: bool) -> ModeInfo {
330 ModeInfo {
331 width,
332 height,
333 vrefresh,
334 preferred,
335 }
336 }
337
338 fn connector(id: u32, kind: ConnectorKind, modes: Vec<ModeInfo>) -> ConnectorInfo {
339 ConnectorInfo {
340 id,
341 kind,
342 connected: true,
343 modes,
344 }
345 }
346
347 fn disconnected(id: u32, kind: ConnectorKind) -> ConnectorInfo {
348 ConnectorInfo {
349 id,
350 kind,
351 connected: false,
352 modes: vec![mode(1920, 1080, 60, true)],
353 }
354 }
355
356 fn pi_with_debug_monitor() -> Vec<ConnectorInfo> {
358 vec![
359 connector(
360 32,
361 ConnectorKind::Hdmi,
362 vec![mode(1920, 1080, 60, true), mode(1280, 720, 60, false)],
363 ),
364 connector(45, ConnectorKind::Dsi, vec![mode(800, 480, 60, true)]),
365 ]
366 }
367
368 #[test]
369 fn no_connectors_is_an_error() {
370 assert_eq!(
371 select(&[], OutputPreference::Auto, ModePreference::Preferred),
372 Err(SelectionError::NoConnectors)
373 );
374 }
375
376 #[test]
377 fn nothing_plugged_in_is_an_error() {
378 let connectors = vec![
379 disconnected(32, ConnectorKind::Hdmi),
380 disconnected(45, ConnectorKind::Dsi),
381 ];
382 assert_eq!(
383 select_connector(&connectors, OutputPreference::Auto),
384 Err(SelectionError::NothingConnected)
385 );
386 }
387
388 #[test]
389 fn a_connector_with_no_modes_is_not_usable() {
390 let connectors = vec![connector(32, ConnectorKind::Hdmi, vec![])];
392 assert_eq!(
393 select_connector(&connectors, OutputPreference::Auto),
394 Err(SelectionError::NothingConnected)
395 );
396 }
397
398 #[test]
399 fn the_built_in_panel_wins_over_a_debug_monitor() {
400 let connectors = pi_with_debug_monitor();
404 let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
405 assert_eq!(connectors[i].kind, ConnectorKind::Dsi);
406 }
407
408 #[test]
409 fn hdmi_is_used_when_there_is_no_panel() {
410 let connectors = vec![connector(
411 32,
412 ConnectorKind::Hdmi,
413 vec![mode(1920, 1080, 60, true)],
414 )];
415 let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
416 assert_eq!(connectors[i].kind, ConnectorKind::Hdmi);
417 }
418
419 #[test]
420 fn a_virtual_output_loses_to_anything_real() {
421 let connectors = vec![
422 connector(1, ConnectorKind::Virtual, vec![mode(1024, 768, 60, true)]),
423 connector(2, ConnectorKind::Vga, vec![mode(640, 480, 60, true)]),
424 ];
425 let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
426 assert_eq!(connectors[i].kind, ConnectorKind::Vga);
427 }
428
429 #[test]
430 fn equal_rank_prefers_the_larger_display() {
431 let connectors = vec![
432 connector(1, ConnectorKind::Hdmi, vec![mode(1280, 720, 60, true)]),
433 connector(
434 2,
435 ConnectorKind::DisplayPort,
436 vec![mode(2560, 1440, 60, true)],
437 ),
438 ];
439 let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
440 assert_eq!(connectors[i].id, 2);
441 }
442
443 #[test]
444 fn an_explicit_id_overrides_the_ranking() {
445 let connectors = pi_with_debug_monitor();
446 let i = select_connector(&connectors, OutputPreference::Id(32)).expect("a connector");
447 assert_eq!(connectors[i].kind, ConnectorKind::Hdmi);
448 }
449
450 #[test]
451 fn an_explicit_id_that_is_absent_is_an_error_not_a_fallback() {
452 let connectors = pi_with_debug_monitor();
455 assert_eq!(
456 select_connector(&connectors, OutputPreference::Id(99)),
457 Err(SelectionError::RequestedIdUnavailable(99))
458 );
459 }
460
461 #[test]
462 fn an_explicit_kind_that_is_absent_is_an_error() {
463 let connectors = pi_with_debug_monitor();
464 assert_eq!(
465 select_connector(&connectors, OutputPreference::Kind(ConnectorKind::Vga)),
466 Err(SelectionError::RequestedKindUnavailable(ConnectorKind::Vga))
467 );
468 }
469
470 #[test]
471 fn a_disconnected_requested_id_is_rejected() {
472 let connectors = vec![disconnected(32, ConnectorKind::Hdmi)];
473 assert_eq!(
474 select_connector(&connectors, OutputPreference::Id(32)),
475 Err(SelectionError::RequestedIdUnavailable(32))
476 );
477 }
478
479 #[test]
480 fn preferred_mode_is_taken_even_when_it_is_not_first() {
481 let c = connector(
482 1,
483 ConnectorKind::Dsi,
484 vec![
485 mode(640, 480, 60, false),
486 mode(800, 480, 60, true),
487 mode(1024, 600, 60, false),
488 ],
489 );
490 let (i, fell_back) = select_mode(&c, ModePreference::Preferred);
491 assert_eq!(i, 1);
492 assert!(!fell_back);
493 }
494
495 #[test]
496 fn no_preferred_flag_falls_back_to_the_largest_not_the_first() {
497 let c = connector(
499 1,
500 ConnectorKind::Hdmi,
501 vec![
502 mode(640, 480, 60, false),
503 mode(1920, 1080, 60, false),
504 mode(1280, 720, 60, false),
505 ],
506 );
507 let (i, fell_back) = select_mode(&c, ModePreference::Preferred);
508 assert_eq!(c.modes[i], mode(1920, 1080, 60, false));
509 assert!(fell_back);
510 }
511
512 #[test]
513 fn exact_mode_is_found() {
514 let c = connector(
515 1,
516 ConnectorKind::Hdmi,
517 vec![mode(1920, 1080, 60, true), mode(1280, 720, 60, false)],
518 );
519 let (i, fell_back) = select_mode(
520 &c,
521 ModePreference::Exact {
522 width: 1280,
523 height: 720,
524 },
525 );
526 assert_eq!(c.modes[i], mode(1280, 720, 60, false));
527 assert!(!fell_back);
528 }
529
530 #[test]
531 fn exact_mode_takes_the_fastest_refresh_available() {
532 let c = connector(
533 1,
534 ConnectorKind::Hdmi,
535 vec![mode(1920, 1080, 50, false), mode(1920, 1080, 60, false)],
536 );
537 let (i, _) = select_mode(
538 &c,
539 ModePreference::Exact {
540 width: 1920,
541 height: 1080,
542 },
543 );
544 assert_eq!(c.modes[i].vrefresh, 60);
545 }
546
547 #[test]
548 fn an_unavailable_exact_mode_falls_back_and_says_so() {
549 let c = connector(1, ConnectorKind::Dsi, vec![mode(800, 480, 60, true)]);
550 let (i, fell_back) = select_mode(
551 &c,
552 ModePreference::Exact {
553 width: 1920,
554 height: 1080,
555 },
556 );
557 assert_eq!(c.modes[i], mode(800, 480, 60, true));
558 assert!(
559 fell_back,
560 "a silently ignored configured mode must be flagged"
561 );
562 }
563
564 #[test]
565 fn exact_refresh_relaxes_to_the_same_resolution_before_giving_up() {
566 let c = connector(
567 1,
568 ConnectorKind::Hdmi,
569 vec![mode(1920, 1080, 60, false), mode(1280, 720, 60, true)],
570 );
571 let (i, fell_back) = select_mode(
572 &c,
573 ModePreference::ExactRefresh {
574 width: 1920,
575 height: 1080,
576 vrefresh: 144,
577 },
578 );
579 assert_eq!(c.modes[i], mode(1920, 1080, 60, false));
580 assert!(
581 !fell_back,
582 "same resolution at another rate is not a fallback"
583 );
584 }
585
586 #[test]
587 fn largest_ignores_the_preferred_flag() {
588 let c = connector(
589 1,
590 ConnectorKind::Hdmi,
591 vec![mode(1280, 720, 60, true), mode(1920, 1080, 60, false)],
592 );
593 let (i, _) = select_mode(&c, ModePreference::Largest);
594 assert_eq!(c.modes[i], mode(1920, 1080, 60, false));
595 }
596
597 #[test]
598 fn selection_never_returns_an_out_of_range_index() {
599 let connectors = pi_with_debug_monitor();
602 let prefs = [
603 ModePreference::Preferred,
604 ModePreference::Largest,
605 ModePreference::Exact {
606 width: 3840,
607 height: 2160,
608 },
609 ModePreference::ExactRefresh {
610 width: 800,
611 height: 480,
612 vrefresh: 75,
613 },
614 ];
615 for pref in prefs {
616 let s = select(&connectors, OutputPreference::Auto, pref).expect("a selection");
617 assert!(s.connector < connectors.len());
618 assert!(s.mode < connectors[s.connector].modes.len());
619 }
620 }
621}