use std::sync::Mutex;
use drm::control::Device as _;
use drm::control::connector::{Interface, State};
use drmkit_core::Device;
use drmkit_display::{ScanoutError, ScanoutTarget};
use drmkit_modeset::ModeInfo as _;
use drmkit_planes::{PlaneRegistry, PlaneType};
static CARD_LOCK: Mutex<()> = Mutex::new(());
fn card_guard() -> std::sync::MutexGuard<'static, ()> {
CARD_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn open_card() -> Option<Device> {
let path = std::env::var("DRMKIT_TEST_CARD").unwrap_or_else(|_| "/dev/dri/card0".to_owned());
drmkit_testkit::announce_card(&path);
let device = match Device::open(&path) {
Ok(device) => device,
Err(error) => {
assert!(
std::env::var_os("DRMKIT_REQUIRE_MASTER").is_none(),
"{path}: {error}, but DRMKIT_REQUIRE_MASTER is set"
);
drmkit_testkit::skipped(&format!("no DRM device at {path} ({error})"));
return None;
}
};
device.enable_atomic().expect("atomic");
Some(device)
}
#[test]
#[ignore = "needs a DRM device"]
fn discovery_finds_an_output_that_is_actually_wired_up() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let target = match ScanoutTarget::discover(&device).expect("discover") {
Ok(target) => target,
Err(ScanoutError::NothingConnected) => {
assert!(
std::env::var_os("DRMKIT_REQUIRE_MASTER").is_none(),
"nothing connected, but DRMKIT_REQUIRE_MASTER is set"
);
drmkit_testkit::skipped("no display attached");
return;
}
Err(error) => panic!("{error}"),
};
println!(
"connector {} -> crtc {} (index {}) -> plane {} at {}x{}@{}",
target.connector_id,
target.crtc.id,
target.crtc.index,
target.primary_plane_id,
target.mode.width(),
target.mode.height(),
target.mode.refresh(),
);
let resources = device.resource_handles().expect("resources");
let handle = resources
.connectors()
.iter()
.find(|handle| u32::from(**handle) == target.connector_id)
.expect("the connector is one of the device's");
let connector = device.get_connector(*handle, false).expect("connector");
assert_eq!(connector.state(), State::Connected);
assert!(
connector.modes().contains(&target.mode),
"the chosen mode is not on the connector's list"
);
assert!(target.mode.width() > 0 && target.mode.height() > 0);
let crtc = resources.crtcs()[target.crtc.index as usize];
assert_eq!(u32::from(crtc), target.crtc.id);
let registry = PlaneRegistry::probe(&device).expect("probe");
let primary = registry
.all()
.iter()
.find(|plane| plane.id == target.primary_plane_id)
.expect("the plane is one of the device's");
assert_eq!(primary.plane_type, PlaneType::Primary);
assert!(
primary.compatible_with_crtc(target.crtc.index),
"plane {} cannot feed CRTC index {}",
primary.id,
target.crtc.index
);
assert!(
!target.primary_formats.is_empty(),
"a plane that can scan out nothing cannot scan out"
);
}
#[test]
#[ignore = "needs a DRM device"]
fn discovery_enables_universal_planes_itself() {
let _guard = card_guard();
let path = std::env::var("DRMKIT_TEST_CARD").unwrap_or_else(|_| "/dev/dri/card0".to_owned());
drmkit_testkit::announce_card(&path);
let Ok(device) = Device::open(&path) else {
drmkit_testkit::skipped(&format!("no DRM device at {path}"));
return;
};
match ScanoutTarget::discover(&device).expect("discover") {
Ok(target) => assert!(target.primary_plane_id > 0),
Err(ScanoutError::NothingConnected) => {
assert!(
std::env::var_os("DRMKIT_REQUIRE_MASTER").is_none(),
"nothing connected, but DRMKIT_REQUIRE_MASTER is set"
);
drmkit_testkit::skipped("no display attached");
}
Err(error) => panic!("{error}"),
}
}
#[test]
#[ignore = "needs a DRM device"]
fn the_chosen_crtc_is_one_the_connectors_encoders_allow() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
device.enable_universal_planes().expect("universal planes");
let resources = device.resource_handles().expect("resources");
let Ok(Ok(target)) = ScanoutTarget::discover(&device) else {
drmkit_testkit::skipped("no display attached");
return;
};
let handle = resources
.connectors()
.iter()
.find(|handle| u32::from(**handle) == target.connector_id)
.expect("the connector is one of the device's");
let connector = device.get_connector(*handle, false).expect("connector");
let allowed: Vec<u32> = connector
.encoders()
.iter()
.filter_map(|encoder| device.get_encoder(*encoder).ok())
.flat_map(|encoder| resources.filter_crtcs(encoder.possible_crtcs()))
.map(u32::from)
.collect();
assert!(
allowed.contains(&target.crtc.id),
"crtc {} is not among {allowed:?}",
target.crtc.id
);
}
#[test]
#[ignore = "needs a DRM device"]
fn a_rank_that_matches_nothing_still_finds_the_display() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let Ok(Ok(preferred)) = ScanoutTarget::discover(&device) else {
drmkit_testkit::skipped("no display attached");
return;
};
let ranks = [Interface::Composite, Interface::TV];
let fallback = ScanoutTarget::discover_ranked(&device, &ranks)
.expect("discover")
.expect("a display is attached");
assert_eq!(fallback.connector_id, preferred.connector_id);
assert_eq!(fallback.crtc.id, preferred.crtc.id);
}
#[test]
#[ignore = "needs a DRM device"]
fn the_rank_decides_which_display_when_there_is_a_choice() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let resources = device.resource_handles().expect("resources");
let kinds: Vec<Interface> = resources
.connectors()
.iter()
.filter_map(|handle| device.get_connector(*handle, false).ok())
.filter(|info| info.state() == State::Connected && !info.modes().is_empty())
.map(|info| info.interface())
.collect();
if kinds.len() < 2 {
println!("note: one display attached ({kinds:?}), nothing to choose between");
return;
}
let mut reversed: Vec<Interface> = drmkit_display::rank::MAIN.to_vec();
reversed.reverse();
let forward = ScanoutTarget::discover(&device)
.expect("discover")
.expect("a display is attached");
let backward = ScanoutTarget::discover_ranked(&device, &reversed)
.expect("discover")
.expect("a display is attached");
println!(
"{kinds:?}: main picked connector {}, reversed picked {}",
forward.connector_id, backward.connector_id
);
assert_ne!(
forward.connector_id, backward.connector_id,
"reversing the rank changed nothing, so the rank is not being read"
);
}
fn eligible_connectors(device: &Device) -> Vec<u32> {
let resources = device.resource_handles().expect("resources");
resources
.connectors()
.iter()
.filter(|handle| {
device
.get_connector(**handle, false)
.is_ok_and(|info| info.state() == State::Connected && !info.modes().is_empty())
})
.map(|handle| u32::from(*handle))
.collect()
}
#[test]
#[ignore = "needs a DRM device"]
fn a_pinned_connector_is_the_one_discovered() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let eligible = eligible_connectors(&device);
let Some(&last) = eligible.last() else {
drmkit_testkit::skipped("no display attached");
return;
};
let pinned = ScanoutTarget::discover_connector(&device, last)
.expect("discover")
.expect("a connected connector with a mode is drivable");
assert_eq!(pinned.connector_id, last);
let unpinned = ScanoutTarget::discover_connector(&device, 0)
.expect("discover")
.expect("discover");
let plain = ScanoutTarget::discover(&device)
.expect("discover")
.expect("discover");
assert_eq!(unpinned.connector_id, plain.connector_id);
if eligible.len() < 2 {
println!("note: one connected output, so pinning chose what discovery would");
}
}
#[test]
#[ignore = "needs a DRM device"]
fn a_pinned_connector_that_is_not_there_is_refused() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
if eligible_connectors(&device).is_empty() {
drmkit_testkit::skipped("no display attached");
return;
}
let resources = device.resource_handles().expect("resources");
let not_a_connector = u32::from(*resources.crtcs().first().expect("a CRTC"));
let refused = ScanoutTarget::discover_connector(&device, not_a_connector).expect("discover");
assert!(
matches!(
refused,
Err(ScanoutError::ConnectorUnavailable { connector_id }) if connector_id == not_a_connector
),
"{:?}",
refused.map(|target| target.connector_id)
);
}