use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ConnectorKind {
Dsi,
Dpi,
Edp,
Lvds,
Hdmi,
DisplayPort,
Dvi,
Vga,
Composite,
Virtual,
Other,
}
impl ConnectorKind {
#[inline]
pub const fn is_internal_panel(self) -> bool {
matches!(
self,
ConnectorKind::Dsi | ConnectorKind::Dpi | ConnectorKind::Edp | ConnectorKind::Lvds
)
}
const fn rank(self) -> u8 {
match self {
ConnectorKind::Dsi | ConnectorKind::Dpi => 0,
ConnectorKind::Edp | ConnectorKind::Lvds => 1,
ConnectorKind::Hdmi | ConnectorKind::DisplayPort => 2,
ConnectorKind::Dvi => 3,
ConnectorKind::Vga => 4,
ConnectorKind::Composite => 5,
ConnectorKind::Other => 6,
ConnectorKind::Virtual => 7,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModeInfo {
pub width: u16,
pub height: u16,
pub vrefresh: u32,
pub preferred: bool,
}
impl ModeInfo {
#[inline]
pub const fn area(&self) -> u64 {
self.width as u64 * self.height as u64
}
}
impl fmt::Display for ModeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}x{}@{}", self.width, self.height, self.vrefresh)
}
}
#[derive(Clone, Debug)]
pub struct ConnectorInfo {
pub id: u32,
pub kind: ConnectorKind,
pub connected: bool,
pub modes: Vec<ModeInfo>,
}
impl ConnectorInfo {
#[inline]
pub fn is_usable(&self) -> bool {
self.connected && !self.modes.is_empty()
}
fn largest(&self) -> Option<usize> {
self.modes
.iter()
.enumerate()
.max_by_key(|(_, m)| (m.area(), m.vrefresh))
.map(|(i, _)| i)
}
fn preferred(&self) -> Option<usize> {
self.modes.iter().position(|m| m.preferred)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OutputPreference {
#[default]
Auto,
Kind(ConnectorKind),
Id(u32),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ModePreference {
#[default]
Preferred,
Exact {
width: u16,
height: u16,
},
ExactRefresh {
width: u16,
height: u16,
vrefresh: u32,
},
Largest,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Selection {
pub connector: usize,
pub mode: usize,
pub fell_back: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SelectionError {
#[error("the device has no connectors")]
NoConnectors,
#[error("no connector has a display attached")]
NothingConnected,
#[error("connector {0} was requested but is not connected")]
RequestedIdUnavailable(u32),
#[error("no usable {0:?} connector")]
RequestedKindUnavailable(ConnectorKind),
}
pub fn select(
connectors: &[ConnectorInfo],
output: OutputPreference,
mode: ModePreference,
) -> Result<Selection, SelectionError> {
if connectors.is_empty() {
return Err(SelectionError::NoConnectors);
}
let index = select_connector(connectors, output)?;
let (mode, fell_back) = select_mode(&connectors[index], mode);
Ok(Selection {
connector: index,
mode,
fell_back,
})
}
pub fn select_connector(
connectors: &[ConnectorInfo],
preference: OutputPreference,
) -> Result<usize, SelectionError> {
let usable = |i: &usize| connectors[*i].is_usable();
match preference {
OutputPreference::Id(id) => (0..connectors.len())
.filter(usable)
.find(|&i| connectors[i].id == id)
.ok_or(SelectionError::RequestedIdUnavailable(id)),
OutputPreference::Kind(kind) => (0..connectors.len())
.filter(usable)
.find(|&i| connectors[i].kind == kind)
.ok_or(SelectionError::RequestedKindUnavailable(kind)),
OutputPreference::Auto => (0..connectors.len())
.filter(usable)
.min_by_key(|&i| {
let c = &connectors[i];
let area = c
.preferred()
.or_else(|| c.largest())
.map(|m| c.modes[m].area())
.unwrap_or(0);
(c.kind.rank(), core::cmp::Reverse(area), i)
})
.ok_or(SelectionError::NothingConnected),
}
}
pub fn select_mode(connector: &ConnectorInfo, preference: ModePreference) -> (usize, bool) {
let exact = |width: u16, height: u16, vrefresh: Option<u32>| {
connector
.modes
.iter()
.enumerate()
.filter(|(_, m)| {
m.width == width && m.height == height && vrefresh.is_none_or(|hz| m.vrefresh == hz)
})
.max_by_key(|(_, m)| m.vrefresh)
.map(|(i, _)| i)
};
let requested = match preference {
ModePreference::Preferred => connector.preferred(),
ModePreference::Exact { width, height } => exact(width, height, None),
ModePreference::ExactRefresh {
width,
height,
vrefresh,
} => exact(width, height, Some(vrefresh)).or_else(|| exact(width, height, None)),
ModePreference::Largest => connector.largest(),
};
match requested {
Some(i) => (i, false),
None => (
connector
.preferred()
.or_else(|| connector.largest())
.unwrap_or(0),
true,
),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mode(width: u16, height: u16, vrefresh: u32, preferred: bool) -> ModeInfo {
ModeInfo {
width,
height,
vrefresh,
preferred,
}
}
fn connector(id: u32, kind: ConnectorKind, modes: Vec<ModeInfo>) -> ConnectorInfo {
ConnectorInfo {
id,
kind,
connected: true,
modes,
}
}
fn disconnected(id: u32, kind: ConnectorKind) -> ConnectorInfo {
ConnectorInfo {
id,
kind,
connected: false,
modes: vec![mode(1920, 1080, 60, true)],
}
}
fn pi_with_debug_monitor() -> Vec<ConnectorInfo> {
vec![
connector(
32,
ConnectorKind::Hdmi,
vec![mode(1920, 1080, 60, true), mode(1280, 720, 60, false)],
),
connector(45, ConnectorKind::Dsi, vec![mode(800, 480, 60, true)]),
]
}
#[test]
fn no_connectors_is_an_error() {
assert_eq!(
select(&[], OutputPreference::Auto, ModePreference::Preferred),
Err(SelectionError::NoConnectors)
);
}
#[test]
fn nothing_plugged_in_is_an_error() {
let connectors = vec![
disconnected(32, ConnectorKind::Hdmi),
disconnected(45, ConnectorKind::Dsi),
];
assert_eq!(
select_connector(&connectors, OutputPreference::Auto),
Err(SelectionError::NothingConnected)
);
}
#[test]
fn a_connector_with_no_modes_is_not_usable() {
let connectors = vec![connector(32, ConnectorKind::Hdmi, vec![])];
assert_eq!(
select_connector(&connectors, OutputPreference::Auto),
Err(SelectionError::NothingConnected)
);
}
#[test]
fn the_built_in_panel_wins_over_a_debug_monitor() {
let connectors = pi_with_debug_monitor();
let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
assert_eq!(connectors[i].kind, ConnectorKind::Dsi);
}
#[test]
fn hdmi_is_used_when_there_is_no_panel() {
let connectors = vec![connector(
32,
ConnectorKind::Hdmi,
vec![mode(1920, 1080, 60, true)],
)];
let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
assert_eq!(connectors[i].kind, ConnectorKind::Hdmi);
}
#[test]
fn a_virtual_output_loses_to_anything_real() {
let connectors = vec![
connector(1, ConnectorKind::Virtual, vec![mode(1024, 768, 60, true)]),
connector(2, ConnectorKind::Vga, vec![mode(640, 480, 60, true)]),
];
let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
assert_eq!(connectors[i].kind, ConnectorKind::Vga);
}
#[test]
fn equal_rank_prefers_the_larger_display() {
let connectors = vec![
connector(1, ConnectorKind::Hdmi, vec![mode(1280, 720, 60, true)]),
connector(
2,
ConnectorKind::DisplayPort,
vec![mode(2560, 1440, 60, true)],
),
];
let i = select_connector(&connectors, OutputPreference::Auto).expect("a connector");
assert_eq!(connectors[i].id, 2);
}
#[test]
fn an_explicit_id_overrides_the_ranking() {
let connectors = pi_with_debug_monitor();
let i = select_connector(&connectors, OutputPreference::Id(32)).expect("a connector");
assert_eq!(connectors[i].kind, ConnectorKind::Hdmi);
}
#[test]
fn an_explicit_id_that_is_absent_is_an_error_not_a_fallback() {
let connectors = pi_with_debug_monitor();
assert_eq!(
select_connector(&connectors, OutputPreference::Id(99)),
Err(SelectionError::RequestedIdUnavailable(99))
);
}
#[test]
fn an_explicit_kind_that_is_absent_is_an_error() {
let connectors = pi_with_debug_monitor();
assert_eq!(
select_connector(&connectors, OutputPreference::Kind(ConnectorKind::Vga)),
Err(SelectionError::RequestedKindUnavailable(ConnectorKind::Vga))
);
}
#[test]
fn a_disconnected_requested_id_is_rejected() {
let connectors = vec![disconnected(32, ConnectorKind::Hdmi)];
assert_eq!(
select_connector(&connectors, OutputPreference::Id(32)),
Err(SelectionError::RequestedIdUnavailable(32))
);
}
#[test]
fn preferred_mode_is_taken_even_when_it_is_not_first() {
let c = connector(
1,
ConnectorKind::Dsi,
vec![
mode(640, 480, 60, false),
mode(800, 480, 60, true),
mode(1024, 600, 60, false),
],
);
let (i, fell_back) = select_mode(&c, ModePreference::Preferred);
assert_eq!(i, 1);
assert!(!fell_back);
}
#[test]
fn no_preferred_flag_falls_back_to_the_largest_not_the_first() {
let c = connector(
1,
ConnectorKind::Hdmi,
vec![
mode(640, 480, 60, false),
mode(1920, 1080, 60, false),
mode(1280, 720, 60, false),
],
);
let (i, fell_back) = select_mode(&c, ModePreference::Preferred);
assert_eq!(c.modes[i], mode(1920, 1080, 60, false));
assert!(fell_back);
}
#[test]
fn exact_mode_is_found() {
let c = connector(
1,
ConnectorKind::Hdmi,
vec![mode(1920, 1080, 60, true), mode(1280, 720, 60, false)],
);
let (i, fell_back) = select_mode(
&c,
ModePreference::Exact {
width: 1280,
height: 720,
},
);
assert_eq!(c.modes[i], mode(1280, 720, 60, false));
assert!(!fell_back);
}
#[test]
fn exact_mode_takes_the_fastest_refresh_available() {
let c = connector(
1,
ConnectorKind::Hdmi,
vec![mode(1920, 1080, 50, false), mode(1920, 1080, 60, false)],
);
let (i, _) = select_mode(
&c,
ModePreference::Exact {
width: 1920,
height: 1080,
},
);
assert_eq!(c.modes[i].vrefresh, 60);
}
#[test]
fn an_unavailable_exact_mode_falls_back_and_says_so() {
let c = connector(1, ConnectorKind::Dsi, vec![mode(800, 480, 60, true)]);
let (i, fell_back) = select_mode(
&c,
ModePreference::Exact {
width: 1920,
height: 1080,
},
);
assert_eq!(c.modes[i], mode(800, 480, 60, true));
assert!(
fell_back,
"a silently ignored configured mode must be flagged"
);
}
#[test]
fn exact_refresh_relaxes_to_the_same_resolution_before_giving_up() {
let c = connector(
1,
ConnectorKind::Hdmi,
vec![mode(1920, 1080, 60, false), mode(1280, 720, 60, true)],
);
let (i, fell_back) = select_mode(
&c,
ModePreference::ExactRefresh {
width: 1920,
height: 1080,
vrefresh: 144,
},
);
assert_eq!(c.modes[i], mode(1920, 1080, 60, false));
assert!(
!fell_back,
"same resolution at another rate is not a fallback"
);
}
#[test]
fn largest_ignores_the_preferred_flag() {
let c = connector(
1,
ConnectorKind::Hdmi,
vec![mode(1280, 720, 60, true), mode(1920, 1080, 60, false)],
);
let (i, _) = select_mode(&c, ModePreference::Largest);
assert_eq!(c.modes[i], mode(1920, 1080, 60, false));
}
#[test]
fn selection_never_returns_an_out_of_range_index() {
let connectors = pi_with_debug_monitor();
let prefs = [
ModePreference::Preferred,
ModePreference::Largest,
ModePreference::Exact {
width: 3840,
height: 2160,
},
ModePreference::ExactRefresh {
width: 800,
height: 480,
vrefresh: 75,
},
];
for pref in prefs {
let s = select(&connectors, OutputPreference::Auto, pref).expect("a selection");
assert!(s.connector < connectors.len());
assert!(s.mode < connectors[s.connector].modes.len());
}
}
}