1#![allow(dead_code)]
8
9use core::sync::atomic::{AtomicBool, Ordering};
10
11use spin::Mutex;
12
13use crate::error::KernelError;
14
15const MAX_OUTPUTS: usize = 8;
21
22const DEFAULT_REFRESH_MHZ: u32 = 60000;
24
25#[derive(Debug, Clone, Copy)]
31pub struct DisplayOutput {
32 pub id: u32,
34 pub connector_id: u32,
36 pub crtc_id: u32,
38 pub width: u32,
40 pub height: u32,
42 pub refresh_hz: u32,
44 pub x_offset: i32,
46 pub y_offset: i32,
48 pub enabled: bool,
50 pub primary: bool,
52 pub connected: bool,
54 pub physical_width_mm: u32,
56 pub physical_height_mm: u32,
58}
59
60impl Default for DisplayOutput {
61 fn default() -> Self {
62 Self {
63 id: 0,
64 connector_id: 0,
65 crtc_id: 0,
66 width: 0,
67 height: 0,
68 refresh_hz: DEFAULT_REFRESH_MHZ,
69 x_offset: 0,
70 y_offset: 0,
71 enabled: false,
72 primary: false,
73 connected: false,
74 physical_width_mm: 0,
75 physical_height_mm: 0,
76 }
77 }
78}
79
80impl DisplayOutput {
81 pub fn right_edge(&self) -> i32 {
83 self.x_offset.saturating_add(self.width as i32)
84 }
85
86 pub fn bottom_edge(&self) -> i32 {
88 self.y_offset.saturating_add(self.height as i32)
89 }
90
91 pub fn contains_point(&self, x: i32, y: i32) -> bool {
93 if !self.enabled {
94 return false;
95 }
96 x >= self.x_offset && x < self.right_edge() && y >= self.y_offset && y < self.bottom_edge()
97 }
98}
99
100pub struct MultiOutputManager {
102 outputs: [DisplayOutput; MAX_OUTPUTS],
104 num_outputs: usize,
106 next_id: u32,
108 total_width: u32,
110 total_height: u32,
112 initialized: bool,
114}
115
116impl Default for MultiOutputManager {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl MultiOutputManager {
123 pub const fn new() -> Self {
125 const DEFAULT: DisplayOutput = DisplayOutput {
126 id: 0,
127 connector_id: 0,
128 crtc_id: 0,
129 width: 0,
130 height: 0,
131 refresh_hz: DEFAULT_REFRESH_MHZ,
132 x_offset: 0,
133 y_offset: 0,
134 enabled: false,
135 primary: false,
136 connected: false,
137 physical_width_mm: 0,
138 physical_height_mm: 0,
139 };
140 Self {
141 outputs: [DEFAULT; MAX_OUTPUTS],
142 num_outputs: 0,
143 next_id: 1,
144 total_width: 0,
145 total_height: 0,
146 initialized: false,
147 }
148 }
149
150 pub fn init(&mut self) {
152 self.num_outputs = 0;
153 self.next_id = 1;
154 self.total_width = 0;
155 self.total_height = 0;
156 self.initialized = true;
157 }
158
159 pub fn add_output(
163 &mut self,
164 connector_id: u32,
165 crtc_id: u32,
166 width: u32,
167 height: u32,
168 refresh_hz: u32,
169 ) -> Result<u32, KernelError> {
170 if self.num_outputs >= MAX_OUTPUTS {
171 return Err(KernelError::ResourceExhausted {
172 resource: "display outputs",
173 });
174 }
175
176 let id = self.next_id;
177 self.next_id = self.next_id.saturating_add(1);
178
179 let x_offset = self.total_width as i32;
181 let is_primary = self.num_outputs == 0;
182
183 self.outputs[self.num_outputs] = DisplayOutput {
184 id,
185 connector_id,
186 crtc_id,
187 width,
188 height,
189 refresh_hz,
190 x_offset,
191 y_offset: 0,
192 enabled: true,
193 primary: is_primary,
194 connected: true,
195 physical_width_mm: 0,
196 physical_height_mm: 0,
197 };
198
199 self.num_outputs += 1;
200 self.recalculate_total_size();
201
202 Ok(id)
203 }
204
205 pub fn remove_output(&mut self, output_id: u32) -> Result<(), KernelError> {
207 let idx = self.find_output_index(output_id)?;
208
209 for i in idx..self.num_outputs.saturating_sub(1) {
211 self.outputs[i] = self.outputs[i + 1];
212 }
213
214 if self.num_outputs > 0 {
215 self.num_outputs -= 1;
216 self.outputs[self.num_outputs] = DisplayOutput::default();
217 }
218
219 if self.num_outputs > 0 {
221 let has_primary = self.outputs[..self.num_outputs].iter().any(|o| o.primary);
222 if !has_primary {
223 self.outputs[0].primary = true;
224 }
225 }
226
227 self.recalculate_total_size();
228 Ok(())
229 }
230
231 pub fn set_position(&mut self, output_id: u32, x: i32, y: i32) -> Result<(), KernelError> {
233 let idx = self.find_output_index(output_id)?;
234 self.outputs[idx].x_offset = x;
235 self.outputs[idx].y_offset = y;
236 self.recalculate_total_size();
237 Ok(())
238 }
239
240 pub fn set_primary(&mut self, output_id: u32) -> Result<(), KernelError> {
242 let idx = self.find_output_index(output_id)?;
243
244 for i in 0..self.num_outputs {
246 self.outputs[i].primary = false;
247 }
248
249 self.outputs[idx].primary = true;
250 Ok(())
251 }
252
253 pub fn get_outputs(&self) -> &[DisplayOutput] {
255 &self.outputs[..self.num_outputs]
256 }
257
258 pub fn get_total_size(&self) -> (u32, u32) {
260 (self.total_width, self.total_height)
261 }
262
263 pub fn point_to_output(&self, x: i32, y: i32) -> Option<(u32, i32, i32)> {
268 for i in 0..self.num_outputs {
269 let output = &self.outputs[i];
270 if output.enabled && output.contains_point(x, y) {
271 let local_x = x - output.x_offset;
272 let local_y = y - output.y_offset;
273 return Some((output.id, local_x, local_y));
274 }
275 }
276 None
277 }
278
279 pub fn primary_output(&self) -> Option<&DisplayOutput> {
281 self.outputs[..self.num_outputs].iter().find(|o| o.primary)
282 }
283
284 pub fn get_output(&self, output_id: u32) -> Option<&DisplayOutput> {
286 self.outputs[..self.num_outputs]
287 .iter()
288 .find(|o| o.id == output_id)
289 }
290
291 pub fn get_output_mut(&mut self, output_id: u32) -> Option<&mut DisplayOutput> {
293 self.outputs[..self.num_outputs]
294 .iter_mut()
295 .find(|o| o.id == output_id)
296 }
297
298 pub fn flip(&self, output_id: u32, _buffer_id: u32) -> Result<(), KernelError> {
302 let _output = self.get_output(output_id).ok_or(KernelError::NotFound {
303 resource: "output",
304 id: output_id as u64,
305 })?;
306 Ok(())
308 }
309
310 pub fn handle_hotplug(
315 &mut self,
316 connector_id: u32,
317 connected: bool,
318 width: u32,
319 height: u32,
320 refresh_hz: u32,
321 ) -> Result<(), KernelError> {
322 if connected {
323 let existing = self.outputs[..self.num_outputs]
325 .iter()
326 .any(|o| o.connector_id == connector_id);
327
328 if !existing {
329 self.add_output(connector_id, 0, width, height, refresh_hz)?;
330 crate::println!(
331 "[MULTI-OUTPUT] Connector {} connected ({}x{}@{}Hz)",
332 connector_id,
333 width,
334 height,
335 refresh_hz / 1000
336 );
337 }
338 } else {
339 if let Some(output_id) = self.outputs[..self.num_outputs]
341 .iter()
342 .find(|o| o.connector_id == connector_id)
343 .map(|o| o.id)
344 {
345 self.remove_output(output_id)?;
346 crate::println!("[MULTI-OUTPUT] Connector {} disconnected", connector_id);
347 }
348 }
349 Ok(())
350 }
351
352 pub fn auto_layout(&mut self) {
354 let mut x_offset: i32 = 0;
355 for i in 0..self.num_outputs {
356 self.outputs[i].x_offset = x_offset;
357 self.outputs[i].y_offset = 0;
358 x_offset = x_offset.saturating_add(self.outputs[i].width as i32);
359 }
360 self.recalculate_total_size();
361 }
362
363 pub fn num_outputs(&self) -> usize {
365 self.num_outputs
366 }
367
368 pub fn is_initialized(&self) -> bool {
370 self.initialized
371 }
372
373 fn find_output_index(&self, output_id: u32) -> Result<usize, KernelError> {
377 for i in 0..self.num_outputs {
378 if self.outputs[i].id == output_id {
379 return Ok(i);
380 }
381 }
382 Err(KernelError::NotFound {
383 resource: "output",
384 id: output_id as u64,
385 })
386 }
387
388 fn recalculate_total_size(&mut self) {
390 let mut max_right: i32 = 0;
391 let mut max_bottom: i32 = 0;
392
393 for i in 0..self.num_outputs {
394 let output = &self.outputs[i];
395 if output.enabled {
396 let right = output.right_edge();
397 let bottom = output.bottom_edge();
398 if right > max_right {
399 max_right = right;
400 }
401 if bottom > max_bottom {
402 max_bottom = bottom;
403 }
404 }
405 }
406
407 self.total_width = if max_right > 0 { max_right as u32 } else { 0 };
408 self.total_height = if max_bottom > 0 { max_bottom as u32 } else { 0 };
409 }
410}
411
412static MULTI_OUTPUT: Mutex<MultiOutputManager> = Mutex::new(MultiOutputManager::new());
417
418static MULTI_OUTPUT_INITIALIZED: AtomicBool = AtomicBool::new(false);
419
420pub fn multi_output_init() {
422 let mut mgr = MULTI_OUTPUT.lock();
423 mgr.init();
424 MULTI_OUTPUT_INITIALIZED.store(true, Ordering::Release);
425 crate::println!(
426 "[MULTI-OUTPUT] Display manager initialized (max {} outputs)",
427 MAX_OUTPUTS
428 );
429}
430
431pub fn multi_output_add(
433 connector_id: u32,
434 crtc_id: u32,
435 width: u32,
436 height: u32,
437 refresh_hz: u32,
438) -> Result<u32, KernelError> {
439 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
440 return Err(KernelError::NotInitialized {
441 subsystem: "multi_output",
442 });
443 }
444 let mut mgr = MULTI_OUTPUT.lock();
445 let id = mgr.add_output(connector_id, crtc_id, width, height, refresh_hz)?;
446 if let Some(output) = mgr.get_output_mut(id) {
448 output.crtc_id = crtc_id;
449 }
450 Ok(id)
451}
452
453pub fn multi_output_remove(output_id: u32) -> Result<(), KernelError> {
455 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
456 return Err(KernelError::NotInitialized {
457 subsystem: "multi_output",
458 });
459 }
460 MULTI_OUTPUT.lock().remove_output(output_id)
461}
462
463pub fn multi_output_set_position(output_id: u32, x: i32, y: i32) -> Result<(), KernelError> {
465 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
466 return Err(KernelError::NotInitialized {
467 subsystem: "multi_output",
468 });
469 }
470 MULTI_OUTPUT.lock().set_position(output_id, x, y)
471}
472
473pub fn multi_output_set_primary(output_id: u32) -> Result<(), KernelError> {
475 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
476 return Err(KernelError::NotInitialized {
477 subsystem: "multi_output",
478 });
479 }
480 MULTI_OUTPUT.lock().set_primary(output_id)
481}
482
483pub fn multi_output_get_total_size() -> (u32, u32) {
485 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
486 return (0, 0);
487 }
488 MULTI_OUTPUT.lock().get_total_size()
489}
490
491pub fn multi_output_point_to_output(x: i32, y: i32) -> Option<(u32, i32, i32)> {
493 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
494 return None;
495 }
496 MULTI_OUTPUT.lock().point_to_output(x, y)
497}
498
499pub fn multi_output_handle_hotplug(
501 connector_id: u32,
502 connected: bool,
503 width: u32,
504 height: u32,
505 refresh_hz: u32,
506) -> Result<(), KernelError> {
507 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
508 return Err(KernelError::NotInitialized {
509 subsystem: "multi_output",
510 });
511 }
512 MULTI_OUTPUT
513 .lock()
514 .handle_hotplug(connector_id, connected, width, height, refresh_hz)
515}
516
517pub fn multi_output_flip(output_id: u32, buffer_id: u32) -> Result<(), KernelError> {
519 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
520 return Err(KernelError::NotInitialized {
521 subsystem: "multi_output",
522 });
523 }
524 MULTI_OUTPUT.lock().flip(output_id, buffer_id)
525}
526
527pub fn multi_output_count() -> usize {
529 if !MULTI_OUTPUT_INITIALIZED.load(Ordering::Acquire) {
530 return 0;
531 }
532 MULTI_OUTPUT.lock().num_outputs()
533}
534
535#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn test_display_output_default() {
545 let output = DisplayOutput::default();
546 assert!(!output.enabled);
547 assert!(!output.primary);
548 assert_eq!(output.width, 0);
549 assert_eq!(output.height, 0);
550 }
551
552 #[test]
553 fn test_display_output_contains_point() {
554 let mut output = DisplayOutput::default();
555 output.enabled = true;
556 output.width = 1920;
557 output.height = 1080;
558 output.x_offset = 100;
559 output.y_offset = 50;
560
561 assert!(output.contains_point(100, 50));
562 assert!(output.contains_point(500, 500));
563 assert!(output.contains_point(2019, 1129));
564 assert!(!output.contains_point(99, 50));
565 assert!(!output.contains_point(100, 49));
566 assert!(!output.contains_point(2020, 500));
567 }
568
569 #[test]
570 fn test_display_output_disabled_no_contains() {
571 let mut output = DisplayOutput::default();
572 output.width = 1920;
573 output.height = 1080;
574 assert!(!output.contains_point(500, 500));
576 }
577
578 #[test]
579 fn test_display_output_edges() {
580 let mut output = DisplayOutput::default();
581 output.x_offset = 100;
582 output.y_offset = 200;
583 output.width = 1920;
584 output.height = 1080;
585
586 assert_eq!(output.right_edge(), 2020);
587 assert_eq!(output.bottom_edge(), 1280);
588 }
589
590 #[test]
591 fn test_multi_output_manager_new() {
592 let mgr = MultiOutputManager::new();
593 assert!(!mgr.is_initialized());
594 assert_eq!(mgr.num_outputs(), 0);
595 }
596
597 #[test]
598 fn test_multi_output_manager_init() {
599 let mut mgr = MultiOutputManager::new();
600 mgr.init();
601 assert!(mgr.is_initialized());
602 assert_eq!(mgr.num_outputs(), 0);
603 assert_eq!(mgr.get_total_size(), (0, 0));
604 }
605
606 #[test]
607 fn test_add_output() {
608 let mut mgr = MultiOutputManager::new();
609 mgr.init();
610
611 let id = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
612 assert_eq!(mgr.num_outputs(), 1);
613 assert_eq!(mgr.get_total_size(), (1920, 1080));
614
615 let output = mgr.get_output(id).unwrap();
616 assert!(output.primary);
617 assert!(output.enabled);
618 assert_eq!(output.x_offset, 0);
619 }
620
621 #[test]
622 fn test_add_two_outputs() {
623 let mut mgr = MultiOutputManager::new();
624 mgr.init();
625
626 let id1 = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
627 let id2 = mgr.add_output(2, 20, 2560, 1440, 60000).unwrap();
628
629 assert_eq!(mgr.num_outputs(), 2);
630 assert_eq!(mgr.get_total_size(), (4480, 1440));
631
632 let o1 = mgr.get_output(id1).unwrap();
633 assert_eq!(o1.x_offset, 0);
634 assert!(o1.primary);
635
636 let o2 = mgr.get_output(id2).unwrap();
637 assert_eq!(o2.x_offset, 1920);
638 assert!(!o2.primary);
639 }
640
641 #[test]
642 fn test_remove_output() {
643 let mut mgr = MultiOutputManager::new();
644 mgr.init();
645
646 let id1 = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
647 let _id2 = mgr.add_output(2, 20, 2560, 1440, 60000).unwrap();
648
649 mgr.remove_output(id1).unwrap();
650 assert_eq!(mgr.num_outputs(), 1);
651 }
652
653 #[test]
654 fn test_remove_nonexistent() {
655 let mut mgr = MultiOutputManager::new();
656 mgr.init();
657
658 assert!(mgr.remove_output(999).is_err());
659 }
660
661 #[test]
662 fn test_max_outputs() {
663 let mut mgr = MultiOutputManager::new();
664 mgr.init();
665
666 for i in 0..MAX_OUTPUTS {
667 assert!(mgr
668 .add_output(i as u32, i as u32 * 10, 1920, 1080, 60000)
669 .is_ok());
670 }
671 assert!(mgr.add_output(99, 990, 1920, 1080, 60000).is_err());
673 }
674
675 #[test]
676 fn test_set_position() {
677 let mut mgr = MultiOutputManager::new();
678 mgr.init();
679
680 let id = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
681 mgr.set_position(id, 500, 300).unwrap();
682
683 let output = mgr.get_output(id).unwrap();
684 assert_eq!(output.x_offset, 500);
685 assert_eq!(output.y_offset, 300);
686 }
687
688 #[test]
689 fn test_set_primary() {
690 let mut mgr = MultiOutputManager::new();
691 mgr.init();
692
693 let id1 = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
694 let id2 = mgr.add_output(2, 20, 2560, 1440, 60000).unwrap();
695
696 mgr.set_primary(id2).unwrap();
697
698 assert!(!mgr.get_output(id1).unwrap().primary);
699 assert!(mgr.get_output(id2).unwrap().primary);
700 }
701
702 #[test]
703 fn test_point_to_output() {
704 let mut mgr = MultiOutputManager::new();
705 mgr.init();
706
707 let id1 = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
708 let id2 = mgr.add_output(2, 20, 2560, 1440, 60000).unwrap();
709
710 let result = mgr.point_to_output(500, 500);
712 assert!(result.is_some());
713 let (oid, lx, ly) = result.unwrap();
714 assert_eq!(oid, id1);
715 assert_eq!(lx, 500);
716 assert_eq!(ly, 500);
717
718 let result = mgr.point_to_output(2000, 500);
720 assert!(result.is_some());
721 let (oid, lx, _ly) = result.unwrap();
722 assert_eq!(oid, id2);
723 assert_eq!(lx, 80); assert!(mgr.point_to_output(5000, 5000).is_none());
727 }
728
729 #[test]
730 fn test_auto_layout() {
731 let mut mgr = MultiOutputManager::new();
732 mgr.init();
733
734 let id1 = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
735 let id2 = mgr.add_output(2, 20, 2560, 1440, 60000).unwrap();
736
737 mgr.set_position(id1, 500, 300).unwrap();
739 mgr.set_position(id2, -100, 200).unwrap();
740
741 mgr.auto_layout();
743
744 let o1 = mgr.get_output(id1).unwrap();
745 assert_eq!(o1.x_offset, 0);
746 assert_eq!(o1.y_offset, 0);
747
748 let o2 = mgr.get_output(id2).unwrap();
749 assert_eq!(o2.x_offset, 1920);
750 assert_eq!(o2.y_offset, 0);
751 }
752
753 #[test]
754 fn test_handle_hotplug_connect() {
755 let mut mgr = MultiOutputManager::new();
756 mgr.init();
757
758 mgr.handle_hotplug(1, true, 1920, 1080, 60000).unwrap();
759 assert_eq!(mgr.num_outputs(), 1);
760 }
761
762 #[test]
763 fn test_handle_hotplug_disconnect() {
764 let mut mgr = MultiOutputManager::new();
765 mgr.init();
766
767 mgr.handle_hotplug(1, true, 1920, 1080, 60000).unwrap();
768 assert_eq!(mgr.num_outputs(), 1);
769
770 mgr.handle_hotplug(1, false, 0, 0, 0).unwrap();
771 assert_eq!(mgr.num_outputs(), 0);
772 }
773
774 #[test]
775 fn test_handle_hotplug_duplicate_connect() {
776 let mut mgr = MultiOutputManager::new();
777 mgr.init();
778
779 mgr.handle_hotplug(1, true, 1920, 1080, 60000).unwrap();
780 mgr.handle_hotplug(1, true, 1920, 1080, 60000).unwrap();
781 assert_eq!(mgr.num_outputs(), 1); }
783
784 #[test]
785 fn test_flip() {
786 let mut mgr = MultiOutputManager::new();
787 mgr.init();
788
789 let id = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
790 assert!(mgr.flip(id, 0).is_ok());
791 assert!(mgr.flip(999, 0).is_err());
792 }
793
794 #[test]
795 fn test_primary_output() {
796 let mut mgr = MultiOutputManager::new();
797 mgr.init();
798
799 assert!(mgr.primary_output().is_none());
800
801 mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
802 assert!(mgr.primary_output().is_some());
803 assert!(mgr.primary_output().unwrap().primary);
804 }
805
806 #[test]
807 fn test_primary_promotion_on_remove() {
808 let mut mgr = MultiOutputManager::new();
809 mgr.init();
810
811 let id1 = mgr.add_output(1, 10, 1920, 1080, 60000).unwrap();
812 let _id2 = mgr.add_output(2, 20, 2560, 1440, 60000).unwrap();
813
814 mgr.remove_output(id1).unwrap();
816
817 assert!(mgr.primary_output().is_some());
819 assert!(mgr.primary_output().unwrap().primary);
820 }
821}