1use super::drm::lib_drm::{drmModeConnector, drmModeCrtc, drmModeEncoder, drmModeModeInfo, DrmModeType};
5use super::drm::{DrmModeConnection, DrmModeSubPixel};
6use crate::DrmConnectorType;
7use ferroui_base::{PixelSize, Size};
8use std::collections::HashMap;
9use std::ffi::CStr;
10use std::sync::atomic::{AtomicI32, Ordering};
11
12static KNOWN_CONNECTOR_TYPES: [&str; 17] = [
13 "None",
14 "VGA",
15 "DVI-I",
16 "DVI-D",
17 "DVI-A",
18 "Composite",
19 "S-Video",
20 "LVDS",
21 "Component",
22 "DIN",
23 "DisplayPort",
24 "HDMI-A",
25 "HDMI-B",
26 "TV",
27 "eDP",
28 "Virtual",
29 "DSI",
30];
31
32#[derive(Clone, Debug)]
34pub struct DrmConnector {
35 connection: DrmModeConnection,
36 id: u32,
37 name: String,
38 size_mm: Size,
39 sub_pixel: DrmModeSubPixel,
40 encoder_id: u32,
41 encoder_ids: Vec<u32>,
42 modes: Vec<DrmModeInfo>,
43 connector_type: DrmConnectorType,
44 connector_type_id: u32,
45}
46
47impl DrmConnector {
48 pub(crate) unsafe fn new(conn: &drmModeConnector) -> Self {
54 let mut encoder_ids = Vec::new();
55 for c in 0..conn.count_encoders.max(0) as usize {
56 encoder_ids.push(unsafe { *conn.encoders.add(c) });
58 }
59 let mut modes = Vec::new();
60 for c in 0..conn.count_modes.max(0) as usize {
61 modes.push(DrmModeInfo::new(unsafe { &*conn.modes.add(c) }));
63 }
64 let name = if conn.connector_type as usize > KNOWN_CONNECTOR_TYPES.len() - 1 {
65 format!("Unknown({})-{}", conn.connector_type, conn.connector_type_id)
66 } else {
67 format!("{}-{}", KNOWN_CONNECTOR_TYPES[conn.connector_type as usize], conn.connector_type_id)
68 };
69 Self {
70 connection: conn.connection,
71 id: conn.connector_id,
72 size_mm: Size::new(conn.mm_width as f64, conn.mm_height as f64),
73 sub_pixel: conn.subpixel,
74 encoder_ids,
75 encoder_id: conn.encoder_id,
76 modes,
77 connector_type: DrmConnectorType(conn.connector_type),
78 connector_type_id: conn.connector_type_id,
79 name,
80 }
81 }
82
83 pub fn connection(&self) -> DrmModeConnection {
84 self.connection
85 }
86
87 pub fn id(&self) -> u32 {
88 self.id
89 }
90
91 pub fn name(&self) -> &str {
92 &self.name
93 }
94
95 pub fn size_mm(&self) -> Size {
96 self.size_mm
97 }
98
99 pub fn sub_pixel(&self) -> DrmModeSubPixel {
100 self.sub_pixel
101 }
102
103 pub(crate) fn encoder_id(&self) -> u32 {
104 self.encoder_id
105 }
106
107 pub(crate) fn encoder_ids(&self) -> &[u32] {
108 &self.encoder_ids
109 }
110
111 pub fn modes(&self) -> &[DrmModeInfo] {
112 &self.modes
113 }
114
115 pub fn connector_type(&self) -> DrmConnectorType {
116 self.connector_type
117 }
118
119 pub fn connector_type_id(&self) -> u32 {
120 self.connector_type_id
121 }
122}
123
124#[derive(Clone, Debug)]
126pub struct DrmModeInfo {
127 pub(crate) mode: drmModeModeInfo,
128 name: Option<String>,
129}
130
131impl DrmModeInfo {
132 pub(crate) fn new(info: &drmModeModeInfo) -> Self {
133 let bytes: Vec<u8> = info.name.iter().map(|&c| c as u8).collect();
135 let name = match CStr::from_bytes_until_nul(&bytes) {
136 Ok(name) => name.to_string_lossy().into_owned(),
137 Err(_) => String::from_utf8_lossy(&bytes).into_owned(),
138 };
139 Self { mode: *info, name: Some(name) }
140 }
141
142 pub fn resolution(&self) -> PixelSize {
143 PixelSize::new(self.mode.hdisplay as i32, self.mode.vdisplay as i32)
144 }
145
146 pub fn is_preferred(&self) -> bool {
147 self.mode.type_.has_all_flags(DrmModeType::DRM_MODE_TYPE_PREFERRED)
148 }
149
150 pub fn name(&self) -> Option<&str> {
151 self.name.as_deref()
152 }
153}
154
155#[derive(Clone, Debug)]
157pub(crate) struct DrmEncoder {
158 encoder: drmModeEncoder,
159 possible_crtcs: Vec<drmModeCrtc>,
160}
161
162impl DrmEncoder {
163 pub fn new(encoder: drmModeEncoder, crtcs: &[drmModeCrtc]) -> Self {
164 let mut possible_crtcs = Vec::new();
165 for (c, crtc) in crtcs.iter().enumerate() {
166 let bit = 1u32.checked_shl(c as u32).unwrap_or(0);
168 if (encoder.possible_crtcs & bit) != 0 {
169 possible_crtcs.push(*crtc);
170 }
171 }
172 Self { encoder, possible_crtcs }
173 }
174
175 pub fn encoder(&self) -> drmModeEncoder {
176 self.encoder
177 }
178
179 pub fn possible_crtcs(&self) -> &[drmModeCrtc] {
180 &self.possible_crtcs
181 }
182}
183
184#[derive(Clone, Debug, Default)]
186pub struct DrmResources {
187 connectors: Vec<DrmConnector>,
188 encoders: HashMap<u32, DrmEncoder>,
189}
190
191impl DrmResources {
192 #[cfg(target_os = "linux")]
196 pub fn new(fd: i32, connectors_force_probe: bool) -> std::io::Result<Self> {
197 use super::drm::lib_drm::LibDrm;
198 use std::io;
199
200 let lib = LibDrm::get()?;
201 let res = unsafe { lib.drm_mode_get_resources(fd) };
203 if res.is_null() {
204 return Err(io::Error::new(
205 io::Error::last_os_error().kind(),
206 format!("drmModeGetResources failed: {}", io::Error::last_os_error()),
207 ));
208 }
209
210 let read = || -> io::Result<Self> {
211 let res = unsafe { &*res };
214 let mut this = Self::default();
215 let mut crtcs = Vec::with_capacity(res.count_crtcs.max(0) as usize);
216 for c in 0..res.count_crtcs.max(0) as usize {
217 let crtc = unsafe { lib.drm_mode_get_crtc(fd, *res.crtcs.add(c)) };
219 if crtc.is_null() {
220 return Err(io::Error::other("drmModeGetCrtc failed"));
221 }
222 unsafe {
224 crtcs.push(*crtc);
225 lib.drm_mode_free_crtc(crtc);
226 }
227 }
228
229 for c in 0..res.count_encoders.max(0) as usize {
230 let id = unsafe { *res.encoders.add(c) };
232 let enc = unsafe { lib.drm_mode_get_encoder(fd, id) };
234 if enc.is_null() {
235 return Err(io::Error::other("drmModeGetEncoder failed"));
236 }
237 unsafe {
239 this.encoders.insert(id, DrmEncoder::new(*enc, &crtcs));
240 lib.drm_mode_free_encoder(enc);
241 }
242 }
243
244 for c in 0..res.count_connectors.max(0) as usize {
245 let id = unsafe { *res.connectors.add(c) };
247 let conn = unsafe {
249 if connectors_force_probe {
250 lib.drm_mode_get_connector(fd, id)
251 } else {
252 lib.drm_mode_get_connector_current(fd, id)
253 }
254 };
255 if conn.is_null() {
256 return Err(io::Error::other("drmModeGetConnector failed"));
257 }
258 unsafe {
260 this.connectors.push(DrmConnector::new(&*conn));
261 lib.drm_mode_free_connector(conn);
262 }
263 }
264 Ok(this)
265 };
266 let result = read();
267 unsafe { lib.drm_mode_free_resources(res) };
269 result
270 }
271
272 #[cfg(test)]
275 pub(crate) fn from_parts(connectors: Vec<DrmConnector>, encoders: HashMap<u32, DrmEncoder>) -> Self {
276 Self { connectors, encoders }
277 }
278
279 pub fn connectors(&self) -> &[DrmConnector] {
280 &self.connectors
281 }
282
283 pub(crate) fn encoders(&self) -> &HashMap<u32, DrmEncoder> {
284 &self.encoders
285 }
286
287 pub fn dump(&self) {
291 fn print(off: usize, s: &str) {
292 println!("{}{s}", " ".repeat(off));
293 }
294 print(0, "Connectors");
295 for conn in &self.connectors {
296 print(1, &format!("{}:", conn.name()));
297 print(2, &format!("Id: {}", conn.id()));
298 print(2, &format!("Size: {} mm", conn.size_mm()));
299 print(2, &format!("Encoder id: {}", conn.encoder_id()));
300 print(2, "Modes");
301 for m in conn.modes() {
302 print(3, &format!("{} {}", m.name().unwrap_or_default(), if m.is_preferred() { "PREFERRED" } else { "" }));
303 }
304 }
305 }
306}
307
308#[derive(Debug)]
310pub struct DrmCard {
311 fd: AtomicI32,
312}
313
314impl DrmCard {
315 #[cfg(target_os = "linux")]
318 pub fn new(path: Option<&str>) -> std::io::Result<Self> {
319 use super::drm::lib_drm::LibDrm;
320 use std::collections::BTreeMap;
321 use std::ffi::CString;
322 use std::io;
323
324 let open = |file: &str| -> i32 {
325 let Ok(file) = CString::new(file) else {
326 return -1;
327 };
328 unsafe { libc::open(file.as_ptr(), libc::O_RDWR, 0) }
330 };
331
332 match path {
333 None => {
334 let lib = LibDrm::get()?;
335 let mut files = BTreeMap::new();
336 if let Ok(entries) = std::fs::read_dir("/dev/dri/") {
337 for entry in entries.flatten() {
338 let file_name = entry.file_name().to_string_lossy().into_owned();
339 let Some(number) = file_name.strip_prefix("card") else {
340 continue;
341 };
342 if number.is_empty() || !number.bytes().all(|b| b.is_ascii_digit()) {
344 continue;
345 }
346 if let Ok(card_number) = number.parse::<u32>() {
347 files.insert(card_number, entry.path().to_string_lossy().into_owned());
348 }
349 }
350 }
351
352 for file in files.values() {
353 let fd = open(file);
354 if fd == -1 {
355 continue;
356 }
357
358 unsafe {
361 let resources = lib.drm_mode_get_resources(fd);
362 if !resources.is_null()
363 && (*resources).count_crtcs > 0
364 && (*resources).count_encoders > 0
365 && (*resources).count_connectors > 0
366 {
367 lib.drm_mode_free_resources(resources);
368 return Ok(Self { fd: AtomicI32::new(fd) });
369 }
370
371 if !resources.is_null() {
372 lib.drm_mode_free_resources(resources);
373 }
374 libc::close(fd);
375 }
376 }
377
378 Err(io::Error::new(io::ErrorKind::NotFound, "Couldn't open /dev/dri/card[0-9]+"))
379 }
380 Some(path) => {
381 let fd = open(path);
382 if fd == -1 {
383 let error = io::Error::last_os_error();
384 return Err(io::Error::new(error.kind(), format!("Couldn't open {path}: {error}")));
385 }
386 Ok(Self { fd: AtomicI32::new(fd) })
387 }
388 }
389 }
390
391 pub fn from_fd(fd: i32) -> Self {
393 Self { fd: AtomicI32::new(fd) }
394 }
395
396 pub fn fd(&self) -> i32 {
398 self.fd.load(Ordering::Acquire)
399 }
400
401 #[cfg(target_os = "linux")]
402 pub fn get_resources(&self, connectors_force_probe: bool) -> std::io::Result<DrmResources> {
403 DrmResources::new(self.fd(), connectors_force_probe)
404 }
405
406 pub fn dispose(&self) {
408 let fd = self.fd.swap(-1, Ordering::AcqRel);
409 if fd == -1 {
410 return;
411 }
412 unsafe {
414 libc::close(fd);
415 }
416 }
417}
418
419impl Drop for DrmCard {
420 fn drop(&mut self) {
421 self.dispose();
422 }
423}
424
425#[cfg(test)]
426pub(crate) mod tests {
427 use super::*;
429
430 pub(crate) fn mode(width: u16, height: u16, preferred: bool) -> drmModeModeInfo {
433 let mut info = drmModeModeInfo { hdisplay: width, vdisplay: height, ..Default::default() };
434 if preferred {
435 info.type_ = DrmModeType::DRM_MODE_TYPE_PREFERRED | DrmModeType::DRM_MODE_TYPE_DRIVER;
436 }
437 for (target, source) in info.name.iter_mut().zip(format!("{width}x{height}").bytes()) {
438 *target = source as _;
439 }
440 info
441 }
442
443 pub(crate) fn connector(
445 id: u32,
446 connector_type: u32,
447 connector_type_id: u32,
448 connection: DrmModeConnection,
449 encoder_id: u32,
450 encoders: &[u32],
451 modes: &[drmModeModeInfo],
452 ) -> DrmConnector {
453 let mut encoders = encoders.to_vec();
454 let mut modes = modes.to_vec();
455 let raw = drmModeConnector {
456 connector_id: id,
457 encoder_id,
458 connector_type,
459 connector_type_id,
460 connection,
461 mm_width: 300,
462 mm_height: 200,
463 subpixel: DrmModeSubPixel::DRM_MODE_SUBPIXEL_UNKNOWN,
464 count_modes: modes.len() as i32,
465 modes: modes.as_mut_ptr(),
466 count_props: 0,
467 props: std::ptr::null_mut(),
468 prop_values: std::ptr::null_mut(),
469 count_encoders: encoders.len() as i32,
470 encoders: encoders.as_mut_ptr(),
471 };
472 unsafe { DrmConnector::new(&raw) }
474 }
475
476 #[test]
477 fn a_connector_is_named_after_its_type_and_reads_its_arrays() {
478 let conn = connector(
479 31,
480 15,
481 1,
482 DrmModeConnection::DRM_MODE_CONNECTED,
483 40,
484 &[40, 41],
485 &[mode(1024, 768, true), mode(800, 600, false)],
486 );
487 assert_eq!(conn.name(), "Virtual-1");
488 assert_eq!(conn.id(), 31);
489 assert_eq!(conn.connector_type(), DrmConnectorType::VIRTUAL);
490 assert_eq!(conn.connector_type_id(), 1);
491 assert_eq!(conn.connection(), DrmModeConnection::DRM_MODE_CONNECTED);
492 assert_eq!(conn.size_mm(), Size::new(300.0, 200.0));
493 assert_eq!(conn.encoder_id(), 40);
494 assert_eq!(conn.encoder_ids(), [40, 41]);
495 assert_eq!(conn.modes().len(), 2);
496 assert_eq!(conn.modes()[0].name(), Some("1024x768"));
497 assert_eq!(conn.modes()[0].resolution(), PixelSize::new(1024, 768));
498 assert!(conn.modes()[0].is_preferred());
499 assert!(!conn.modes()[1].is_preferred());
500
501 let hdmi = connector(32, 11, 2, DrmModeConnection::DRM_MODE_DISCONNECTED, 0, &[], &[]);
502 assert_eq!(hdmi.name(), "HDMI-A-2");
503 assert!(hdmi.modes().is_empty());
504 let unknown = connector(33, 18, 1, DrmModeConnection::DRM_MODE_UNKNOWNCONNECTION, 0, &[], &[]);
506 assert_eq!(unknown.name(), "Unknown(18)-1");
507 assert_eq!(unknown.connector_type(), DrmConnectorType(18));
508 }
509
510 #[test]
511 fn a_name_of_32_characters_without_its_zero_is_read_whole() {
512 let mut info = drmModeModeInfo::default();
513 info.name = [b'a' as _; 32];
514 assert_eq!(DrmModeInfo::new(&info).name().map(str::len), Some(32));
515 }
516
517 #[test]
518 fn an_encoder_lists_the_crtcs_of_its_bits() {
519 let crtcs: Vec<drmModeCrtc> =
520 (0..3).map(|c| drmModeCrtc { crtc_id: 100 + c, ..Default::default() }).collect();
521 let encoder = DrmEncoder::new(drmModeEncoder { encoder_id: 7, possible_crtcs: 0b101, ..Default::default() }, &crtcs);
522 assert_eq!(encoder.encoder().encoder_id, 7);
523 let ids: Vec<u32> = encoder.possible_crtcs().iter().map(|crtc| crtc.crtc_id).collect();
524 assert_eq!(ids, [100, 102]);
525 }
526
527 #[test]
528 fn a_card_closes_its_descriptor_once() {
529 let mut fds = [0; 2];
530 assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0);
532 let card = DrmCard::from_fd(fds[0]);
533 assert_eq!(card.fd(), fds[0]);
534 card.dispose();
535 assert_eq!(card.fd(), -1);
536 card.dispose();
537 drop(card);
538 unsafe { libc::close(fds[1]) };
540 }
541}