1#![allow(dead_code)]
11
12use alloc::vec;
13use core::sync::atomic::{AtomicU32, Ordering};
14
15use crate::graphics::{
16 cursor,
17 fbcon::{self, FbPixelFormat},
18};
19
20static NEXT_SURFACE_ID: AtomicU32 = AtomicU32::new(2000);
22static NEXT_POOL_ID: AtomicU32 = AtomicU32::new(200);
23
24pub fn start_desktop() {
33 let hw = match fbcon::get_hw_info() {
34 Some(info) => info,
35 None => {
36 crate::println!("[DESKTOP] No framebuffer available -- cannot start GUI");
37 return;
38 }
39 };
40
41 crate::println!(
42 "[DESKTOP] Starting GUI on {}x{} framebuffer (stride={}, bpp={})",
43 hw.width,
44 hw.height,
45 hw.stride,
46 hw.bpp,
47 );
48
49 match crate::desktop::init() {
52 Ok(()) => {}
53 Err(crate::error::KernelError::InvalidState { .. }) => {
54 crate::println!("[DESKTOP] Desktop subsystem already initialized, proceeding");
55 }
56 Err(e) => {
57 crate::println!("[DESKTOP] Failed to initialize desktop: {:?}", e);
58 return;
59 }
60 }
61
62 crate::desktop::wayland::with_display(|display| {
64 display
65 .wl_compositor
66 .set_output_size(hw.width as u32, hw.height as u32);
67 });
68
69 crate::println!(
70 "[DESKTOP] Compositor configured for {}x{}",
71 hw.width,
72 hw.height,
73 );
74
75 let mut state = create_desktop_scene(hw.width as u32, hw.height as u32);
77
78 fbcon::disable_output();
80
81 unsafe {
84 core::ptr::write_bytes(hw.fb_ptr, 0, hw.stride * hw.height);
85 }
86
87 crate::serial::_serial_print(format_args!("[DESKTOP] Entering compositor render loop\n"));
88
89 crate::drivers::keyboard::set_gui_mode(true);
93
94 render_loop(&hw, &mut state);
96
97 crate::drivers::keyboard::set_gui_mode(false);
99
100 unsafe {
104 core::ptr::write_bytes(hw.fb_ptr, 0, hw.stride * hw.height);
105 }
106
107 fbcon::mark_all_dirty_and_flush();
109 crate::println!("[DESKTOP] GUI stopped, returning to text console");
110}
111
112struct AppInfo {
114 wid: u32,
115 surface_id: u32,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120enum AppKind {
121 ImageViewer,
122 Settings,
123 MediaPlayer,
124 SystemMonitor,
125 Browser,
126 PdfViewer,
127 Calculator,
128}
129
130struct DynamicApp {
132 kind: AppKind,
133 wid: u32,
134 surface_id: u32,
135 pool_id: u32,
136 pool_buf_id: u32,
137 width: u32,
138 height: u32,
139}
140
141struct CalculatorState {
143 display_value: i64,
144 accumulator: i64,
145 pending_op: Option<u8>,
146 input_started: bool,
147 error: bool,
148}
149
150impl CalculatorState {
151 fn new() -> Self {
152 Self {
153 display_value: 0,
154 accumulator: 0,
155 pending_op: None,
156 input_started: false,
157 error: false,
158 }
159 }
160
161 fn handle_key(&mut self, key: u8) {
162 if self.error && key != b'C' {
163 return;
164 }
165 match key {
166 b'0'..=b'9' => {
167 let digit = (key - b'0') as i64;
168 if !self.input_started {
169 self.display_value = digit;
170 self.input_started = true;
171 } else {
172 self.display_value =
173 self.display_value.saturating_mul(10).saturating_add(digit);
174 }
175 }
176 b'+' | b'-' | b'*' | b'/' => {
177 self.evaluate_pending();
178 self.accumulator = self.display_value;
179 self.pending_op = Some(key);
180 self.input_started = false;
181 }
182 b'=' => {
183 self.evaluate_pending();
184 self.pending_op = None;
185 self.input_started = false;
186 }
187 b'C' => {
188 *self = Self::new();
189 }
190 b'<' => {
191 self.display_value /= 10;
192 }
193 _ => {}
194 }
195 }
196
197 fn evaluate_pending(&mut self) {
198 if let Some(op) = self.pending_op {
199 let result = match op {
200 b'+' => self.accumulator.checked_add(self.display_value),
201 b'-' => self.accumulator.checked_sub(self.display_value),
202 b'*' => self.accumulator.checked_mul(self.display_value),
203 b'/' => {
204 if self.display_value == 0 {
205 self.error = true;
206 self.display_value = 0;
207 return;
208 }
209 self.accumulator.checked_div(self.display_value)
210 }
211 _ => Some(self.display_value),
212 };
213 match result {
214 Some(v) => self.display_value = v,
215 None => {
216 self.error = true;
217 self.display_value = 0;
218 }
219 }
220 self.accumulator = self.display_value;
221 }
222 }
223}
224
225struct DesktopState {
227 terminal: AppInfo,
229 file_manager: AppInfo,
230 text_editor: AppInfo,
231 panel_surface_id: u32,
232 panel_pool_id: u32,
233 panel_pool_buf_id: u32,
234
235 app_switcher: crate::desktop::app_switcher::AppSwitcher,
237 screen_locker: crate::desktop::screen_lock::ScreenLocker,
238 animation_mgr: crate::desktop::animation::AnimationManager,
239
240 settings_app: crate::desktop::settings::SettingsApp,
242
243 image_viewer: crate::desktop::image_viewer::ImageViewer,
245
246 calculator: CalculatorState,
248
249 theme: crate::desktop::desktop_ext::theme::ThemeManager,
251
252 clipboard: crate::desktop::desktop_ext::clipboard::ClipboardManager,
254
255 dnd: crate::desktop::desktop_ext::dnd::DndManager,
257
258 icon_grid: crate::desktop::desktop_icons::IconGrid,
260
261 browser: Option<crate::browser::browser_main::Browser>,
263
264 pdf_page_index: usize,
266
267 dynamic_apps: alloc::vec::Vec<DynamicApp>,
269
270 frame_count: u64,
272 drag: Option<DragState>,
273 prev_focused: Option<u32>,
274}
275
276impl DesktopState {
277 fn surface_for_window(&self, wid: u32) -> Option<u32> {
279 if wid > 0 && wid == self.terminal.wid {
280 return Some(self.terminal.surface_id);
281 }
282 if wid > 0 && wid == self.file_manager.wid {
283 return Some(self.file_manager.surface_id);
284 }
285 if wid > 0 && wid == self.text_editor.wid {
286 return Some(self.text_editor.surface_id);
287 }
288 for app in &self.dynamic_apps {
289 if wid > 0 && wid == app.wid {
290 return Some(app.surface_id);
291 }
292 }
293 None
294 }
295}
296
297fn create_desktop_scene(width: u32, height: u32) -> DesktopState {
299 let bg_surface_id = 1000;
301 crate::desktop::wayland::with_display(|display| {
302 let _ = display.wl_compositor.create_surface(bg_surface_id);
303 display
304 .wl_compositor
305 .set_surface_position(bg_surface_id, 0, 0);
306 });
307
308 let pool_size = (width as usize) * (height as usize) * 4;
309 let mut bg_pixels = vec![0u8; pool_size];
310 paint_gradient_background(&mut bg_pixels, width as usize, height as usize);
311
312 let panel_h = crate::desktop::panel::PANEL_HEIGHT;
315 let icon_grid = create_default_icon_grid(width, height - panel_h);
316 render_icons_into_bgra(&icon_grid, &mut bg_pixels, width as usize, height as usize);
317
318 let pool_id = 100;
319 let mut pool = crate::desktop::wayland::buffer::WlShmPool::new(pool_id, 0, pool_size);
320 pool.write_data(0, &bg_pixels);
321 let buf_id = pool
322 .create_buffer(
323 0,
324 width,
325 height,
326 width * 4,
327 crate::graphics::PixelFormat::Xrgb8888,
328 )
329 .unwrap_or(0);
330 crate::desktop::wayland::buffer::register_pool(pool);
331
332 let bg_buffer = crate::desktop::wayland::buffer::Buffer::from_pool(
333 1,
334 pool_id,
335 buf_id,
336 width,
337 height,
338 width * 4,
339 crate::graphics::PixelFormat::Xrgb8888,
340 );
341 crate::desktop::wayland::with_display(|display| {
342 display
343 .wl_compositor
344 .with_surface_mut(bg_surface_id, |surface| {
345 surface.attach_buffer(bg_buffer.clone());
346 surface.damage_full();
347 let _ = surface.commit();
348 });
349 display.wl_compositor.request_composite();
350 });
351
352 let _ = crate::desktop::panel::init(width, height);
354 let (panel_surface_id, panel_pool_id, panel_pool_buf_id) =
355 create_app_surface(0, (height - panel_h) as i32, width, panel_h);
356
357 let (terminal_wid, terminal_sid) =
360 crate::desktop::terminal::with_terminal_manager(|tm| match tm.create_terminal(640, 384) {
361 Ok(idx) => (
362 tm.get_window_id(idx).unwrap_or(0),
363 tm.get_surface_id(idx).unwrap_or(0),
364 ),
365 Err(_) => (0, 0),
366 })
367 .unwrap_or((0, 0));
368
369 let (file_manager_wid, file_manager_sid) =
371 if crate::desktop::file_manager::create_file_manager().is_ok() {
372 crate::desktop::file_manager::with_file_manager(|fm| {
373 let r = fm.read();
374 (r.window_id(), r.surface_id())
375 })
376 .unwrap_or((0, 0))
377 } else {
378 (0, 0)
379 };
380
381 let (text_editor_wid, text_editor_sid) =
383 if crate::desktop::text_editor::create_text_editor(None).is_ok() {
384 crate::desktop::text_editor::with_text_editor(|te| {
385 let r = te.read();
386 (r.window_id(), r.surface_id())
387 })
388 .unwrap_or((0, 0))
389 } else {
390 (0, 0)
391 };
392
393 crate::desktop::window_manager::with_window_manager(|wm| {
395 wm.set_window_title(terminal_wid, "Terminal");
396 wm.set_window_title(file_manager_wid, "Files");
397 wm.set_window_title(text_editor_wid, "Text Editor");
398 });
399
400 crate::desktop::terminal::with_terminal_manager(|tm| {
402 tm.write_welcome(0);
403 });
404
405 if terminal_wid > 0 {
407 crate::desktop::window_manager::with_window_manager(|wm| {
408 let _ = wm.focus_window(terminal_wid);
409 });
410 crate::desktop::wayland::with_display(|display| {
412 display.wl_compositor.raise_surface(terminal_sid);
413 display.wl_compositor.raise_surface(panel_surface_id);
414 });
415 }
416
417 crate::serial::_serial_print(format_args!(
418 "[DESKTOP] Desktop scene created: bg + terminal({}/{}) + files({}/{}) + editor({}/{}) + \
419 panel\n",
420 terminal_wid,
421 terminal_sid,
422 file_manager_wid,
423 file_manager_sid,
424 text_editor_wid,
425 text_editor_sid
426 ));
427
428 crate::desktop::notification::notify(
430 "VeridianOS Desktop",
431 "Welcome to VeridianOS v0.25.2",
432 crate::desktop::notification::NotificationUrgency::Normal,
433 "desktop",
434 );
435
436 DesktopState {
437 terminal: AppInfo {
438 wid: terminal_wid,
439 surface_id: terminal_sid,
440 },
441 file_manager: AppInfo {
442 wid: file_manager_wid,
443 surface_id: file_manager_sid,
444 },
445 text_editor: AppInfo {
446 wid: text_editor_wid,
447 surface_id: text_editor_sid,
448 },
449 panel_surface_id,
450 panel_pool_id,
451 panel_pool_buf_id,
452 app_switcher: crate::desktop::app_switcher::AppSwitcher::new(),
453 screen_locker: crate::desktop::screen_lock::ScreenLocker::new(
454 width as usize,
455 height as usize,
456 ),
457 animation_mgr: crate::desktop::animation::AnimationManager::new(),
458 settings_app: crate::desktop::settings::SettingsApp::new(),
459 image_viewer: crate::desktop::image_viewer::ImageViewer::new(),
460 calculator: CalculatorState::new(),
461 theme: crate::desktop::desktop_ext::theme::ThemeManager::new(),
462 clipboard: crate::desktop::desktop_ext::clipboard::ClipboardManager::new(),
463 dnd: crate::desktop::desktop_ext::dnd::DndManager::new(),
464 icon_grid,
465 browser: None,
466 pdf_page_index: 0,
467 dynamic_apps: alloc::vec::Vec::new(),
468 frame_count: 0,
469 drag: None,
470 prev_focused: None,
471 }
472}
473
474fn create_default_icon_grid(
476 desktop_width: u32,
477 desktop_height: u32,
478) -> crate::desktop::desktop_icons::IconGrid {
479 use crate::desktop::desktop_icons::{DesktopIcon, IconGrid};
480
481 let mut grid = IconGrid::new(desktop_width, desktop_height);
482
483 let apps = [
484 ("Terminal", "terminal", 0xFF2ECC71u32),
485 ("Files", "files", 0xFF3498DB),
486 ("Editor", "editor", 0xFFE67E22),
487 ("Settings", "settings", 0xFF9B59B6),
488 ("Browser", "browser", 0xFF1ABC9C),
489 ];
490
491 for (name, _exec, color) in &apps {
492 let mut icon = DesktopIcon::new(name, 0, 0);
493 let pixel_count = (crate::desktop::desktop_icons::ICON_SIZE
495 * crate::desktop::desktop_icons::ICON_SIZE) as usize;
496 let data = alloc::vec![*color; pixel_count];
497 icon.set_icon_data(&data);
498 grid.add_icon(icon);
499 }
500
501 grid.arrange();
502 grid
503}
504
505fn render_icons_into_bgra(
510 grid: &crate::desktop::desktop_icons::IconGrid,
511 buf: &mut [u8],
512 buf_width: usize,
513 buf_height: usize,
514) {
515 let icon_sz = crate::desktop::desktop_icons::ICON_SIZE as i32;
516 let bw = buf_width as i32;
517 let bh = buf_height as i32;
518
519 for icon in &grid.icons {
520 for row in 0..icon_sz {
522 let dy = icon.y + row;
523 if dy < 0 || dy >= bh {
524 continue;
525 }
526 for col in 0..icon_sz {
527 let dx = icon.x + col;
528 if dx < 0 || dx >= bw {
529 continue;
530 }
531 let src = icon.icon_data[(row * icon_sz + col) as usize];
532 let off = ((dy * bw + dx) as usize) * 4;
533 if off + 3 < buf.len() {
534 buf[off] = (src & 0xFF) as u8; buf[off + 1] = ((src >> 8) & 0xFF) as u8; buf[off + 2] = ((src >> 16) & 0xFF) as u8; buf[off + 3] = 0xFF; }
539 }
540 }
541
542 let label_x = icon.x.max(0) as usize;
544 let label_y = (icon.y + icon_sz + 2).max(0) as usize;
545 let name_bytes = icon.name.as_bytes();
546 let label_len = name_bytes.len().min(8);
547 for (ci, &ch) in name_bytes[..label_len].iter().enumerate() {
548 draw_char_into_buffer(buf, buf_width, ch, label_x + ci * 8, label_y, 0xCCCCCC);
549 }
550 }
551}
552
553pub fn draw_string_into_buffer(
559 buf: &mut [u8],
560 buf_width: usize,
561 text: &[u8],
562 px: usize,
563 py: usize,
564 color: u32,
565) {
566 let mut cursor_x = px;
567 for &ch in text.iter() {
568 let w = crate::desktop::desktop_ext::cjk::char_width(ch as char);
569 if w == 0 {
570 continue; }
572 draw_char_into_buffer(buf, buf_width, ch, cursor_x, py, color);
573 cursor_x += (w as usize) * 8;
574 }
575}
576
577pub fn draw_char_into_buffer(
579 buf: &mut [u8],
580 buf_width: usize,
581 ch: u8,
582 px: usize,
583 py: usize,
584 color: u32,
585) {
586 let glyph = crate::graphics::font8x16::glyph(ch);
587 let r = ((color >> 16) & 0xFF) as u8;
588 let g = ((color >> 8) & 0xFF) as u8;
589 let b = (color & 0xFF) as u8;
590
591 for (row, &bits) in glyph.iter().enumerate() {
592 for col in 0..8 {
593 if (bits >> (7 - col)) & 1 != 0 {
594 let x = px + col;
595 let y = py + row;
596 let offset = (y * buf_width + x) * 4;
597 if offset + 3 < buf.len() {
598 buf[offset] = b;
599 buf[offset + 1] = g;
600 buf[offset + 2] = r;
601 buf[offset + 3] = 0xFF;
602 }
603 }
604 }
605 }
606}
607
608pub fn create_app_surface(x: i32, y: i32, w: u32, h: u32) -> (u32, u32, u32) {
613 let surface_id = NEXT_SURFACE_ID.fetch_add(1, Ordering::Relaxed);
614 let pool_id = NEXT_POOL_ID.fetch_add(1, Ordering::Relaxed);
615
616 crate::desktop::wayland::with_display(|display| {
618 let _ = display.wl_compositor.create_surface(surface_id);
619 display.wl_compositor.set_surface_position(surface_id, x, y);
620 });
621
622 let pool_size = (w as usize) * (h as usize) * 4;
624 let mut pool = crate::desktop::wayland::buffer::WlShmPool::new(pool_id, 0, pool_size);
625
626 let pool_buf_id = pool
628 .create_buffer(0, w, h, w * 4, crate::graphics::PixelFormat::Xrgb8888)
629 .unwrap_or(0);
630
631 crate::desktop::wayland::buffer::register_pool(pool);
632
633 let buffer = crate::desktop::wayland::buffer::Buffer::from_pool(
635 surface_id + 1000,
636 pool_id,
637 pool_buf_id,
638 w,
639 h,
640 w * 4,
641 crate::graphics::PixelFormat::Xrgb8888,
642 );
643
644 crate::desktop::wayland::with_display(|display| {
645 display
646 .wl_compositor
647 .with_surface_mut(surface_id, |surface| {
648 surface.attach_buffer(buffer.clone());
649 surface.damage_full();
650 let _ = surface.commit();
651 });
652 });
653
654 (surface_id, pool_id, pool_buf_id)
655}
656
657pub fn update_surface_pixels(surface_id: u32, pool_id: u32, pool_buf_id: u32, pixels: &[u8]) {
661 crate::desktop::wayland::buffer::with_pool_mut(pool_id, |pool| {
663 pool.write_buffer_pixels(pool_buf_id, pixels);
664 });
665
666 crate::desktop::wayland::with_display(|display| {
668 display
669 .wl_compositor
670 .with_surface_mut(surface_id, |surface| {
671 surface.damage_full();
672 let _ = surface.commit();
673 });
674 display.wl_compositor.request_composite();
675 });
676}
677
678fn paint_gradient_background(buf: &mut [u8], width: usize, height: usize) {
680 for y in 0..height {
681 let t256 = (y * 256) / height; let inv_t = 256 - t256;
685 let r = ((0x2D * inv_t + 0x1a * t256) / 256) as u8;
686 let g = ((0x34 * inv_t + 0x1a * t256) / 256) as u8;
687 let b = ((0x36 * inv_t + 0x2e * t256) / 256) as u8;
688
689 for x in 0..width {
690 let offset = (y * width + x) * 4;
691 buf[offset] = b; buf[offset + 1] = g; buf[offset + 2] = r; buf[offset + 3] = 0xFF; }
696 }
697
698 let title = b"VeridianOS Desktop Environment";
700 let title_x = (width - title.len() * 8) / 2;
701 let title_y = height / 2 - 20;
702 for (i, &ch) in title.iter().enumerate() {
703 draw_char_into_buffer(buf, width, ch, title_x + i * 8, title_y, 0xAAAAAA);
704 }
705
706 let sub = b"Wayland Compositor Active";
708 let sub_x = (width - sub.len() * 8) / 2;
709 let sub_y = height / 2 + 4;
710 for (i, &ch) in sub.iter().enumerate() {
711 draw_char_into_buffer(buf, width, ch, sub_x + i * 8, sub_y, 0x666666);
712 }
713}
714
715fn translate_input_event(
717 raw: &crate::drivers::input_event::InputEvent,
718 mouse_x: i32,
719 mouse_y: i32,
720) -> Option<crate::desktop::window_manager::InputEvent> {
721 use crate::{desktop::window_manager::InputEvent as WmEvent, drivers::input_event::*};
722
723 match raw.event_type {
724 EV_KEY => {
725 let code = raw.code;
726 let pressed = raw.value != 0;
727
728 if (BTN_LEFT..=BTN_MIDDLE).contains(&code) {
730 let button = (code - BTN_LEFT) as u8;
731 return Some(WmEvent::MouseButton {
732 button,
733 pressed,
734 x: mouse_x,
735 y: mouse_y,
736 });
737 }
738
739 if pressed {
741 let ch = if code < 0x80 {
743 code as u8 as char
744 } else {
745 '\0'
746 };
747 Some(WmEvent::KeyPress {
748 scancode: code as u8,
749 character: ch,
750 })
751 } else {
752 Some(WmEvent::KeyRelease {
753 scancode: code as u8,
754 })
755 }
756 }
757 EV_REL => {
758 if raw.code == REL_X {
762 Some(WmEvent::MouseMove {
763 x: mouse_x,
764 y: mouse_y,
765 })
766 } else {
767 None
768 }
769 }
770 _ => None,
771 }
772}
773
774const TITLE_BAR_HEIGHT: i32 = 28;
776
777struct DragState {
779 wid: u32,
781 surface_id: u32,
783 offset_x: i32,
785 offset_y: i32,
786}
787
788fn sync_compositor_focus(state: &DesktopState, focused_wid: u32) {
790 if let Some(surface_id) = state.surface_for_window(focused_wid) {
791 crate::desktop::wayland::with_display(|display| {
792 display.wl_compositor.raise_surface(surface_id);
793 display.wl_compositor.raise_surface(state.panel_surface_id);
795 });
796 }
797}
798
799fn spawn_dynamic_app(
802 state: &mut DesktopState,
803 kind: AppKind,
804 title: &str,
805 w: u32,
806 h: u32,
807) -> Option<usize> {
808 if state.dynamic_apps.iter().any(|a| a.kind == kind) {
810 return None;
811 }
812
813 let title_bar_h = 28u32;
814 let wid = crate::desktop::window_manager::with_window_manager(|wm| {
815 wm.create_window(100, 80, w, h + title_bar_h, 0)
816 })
817 .and_then(|r| r.ok())?;
818
819 let (surface_id, pool_id, pool_buf_id) = create_app_surface(100, 80, w, h + title_bar_h);
821
822 crate::desktop::window_manager::with_window_manager(|wm| {
823 wm.set_window_title(wid, title);
824 let _ = wm.focus_window(wid);
825 });
826
827 crate::desktop::wayland::with_display(|display| {
829 display.wl_compositor.raise_surface(surface_id);
830 display.wl_compositor.raise_surface(state.panel_surface_id);
831 });
832
833 state.dynamic_apps.push(DynamicApp {
834 kind,
835 wid,
836 surface_id,
837 pool_id,
838 pool_buf_id,
839 width: w,
840 height: h,
841 });
842
843 Some(state.dynamic_apps.len() - 1)
844}
845
846fn close_dynamic_app(state: &mut DesktopState, wid: u32) {
848 if let Some(pos) = state.dynamic_apps.iter().position(|a| a.wid == wid) {
849 let app = state.dynamic_apps.remove(pos);
850 crate::desktop::wayland::with_display(|display| {
852 display
853 .wl_compositor
854 .set_surface_mapped(app.surface_id, false);
855 });
856 crate::desktop::window_manager::with_window_manager(|wm| {
857 let _ = wm.destroy_window(app.wid);
858 });
859 }
860}
861
862fn close_any_window(state: &mut DesktopState, wid: u32) {
864 if state.dynamic_apps.iter().any(|a| a.wid == wid) {
866 close_dynamic_app(state, wid);
867 return;
868 }
869 if wid == state.terminal.wid {
871 crate::desktop::wayland::with_display(|display| {
872 display
873 .wl_compositor
874 .set_surface_mapped(state.terminal.surface_id, false);
875 });
876 crate::desktop::window_manager::with_window_manager(|wm| {
877 let _ = wm.destroy_window(wid);
878 });
879 state.terminal.wid = 0;
880 } else if wid == state.file_manager.wid {
881 crate::desktop::wayland::with_display(|display| {
882 display
883 .wl_compositor
884 .set_surface_mapped(state.file_manager.surface_id, false);
885 });
886 crate::desktop::window_manager::with_window_manager(|wm| {
887 let _ = wm.destroy_window(wid);
888 });
889 state.file_manager.wid = 0;
890 } else if wid == state.text_editor.wid {
891 crate::desktop::wayland::with_display(|display| {
892 display
893 .wl_compositor
894 .set_surface_mapped(state.text_editor.surface_id, false);
895 });
896 crate::desktop::window_manager::with_window_manager(|wm| {
897 let _ = wm.destroy_window(wid);
898 });
899 state.text_editor.wid = 0;
900 }
901}
902
903pub fn draw_title_bar_into_surface(pixels: &mut [u8], width: usize, _total_h: usize, wid: u32) {
907 let cfg = DecorationConfig::default_config();
908 let tbh = cfg.title_bar_height as usize;
909
910 let (title_buf, title_len, focused) =
912 crate::desktop::window_manager::with_window_manager(|wm| {
913 wm.get_window(wid)
914 .map(|w| (w.title, w.title_len, w.focused))
915 })
916 .flatten()
917 .unwrap_or(([0u8; 64], 0, false));
918
919 let bg_argb = if focused {
920 cfg.title_bg_focused
921 } else {
922 cfg.title_bg_unfocused
923 };
924 let bg_r = ((bg_argb >> 16) & 0xFF) as u8;
926 let bg_g = ((bg_argb >> 8) & 0xFF) as u8;
927 let bg_b = (bg_argb & 0xFF) as u8;
928
929 for y in 0..tbh {
931 for x in 0..width {
932 let off = (y * width + x) * 4;
933 if off + 3 < pixels.len() {
934 pixels[off] = bg_b;
935 pixels[off + 1] = bg_g;
936 pixels[off + 2] = bg_r;
937 pixels[off + 3] = 0xFF;
938 }
939 }
940 }
941
942 let text_color = cfg.title_text_color & 0x00FF_FFFF; let text_y = (tbh.saturating_sub(16)) / 2;
945 for (ci, &ch) in title_buf[..title_len].iter().enumerate() {
946 draw_char_into_buffer(pixels, width, ch, 8 + ci * 8, text_y, text_color);
947 }
948
949 let btn_sz = 16usize;
951 let btn_x = width.saturating_sub(22);
952 let btn_y = (tbh.saturating_sub(btn_sz)) / 2;
953 let close_r: u8 = 0xE7;
954 let close_g: u8 = 0x4C;
955 let close_b: u8 = 0x3C;
956 for dy in 0..btn_sz {
957 for dx in 0..btn_sz {
958 let off = ((btn_y + dy) * width + btn_x + dx) * 4;
959 if off + 3 < pixels.len() {
960 pixels[off] = close_b;
961 pixels[off + 1] = close_g;
962 pixels[off + 2] = close_r;
963 pixels[off + 3] = 0xFF;
964 }
965 }
966 }
967 for i in 0..14usize {
969 let coords = [
970 (btn_x + 1 + i, btn_y + 1 + i),
971 (btn_x + 2 + i, btn_y + 1 + i),
972 (btn_x + 14 - i, btn_y + 1 + i),
973 (btn_x + 13 - i, btn_y + 1 + i),
974 ];
975 for (px, py) in coords {
976 let off = (py * width + px) * 4;
977 if off + 3 < pixels.len() {
978 pixels[off] = 0xFF;
979 pixels[off + 1] = 0xFF;
980 pixels[off + 2] = 0xFF;
981 pixels[off + 3] = 0xFF;
982 }
983 }
984 }
985}
986
987fn focus_dynamic_app(state: &DesktopState, kind: AppKind) -> bool {
990 if let Some(app) = state.dynamic_apps.iter().find(|a| a.kind == kind) {
991 let wid = app.wid;
992 crate::desktop::window_manager::with_window_manager(|wm| {
993 let _ = wm.focus_window(wid);
994 });
995 sync_compositor_focus(state, wid);
996 true
997 } else {
998 false
999 }
1000}
1001
1002fn handle_launcher_launch(state: &mut DesktopState, exec_path: &str) {
1004 match exec_path {
1005 "terminal" | "/usr/bin/terminal" => {
1006 if state.terminal.wid > 0 {
1007 crate::desktop::window_manager::with_window_manager(|wm| {
1008 let _ = wm.focus_window(state.terminal.wid);
1009 });
1010 sync_compositor_focus(state, state.terminal.wid);
1011 }
1012 }
1013 "files" | "/usr/bin/files" => {
1014 if state.file_manager.wid > 0 {
1015 crate::desktop::window_manager::with_window_manager(|wm| {
1016 let _ = wm.focus_window(state.file_manager.wid);
1017 });
1018 sync_compositor_focus(state, state.file_manager.wid);
1019 }
1020 }
1021 "editor" | "/usr/bin/editor" => {
1022 if state.text_editor.wid > 0 {
1023 crate::desktop::window_manager::with_window_manager(|wm| {
1024 let _ = wm.focus_window(state.text_editor.wid);
1025 });
1026 sync_compositor_focus(state, state.text_editor.wid);
1027 }
1028 }
1029 "settings" | "/usr/bin/settings" => {
1030 if !focus_dynamic_app(state, AppKind::Settings) {
1031 spawn_dynamic_app(state, AppKind::Settings, "Settings", 600, 450);
1032 }
1033 }
1034 "sysmonitor" | "/usr/bin/sysmonitor" => {
1035 if !focus_dynamic_app(state, AppKind::SystemMonitor) {
1036 spawn_dynamic_app(state, AppKind::SystemMonitor, "System Monitor", 640, 400);
1037 }
1038 }
1039 "imageviewer" | "/usr/bin/image-viewer" => {
1040 if !focus_dynamic_app(state, AppKind::ImageViewer) {
1041 spawn_dynamic_app(state, AppKind::ImageViewer, "Image Viewer", 800, 600);
1042 if state.image_viewer.image.is_none() {
1044 let sample_paths = [
1046 "/usr/share/images/wallpaper.ppm",
1047 "/usr/share/images/sample.bmp",
1048 "/usr/share/images/logo.qoi",
1049 ];
1050 for path in &sample_paths {
1051 if let Ok(data) = crate::fs::read_file(path) {
1052 state.image_viewer.load_file(path, &data);
1053 break;
1054 }
1055 }
1056 }
1057 }
1058 }
1059 "mediaplayer" | "/usr/bin/mediaplayer" => {
1060 if !focus_dynamic_app(state, AppKind::MediaPlayer) {
1061 spawn_dynamic_app(state, AppKind::MediaPlayer, "Media Player", 640, 300);
1062 }
1063 }
1064 "browser" | "/usr/bin/browser" => {
1065 if !focus_dynamic_app(state, AppKind::Browser) {
1066 spawn_dynamic_app(state, AppKind::Browser, "Web Browser", 900, 600);
1067 }
1068 }
1069 "pdfviewer" | "/usr/bin/pdfviewer" => {
1070 if !focus_dynamic_app(state, AppKind::PdfViewer) {
1071 spawn_dynamic_app(state, AppKind::PdfViewer, "PDF Viewer", 800, 600);
1072 }
1073 }
1074 "calculator" | "/usr/bin/calculator" => {
1075 if !focus_dynamic_app(state, AppKind::Calculator) {
1076 spawn_dynamic_app(state, AppKind::Calculator, "Calculator", 320, 400);
1077 }
1078 }
1079 _ => {}
1080 }
1081}
1082
1083struct FrameLayout {
1085 fb_ptr: *mut u8,
1086 fb_width: usize,
1087 fb_height: usize,
1088 fb_stride: usize,
1089 is_bgr: bool,
1090 panel_y: i32,
1091}
1092
1093fn render_loop(hw: &fbcon::FbHwInfo, state: &mut DesktopState) {
1101 let layout = FrameLayout {
1102 fb_ptr: hw.fb_ptr,
1103 fb_width: hw.width,
1104 fb_height: hw.height,
1105 fb_stride: hw.stride,
1106 is_bgr: matches!(hw.pixel_format, FbPixelFormat::Bgr),
1107 panel_y: (hw.height as u32 - crate::desktop::panel::PANEL_HEIGHT) as i32,
1108 };
1109
1110 let _drawn = crate::desktop::wayland::with_display(|display| display.wl_compositor.composite());
1112
1113 render_all_apps(state);
1115
1116 crate::desktop::window_manager::with_window_manager(|wm| {
1118 wm.set_screen_size(layout.fb_width as u32, layout.fb_height as u32);
1119 });
1120
1121 loop {
1122 state.frame_count += 1;
1123 let tick = crate::arch::timer::get_ticks();
1124
1125 if state.screen_locker.is_locked() {
1127 handle_screen_lock(state, &layout, tick);
1128 continue;
1129 }
1130
1131 if handle_input_events(state, &layout) {
1133 return;
1134 }
1135
1136 update_ui_state(state, tick);
1138
1139 render_and_composite(state, &layout, tick);
1141 }
1142}
1143
1144fn handle_screen_lock(state: &mut DesktopState, layout: &FrameLayout, tick: u64) {
1146 crate::drivers::input_event::poll_all();
1147 while let Some(raw_event) = crate::drivers::input_event::read_event() {
1148 if raw_event.event_type == crate::drivers::input_event::EV_KEY && raw_event.value == 1 {
1149 let action = state.screen_locker.handle_key(raw_event.code as u8, tick);
1150 if matches!(action, crate::desktop::screen_lock::LockAction::Unlocked) {
1151 break;
1152 }
1153 }
1154 }
1155 state.screen_locker.tick(tick);
1156 crate::desktop::wayland::with_display(|display| {
1157 display.wl_compositor.with_back_buffer_mut(|bb| {
1158 state
1159 .screen_locker
1160 .render_to_buffer(bb, layout.fb_width, layout.fb_height, tick);
1161 });
1162 blit_back_buffer(
1163 &display.wl_compositor,
1164 layout.fb_ptr,
1165 layout.fb_width,
1166 layout.fb_height,
1167 layout.fb_stride,
1168 layout.is_bgr,
1169 );
1170 });
1171 for _ in 0..50_000 {
1172 core::hint::spin_loop();
1173 }
1174}
1175
1176fn handle_input_events(state: &mut DesktopState, layout: &FrameLayout) -> bool {
1180 crate::drivers::input_event::poll_all();
1181 let (mouse_x, mouse_y) = crate::drivers::mouse::cursor_position();
1182 let mods = crate::drivers::keyboard::get_modifiers();
1183 let tick = crate::arch::timer::get_ticks();
1184
1185 state.screen_locker.record_activity(tick);
1187
1188 while let Some(raw_event) = crate::drivers::input_event::read_event() {
1189 let is_key_press =
1190 raw_event.event_type == crate::drivers::input_event::EV_KEY && raw_event.value == 1;
1191
1192 if is_key_press && raw_event.code == 0x1B && mods == 0 {
1194 if !state.app_switcher.is_visible() {
1196 crate::serial::_serial_print(format_args!("[DESKTOP] ESC pressed, exiting GUI\n"));
1197 return true;
1198 }
1199 }
1200
1201 if is_key_press && raw_event.code == 0x09 && mods & crate::drivers::keyboard::MOD_ALT != 0 {
1205 if !state.app_switcher.is_visible() {
1206 let windows = get_window_list_for_switcher();
1207 state.app_switcher.show(windows);
1208 } else {
1209 state.app_switcher.next();
1210 }
1211 continue;
1212 }
1213
1214 if state.app_switcher.is_visible()
1216 && mods & crate::drivers::keyboard::MOD_ALT == 0
1217 && raw_event.event_type == crate::drivers::input_event::EV_KEY
1218 {
1219 if let Some(wid) = state.app_switcher.hide() {
1220 crate::desktop::window_manager::with_window_manager(|wm| {
1221 let _ = wm.focus_window(wid);
1222 });
1223 sync_compositor_focus(state, wid);
1224 }
1225 continue;
1226 }
1227
1228 if is_key_press
1230 && raw_event.code == b'l' as u16
1231 && mods & (crate::drivers::keyboard::MOD_CTRL | crate::drivers::keyboard::MOD_ALT)
1232 == (crate::drivers::keyboard::MOD_CTRL | crate::drivers::keyboard::MOD_ALT)
1233 {
1234 state.screen_locker.lock();
1235 continue;
1236 }
1237
1238 if is_key_press
1240 && mods & (crate::drivers::keyboard::MOD_CTRL | crate::drivers::keyboard::MOD_ALT)
1241 == (crate::drivers::keyboard::MOD_CTRL | crate::drivers::keyboard::MOD_ALT)
1242 {
1243 if raw_event.code == crate::drivers::keyboard::KEY_LEFT as u16 {
1244 switch_workspace_prev(state);
1245 continue;
1246 }
1247 if raw_event.code == crate::drivers::keyboard::KEY_RIGHT as u16 {
1248 switch_workspace_next(state);
1249 continue;
1250 }
1251 }
1252
1253 if is_key_press && mods & crate::drivers::keyboard::MOD_SUPER != 0 {
1255 crate::desktop::launcher::with_launcher(|l| l.toggle());
1256 continue;
1257 }
1258
1259 if is_key_press && raw_event.code == 0x1B {
1261 if state.app_switcher.is_visible() {
1262 let _ = state.app_switcher.hide();
1263 continue;
1264 }
1265 let launcher_visible =
1266 crate::desktop::launcher::with_launcher_ref(|l| l.is_visible()).unwrap_or(false);
1267 if launcher_visible {
1268 crate::desktop::launcher::with_launcher(|l| l.hide());
1269 continue;
1270 }
1271 }
1272
1273 let launcher_visible =
1275 crate::desktop::launcher::with_launcher_ref(|l| l.is_visible()).unwrap_or(false);
1276 if launcher_visible && is_key_press && raw_event.code < 0x80 {
1277 let action =
1278 crate::desktop::launcher::with_launcher(|l| l.handle_key(raw_event.code as u8));
1279 if let Some(Some(action)) = action {
1280 match action {
1281 crate::desktop::launcher::LauncherAction::Launch(exec) => {
1282 handle_launcher_launch(state, &exec);
1283 }
1284 crate::desktop::launcher::LauncherAction::Hide => {}
1285 }
1286 }
1287 continue;
1288 }
1289
1290 if is_key_press
1292 && raw_event.code == b'c' as u16
1293 && mods & crate::drivers::keyboard::MOD_CTRL != 0
1294 && mods & crate::drivers::keyboard::MOD_ALT == 0
1295 {
1296 let _ = state.clipboard.copy(
1297 crate::desktop::desktop_ext::clipboard::SelectionType::Clipboard,
1298 0,
1299 crate::desktop::desktop_ext::clipboard::ClipboardMime::TextPlainUtf8,
1300 alloc::vec![],
1301 );
1302 continue;
1303 }
1304
1305 if is_key_press
1307 && raw_event.code == b'v' as u16
1308 && mods & crate::drivers::keyboard::MOD_CTRL != 0
1309 && mods & crate::drivers::keyboard::MOD_ALT == 0
1310 {
1311 if let Ok(data) = state.clipboard.paste(
1312 crate::desktop::desktop_ext::clipboard::SelectionType::Clipboard,
1313 crate::desktop::desktop_ext::clipboard::ClipboardMime::TextPlainUtf8,
1314 ) {
1315 if state.terminal.wid > 0 {
1316 crate::desktop::terminal::with_terminal_manager(|tm| {
1317 for &byte in data {
1318 let event = crate::desktop::window_manager::InputEvent::KeyPress {
1319 scancode: 0,
1320 character: byte as char,
1321 };
1322 let _ = tm.process_input(0, event);
1323 }
1324 });
1325 }
1326 }
1327 continue;
1328 }
1329
1330 if let Some(wm_event) = translate_input_event(&raw_event, mouse_x, mouse_y) {
1332 dispatch_mouse_and_keyboard(state, layout, wm_event, launcher_visible);
1333 }
1334 }
1335
1336 false
1337}
1338
1339fn dispatch_mouse_and_keyboard(
1341 state: &mut DesktopState,
1342 layout: &FrameLayout,
1343 wm_event: crate::desktop::window_manager::InputEvent,
1344 launcher_visible: bool,
1345) {
1346 if let crate::desktop::window_manager::InputEvent::MouseButton {
1348 button: 0,
1349 pressed: true,
1350 x,
1351 y,
1352 } = wm_event
1353 {
1354 if launcher_visible {
1356 crate::desktop::launcher::with_launcher(|l| l.hide());
1357 }
1358
1359 if y >= layout.panel_y {
1361 crate::desktop::panel::with_panel(|p| p.update_buttons());
1362 if let Some(focus_wid) =
1363 crate::desktop::panel::with_panel(|p| p.handle_click(x, y - layout.panel_y))
1364 .flatten()
1365 {
1366 crate::desktop::window_manager::with_window_manager(|wm| {
1367 let _ = wm.focus_window(focus_wid);
1368 });
1369 sync_compositor_focus(state, focus_wid);
1370 }
1371 return;
1372 }
1373
1374 let hit =
1376 crate::desktop::window_manager::with_window_manager(|wm| wm.window_at_position(x, y))
1377 .flatten();
1378
1379 if let Some(wid) = hit {
1380 crate::desktop::window_manager::with_window_manager(|wm| {
1381 let _ = wm.focus_window(wid);
1382 });
1383 sync_compositor_focus(state, wid);
1384
1385 let in_title_bar = crate::desktop::window_manager::with_window_manager(|wm| {
1386 wm.get_window(wid).map(|w| y < w.y + TITLE_BAR_HEIGHT)
1387 })
1388 .flatten()
1389 .unwrap_or(false);
1390
1391 if in_title_bar {
1392 let is_close = crate::desktop::window_manager::with_window_manager(|wm| {
1393 wm.get_window(wid).map(|w| x >= w.x + w.width as i32 - 28)
1394 })
1395 .flatten()
1396 .unwrap_or(false);
1397
1398 if is_close {
1399 close_any_window(state, wid);
1400 return;
1401 }
1402
1403 if let Some(surface_id) = state.surface_for_window(wid) {
1405 let win_pos = crate::desktop::window_manager::with_window_manager(|wm| {
1406 wm.get_window(wid).map(|w| (w.x, w.y))
1407 })
1408 .flatten();
1409 if let Some((wx, wy)) = win_pos {
1410 state.drag = Some(DragState {
1411 wid,
1412 surface_id,
1413 offset_x: x - wx,
1414 offset_y: y - wy,
1415 });
1416 }
1417 }
1418 } else {
1419 crate::desktop::window_manager::with_window_manager(|wm| {
1421 wm.queue_event(crate::desktop::window_manager::WindowEvent {
1422 window_id: wid,
1423 event: wm_event,
1424 });
1425 });
1426 }
1427 }
1428 return;
1429 }
1430
1431 if let crate::desktop::window_manager::InputEvent::MouseButton {
1433 button: 1,
1434 pressed: true,
1435 x,
1436 y,
1437 } = wm_event
1438 {
1439 let launcher_visible =
1440 crate::desktop::launcher::with_launcher_ref(|l| l.is_visible()).unwrap_or(false);
1441 if launcher_visible {
1442 crate::desktop::launcher::with_launcher(|l| l.hide());
1443 }
1444
1445 if y < layout.panel_y {
1446 let hit = crate::desktop::window_manager::with_window_manager(|wm| {
1447 wm.window_at_position(x, y)
1448 })
1449 .flatten();
1450
1451 if let Some(wid) = hit {
1452 let in_title_bar = crate::desktop::window_manager::with_window_manager(|wm| {
1453 wm.get_window(wid).map(|w| y < w.y + TITLE_BAR_HEIGHT)
1454 })
1455 .flatten()
1456 .unwrap_or(false);
1457 if in_title_bar {
1458 close_any_window(state, wid);
1459 }
1460 } else {
1461 crate::desktop::launcher::with_launcher(|l| l.toggle());
1462 }
1463 }
1464 return;
1465 }
1466
1467 if let crate::desktop::window_manager::InputEvent::MouseButton {
1469 button: 0,
1470 pressed: false,
1471 x,
1472 y,
1473 } = wm_event
1474 {
1475 if let Some(ref d) = state.drag {
1476 let zone = crate::desktop::window_manager::WindowManager::detect_snap_zone(
1477 x,
1478 y,
1479 layout.fb_width as u32,
1480 layout.fb_height as u32,
1481 );
1482 if zone != crate::desktop::window_manager::SnapZone::None {
1483 let drag_wid = d.wid;
1484 let drag_sid = d.surface_id;
1485 crate::desktop::window_manager::with_window_manager(|wm| {
1486 wm.snap_window(drag_wid, zone);
1487 if let Some(w) = wm.get_window(drag_wid) {
1488 crate::desktop::wayland::with_display(|display| {
1489 display
1490 .wl_compositor
1491 .set_surface_position(drag_sid, w.x, w.y);
1492 });
1493 }
1494 });
1495 }
1496 }
1497 state.drag = None;
1498 crate::desktop::window_manager::with_window_manager(|wm| {
1499 wm.process_input(wm_event);
1500 });
1501 return;
1502 }
1503
1504 if let crate::desktop::window_manager::InputEvent::MouseMove { x, y } = wm_event {
1506 if let Some(ref d) = state.drag {
1507 let new_x = x - d.offset_x;
1508 let new_y = y - d.offset_y;
1509 crate::desktop::window_manager::with_window_manager(|wm| {
1510 let _ = wm.move_window(d.wid, new_x, new_y);
1511 });
1512 crate::desktop::wayland::with_display(|display| {
1513 display
1514 .wl_compositor
1515 .set_surface_position(d.surface_id, new_x, new_y);
1516 });
1517 return;
1518 }
1519 }
1520
1521 crate::desktop::window_manager::with_window_manager(|wm| {
1523 wm.process_input(wm_event);
1524 });
1525}
1526
1527fn update_ui_state(state: &mut DesktopState, tick: u64) {
1529 let current_focused =
1531 crate::desktop::window_manager::with_window_manager(|wm| wm.get_focused_window_id())
1532 .flatten();
1533 if current_focused != state.prev_focused {
1534 if let Some(fwid) = current_focused {
1535 sync_compositor_focus(state, fwid);
1536 }
1537 state.prev_focused = current_focused;
1538 }
1539
1540 if state.screen_locker.check_idle_timeout(tick) {
1542 state.screen_locker.lock();
1543 }
1544
1545 forward_events_to_apps(state);
1547
1548 crate::desktop::terminal::with_terminal_manager(|tm| {
1550 let _ = tm.update_all();
1551 });
1552
1553 state.animation_mgr.tick(16); state.animation_mgr.remove_completed();
1556
1557 if state.frame_count.is_multiple_of(30) {
1559 crate::desktop::notification::with_notification_manager(|mgr| {
1560 mgr.tick(tick);
1561 });
1562 }
1563}
1564
1565fn render_and_composite(state: &mut DesktopState, layout: &FrameLayout, tick: u64) {
1567 render_all_apps(state);
1568
1569 if state.frame_count.is_multiple_of(10) {
1571 render_panel(state, layout.fb_width as u32);
1572 }
1573
1574 crate::desktop::wayland::with_display(|display| {
1576 display.wl_compositor.request_composite();
1577 let composited = display.wl_compositor.composite().unwrap_or(false);
1578
1579 if composited {
1580 display.wl_compositor.with_back_buffer_mut(|bb| {
1581 render_overlays(state, bb, layout.fb_width, layout.fb_height, tick);
1582 });
1583
1584 blit_back_buffer(
1585 &display.wl_compositor,
1586 layout.fb_ptr,
1587 layout.fb_width,
1588 layout.fb_height,
1589 layout.fb_stride,
1590 layout.is_bgr,
1591 );
1592 }
1593 });
1594
1595 let (mouse_x, mouse_y) = crate::drivers::mouse::cursor_position();
1597 let fb_slice = unsafe {
1599 core::slice::from_raw_parts_mut(layout.fb_ptr, layout.fb_stride * layout.fb_height)
1600 };
1601 cursor::draw_cursor(
1602 fb_slice,
1603 layout.fb_stride,
1604 layout.fb_width,
1605 layout.fb_height,
1606 mouse_x,
1607 mouse_y,
1608 );
1609
1610 for _ in 0..5_000 {
1612 core::hint::spin_loop();
1613 }
1614}
1615
1616fn render_overlays(
1623 state: &DesktopState,
1624 bb: &mut [u32],
1625 fb_width: usize,
1626 fb_height: usize,
1627 tick: u64,
1628) {
1629 if state.app_switcher.is_visible() {
1636 state
1637 .app_switcher
1638 .render(bb, fb_width as u32, fb_height as u32);
1639 }
1640
1641 crate::desktop::launcher::with_launcher_ref(|l| {
1643 if l.is_visible() {
1644 l.render_to_buffer(bb, fb_width, fb_height);
1645 }
1646 });
1647
1648 crate::desktop::notification::with_notification_manager(|mgr| {
1650 if mgr.active_count() > 0 {
1651 mgr.render_to_buffer(bb, fb_width, fb_height, tick);
1652 }
1653 });
1654}
1655
1656#[cfg(target_arch = "x86_64")]
1661fn try_gpu_blit(compositor: &crate::desktop::wayland::compositor::Compositor) -> bool {
1662 if !crate::drivers::virtio_gpu::is_available() {
1663 return false;
1664 }
1665 crate::drivers::virtio_gpu::with_driver(|gpu| {
1666 compositor.with_back_buffer(|bb| {
1667 if let Some(backing) = gpu.get_framebuffer_mut() {
1668 let copy_len = bb.len().min(backing.len());
1669 backing[..copy_len].copy_from_slice(&bb[..copy_len]);
1670 }
1671 });
1672 let _ = gpu.flush_framebuffer();
1673 });
1674 true
1675}
1676
1677fn blit_back_buffer(
1679 compositor: &crate::desktop::wayland::compositor::Compositor,
1680 fb_ptr: *mut u8,
1681 fb_width: usize,
1682 fb_height: usize,
1683 fb_stride: usize,
1684 is_bgr: bool,
1685) {
1686 #[cfg(target_arch = "x86_64")]
1688 if try_gpu_blit(compositor) {
1689 return;
1690 }
1691
1692 compositor.with_back_buffer(|bb| {
1693 if is_bgr {
1694 for y in 0..fb_height {
1696 let src_start = y * fb_width;
1697 let src_end = src_start + fb_width;
1698 if src_end > bb.len() {
1699 break;
1700 }
1701 let dst_offset = y * fb_stride;
1702 unsafe {
1705 core::ptr::copy_nonoverlapping(
1706 bb[src_start..src_end].as_ptr() as *const u8,
1707 fb_ptr.add(dst_offset),
1708 fb_width * 4,
1709 );
1710 }
1711 }
1712 } else {
1713 for y in 0..fb_height {
1715 for x in 0..fb_width {
1716 let src_idx = y * fb_width + x;
1717 if src_idx >= bb.len() {
1718 break;
1719 }
1720 let pixel = bb[src_idx];
1721 let r = (pixel >> 16) & 0xFF;
1722 let g = (pixel >> 8) & 0xFF;
1723 let b = pixel & 0xFF;
1724 let swapped = 0xFF00_0000 | (b << 16) | (g << 8) | r;
1725 let dst_offset = y * fb_stride + x * 4;
1726 unsafe {
1727 (fb_ptr.add(dst_offset) as *mut u32).write(swapped);
1728 }
1729 }
1730 }
1731 }
1732 });
1733}
1734
1735fn get_window_list_for_switcher() -> alloc::vec::Vec<(u32, alloc::string::String)> {
1737 use alloc::string::ToString;
1738 crate::desktop::window_manager::with_window_manager(|wm| {
1739 wm.get_visible_windows()
1740 .iter()
1741 .map(|w| (w.id, w.title_str().to_string()))
1742 .collect()
1743 })
1744 .unwrap_or_default()
1745}
1746
1747fn switch_workspace_prev(state: &DesktopState) {
1749 crate::desktop::window_manager::with_window_manager(|wm| {
1750 let current = wm.get_active_workspace();
1751 if current > 0 {
1752 wm.switch_workspace(current - 1);
1753 update_surface_visibility(state, current - 1);
1754 }
1755 });
1756}
1757
1758fn switch_workspace_next(state: &DesktopState) {
1760 crate::desktop::window_manager::with_window_manager(|wm| {
1761 let current = wm.get_active_workspace();
1762 if current < (crate::desktop::window_manager::MAX_WORKSPACES as u8 - 1) {
1763 wm.switch_workspace(current + 1);
1764 update_surface_visibility(state, current + 1);
1765 }
1766 });
1767}
1768
1769fn update_surface_visibility(state: &DesktopState, target_ws: u8) {
1774 crate::desktop::window_manager::with_window_manager(|wm| {
1775 let all_windows = wm.get_all_windows();
1776 for w in &all_windows {
1777 if let Some(sid) = state.surface_for_window(w.id) {
1778 let should_show = w.workspace == target_ws;
1779 crate::desktop::wayland::with_display(|display| {
1780 display.wl_compositor.set_surface_mapped(sid, should_show);
1781 });
1782 }
1783 }
1784 });
1785 crate::desktop::panel::with_panel(|p| {
1787 p.set_active_workspace(target_ws as usize);
1788 });
1789}
1790
1791fn render_all_apps(state: &mut DesktopState) {
1793 if state.terminal.wid > 0 {
1795 crate::desktop::terminal::with_terminal_manager(|tm| {
1796 tm.render_all_surfaces();
1797 });
1798 }
1799
1800 crate::desktop::file_manager::with_file_manager(|fm| {
1802 fm.read().render_to_surface();
1803 });
1804
1805 crate::desktop::text_editor::with_text_editor(|te| {
1807 te.read().render_to_surface();
1808 });
1809
1810 let app_bg = state.theme.colors().window_background.0 & 0x00FFFFFF;
1812 let app_error_color = state.theme.colors().error.0 & 0x00FFFFFF;
1813 let app_accent_color = state.theme.colors().accent.0 & 0x00FFFFFF;
1814
1815 let title_bar_h: usize = 28;
1817 for app in &state.dynamic_apps {
1818 let w = app.width as usize;
1819 let content_h = app.height as usize;
1820 let total_h = content_h + title_bar_h;
1821 let content_size = w * content_h * 4;
1822 let mut content = alloc::vec![0u8; content_size];
1823
1824 match app.kind {
1825 AppKind::SystemMonitor => {
1826 render_system_monitor(&mut content, w, content_h, state.frame_count, app_bg);
1827 }
1828 AppKind::MediaPlayer => {
1829 render_media_player(&mut content, w, content_h, app_bg);
1830 }
1831 AppKind::ImageViewer => {
1832 state
1833 .image_viewer
1834 .render_to_u8_buffer(&mut content, w, content_h);
1835 }
1836 AppKind::Settings => {
1837 state
1838 .settings_app
1839 .render_to_u8_buffer(&mut content, w, content_h);
1840 }
1841 AppKind::Browser => {
1842 render_browser(&mut content, w, content_h, app_bg, &mut state.browser);
1843 }
1844 AppKind::PdfViewer => {
1845 render_pdf_viewer(&mut content, w, content_h, app_bg, state.pdf_page_index);
1846 }
1847 AppKind::Calculator => {
1848 render_calculator(
1849 &mut content,
1850 w,
1851 content_h,
1852 &state.calculator,
1853 app_bg,
1854 app_accent_color,
1855 app_error_color,
1856 );
1857 }
1858 }
1859
1860 let mut pixels = alloc::vec![0u8; w * total_h * 4];
1862 for y in 0..content_h {
1863 let src_off = y * w * 4;
1864 let dst_off = (y + title_bar_h) * w * 4;
1865 pixels[dst_off..dst_off + w * 4].copy_from_slice(&content[src_off..src_off + w * 4]);
1866 }
1867 draw_title_bar_into_surface(&mut pixels, w, total_h, app.wid);
1868
1869 update_surface_pixels(app.surface_id, app.pool_id, app.pool_buf_id, &pixels);
1870 }
1871}
1872
1873fn render_placeholder_app(buf: &mut [u8], w: usize, h: usize, title: &str, bg_color: u32) {
1875 let r = ((bg_color >> 16) & 0xFF) as u8;
1876 let g = ((bg_color >> 8) & 0xFF) as u8;
1877 let b = (bg_color & 0xFF) as u8;
1878 for y in 0..h {
1880 for x in 0..w {
1881 let off = (y * w + x) * 4;
1882 if off + 3 < buf.len() {
1883 buf[off] = b;
1884 buf[off + 1] = g;
1885 buf[off + 2] = r;
1886 buf[off + 3] = 0xFF;
1887 }
1888 }
1889 }
1890 let title_bytes = title.as_bytes();
1892 let tx = w.saturating_sub(title_bytes.len() * 8) / 2;
1893 draw_string_into_buffer(buf, w, title_bytes, tx, 20, 0xFFFFFF);
1894}
1895
1896fn render_system_monitor(buf: &mut [u8], w: usize, h: usize, frame_count: u64, bg_color: u32) {
1898 render_placeholder_app(buf, w, h, "System Monitor", bg_color);
1899 let mem = crate::mm::get_memory_stats();
1900 let total_mb = (mem.total_frames * 4096) / (1024 * 1024);
1901 let used_frames = mem.total_frames.saturating_sub(mem.free_frames);
1902 let used_mb = (used_frames * 4096) / (1024 * 1024);
1903 let pct = if mem.total_frames > 0 {
1904 (used_frames * 100) / mem.total_frames
1905 } else {
1906 0
1907 };
1908
1909 let perf = crate::perf::get_stats();
1910
1911 let mut line_buf = [0u8; 64];
1913 let line = format_stat_line(&mut line_buf, b"Memory: ", used_mb, b"/", total_mb, b" MB");
1914 draw_string_into_buffer(buf, w, line, 20, 50, 0x55EFC4);
1915
1916 let bar_w = w.saturating_sub(60);
1918 let filled = (bar_w * pct) / 100;
1919 for x in 20..(20 + bar_w).min(w) {
1920 let color = if x < 20 + filled { 0x55EFC4 } else { 0x333333 };
1921 for dy in 0..12 {
1922 let y = 70 + dy;
1923 if y < h {
1924 draw_pixel(buf, w, x, y, color);
1925 }
1926 }
1927 }
1928
1929 let stats_y = 95;
1931 let line2 = format_simple(&mut line_buf, b"Ctx switches: ", perf.context_switches);
1932 draw_string_into_buffer(buf, w, line2, 20, stats_y, 0xD4D4D4);
1933 let line3 = format_simple(&mut line_buf, b"Syscalls: ", perf.syscalls);
1934 draw_string_into_buffer(buf, w, line3, 20, stats_y + 20, 0xD4D4D4);
1935 let line4 = format_simple(&mut line_buf, b"Interrupts: ", perf.interrupts);
1936 draw_string_into_buffer(buf, w, line4, 20, stats_y + 40, 0xD4D4D4);
1937 let line5 = format_simple(&mut line_buf, b"Page faults: ", perf.page_faults);
1938 draw_string_into_buffer(buf, w, line5, 20, stats_y + 60, 0xD4D4D4);
1939 let line6 = format_simple(&mut line_buf, b"GUI frames: ", frame_count);
1940 draw_string_into_buffer(buf, w, line6, 20, stats_y + 80, 0xD4D4D4);
1941
1942 let proc_y = stats_y + 110;
1944 draw_string_into_buffer(buf, w, b"PID NAME STATE", 20, proc_y, 0x55EFC4);
1945
1946 for x in 20..(w.saturating_sub(20)) {
1948 if proc_y + 16 < h {
1949 draw_pixel(buf, w, x, proc_y + 16, 0x444444);
1950 }
1951 }
1952
1953 let process_server = crate::services::process_server::get_process_server();
1955 let processes = process_server.list_processes();
1956 let max_display = ((h.saturating_sub(proc_y + 20)) / 16).min(processes.len());
1957 for (i, proc_info) in processes.iter().take(max_display).enumerate() {
1958 let py = proc_y + 20 + i * 16;
1959 if py + 16 > h {
1960 break;
1961 }
1962 let pid_line = format_simple(&mut line_buf, b"", proc_info.pid.0);
1964 draw_string_into_buffer(buf, w, pid_line, 20, py, 0xD4D4D4);
1965
1966 let name_bytes = proc_info.name.as_bytes();
1967 let name_len = name_bytes.len().min(15);
1968 draw_string_into_buffer(buf, w, &name_bytes[..name_len], 68, py, 0xD4D4D4);
1969
1970 let state_str = match proc_info.state {
1971 crate::services::process_server::ProcessState::Running => b"Running" as &[u8],
1972 crate::services::process_server::ProcessState::Sleeping => b"Sleeping",
1973 crate::services::process_server::ProcessState::Waiting => b"Waiting",
1974 crate::services::process_server::ProcessState::Stopped => b"Stopped",
1975 crate::services::process_server::ProcessState::Zombie => b"Zombie",
1976 crate::services::process_server::ProcessState::Dead => b"Dead",
1977 };
1978 draw_string_into_buffer(buf, w, state_str, 196, py, 0xD4D4D4);
1979 }
1980}
1981
1982fn render_media_player(buf: &mut [u8], w: usize, h: usize, bg_color: u32) {
1984 render_placeholder_app(buf, w, h, "Media Player", bg_color);
1985
1986 let (stream_count, sample_rate, channels) = crate::audio::client::with_client(|client| {
1988 (
1989 client.stream_count(),
1990 client.default_sample_rate(),
1991 client.default_channels(),
1992 )
1993 })
1994 .unwrap_or((0, 48000, 2));
1995
1996 let mut line_buf = [0u8; 64];
1997
1998 draw_string_into_buffer(buf, w, b"Audio Engine: Active", 20, 50, 0x55EFC4);
2000
2001 let line = format_simple(&mut line_buf, b"Active streams: ", stream_count as u64);
2003 draw_string_into_buffer(buf, w, line, 20, 70, 0xD4D4D4);
2004
2005 let line2 = format_simple(&mut line_buf, b"Sample rate: ", sample_rate as u64);
2007 draw_string_into_buffer(buf, w, line2, 20, 90, 0xD4D4D4);
2008
2009 let line3 = format_simple(&mut line_buf, b"Channels: ", channels as u64);
2011 draw_string_into_buffer(buf, w, line3, 20, 110, 0xD4D4D4);
2012
2013 if stream_count == 0 {
2014 draw_string_into_buffer(buf, w, b"No media loaded", 20, 145, 0x95A5A6);
2015 draw_string_into_buffer(
2016 buf,
2017 w,
2018 b"Use file manager to open audio files",
2019 20,
2020 165,
2021 0x7F8C8D,
2022 );
2023 }
2024
2025 draw_string_into_buffer(
2026 buf,
2027 w,
2028 b"Controls: Space=play/pause S=stop +/-=volume",
2029 20,
2030 h.saturating_sub(30),
2031 0x7F8C8D,
2032 );
2033}
2034
2035fn render_browser(
2037 buf: &mut [u8],
2038 w: usize,
2039 h: usize,
2040 bg_color: u32,
2041 browser: &mut Option<crate::browser::browser_main::Browser>,
2042) {
2043 if browser.is_none() {
2045 let config = crate::browser::browser_main::BrowserConfig {
2046 viewport_width: w as u32,
2047 viewport_height: h as u32,
2048 ..crate::browser::browser_main::BrowserConfig::default()
2049 };
2050 let mut b = crate::browser::browser_main::Browser::new(config);
2051 b.init();
2052 *browser = Some(b);
2053 }
2054
2055 if let Some(ref mut b) = browser {
2056 b.tick();
2058 b.render();
2059
2060 let fb = b.framebuffer();
2062 let (bw, bh) = b.dimensions();
2063 let bw = bw as usize;
2064 let bh = bh as usize;
2065
2066 for y in 0..h.min(bh) {
2067 for x in 0..w.min(bw) {
2068 let src_idx = y * bw + x;
2069 let dst_off = (y * w + x) * 4;
2070 if src_idx < fb.len() && dst_off + 3 < buf.len() {
2071 let pixel = fb[src_idx];
2072 buf[dst_off] = (pixel & 0xFF) as u8; buf[dst_off + 1] = ((pixel >> 8) & 0xFF) as u8; buf[dst_off + 2] = ((pixel >> 16) & 0xFF) as u8; buf[dst_off + 3] = ((pixel >> 24) & 0xFF) as u8; }
2077 }
2078 }
2079 } else {
2080 render_placeholder_app(buf, w, h, "Web Browser", bg_color);
2082 draw_string_into_buffer(buf, w, b"Browser engine unavailable", 20, 75, 0xFF6666);
2083 }
2084}
2085
2086fn render_pdf_viewer(buf: &mut [u8], w: usize, h: usize, bg_color: u32, page_index: usize) {
2088 render_placeholder_app(buf, w, h, "PDF Viewer", bg_color);
2089
2090 let pdf_status = crate::fs::read_file("/usr/share/doc/sample.pdf").ok();
2092 let doc_opt = pdf_status.and_then(crate::desktop::pdf::PdfDocument::open);
2093
2094 let (has_pdf, page_count) = if let Some(ref d) = doc_opt {
2095 (true, d.page_count())
2096 } else {
2097 (false, 0)
2098 };
2099
2100 let display_page = if page_count > 0 {
2102 page_index.min(page_count.saturating_sub(1))
2103 } else {
2104 0
2105 };
2106
2107 let mut line_buf = [0u8; 64];
2109 if has_pdf {
2110 let toolbar = format_stat_line(
2111 &mut line_buf,
2112 b"[Open] [<] Page ",
2113 display_page.saturating_add(1),
2114 b" of ",
2115 page_count,
2116 b" [>] [Zoom: 100%]",
2117 );
2118 draw_string_into_buffer(buf, w, toolbar, 10, 40, 0xBBBBBB);
2119 } else {
2120 draw_string_into_buffer(
2121 buf,
2122 w,
2123 b"[Open] [<] Page 0 of 0 [>] [Zoom: 100%]",
2124 10,
2125 40,
2126 0xBBBBBB,
2127 );
2128 }
2129
2130 let page_x = 40;
2132 let page_y = 65;
2133 let page_w = w.saturating_sub(80);
2134 let page_h = h.saturating_sub(100);
2135
2136 if has_pdf {
2138 if let Some(ref doc) = doc_opt {
2139 if let Some(page) = doc.get_page(display_page) {
2140 let mut render_buf = vec![0xFFF0F0F0u32; page_w * page_h];
2142 let mut renderer =
2143 crate::desktop::pdf::PdfRenderer::new(page_w as u32, page_h as u32);
2144 renderer.render_page(page, &mut render_buf);
2145
2146 for py in 0..page_h.min(h.saturating_sub(page_y)) {
2148 for px in 0..page_w.min(w.saturating_sub(page_x)) {
2149 let src_idx = py * page_w + px;
2150 let dst_off = ((page_y + py) * w + (page_x + px)) * 4;
2151 if src_idx < render_buf.len() && dst_off + 3 < buf.len() {
2152 let pixel = render_buf[src_idx];
2153 buf[dst_off] = (pixel & 0xFF) as u8; buf[dst_off + 1] = ((pixel >> 8) & 0xFF) as u8; buf[dst_off + 2] = ((pixel >> 16) & 0xFF) as u8; buf[dst_off + 3] = 0xFF; }
2158 }
2159 }
2160
2161 let page_line = format_simple(&mut line_buf, b"Pages: ", page_count as u64);
2163 draw_string_into_buffer(
2164 buf,
2165 w,
2166 page_line,
2167 page_x + 20,
2168 page_y + page_h.saturating_sub(25),
2169 0x888888,
2170 );
2171 } else {
2172 render_pdf_page_background(buf, w, page_x, page_y, page_w, page_h);
2174 draw_string_into_buffer(
2175 buf,
2176 w,
2177 b"Page not available",
2178 page_x + 20,
2179 page_y + 20,
2180 0x555555,
2181 );
2182 }
2183 }
2184 } else {
2185 render_pdf_page_background(buf, w, page_x, page_y, page_w, page_h);
2187 draw_string_into_buffer(
2188 buf,
2189 w,
2190 b"PDF Engine: VeridianPDF v0.25.2",
2191 page_x + 20,
2192 page_y + 20,
2193 0x333333,
2194 );
2195 draw_string_into_buffer(
2196 buf,
2197 w,
2198 b"No document loaded",
2199 page_x + 20,
2200 page_y + 40,
2201 0x555555,
2202 );
2203 draw_string_into_buffer(
2204 buf,
2205 w,
2206 b"Use File Manager to open a PDF",
2207 page_x + 20,
2208 page_y + 60,
2209 0x777777,
2210 );
2211 }
2212
2213 draw_string_into_buffer(
2215 buf,
2216 w,
2217 b"Supports: PDF 1.0-2.0, xref, text extraction",
2218 page_x + 20,
2219 page_y + page_h.saturating_sub(10),
2220 0x888888,
2221 );
2222}
2223
2224fn render_pdf_page_background(
2226 buf: &mut [u8],
2227 w: usize,
2228 page_x: usize,
2229 page_y: usize,
2230 page_w: usize,
2231 page_h: usize,
2232) {
2233 for y in page_y..(page_y + page_h).min(buf.len() / (w * 4)) {
2234 for x in page_x..(page_x + page_w).min(w) {
2235 let off = (y * w + x) * 4;
2236 if off + 3 < buf.len() {
2237 buf[off] = 0xF0; buf[off + 1] = 0xF0; buf[off + 2] = 0xF0; buf[off + 3] = 0xFF;
2241 }
2242 }
2243 }
2244}
2245
2246fn format_i64(fmtbuf: &mut [u8; 24], value: i64) -> &[u8] {
2248 if value == 0 {
2249 fmtbuf[0] = b'0';
2250 return &fmtbuf[..1];
2251 }
2252 let mut pos = 24;
2253 let (mut n, negative) = if value < 0 {
2254 (-(value as i128) as u64, true)
2255 } else {
2256 (value as u64, false)
2257 };
2258 while n > 0 {
2259 pos -= 1;
2260 fmtbuf[pos] = b'0' + (n % 10) as u8;
2261 n /= 10;
2262 }
2263 if negative {
2264 pos -= 1;
2265 fmtbuf[pos] = b'-';
2266 }
2267 &fmtbuf[pos..]
2268}
2269
2270fn render_calculator(
2272 buf: &mut [u8],
2273 w: usize,
2274 h: usize,
2275 calc: &CalculatorState,
2276 bg_color: u32,
2277 accent_color: u32,
2278 error_color: u32,
2279) {
2280 render_placeholder_app(buf, w, h, "Calculator", bg_color);
2281
2282 let display_x = 16;
2284 let display_y = 44;
2285 let display_w = w.saturating_sub(32);
2286 let display_h = 32;
2287 for y in display_y..(display_y + display_h).min(h) {
2288 for x in display_x..(display_x + display_w).min(w) {
2289 let off = (y * w + x) * 4;
2290 if off + 3 < buf.len() {
2291 buf[off] = 0x18;
2292 buf[off + 1] = 0x18;
2293 buf[off + 2] = 0x18;
2294 buf[off + 3] = 0xFF;
2295 }
2296 }
2297 }
2298 let mut fmtbuf = [0u8; 24];
2300 let display_text: &[u8] = if calc.error {
2301 b"Error"
2302 } else {
2303 format_i64(&mut fmtbuf, calc.display_value)
2304 };
2305 let text_pixel_width = display_text.len() * 8;
2306 let text_x = display_x + display_w.saturating_sub(text_pixel_width + 8);
2307 draw_string_into_buffer(buf, w, display_text, text_x, display_y + 8, 0xFFFFFF);
2308
2309 let buttons: [&[u8]; 20] = [
2311 b"C", b"(", b")", b"/", b"7", b"8", b"9", b"*", b"4", b"5", b"6", b"-", b"1", b"2", b"3",
2312 b"+", b"0", b".", b"<", b"=",
2313 ];
2314 let grid_x = 16;
2315 let grid_y = display_y + display_h + 12;
2316 let btn_w = (w.saturating_sub(48)) / 4;
2317 let btn_h = 36;
2318 let gap = 4;
2319
2320 for (i, label) in buttons.iter().enumerate() {
2321 let col = i % 4;
2322 let row = i / 4;
2323 let bx = grid_x + col * (btn_w + gap);
2324 let by = grid_y + row * (btn_h + gap);
2325
2326 let btn_color: u32 = match label[0] {
2328 b'/' | b'*' | b'-' | b'+' | b'=' => accent_color,
2329 b'C' => error_color,
2330 _ => 0x3A3A4A,
2331 };
2332 let cr = ((btn_color >> 16) & 0xFF) as u8;
2333 let cg = ((btn_color >> 8) & 0xFF) as u8;
2334 let cb = (btn_color & 0xFF) as u8;
2335
2336 for dy in 0..btn_h {
2337 for dx in 0..btn_w {
2338 let px = bx + dx;
2339 let py = by + dy;
2340 if px < w && py < h {
2341 let off = (py * w + px) * 4;
2342 if off + 3 < buf.len() {
2343 buf[off] = cb;
2344 buf[off + 1] = cg;
2345 buf[off + 2] = cr;
2346 buf[off + 3] = 0xFF;
2347 }
2348 }
2349 }
2350 }
2351
2352 let lx = bx + (btn_w.saturating_sub(label.len() * 8)) / 2;
2354 let ly = by + (btn_h.saturating_sub(8)) / 2;
2355 if ly < h {
2356 draw_string_into_buffer(buf, w, label, lx, ly, 0xFFFFFF);
2357 }
2358 }
2359}
2360
2361fn draw_pixel(buf: &mut [u8], w: usize, x: usize, y: usize, color: u32) {
2363 let off = (y * w + x) * 4;
2364 if off + 3 < buf.len() {
2365 buf[off] = (color & 0xFF) as u8;
2366 buf[off + 1] = ((color >> 8) & 0xFF) as u8;
2367 buf[off + 2] = ((color >> 16) & 0xFF) as u8;
2368 buf[off + 3] = 0xFF;
2369 }
2370}
2371
2372fn format_stat_line<'a>(
2375 buf: &'a mut [u8; 64],
2376 prefix: &[u8],
2377 value: usize,
2378 sep: &[u8],
2379 value2: usize,
2380 suffix: &[u8],
2381) -> &'a [u8] {
2382 let mut pos = 0;
2383 for &b in prefix {
2384 if pos < 64 {
2385 buf[pos] = b;
2386 pos += 1;
2387 }
2388 }
2389 pos = write_usize_to_buf(buf, pos, value);
2390 for &b in sep {
2391 if pos < 64 {
2392 buf[pos] = b;
2393 pos += 1;
2394 }
2395 }
2396 pos = write_usize_to_buf(buf, pos, value2);
2397 for &b in suffix {
2398 if pos < 64 {
2399 buf[pos] = b;
2400 pos += 1;
2401 }
2402 }
2403 &buf[..pos]
2404}
2405
2406fn format_simple<'a>(buf: &'a mut [u8; 64], prefix: &[u8], value: u64) -> &'a [u8] {
2408 let mut pos = 0;
2409 for &b in prefix {
2410 if pos < 64 {
2411 buf[pos] = b;
2412 pos += 1;
2413 }
2414 }
2415 pos = write_usize_to_buf(buf, pos, value as usize);
2416 &buf[..pos]
2417}
2418
2419fn write_usize_to_buf(buf: &mut [u8; 64], mut pos: usize, value: usize) -> usize {
2421 if value == 0 {
2422 if pos < 64 {
2423 buf[pos] = b'0';
2424 pos += 1;
2425 }
2426 return pos;
2427 }
2428 let mut digits = [0u8; 20];
2429 let mut n = value;
2430 let mut count = 0;
2431 while n > 0 {
2432 digits[count] = b'0' + (n % 10) as u8;
2433 n /= 10;
2434 count += 1;
2435 }
2436 for i in (0..count).rev() {
2437 if pos < 64 {
2438 buf[pos] = digits[i];
2439 pos += 1;
2440 }
2441 }
2442 pos
2443}
2444
2445fn render_panel(state: &DesktopState, screen_width: u32) {
2447 crate::desktop::systray::with_system_tray(|tray| {
2449 let mem_stats = crate::mm::get_memory_stats();
2450 let total_mb = (mem_stats.total_frames * 4096 / (1024 * 1024)) as u32;
2451 let used_frames = mem_stats.total_frames.saturating_sub(mem_stats.free_frames);
2452 let used_mb = (used_frames * 4096 / (1024 * 1024)) as u32;
2453 tray.update_memory(used_mb, total_mb);
2454
2455 let ctx_switches = crate::sched::metrics::SCHEDULER_METRICS
2457 .context_switches
2458 .load(core::sync::atomic::Ordering::Relaxed);
2459 let cpu_pct = (ctx_switches % 100) as u8;
2461 tray.update_cpu(cpu_pct);
2462
2463 let iface = crate::net::ip::get_interface_config();
2465 let is_up = iface.ip_addr.0 != [0, 0, 0, 0];
2466 tray.update_network(is_up);
2467
2468 if let Ok(vol) =
2470 crate::audio::mixer::with_mixer(|m: &mut crate::audio::mixer::AudioMixer| {
2471 m.get_master_volume()
2472 })
2473 {
2474 let vol_pct = ((vol as u32) * 100 / 65535) as u8;
2475 tray.update_volume(vol_pct);
2476 }
2477
2478 let battery_label = if crate::power::is_initialized() {
2480 "AC"
2481 } else {
2482 "--"
2483 };
2484 tray.update_battery(battery_label);
2485 });
2486
2487 let panel_h = crate::desktop::panel::PANEL_HEIGHT;
2488 crate::desktop::panel::with_panel(|panel| {
2489 panel.update_buttons();
2490 panel.update_clock();
2491 let mut buf = vec![0u8; (screen_width as usize) * (panel_h as usize) * 4];
2492 panel.render(&mut buf);
2493 update_surface_pixels(
2494 state.panel_surface_id,
2495 state.panel_pool_id,
2496 state.panel_pool_buf_id,
2497 &buf,
2498 );
2499 });
2500}
2501
2502fn forward_events_to_apps(state: &mut DesktopState) {
2504 if state.terminal.wid > 0 {
2506 let events = crate::desktop::window_manager::with_window_manager(|wm| {
2507 wm.get_events(state.terminal.wid)
2508 })
2509 .unwrap_or_default();
2510 if !events.is_empty() {
2511 crate::desktop::terminal::with_terminal_manager(|tm| {
2512 for event in &events {
2513 let _ = tm.process_input(0, *event);
2514 }
2515 });
2516 }
2517 }
2518
2519 if state.file_manager.wid > 0 {
2521 let events = crate::desktop::window_manager::with_window_manager(|wm| {
2522 wm.get_events(state.file_manager.wid)
2523 })
2524 .unwrap_or_default();
2525 if !events.is_empty() {
2526 crate::desktop::file_manager::with_file_manager(|fm| {
2527 let mut manager = fm.write();
2528 for event in &events {
2529 let _ = manager.process_input(*event);
2530 }
2531 });
2532 }
2533 }
2534
2535 if state.text_editor.wid > 0 {
2537 let events = crate::desktop::window_manager::with_window_manager(|wm| {
2538 wm.get_events(state.text_editor.wid)
2539 })
2540 .unwrap_or_default();
2541 if !events.is_empty() {
2542 crate::desktop::text_editor::with_text_editor(|te| {
2543 let mut editor = te.write();
2544 for event in &events {
2545 let _ = editor.process_input(*event);
2546 }
2547 });
2548 }
2549 }
2550
2551 let dynamic_info: alloc::vec::Vec<(u32, AppKind)> =
2553 state.dynamic_apps.iter().map(|a| (a.wid, a.kind)).collect();
2554 for (wid, kind) in dynamic_info {
2555 let events = crate::desktop::window_manager::with_window_manager(|wm| wm.get_events(wid))
2556 .unwrap_or_default();
2557 if events.is_empty() {
2558 continue;
2559 }
2560 let win_pos = crate::desktop::window_manager::with_window_manager(|wm| {
2562 wm.get_window(wid).map(|w| (w.x, w.y))
2563 })
2564 .flatten()
2565 .unwrap_or((0, 0));
2566
2567 let mut should_close = false;
2568 for event in &events {
2569 if let crate::desktop::window_manager::InputEvent::KeyPress { scancode: 0x1B, .. } =
2571 event
2572 {
2573 should_close = true;
2574 break;
2575 }
2576 match kind {
2577 AppKind::Settings => match event {
2578 crate::desktop::window_manager::InputEvent::KeyPress { scancode, .. } => {
2579 state.settings_app.handle_key(*scancode);
2580 }
2581 crate::desktop::window_manager::InputEvent::MouseButton {
2582 button: 0,
2583 pressed: true,
2584 x,
2585 y,
2586 } => {
2587 let local_x = (*x - win_pos.0).max(0) as usize;
2588 let local_y = (*y - win_pos.1).max(0) as usize;
2589 state.settings_app.handle_click(local_x, local_y);
2590 }
2591 _ => {}
2592 },
2593 AppKind::ImageViewer => {
2594 if let crate::desktop::window_manager::InputEvent::KeyPress {
2595 scancode, ..
2596 } = event
2597 {
2598 state.image_viewer.handle_key(*scancode);
2599 }
2600 }
2601 AppKind::Calculator => {
2602 if let crate::desktop::window_manager::InputEvent::KeyPress {
2603 scancode, ..
2604 } = event
2605 {
2606 state.calculator.handle_key(*scancode);
2607 }
2608 }
2609 AppKind::MediaPlayer => {
2610 if let crate::desktop::window_manager::InputEvent::KeyPress {
2611 scancode, ..
2612 } = event
2613 {
2614 match scancode {
2615 b'+' => {
2616 let _ = crate::audio::mixer::with_mixer(|m| {
2617 let vol = m.get_master_volume();
2618 m.set_master_volume(vol.saturating_add(3276));
2619 });
2620 }
2621 b'-' => {
2622 let _ = crate::audio::mixer::with_mixer(|m| {
2623 let vol = m.get_master_volume();
2624 m.set_master_volume(vol.saturating_sub(3276));
2625 });
2626 }
2627 _ => {}
2628 }
2629 }
2630 }
2631 AppKind::PdfViewer => {
2632 if let crate::desktop::window_manager::InputEvent::KeyPress {
2633 scancode, ..
2634 } = event
2635 {
2636 match scancode {
2637 0x81 | b'n' => {
2638 state.pdf_page_index = state.pdf_page_index.saturating_add(1);
2640 }
2641 0x80 | b'p' => {
2642 state.pdf_page_index = state.pdf_page_index.saturating_sub(1);
2644 }
2645 _ => {}
2646 }
2647 }
2648 }
2649 AppKind::Browser => match event {
2650 crate::desktop::window_manager::InputEvent::KeyPress { scancode, .. } => {
2651 let mods = crate::drivers::keyboard::get_modifiers();
2652 let ctrl = mods & crate::drivers::keyboard::MOD_CTRL != 0;
2653 let shift = mods & crate::drivers::keyboard::MOD_SHIFT != 0;
2654 let alt = mods & crate::drivers::keyboard::MOD_ALT != 0;
2655 if let Some(ref mut b) = state.browser {
2656 b.handle_key(*scancode, ctrl, shift, alt);
2657 }
2658 }
2659 crate::desktop::window_manager::InputEvent::MouseButton {
2660 button: 0,
2661 pressed: true,
2662 x,
2663 y,
2664 } => {
2665 let local_x = (*x - win_pos.0).max(0);
2666 let local_y = (*y - win_pos.1).max(0);
2667 if let Some(ref mut b) = state.browser {
2668 b.handle_click(local_x, local_y);
2669 }
2670 }
2671 _ => {}
2672 },
2673 AppKind::SystemMonitor => {
2674 }
2677 }
2678 }
2679 if should_close {
2680 close_dynamic_app(state, wid);
2681 }
2682 }
2683}
2684
2685fn blit_to_framebuffer(
2691 back_buffer: &[u32],
2692 fb_ptr: *mut u8,
2693 fb_width: usize,
2694 fb_height: usize,
2695 fb_stride: usize,
2696 is_bgr: bool,
2697) {
2698 for y in 0..fb_height {
2699 for x in 0..fb_width {
2700 let src_idx = y * fb_width + x;
2701 if src_idx >= back_buffer.len() {
2702 break;
2703 }
2704 let pixel = back_buffer[src_idx];
2705 let r = ((pixel >> 16) & 0xFF) as u8;
2706 let g = ((pixel >> 8) & 0xFF) as u8;
2707 let b = (pixel & 0xFF) as u8;
2708
2709 let dst_offset = y * fb_stride + x * 4;
2710 unsafe {
2713 let ptr = fb_ptr.add(dst_offset);
2714 if is_bgr {
2715 ptr.write(b);
2717 ptr.add(1).write(g);
2718 ptr.add(2).write(r);
2719 ptr.add(3).write(0xFF);
2720 } else {
2721 ptr.write(r);
2723 ptr.add(1).write(g);
2724 ptr.add(2).write(b);
2725 ptr.add(3).write(0xFF);
2726 }
2727 }
2728 }
2729 }
2730}
2731
2732#[derive(Debug, Clone, Copy)]
2738pub struct DecorationConfig {
2739 pub title_bar_height: u32,
2741 pub border_width: u32,
2743 pub title_bg_focused: u32,
2745 pub title_bg_unfocused: u32,
2747 pub title_text_color: u32,
2749 pub border_focused: u32,
2751 pub border_unfocused: u32,
2753 pub button_size: u32,
2755 pub button_padding: u32,
2757}
2758
2759impl DecorationConfig {
2760 pub fn default_config() -> Self {
2762 Self {
2763 title_bar_height: 28,
2764 border_width: 1,
2765 title_bg_focused: 0xFF34_495E,
2766 title_bg_unfocused: 0xFF57_6574,
2767 title_text_color: 0xFFEC_F0F1,
2768 border_focused: 0xFF2C_3E50,
2769 border_unfocused: 0xFF7F_8C8D,
2770 button_size: 16,
2771 button_padding: 6,
2772 }
2773 }
2774}
2775
2776impl Default for DecorationConfig {
2777 fn default() -> Self {
2778 Self::default_config()
2779 }
2780}
2781
2782#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2784pub enum DecorationButton {
2785 Close,
2786 Maximize,
2787 Minimize,
2788}
2789
2790#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2792pub enum DecorationHitTest {
2793 None,
2795 TitleBar,
2797 CloseButton,
2799 MaximizeButton,
2801 MinimizeButton,
2803 Border(BorderEdge),
2805}
2806
2807#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2809pub enum BorderEdge {
2810 Top,
2811 Bottom,
2812 Left,
2813 Right,
2814 TopLeft,
2815 TopRight,
2816 BottomLeft,
2817 BottomRight,
2818}
2819
2820pub fn render_window_decoration(
2829 buffer: &mut [u32],
2830 buf_w: u32,
2831 config: &DecorationConfig,
2832 title: &str,
2833 focused: bool,
2834 width: u32,
2835 height: u32,
2836) {
2837 let bw = config.border_width;
2838 let tbh = config.title_bar_height;
2839 let border_color = if focused {
2840 config.border_focused
2841 } else {
2842 config.border_unfocused
2843 };
2844 let title_bg = if focused {
2845 config.title_bg_focused
2846 } else {
2847 config.title_bg_unfocused
2848 };
2849
2850 for y in 0..height {
2852 for x in 0..width {
2853 let idx = (y * buf_w + x) as usize;
2854 if idx >= buffer.len() {
2855 continue;
2856 }
2857 let in_top_border = y < bw;
2858 let in_bottom_border = y >= height.saturating_sub(bw);
2859 let in_left_border = x < bw;
2860 let in_right_border = x >= width.saturating_sub(bw);
2861
2862 if in_top_border || in_bottom_border || in_left_border || in_right_border {
2863 buffer[idx] = border_color;
2864 }
2865 }
2866 }
2867
2868 for y in bw..bw.saturating_add(tbh).min(height) {
2870 for x in bw..width.saturating_sub(bw) {
2871 let idx = (y * buf_w + x) as usize;
2872 if idx < buffer.len() {
2873 buffer[idx] = title_bg;
2874 }
2875 }
2876 }
2877
2878 let text_x = bw + 8;
2880 let text_y = bw + (tbh.saturating_sub(16)) / 2;
2881 let mut cx = text_x;
2882 for &ch in title.as_bytes() {
2883 if cx + 8 >= width.saturating_sub(bw + 3 * (config.button_size + config.button_padding)) {
2884 break;
2885 }
2886 let glyph = crate::graphics::font8x16::glyph(ch);
2887 for row in 0..16u32 {
2888 let bits = glyph[row as usize];
2889 for col in 0..8u32 {
2890 if bits & (0x80 >> col) != 0 {
2891 let px = cx + col;
2892 let py = text_y + row;
2893 let idx = (py * buf_w + px) as usize;
2894 if idx < buffer.len() {
2895 buffer[idx] = config.title_text_color;
2896 }
2897 }
2898 }
2899 }
2900 cx += 8;
2901 }
2902
2903 let btn_y = bw + (tbh.saturating_sub(config.button_size)) / 2;
2905 let btn_sz = config.button_size;
2906 let btn_pad = config.button_padding;
2907
2908 let close_x = width.saturating_sub(bw + btn_pad + btn_sz);
2910 render_decoration_button(
2911 buffer,
2912 buf_w,
2913 close_x,
2914 btn_y,
2915 btn_sz,
2916 DecorationButton::Close,
2917 false,
2918 );
2919
2920 let max_x = close_x.saturating_sub(btn_pad + btn_sz);
2922 render_decoration_button(
2923 buffer,
2924 buf_w,
2925 max_x,
2926 btn_y,
2927 btn_sz,
2928 DecorationButton::Maximize,
2929 false,
2930 );
2931
2932 let min_x = max_x.saturating_sub(btn_pad + btn_sz);
2934 render_decoration_button(
2935 buffer,
2936 buf_w,
2937 min_x,
2938 btn_y,
2939 btn_sz,
2940 DecorationButton::Minimize,
2941 false,
2942 );
2943}
2944
2945pub fn hit_test_decoration(
2948 x: i32,
2949 y: i32,
2950 config: &DecorationConfig,
2951 width: u32,
2952 height: u32,
2953) -> DecorationHitTest {
2954 let bw = config.border_width as i32;
2955 let tbh = config.title_bar_height as i32;
2956 let w = width as i32;
2957 let h = height as i32;
2958 let btn_sz = config.button_size as i32;
2959 let btn_pad = config.button_padding as i32;
2960
2961 if x < 0 || y < 0 || x >= w || y >= h {
2963 return DecorationHitTest::None;
2964 }
2965
2966 let corner = bw.max(8);
2968 if x < corner && y < corner {
2969 return DecorationHitTest::Border(BorderEdge::TopLeft);
2970 }
2971 if x >= w - corner && y < corner {
2972 return DecorationHitTest::Border(BorderEdge::TopRight);
2973 }
2974 if x < corner && y >= h - corner {
2975 return DecorationHitTest::Border(BorderEdge::BottomLeft);
2976 }
2977 if x >= w - corner && y >= h - corner {
2978 return DecorationHitTest::Border(BorderEdge::BottomRight);
2979 }
2980
2981 if y < bw {
2983 return DecorationHitTest::Border(BorderEdge::Top);
2984 }
2985 if y >= h - bw {
2986 return DecorationHitTest::Border(BorderEdge::Bottom);
2987 }
2988 if x < bw {
2989 return DecorationHitTest::Border(BorderEdge::Left);
2990 }
2991 if x >= w - bw {
2992 return DecorationHitTest::Border(BorderEdge::Right);
2993 }
2994
2995 if y >= bw && y < bw + tbh {
2997 let close_x = w - bw - btn_pad - btn_sz;
2999 if x >= close_x && x < close_x + btn_sz {
3000 return DecorationHitTest::CloseButton;
3001 }
3002
3003 let max_x = close_x - btn_pad - btn_sz;
3004 if x >= max_x && x < max_x + btn_sz {
3005 return DecorationHitTest::MaximizeButton;
3006 }
3007
3008 let min_x = max_x - btn_pad - btn_sz;
3009 if x >= min_x && x < min_x + btn_sz {
3010 return DecorationHitTest::MinimizeButton;
3011 }
3012
3013 return DecorationHitTest::TitleBar;
3014 }
3015
3016 DecorationHitTest::None
3017}
3018
3019pub fn render_decoration_button(
3024 buffer: &mut [u32],
3025 buf_w: u32,
3026 x: u32,
3027 y: u32,
3028 size: u32,
3029 button_type: DecorationButton,
3030 hovered: bool,
3031) {
3032 let bg = if hovered {
3034 match button_type {
3035 DecorationButton::Close => 0xFFE7_4C3C, DecorationButton::Maximize => 0xFF2E_CC71, DecorationButton::Minimize => 0xFFF3_9C12, }
3039 } else {
3040 match button_type {
3041 DecorationButton::Close => 0xFFC0_392B,
3042 DecorationButton::Maximize => 0xFF27_AE60,
3043 DecorationButton::Minimize => 0xFFF1_C40F,
3044 }
3045 };
3046
3047 let inset = size / 6;
3049 for dy in inset..size.saturating_sub(inset) {
3050 for dx in inset..size.saturating_sub(inset) {
3051 let px = x + dx;
3052 let py = y + dy;
3053 let idx = (py * buf_w + px) as usize;
3054 if idx < buffer.len() {
3055 buffer[idx] = bg;
3056 }
3057 }
3058 }
3059
3060 let icon_color: u32 = 0xFFFF_FFFF;
3062 let cx = x + size / 2;
3063 let cy = y + size / 2;
3064 let half = (size / 4).max(2);
3065
3066 match button_type {
3067 DecorationButton::Close => {
3068 for i in 0..half {
3070 let coords = [
3071 (cx - half + i, cy - half + i),
3072 (cx + half - i - 1, cy - half + i),
3073 (cx - half + i, cy + half - i - 1),
3074 (cx + half - i - 1, cy + half - i - 1),
3075 ];
3076 for &(px, py) in &coords {
3077 let idx = (py * buf_w + px) as usize;
3078 if idx < buffer.len() {
3079 buffer[idx] = icon_color;
3080 }
3081 }
3082 }
3083 }
3084 DecorationButton::Maximize => {
3085 for i in 0..(half * 2) {
3087 let coords = [
3088 (cx - half + i, cy - half), (cx - half + i, cy + half - 1), (cx - half, cy - half + i), (cx + half - 1, cy - half + i), ];
3093 for &(px, py) in &coords {
3094 let idx = (py * buf_w + px) as usize;
3095 if idx < buffer.len() {
3096 buffer[idx] = icon_color;
3097 }
3098 }
3099 }
3100 }
3101 DecorationButton::Minimize => {
3102 for i in 0..(half * 2) {
3104 let px = cx - half + i;
3105 let py = cy + half / 2;
3106 let idx = (py * buf_w + px) as usize;
3107 if idx < buffer.len() {
3108 buffer[idx] = icon_color;
3109 }
3110 }
3111 }
3112 }
3113}