use crate::error::{RenderError, Result};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct DriverVersion {
pub major: i32,
pub minor: i32,
pub patchlevel: i32,
pub name: String,
pub date: String,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct CardInfo {
pub path: PathBuf,
pub driver: Option<DriverVersion>,
}
#[derive(Debug, Clone)]
pub struct ConnectorSummary {
pub id: u32,
pub connector_type: String,
pub connected: bool,
pub mm_width: u32,
pub mm_height: u32,
}
#[must_use]
pub fn list_cards() -> Vec<CardInfo> {
tracing::debug!("listing drm cards");
#[cfg(feature = "drm")]
{
_list_cards_impl()
}
#[cfg(not(feature = "drm"))]
{
Vec::new()
}
}
#[cfg(feature = "drm")]
fn _list_cards_impl() -> Vec<CardInfo> {
let paths = match agnosys::drm::enumerate_cards() {
Ok(p) => p,
Err(_) => return Vec::new(),
};
paths
.into_iter()
.map(|path| {
let driver = agnosys::drm::Device::open(&path)
.and_then(|dev| dev.version())
.ok()
.map(|v| DriverVersion {
major: v.major,
minor: v.minor,
patchlevel: v.patchlevel,
name: v.name,
date: v.date,
description: v.desc,
});
CardInfo { path, driver }
})
.collect()
}
#[must_use]
pub fn list_render_nodes() -> Vec<PathBuf> {
#[cfg(feature = "drm")]
{
agnosys::drm::enumerate_render_nodes().unwrap_or_default()
}
#[cfg(not(feature = "drm"))]
{
Vec::new()
}
}
pub fn driver_version(card_path: &std::path::Path) -> Result<DriverVersion> {
#[cfg(feature = "drm")]
{
let dev = agnosys::drm::Device::open(card_path)
.map_err(|e| RenderError::DeviceRequest(format!("DRM open failed: {e}")))?;
let v = dev
.version()
.map_err(|e| RenderError::DeviceRequest(format!("DRM version query failed: {e}")))?;
Ok(DriverVersion {
major: v.major,
minor: v.minor,
patchlevel: v.patchlevel,
name: v.name,
date: v.date,
description: v.desc,
})
}
#[cfg(not(feature = "drm"))]
{
let _ = card_path;
Err(RenderError::DeviceRequest(
"DRM support requires the 'drm' feature".into(),
))
}
}
pub fn list_connectors(card_path: &std::path::Path) -> Result<Vec<ConnectorSummary>> {
#[cfg(feature = "drm")]
{
let dev = agnosys::drm::Device::open(card_path)
.map_err(|e| RenderError::DeviceRequest(format!("DRM open failed: {e}")))?;
let res = dev
.mode_resources()
.map_err(|e| RenderError::DeviceRequest(format!("DRM mode resources failed: {e}")))?;
let mut connectors = Vec::new();
for &id in &res.connector_ids {
if let Ok(info) = dev.connector_info(id) {
connectors.push(ConnectorSummary {
id: info.id,
connector_type: format!("{:?}", info.connector_type),
connected: info.status == agnosys::drm::ConnectionStatus::Connected,
mm_width: info.mm_width,
mm_height: info.mm_height,
});
}
}
Ok(connectors)
}
#[cfg(not(feature = "drm"))]
{
let _ = card_path;
Err(RenderError::DeviceRequest(
"DRM support requires the 'drm' feature".into(),
))
}
}
#[must_use]
pub fn supports_prime(card_path: &std::path::Path) -> bool {
#[cfg(feature = "drm")]
{
agnosys::drm::Device::open(card_path)
.and_then(|dev| dev.get_cap(agnosys::drm::Cap::Prime))
.map(|v| v != 0)
.unwrap_or(false)
}
#[cfg(not(feature = "drm"))]
{
let _ = card_path;
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_cards_returns_vec() {
let cards = list_cards();
let _ = cards;
}
#[test]
fn list_render_nodes_returns_vec() {
let nodes = list_render_nodes();
let _ = nodes;
}
#[test]
fn driver_version_nonexistent_path() {
let result = driver_version(std::path::Path::new("/dev/dri/card999"));
assert!(result.is_err());
}
#[test]
fn list_connectors_nonexistent_path() {
let result = list_connectors(std::path::Path::new("/dev/dri/card999"));
assert!(result.is_err());
}
#[test]
fn supports_prime_nonexistent_path() {
assert!(!supports_prime(std::path::Path::new("/dev/dri/card999")));
}
#[test]
fn card_info_debug() {
let info = CardInfo {
path: PathBuf::from("/dev/dri/card0"),
driver: Some(DriverVersion {
major: 1,
minor: 2,
patchlevel: 3,
name: "test".into(),
date: "20260324".into(),
description: "Test driver".into(),
}),
};
let dbg = format!("{:?}", info);
assert!(dbg.contains("card0"));
assert!(dbg.contains("test"));
}
#[test]
fn card_info_no_driver() {
let info = CardInfo {
path: PathBuf::from("/dev/dri/card0"),
driver: None,
};
assert!(info.driver.is_none());
}
#[test]
fn connector_summary_debug() {
let conn = ConnectorSummary {
id: 42,
connector_type: "HDMIA".into(),
connected: true,
mm_width: 530,
mm_height: 300,
};
let dbg = format!("{:?}", conn);
assert!(dbg.contains("HDMIA"));
assert!(dbg.contains("42"));
}
#[test]
fn driver_version_clone() {
let v = DriverVersion {
major: 5,
minor: 15,
patchlevel: 0,
name: "amdgpu".into(),
date: "20260101".into(),
description: "AMD GPU driver".into(),
};
let cloned = v.clone();
assert_eq!(cloned.name, "amdgpu");
assert_eq!(cloned.major, 5);
}
}