1use std::cell::Cell;
4use std::fs::OpenOptions;
5use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
6use std::path::{Path, PathBuf};
7
8use drm::Device as BasicDevice;
9use drm::control::{Device as ControlDevice, Mode, ModeTypeFlags, connector, crtc};
10
11use crate::error::DrmError;
12use crate::mode::{ConnectorInfo, ConnectorKind, ModeInfo};
13
14const DRI_DIR: &str = "/dev/dri";
16
17#[derive(Debug)]
22pub struct Card {
23 fd: OwnedFd,
24 path: Option<PathBuf>,
25 mastered: Cell<bool>,
26}
27
28impl AsFd for Card {
29 fn as_fd(&self) -> BorrowedFd<'_> {
30 self.fd.as_fd()
31 }
32}
33
34impl BasicDevice for Card {}
35impl ControlDevice for Card {}
36
37impl Card {
38 pub fn open(path: impl AsRef<Path>) -> Result<Self, DrmError> {
40 let path = path.as_ref();
41 let file = OpenOptions::new()
44 .read(true)
45 .write(true)
46 .open(path)
47 .map_err(|source| DrmError::Open {
48 path: path.to_path_buf(),
49 source,
50 })?;
51
52 Ok(Self {
53 fd: OwnedFd::from(file),
54 path: Some(path.to_path_buf()),
55 mastered: Cell::new(false),
56 })
57 }
58
59 pub fn open_first() -> Result<Self, DrmError> {
64 let dir = std::fs::read_dir(DRI_DIR).map_err(|source| DrmError::Open {
65 path: PathBuf::from(DRI_DIR),
66 source,
67 })?;
68
69 let mut cards: Vec<PathBuf> = dir
70 .filter_map(Result::ok)
71 .map(|entry| entry.path())
72 .filter(|path| {
73 path.file_name()
74 .and_then(|name| name.to_str())
75 .is_some_and(|name| name.starts_with("card"))
76 })
77 .collect();
78 cards.sort();
79
80 if cards.is_empty() {
81 return Err(DrmError::NoDevice);
82 }
83
84 let mut last_error = None;
85 for path in cards {
86 match Card::open(&path) {
87 Ok(card) if card.has_display_output() => return Ok(card),
88 Ok(_) => {}
89 Err(err) => last_error = Some(err),
90 }
91 }
92
93 Err(last_error.unwrap_or(DrmError::NoDisplayCapableDevice))
94 }
95
96 pub fn from_fd(fd: OwnedFd) -> Self {
101 Self {
102 fd,
103 path: None,
104 mastered: Cell::new(false),
105 }
106 }
107
108 pub fn path(&self) -> Option<&Path> {
110 self.path.as_deref()
111 }
112
113 pub fn has_display_output(&self) -> bool {
116 self.resource_handles()
117 .map(|res| !res.connectors().is_empty())
118 .unwrap_or(false)
119 }
120
121 pub fn become_master(&self) -> Result<(), DrmError> {
126 self.acquire_master_lock().map_err(DrmError::NotMaster)?;
127 self.mastered.set(true);
128 Ok(())
129 }
130
131 pub fn release_master(&self) {
133 if self.mastered.replace(false) {
134 let _ = self.release_master_lock();
135 }
136 }
137
138 pub fn is_master(&self) -> bool {
140 self.mastered.get()
141 }
142
143 pub fn connectors(&self) -> Result<(Vec<connector::Handle>, Vec<ConnectorInfo>), DrmError> {
148 let resources = self.resource_handles().map_err(DrmError::Resources)?;
149 let mut handles = Vec::with_capacity(resources.connectors().len());
150 let mut infos = Vec::with_capacity(resources.connectors().len());
151
152 for &handle in resources.connectors() {
153 let info = self
157 .get_connector(handle, true)
158 .map_err(DrmError::Resources)?;
159
160 handles.push(handle);
161 infos.push(ConnectorInfo {
162 id: u32::from(handle),
163 kind: connector_kind(info.interface()),
164 connected: info.state() == connector::State::Connected,
165 modes: info.modes().iter().map(mode_info).collect(),
166 });
167 }
168
169 Ok((handles, infos))
170 }
171
172 pub fn crtc_for(&self, connector: connector::Handle) -> Result<crtc::Handle, DrmError> {
177 let resources = self.resource_handles().map_err(DrmError::Resources)?;
178 let info = self
179 .get_connector(connector, false)
180 .map_err(DrmError::Resources)?;
181
182 if let Some(encoder) = info.current_encoder()
183 && let Ok(encoder) = self.get_encoder(encoder)
184 && let Some(crtc) = encoder.crtc()
185 {
186 return Ok(crtc);
187 }
188
189 for &handle in info.encoders() {
190 let Ok(encoder) = self.get_encoder(handle) else {
191 continue;
192 };
193 if let Some(&crtc) = resources.filter_crtcs(encoder.possible_crtcs()).first() {
194 return Ok(crtc);
195 }
196 }
197
198 Err(DrmError::NoCrtc {
199 connector: u32::from(connector),
200 })
201 }
202}
203
204impl Drop for Card {
205 fn drop(&mut self) {
206 self.release_master();
207 }
208}
209
210fn mode_info(mode: &Mode) -> ModeInfo {
212 let (width, height) = mode.size();
213 ModeInfo {
214 width,
215 height,
216 vrefresh: mode.vrefresh(),
217 preferred: mode.mode_type().contains(ModeTypeFlags::PREFERRED),
218 }
219}
220
221fn connector_kind(interface: connector::Interface) -> ConnectorKind {
223 use connector::Interface;
224
225 match interface {
226 Interface::DSI => ConnectorKind::Dsi,
227 Interface::DPI => ConnectorKind::Dpi,
228 Interface::EmbeddedDisplayPort => ConnectorKind::Edp,
229 Interface::LVDS => ConnectorKind::Lvds,
230 Interface::HDMIA | Interface::HDMIB => ConnectorKind::Hdmi,
231 Interface::DisplayPort => ConnectorKind::DisplayPort,
232 Interface::DVII | Interface::DVID | Interface::DVIA => ConnectorKind::Dvi,
233 Interface::VGA => ConnectorKind::Vga,
234 Interface::Composite | Interface::SVideo | Interface::Component | Interface::TV => {
235 ConnectorKind::Composite
236 }
237 Interface::Virtual => ConnectorKind::Virtual,
238 _ => ConnectorKind::Other,
239 }
240}