1#![allow(dead_code)]
30
31use alloc::{collections::BTreeMap, string::String, vec::Vec};
32use core::sync::atomic::{AtomicU32, Ordering};
33
34use crate::error::KernelError;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[repr(u32)]
45pub enum OutputTransform {
46 Normal = 0,
48 Rotate90 = 1,
50 Rotate180 = 2,
52 Rotate270 = 3,
54 Flipped = 4,
56 FlippedRotate90 = 5,
58 FlippedRotate180 = 6,
60 FlippedRotate270 = 7,
62}
63
64impl OutputTransform {
65 pub fn from_wl(value: u32) -> Self {
67 match value {
68 0 => Self::Normal,
69 1 => Self::Rotate90,
70 2 => Self::Rotate180,
71 3 => Self::Rotate270,
72 4 => Self::Flipped,
73 5 => Self::FlippedRotate90,
74 6 => Self::FlippedRotate180,
75 7 => Self::FlippedRotate270,
76 _ => Self::Normal,
77 }
78 }
79
80 pub fn swaps_dimensions(self) -> bool {
83 matches!(
84 self,
85 Self::Rotate90 | Self::Rotate270 | Self::FlippedRotate90 | Self::FlippedRotate270
86 )
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[repr(u32)]
99pub enum SubpixelLayout {
100 Unknown = 0,
102 None = 1,
104 HorizontalRgb = 2,
106 HorizontalBgr = 3,
108 VerticalRgb = 4,
110 VerticalBgr = 5,
112}
113
114impl SubpixelLayout {
115 pub fn from_wl(value: u32) -> Self {
117 match value {
118 0 => Self::Unknown,
119 1 => Self::None,
120 2 => Self::HorizontalRgb,
121 3 => Self::HorizontalBgr,
122 4 => Self::VerticalRgb,
123 5 => Self::VerticalBgr,
124 _ => Self::Unknown,
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
138pub struct OutputMode {
139 pub width: u32,
141 pub height: u32,
143 pub refresh_mhz: u32,
145 pub preferred: bool,
147 pub current: bool,
149}
150
151impl OutputMode {
152 pub fn new(width: u32, height: u32, refresh_mhz: u32) -> Self {
154 Self {
155 width,
156 height,
157 refresh_mhz,
158 preferred: false,
159 current: false,
160 }
161 }
162
163 pub fn new_current_preferred(width: u32, height: u32, refresh_mhz: u32) -> Self {
165 Self {
166 width,
167 height,
168 refresh_mhz,
169 preferred: true,
170 current: true,
171 }
172 }
173
174 pub fn wl_flags(&self) -> u32 {
176 let mut flags = 0u32;
177 if self.current {
178 flags |= 0x1; }
180 if self.preferred {
181 flags |= 0x2; }
183 flags
184 }
185
186 pub fn pixel_count(&self) -> u64 {
188 self.width as u64 * self.height as u64
189 }
190}
191
192pub struct Output {
198 pub id: u32,
200 pub name: String,
202 pub description: String,
204 pub make: String,
206 pub model: String,
208 pub x: i32,
210 pub y: i32,
211 pub physical_width_mm: u32,
213 pub physical_height_mm: u32,
215 pub subpixel: SubpixelLayout,
217 pub transform: OutputTransform,
219 pub scale: u32,
221 pub modes: Vec<OutputMode>,
223 pub enabled: bool,
225}
226
227impl Output {
228 pub fn new(id: u32, name: &str) -> Self {
230 Self {
231 id,
232 name: String::from(name),
233 description: String::new(),
234 make: String::from("VeridianOS"),
235 model: String::from("Virtual Display"),
236 x: 0,
237 y: 0,
238 physical_width_mm: 0,
239 physical_height_mm: 0,
240 subpixel: SubpixelLayout::Unknown,
241 transform: OutputTransform::Normal,
242 scale: 1,
243 modes: Vec::new(),
244 enabled: true,
245 }
246 }
247
248 pub fn new_virtual(id: u32, name: &str, width: u32, height: u32) -> Self {
250 let mut output = Self::new(id, name);
251 output
252 .modes
253 .push(OutputMode::new_current_preferred(width, height, 60000));
254 output
255 }
256
257 pub fn current_mode(&self) -> Option<&OutputMode> {
259 self.modes.iter().find(|m| m.current)
260 }
261
262 pub fn logical_width(&self) -> u32 {
264 let mode = match self.current_mode() {
265 Some(m) => m,
266 None => return 0,
267 };
268 let (w, h) = if self.transform.swaps_dimensions() {
269 (mode.height, mode.width)
270 } else {
271 (mode.width, mode.height)
272 };
273 let _ = h; w / self.scale
275 }
276
277 pub fn logical_height(&self) -> u32 {
279 let mode = match self.current_mode() {
280 Some(m) => m,
281 None => return 0,
282 };
283 let (w, h) = if self.transform.swaps_dimensions() {
284 (mode.height, mode.width)
285 } else {
286 (mode.width, mode.height)
287 };
288 let _ = w; h / self.scale
290 }
291
292 pub fn pixel_width(&self) -> u32 {
294 self.current_mode().map(|m| m.width).unwrap_or(0)
295 }
296
297 pub fn pixel_height(&self) -> u32 {
299 self.current_mode().map(|m| m.height).unwrap_or(0)
300 }
301
302 pub fn dpi(&self) -> (u32, u32) {
306 if self.physical_width_mm == 0 || self.physical_height_mm == 0 {
307 return (0, 0);
308 }
309 let mode = match self.current_mode() {
310 Some(m) => m,
311 None => return (0, 0),
312 };
313 let dpi_x = (mode.width * 254) / (self.physical_width_mm * 10);
315 let dpi_y = (mode.height * 254) / (self.physical_height_mm * 10);
316 (dpi_x, dpi_y)
317 }
318
319 pub fn contains_point(&self, px: i32, py: i32) -> bool {
322 let w = self.logical_width() as i32;
323 let h = self.logical_height() as i32;
324 px >= self.x && px < self.x + w && py >= self.y && py < self.y + h
325 }
326
327 pub fn bounds(&self) -> (i32, i32, u32, u32) {
329 (self.x, self.y, self.logical_width(), self.logical_height())
330 }
331
332 pub fn set_current_mode(&mut self, index: usize) -> Result<(), KernelError> {
334 if index >= self.modes.len() {
335 return Err(KernelError::InvalidArgument {
336 name: "mode_index",
337 value: "out of range",
338 });
339 }
340 for mode in self.modes.iter_mut() {
341 mode.current = false;
342 }
343 self.modes[index].current = true;
344 Ok(())
345 }
346}
347
348pub struct OutputManager {
357 outputs: BTreeMap<u32, Output>,
359 next_id: AtomicU32,
361 primary_output: Option<u32>,
363}
364
365impl OutputManager {
366 pub fn new() -> Self {
368 Self {
369 outputs: BTreeMap::new(),
370 next_id: AtomicU32::new(1),
371 primary_output: None,
372 }
373 }
374
375 pub fn new_with_framebuffer(width: u32, height: u32) -> Self {
378 let mut manager = Self::new();
379 let id = manager.next_id.fetch_add(1, Ordering::Relaxed);
380 let output = Output::new_virtual(id, "FBCON-1", width, height);
381 manager.outputs.insert(id, output);
382 manager.primary_output = Some(id);
383 manager
384 }
385
386 pub fn add_output(&mut self, mut output: Output) -> u32 {
388 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
389 output.id = id;
390
391 if self.primary_output.is_none() {
393 self.primary_output = Some(id);
394 }
395
396 self.outputs.insert(id, output);
397 id
398 }
399
400 pub fn remove_output(&mut self, id: u32) -> Option<Output> {
405 let output = self.outputs.remove(&id);
406
407 if self.primary_output == Some(id) {
409 self.primary_output = self.outputs.keys().next().copied();
410 }
411
412 output
413 }
414
415 pub fn get_output(&self, id: u32) -> Option<&Output> {
417 self.outputs.get(&id)
418 }
419
420 pub fn get_output_mut(&mut self, id: u32) -> Option<&mut Output> {
422 self.outputs.get_mut(&id)
423 }
424
425 pub fn get_primary(&self) -> Option<&Output> {
427 self.primary_output.and_then(|id| self.outputs.get(&id))
428 }
429
430 pub fn get_primary_id(&self) -> Option<u32> {
432 self.primary_output
433 }
434
435 pub fn set_primary(&mut self, id: u32) -> Result<(), KernelError> {
437 if !self.outputs.contains_key(&id) {
438 return Err(KernelError::NotFound {
439 resource: "output",
440 id: id as u64,
441 });
442 }
443 self.primary_output = Some(id);
444 Ok(())
445 }
446
447 pub fn get_all_outputs(&self) -> Vec<&Output> {
449 self.outputs.values().collect()
450 }
451
452 pub fn output_count(&self) -> usize {
454 self.outputs.len()
455 }
456
457 pub fn get_total_area(&self) -> (i32, i32, u32, u32) {
461 if self.outputs.is_empty() {
462 return (0, 0, 0, 0);
463 }
464
465 let mut min_x = i32::MAX;
466 let mut min_y = i32::MAX;
467 let mut max_x = i32::MIN;
468 let mut max_y = i32::MIN;
469
470 for output in self.outputs.values() {
471 if !output.enabled {
472 continue;
473 }
474 let (ox, oy, ow, oh) = output.bounds();
475 if ox < min_x {
476 min_x = ox;
477 }
478 if oy < min_y {
479 min_y = oy;
480 }
481 let right = ox + ow as i32;
482 let bottom = oy + oh as i32;
483 if right > max_x {
484 max_x = right;
485 }
486 if bottom > max_y {
487 max_y = bottom;
488 }
489 }
490
491 if min_x == i32::MAX {
492 return (0, 0, 0, 0);
493 }
494
495 (min_x, min_y, (max_x - min_x) as u32, (max_y - min_y) as u32)
496 }
497
498 pub fn get_output_at_point(&self, x: i32, y: i32) -> Option<u32> {
502 for output in self.outputs.values() {
503 if output.enabled && output.contains_point(x, y) {
504 return Some(output.id);
505 }
506 }
507 None
508 }
509
510 pub fn set_scale(&mut self, id: u32, scale: u32) -> Result<(), KernelError> {
512 let output = self.outputs.get_mut(&id).ok_or(KernelError::NotFound {
513 resource: "output",
514 id: id as u64,
515 })?;
516
517 if scale == 0 {
518 return Err(KernelError::InvalidArgument {
519 name: "scale",
520 value: "must be >= 1",
521 });
522 }
523
524 output.scale = scale;
525 Ok(())
526 }
527
528 pub fn set_position(&mut self, id: u32, x: i32, y: i32) -> Result<(), KernelError> {
530 let output = self.outputs.get_mut(&id).ok_or(KernelError::NotFound {
531 resource: "output",
532 id: id as u64,
533 })?;
534 output.x = x;
535 output.y = y;
536 Ok(())
537 }
538
539 pub fn set_transform(
541 &mut self,
542 id: u32,
543 transform: OutputTransform,
544 ) -> Result<(), KernelError> {
545 let output = self.outputs.get_mut(&id).ok_or(KernelError::NotFound {
546 resource: "output",
547 id: id as u64,
548 })?;
549 output.transform = transform;
550 Ok(())
551 }
552
553 pub fn handle_hotplug(&mut self, output: Output) -> u32 {
558 let (_, _, total_w, _) = self.get_total_area();
560 let mut new_output = output;
561 new_output.x = total_w as i32;
562 new_output.y = 0;
563 self.add_output(new_output)
564 }
565
566 pub fn arrange_horizontal(&mut self) {
569 let mut x_offset = 0i32;
570 let ids: Vec<u32> = self.outputs.keys().copied().collect();
571 for id in ids {
572 if let Some(output) = self.outputs.get_mut(&id) {
573 if !output.enabled {
574 continue;
575 }
576 output.x = x_offset;
577 output.y = 0;
578 x_offset += output.logical_width() as i32;
579 }
580 }
581 }
582
583 pub fn arrange_vertical(&mut self) {
585 let mut y_offset = 0i32;
586 let ids: Vec<u32> = self.outputs.keys().copied().collect();
587 for id in ids {
588 if let Some(output) = self.outputs.get_mut(&id) {
589 if !output.enabled {
590 continue;
591 }
592 output.x = 0;
593 output.y = y_offset;
594 y_offset += output.logical_height() as i32;
595 }
596 }
597 }
598
599 pub fn set_enabled(&mut self, id: u32, enabled: bool) -> Result<(), KernelError> {
601 let output = self.outputs.get_mut(&id).ok_or(KernelError::NotFound {
602 resource: "output",
603 id: id as u64,
604 })?;
605 output.enabled = enabled;
606
607 if !enabled && self.primary_output == Some(id) {
609 self.primary_output = self
610 .outputs
611 .values()
612 .find(|o| o.enabled && o.id != id)
613 .map(|o| o.id);
614 }
615 Ok(())
616 }
617
618 pub fn scale_at_point(&self, x: i32, y: i32) -> u32 {
623 self.get_output_at_point(x, y)
624 .and_then(|id| self.outputs.get(&id))
625 .map(|o| o.scale)
626 .unwrap_or(1)
627 }
628
629 pub fn outputs_for_rect(&self, x: i32, y: i32, w: u32, h: u32) -> Vec<u32> {
631 let rect_right = x + w as i32;
632 let rect_bottom = y + h as i32;
633 let mut result = Vec::new();
634
635 for output in self.outputs.values() {
636 if !output.enabled {
637 continue;
638 }
639 let (ox, oy, ow, oh) = output.bounds();
640 let out_right = ox + ow as i32;
641 let out_bottom = oy + oh as i32;
642
643 if x < out_right && rect_right > ox && y < out_bottom && rect_bottom > oy {
645 result.push(output.id);
646 }
647 }
648 result
649 }
650}
651
652impl Default for OutputManager {
653 fn default() -> Self {
654 Self::new()
655 }
656}
657
658#[cfg(test)]
663mod tests {
664 use super::*;
665
666 #[test]
667 fn test_output_manager_basic() {
668 let mut mgr = OutputManager::new();
669 assert_eq!(mgr.output_count(), 0);
670
671 let output = Output::new_virtual(0, "TEST-1", 1920, 1080);
672 let id = mgr.add_output(output);
673 assert_eq!(mgr.output_count(), 1);
674 assert_eq!(mgr.get_primary_id(), Some(id));
675 }
676
677 #[test]
678 fn test_output_manager_with_framebuffer() {
679 let mgr = OutputManager::new_with_framebuffer(1280, 800);
680 assert_eq!(mgr.output_count(), 1);
681 let primary = mgr.get_primary().unwrap();
682 assert_eq!(primary.pixel_width(), 1280);
683 assert_eq!(primary.pixel_height(), 800);
684 }
685
686 #[test]
687 fn test_total_area_multi_output() {
688 let mut mgr = OutputManager::new();
689 let out1 = Output::new_virtual(0, "LEFT", 1920, 1080);
690 mgr.add_output(out1);
691 let mut out2 = Output::new_virtual(0, "RIGHT", 1920, 1080);
692 out2.x = 1920;
693 mgr.add_output(out2);
694
695 let (x, y, w, h) = mgr.get_total_area();
696 assert_eq!(x, 0);
697 assert_eq!(y, 0);
698 assert_eq!(w, 3840);
699 assert_eq!(h, 1080);
700 }
701
702 #[test]
703 fn test_output_at_point() {
704 let mut mgr = OutputManager::new();
705 let out1 = Output::new_virtual(0, "LEFT", 1920, 1080);
706 let id1 = mgr.add_output(out1);
707 let mut out2 = Output::new_virtual(0, "RIGHT", 1920, 1080);
708 out2.x = 1920;
709 let id2 = mgr.add_output(out2);
710
711 assert_eq!(mgr.get_output_at_point(100, 100), Some(id1));
712 assert_eq!(mgr.get_output_at_point(2000, 100), Some(id2));
713 assert_eq!(mgr.get_output_at_point(5000, 100), None);
714 }
715
716 #[test]
717 fn test_hidpi_scale() {
718 let mut mgr = OutputManager::new();
719 let output = Output::new_virtual(0, "HIDPI", 3840, 2160);
720 let id = mgr.add_output(output);
721 mgr.set_scale(id, 2).unwrap();
722
723 let out = mgr.get_output(id).unwrap();
724 assert_eq!(out.logical_width(), 1920);
725 assert_eq!(out.logical_height(), 1080);
726 assert_eq!(out.pixel_width(), 3840);
727 assert_eq!(out.pixel_height(), 2160);
728 }
729
730 #[test]
731 fn test_output_transform_swap() {
732 assert!(!OutputTransform::Normal.swaps_dimensions());
733 assert!(OutputTransform::Rotate90.swaps_dimensions());
734 assert!(!OutputTransform::Rotate180.swaps_dimensions());
735 assert!(OutputTransform::Rotate270.swaps_dimensions());
736 }
737
738 #[test]
739 fn test_remove_primary() {
740 let mut mgr = OutputManager::new();
741 let out1 = Output::new_virtual(0, "A", 1920, 1080);
742 let id1 = mgr.add_output(out1);
743 let out2 = Output::new_virtual(0, "B", 1920, 1080);
744 let id2 = mgr.add_output(out2);
745
746 assert_eq!(mgr.get_primary_id(), Some(id1));
747 mgr.remove_output(id1);
748 assert_eq!(mgr.get_primary_id(), Some(id2));
749 }
750
751 #[test]
752 fn test_outputs_for_rect() {
753 let mut mgr = OutputManager::new();
754 let out1 = Output::new_virtual(0, "LEFT", 1920, 1080);
755 let id1 = mgr.add_output(out1);
756 let mut out2 = Output::new_virtual(0, "RIGHT", 1920, 1080);
757 out2.x = 1920;
758 let id2 = mgr.add_output(out2);
759
760 let ids = mgr.outputs_for_rect(1800, 0, 240, 100);
762 assert!(ids.contains(&id1));
763 assert!(ids.contains(&id2));
764
765 let ids = mgr.outputs_for_rect(0, 0, 100, 100);
767 assert!(ids.contains(&id1));
768 assert!(!ids.contains(&id2));
769 }
770
771 #[test]
772 fn test_output_dpi() {
773 let mut output = Output::new_virtual(1, "TEST", 3840, 2160);
774 output.physical_width_mm = 600; output.physical_height_mm = 340;
776
777 let (dpi_x, dpi_y) = output.dpi();
778 assert!(dpi_x > 150 && dpi_x < 170);
780 assert!(dpi_y > 150 && dpi_y < 170);
781 }
782}