1#![allow(dead_code)]
25
26use alloc::{string::String, vec::Vec};
27
28use spin::RwLock;
29
30use super::window_manager::{with_window_manager, WindowId};
31use crate::{error::KernelError, sync::once_lock::GlobalState};
32
33pub const PANEL_HEIGHT: u32 = 32;
35
36const NUM_WORKSPACES: usize = 4;
38
39const WORKSPACE_BUTTON_WIDTH: u32 = 24;
41
42const WORKSPACE_BUTTON_GAP: u32 = 2;
44
45const WORKSPACE_AREA_WIDTH: u32 =
47 NUM_WORKSPACES as u32 * (WORKSPACE_BUTTON_WIDTH + WORKSPACE_BUTTON_GAP) + 8;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum LayerShellLayer {
56 Background = 0,
58 Bottom = 1,
60 Top = 2,
62 Overlay = 3,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum LayerShellAnchor {
69 None = 0,
71 Top = 1,
73 Bottom = 2,
75 Left = 4,
77 Right = 8,
79}
80
81#[derive(Debug, Clone)]
83pub struct LayerSurfaceConfig {
84 pub layer: LayerShellLayer,
86 pub anchor: u32,
88 pub exclusive_zone: i32,
91 pub margin: (i32, i32, i32, i32),
93 pub width: u32,
95 pub height: u32,
97 pub keyboard_interactivity: bool,
99}
100
101impl LayerSurfaceConfig {
102 pub fn bottom_panel(screen_width: u32, height: u32) -> Self {
104 Self {
105 layer: LayerShellLayer::Top,
106 anchor: LayerShellAnchor::Bottom as u32
107 | LayerShellAnchor::Left as u32
108 | LayerShellAnchor::Right as u32,
109 exclusive_zone: height as i32,
110 margin: (0, 0, 0, 0),
111 width: screen_width,
112 height,
113 keyboard_interactivity: false,
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
124struct PanelButton {
125 window_id: WindowId,
126 title: String,
127 x: i32,
128 width: u32,
129 focused: bool,
130}
131
132struct WorkspaceState {
138 active: usize,
140 window_counts: [u32; NUM_WORKSPACES],
142}
143
144impl WorkspaceState {
145 fn new() -> Self {
146 Self {
147 active: 0,
148 window_counts: [0; NUM_WORKSPACES],
149 }
150 }
151}
152
153pub struct Panel {
159 screen_width: u32,
161 screen_height: u32,
163 buttons: RwLock<Vec<PanelButton>>,
165 clock_text: RwLock<String>,
167 layer_surface_id: Option<u32>,
169 layer_config: Option<LayerSurfaceConfig>,
171 workspaces: RwLock<WorkspaceState>,
173}
174
175impl Panel {
176 pub fn new(screen_width: u32, screen_height: u32) -> Self {
178 Self {
179 screen_width,
180 screen_height,
181 buttons: RwLock::new(Vec::new()),
182 clock_text: RwLock::new(String::from("00:00")),
183 layer_surface_id: None,
184 layer_config: None,
185 workspaces: RwLock::new(WorkspaceState::new()),
186 }
187 }
188
189 pub fn y(&self) -> i32 {
191 (self.screen_height - PANEL_HEIGHT) as i32
192 }
193
194 pub fn init_layer_surface(&mut self) -> Option<u32> {
203 if self.layer_surface_id.is_some() {
204 return self.layer_surface_id;
205 }
206
207 let config = LayerSurfaceConfig::bottom_panel(self.screen_width, PANEL_HEIGHT);
208
209 let (surface_id, _pool_id, _pool_buf_id) =
211 super::renderer::create_app_surface(0, self.y(), self.screen_width, PANEL_HEIGHT);
212
213 crate::desktop::wayland::with_display(|display| {
215 display
216 .wl_compositor
217 .set_surface_position(surface_id, 0, self.y());
218 display.wl_compositor.raise_surface(surface_id);
220 });
221
222 crate::println!(
223 "[PANEL] Layer-shell surface initialized: id={}, layer=Top, anchor=Bottom|Left|Right, \
224 exclusive_zone={}",
225 surface_id,
226 PANEL_HEIGHT
227 );
228
229 self.layer_surface_id = Some(surface_id);
230 self.layer_config = Some(config);
231 self.layer_surface_id
232 }
233
234 pub fn layer_surface_id(&self) -> Option<u32> {
236 self.layer_surface_id
237 }
238
239 pub fn set_active_workspace(&self, index: usize) {
241 if index < NUM_WORKSPACES {
242 self.workspaces.write().active = index;
243 }
244 }
245
246 pub fn active_workspace(&self) -> usize {
248 self.workspaces.read().active
249 }
250
251 pub fn update_workspace_counts(&self) {
253 let windows = with_window_manager(|wm| wm.get_all_windows()).unwrap_or_default();
254 let mut ws = self.workspaces.write();
255
256 for count in ws.window_counts.iter_mut() {
258 *count = 0;
259 }
260
261 for window in &windows {
264 if window.visible {
265 ws.window_counts[0] += 1;
266 }
267 }
268 }
269
270 fn handle_workspace_click(&self, x: i32) -> Option<usize> {
274 let start_x = 4i32;
275 for i in 0..NUM_WORKSPACES {
276 let btn_x =
277 start_x + i as i32 * (WORKSPACE_BUTTON_WIDTH as i32 + WORKSPACE_BUTTON_GAP as i32);
278 let btn_end = btn_x + WORKSPACE_BUTTON_WIDTH as i32;
279 if x >= btn_x && x < btn_end {
280 return Some(i);
281 }
282 }
283 None
284 }
285
286 pub fn update_buttons(&self) {
288 let windows = with_window_manager(|wm| wm.get_all_windows()).unwrap_or_default();
289
290 let mut buttons = self.buttons.write();
291 buttons.clear();
292
293 let button_width = 120u32;
294 let mut x = WORKSPACE_AREA_WIDTH as i32 + 4;
296
297 for window in &windows {
298 if !window.visible {
299 continue;
300 }
301 buttons.push(PanelButton {
302 window_id: window.id,
303 title: String::from(window.title_str()),
304 x,
305 width: button_width,
306 focused: window.focused,
307 });
308 x += button_width as i32 + 4;
309 }
310 }
311
312 pub fn update_clock(&self) {
314 #[cfg(target_arch = "x86_64")]
317 let epoch_secs = crate::arch::x86_64::rtc::current_epoch_secs();
318 #[cfg(not(target_arch = "x86_64"))]
319 let epoch_secs = {
320 let ticks = crate::arch::timer::read_hw_timestamp();
321 ticks / 1_000_000_000
322 };
323
324 let tz_offset_secs: u64 = 5 * 3600;
326 let local_epoch = epoch_secs.saturating_sub(tz_offset_secs);
327
328 let secs_of_day = local_epoch % 86400;
330 let hours = (secs_of_day / 3600) % 24;
331 let minutes = (secs_of_day / 60) % 60;
332
333 let mut remaining_days = (local_epoch / 86400) as u32;
335
336 let day_of_week = (remaining_days + 4) % 7; let day_name = match day_of_week {
339 0 => "Sun",
340 1 => "Mon",
341 2 => "Tue",
342 3 => "Wed",
343 4 => "Thu",
344 5 => "Fri",
345 6 => "Sat",
346 _ => "???",
347 };
348
349 let mut year: u32 = 1970;
351 loop {
352 let days_in_year = if is_leap_year(year) { 366 } else { 365 };
353 if remaining_days < days_in_year {
354 break;
355 }
356 remaining_days -= days_in_year;
357 year += 1;
358 }
359
360 let month_days: [u32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
361 let mut month_idx: usize = 0;
362 for (i, &md) in month_days.iter().enumerate() {
363 let days = if i == 1 && is_leap_year(year) { 29 } else { md };
364 if remaining_days < days {
365 month_idx = i;
366 break;
367 }
368 remaining_days -= days;
369 if i == 11 {
370 month_idx = 11;
371 }
372 }
373 let month_day = remaining_days + 1;
374
375 let month = match month_idx {
376 0 => "Jan",
377 1 => "Feb",
378 2 => "Mar",
379 3 => "Apr",
380 4 => "May",
381 5 => "Jun",
382 6 => "Jul",
383 7 => "Aug",
384 8 => "Sep",
385 9 => "Oct",
386 10 => "Nov",
387 11 => "Dec",
388 _ => "???",
389 };
390
391 let mut clock = self.clock_text.write();
392 clock.clear();
393
394 for ch in day_name.chars() {
396 clock.push(ch);
397 }
398 clock.push(' ');
399 for ch in month.chars() {
400 clock.push(ch);
401 }
402 clock.push(' ');
403 if month_day < 10 {
404 clock.push(' ');
405 }
406 for ch in fmt_u64(month_day as u64).chars() {
407 clock.push(ch);
408 }
409 clock.push(' ');
410 if hours < 10 {
411 clock.push('0');
412 }
413 for ch in fmt_u64(hours).chars() {
414 clock.push(ch);
415 }
416 clock.push(':');
417 if minutes < 10 {
418 clock.push('0');
419 }
420 for ch in fmt_u64(minutes).chars() {
421 clock.push(ch);
422 }
423 }
424
425 pub fn handle_click(&self, x: i32, _y: i32) -> Option<WindowId> {
429 if let Some(ws_idx) = self.handle_workspace_click(x) {
431 self.set_active_workspace(ws_idx);
432 return None;
433 }
434
435 let buttons = self.buttons.read();
437 for button in buttons.iter() {
438 if x >= button.x && x < button.x + button.width as i32 {
439 return Some(button.window_id);
440 }
441 }
442 None
443 }
444
445 pub fn render(&self, buf: &mut [u8]) {
451 let w = self.screen_width as usize;
452 let h = PANEL_HEIGHT as usize;
453 let stride = w * 4;
454
455 for y in 0..h {
457 for x in 0..w {
458 let offset = y * stride + x * 4;
459 if offset + 3 < buf.len() {
460 buf[offset] = 0x2D; buf[offset + 1] = 0x2D; buf[offset + 2] = 0x2D; buf[offset + 3] = 0xFF; }
465 }
466 }
467
468 for x in 0..w {
470 let offset = x * 4;
471 if offset + 3 < buf.len() {
472 buf[offset] = 0x44; buf[offset + 1] = 0x44; buf[offset + 2] = 0x44; buf[offset + 3] = 0xFF;
476 }
477 }
478
479 self.render_workspaces(buf, stride, w, h);
481
482 let sep_x = WORKSPACE_AREA_WIDTH as usize;
484 for y in 4..h - 4 {
485 let offset = y * stride + sep_x * 4;
486 if offset + 3 < buf.len() {
487 buf[offset] = 0x55; buf[offset + 1] = 0x55; buf[offset + 2] = 0x55; buf[offset + 3] = 0xFF;
491 }
492 }
493
494 let buttons = self.buttons.read();
496 for button in buttons.iter() {
497 let btn_x = button.x as usize;
498 let btn_w = button.width as usize;
499
500 let (br, bg, bb) = if button.focused {
502 (0x50, 0x50, 0x70)
503 } else {
504 (0x40, 0x40, 0x40)
505 };
506
507 for y in 4..h - 4 {
508 for x in btn_x..(btn_x + btn_w).min(w) {
509 let offset = y * stride + x * 4;
510 if offset + 3 < buf.len() {
511 buf[offset] = bb;
512 buf[offset + 1] = bg;
513 buf[offset + 2] = br;
514 buf[offset + 3] = 0xFF;
515 }
516 }
517 }
518
519 if button.focused {
521 for x in btn_x..(btn_x + btn_w).min(w) {
522 let offset = (h - 3) * stride + x * 4;
523 if offset + 3 < buf.len() {
524 buf[offset] = 0xDD; buf[offset + 1] = 0x88; buf[offset + 2] = 0x44; buf[offset + 3] = 0xFF;
528 }
529 }
530 }
531
532 let title_bytes = button.title.as_bytes();
534 let max_chars = (btn_w / 8).min(14);
535 for (i, &ch) in title_bytes.iter().take(max_chars).enumerate() {
536 render_char_to_buf(buf, stride, btn_x + 4 + i * 8, 10, ch, (0xCC, 0xCC, 0xCC));
537 }
538 }
539
540 let clock = self.clock_text.read();
542 let clock_x = w.saturating_sub(clock.len() * 8 + 12);
543 for (i, &ch) in clock.as_bytes().iter().enumerate() {
544 render_char_to_buf(buf, stride, clock_x + i * 8, 10, ch, (0xBB, 0xBB, 0xBB));
545 }
546 }
547
548 fn render_workspaces(&self, buf: &mut [u8], stride: usize, max_w: usize, h: usize) {
550 let ws = self.workspaces.read();
551 let start_x = 4usize;
552
553 for i in 0..NUM_WORKSPACES {
554 let btn_x =
555 start_x + i * (WORKSPACE_BUTTON_WIDTH as usize + WORKSPACE_BUTTON_GAP as usize);
556 let btn_w = WORKSPACE_BUTTON_WIDTH as usize;
557
558 let (br, bg, bb) = if i == ws.active {
560 (0x55, 0x66, 0x99) } else if ws.window_counts[i] > 0 {
562 (0x48, 0x48, 0x48) } else {
564 (0x38, 0x38, 0x38) };
566
567 for y in 6..h - 6 {
569 for x in btn_x..(btn_x + btn_w).min(max_w) {
570 let offset = y * stride + x * 4;
571 if offset + 3 < buf.len() {
572 buf[offset] = bb;
573 buf[offset + 1] = bg;
574 buf[offset + 2] = br;
575 buf[offset + 3] = 0xFF;
576 }
577 }
578 }
579
580 if i == ws.active {
582 for x in btn_x..(btn_x + btn_w).min(max_w) {
583 let offset = (h - 4) * stride + x * 4;
584 if offset + 3 < buf.len() {
585 buf[offset] = 0xDD; buf[offset + 1] = 0x88; buf[offset + 2] = 0x44; buf[offset + 3] = 0xFF;
589 }
590 }
591 }
592
593 let digit = b'1' + i as u8;
595 let char_x = btn_x + (btn_w / 2).saturating_sub(4);
596 let text_color = if i == ws.active {
597 (0xFF, 0xFF, 0xFF) } else {
599 (0x99, 0x99, 0x99) };
601 render_char_to_buf(buf, stride, char_x, 10, digit, text_color);
602 }
603 }
604}
605
606fn render_char_to_buf(
608 buf: &mut [u8],
609 stride: usize,
610 px: usize,
611 py: usize,
612 ch: u8,
613 color: (u8, u8, u8),
614) {
615 use crate::graphics::font8x16;
616
617 let glyph = font8x16::glyph(ch);
618 for (row, &bits) in glyph.iter().enumerate() {
619 for col in 0..8 {
620 if (bits >> (7 - col)) & 1 != 0 {
621 let x = px + col;
622 let y = py + row;
623 let offset = y * stride + x * 4;
624 if offset + 3 < buf.len() {
625 buf[offset] = color.2; buf[offset + 1] = color.1; buf[offset + 2] = color.0; buf[offset + 3] = 0xFF;
629 }
630 }
631 }
632 }
633}
634
635fn is_leap_year(y: u32) -> bool {
637 (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400)
638}
639
640fn fmt_u64(n: u64) -> &'static str {
642 match n {
643 0 => "0",
644 1 => "1",
645 2 => "2",
646 3 => "3",
647 4 => "4",
648 5 => "5",
649 6 => "6",
650 7 => "7",
651 8 => "8",
652 9 => "9",
653 10 => "10",
654 11 => "11",
655 12 => "12",
656 13 => "13",
657 14 => "14",
658 15 => "15",
659 16 => "16",
660 17 => "17",
661 18 => "18",
662 19 => "19",
663 20 => "20",
664 21 => "21",
665 22 => "22",
666 23 => "23",
667 24 => "24",
668 25 => "25",
669 26 => "26",
670 27 => "27",
671 28 => "28",
672 29 => "29",
673 30 => "30",
674 31 => "31",
675 32 => "32",
676 33 => "33",
677 34 => "34",
678 35 => "35",
679 36 => "36",
680 37 => "37",
681 38 => "38",
682 39 => "39",
683 40 => "40",
684 41 => "41",
685 42 => "42",
686 43 => "43",
687 44 => "44",
688 45 => "45",
689 46 => "46",
690 47 => "47",
691 48 => "48",
692 49 => "49",
693 50 => "50",
694 51 => "51",
695 52 => "52",
696 53 => "53",
697 54 => "54",
698 55 => "55",
699 56 => "56",
700 57 => "57",
701 58 => "58",
702 59 => "59",
703 _ => "??",
704 }
705}
706
707static PANEL: GlobalState<Panel> = GlobalState::new();
709
710pub fn init(screen_width: u32, screen_height: u32) -> Result<(), KernelError> {
712 PANEL
713 .init(Panel::new(screen_width, screen_height))
714 .map_err(|_| KernelError::InvalidState {
715 expected: "uninitialized",
716 actual: "initialized",
717 })?;
718
719 crate::println!(
720 "[PANEL] Desktop panel initialized ({}x{}, layer-shell ready)",
721 screen_width,
722 PANEL_HEIGHT
723 );
724 Ok(())
725}
726
727pub fn with_panel<R, F: FnOnce(&Panel) -> R>(f: F) -> Option<R> {
729 PANEL.with(f)
730}