use drm::control::{Mode, ModeFlags, ModeTypeFlags};
use crate::error::{ModesetError, Result};
pub trait ModeInfo {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn refresh(&self) -> u32;
fn preferred(&self) -> bool;
fn interlaced(&self) -> bool;
fn clock_khz(&self) -> u32;
fn area(&self) -> u64;
}
impl ModeInfo for Mode {
fn width(&self) -> u32 {
u32::from(self.size().0)
}
fn height(&self) -> u32 {
u32::from(self.size().1)
}
fn refresh(&self) -> u32 {
self.vrefresh()
}
fn preferred(&self) -> bool {
self.mode_type().contains(ModeTypeFlags::PREFERRED)
}
fn interlaced(&self) -> bool {
self.flags().contains(ModeFlags::INTERLACE)
}
fn clock_khz(&self) -> u32 {
self.clock()
}
fn area(&self) -> u64 {
u64::from(self.width()) * u64::from(self.height())
}
}
pub fn select_preferred_mode(modes: &[Mode]) -> Result<Mode> {
if modes.is_empty() {
return Err(ModesetError::NoModes);
}
if let Some(preferred) = modes.iter().find(|m| m.preferred()) {
return Ok(*preferred);
}
let best = modes
.iter()
.copied()
.reduce(|best, m| {
if (m.area(), m.refresh()) > (best.area(), best.refresh()) {
m
} else {
best
}
})
.ok_or(ModesetError::NoModes)?;
Ok(best)
}
pub fn select_mode(
modes: &[Mode],
target_width: u32,
target_height: u32,
target_refresh: u32,
) -> Result<Mode> {
if modes.is_empty() {
return Err(ModesetError::NoModes);
}
let score = |m: &Mode| -> u64 {
let dw = u64::from(m.width().abs_diff(target_width));
let dh = u64::from(m.height().abs_diff(target_height));
let mut score = dw * dw + dh * dh;
if target_refresh > 0 {
score += u64::from(m.refresh().abs_diff(target_refresh)) * 100;
}
score
};
let mut best: Option<(Mode, u64)> = None;
for mode in modes.iter().filter(|m| !m.interlaced()) {
let candidate = score(mode);
let replace = match best {
None => true,
Some((current, current_score)) => {
candidate < current_score
|| (candidate == current_score && mode.refresh() > current.refresh())
}
};
if replace {
best = Some((*mode, candidate));
}
}
best.map(|(mode, _)| mode).ok_or(ModesetError::NoModes)
}