1#![allow(dead_code)]
3#![allow(static_mut_refs)]
4
5extern crate alloc;
6use alloc::format;
7use alloc::string::String;
8
9use crate::kernel::draw::{Color, Screen};
10use crate::kernel::timer;
11use crate::kernel::window_mgr;
12
13#[derive(Clone, Copy, PartialEq, Eq)]
14pub enum SortColumn {
15 Name,
16 Cpu,
17 Ram,
18 Info,
19}
20
21pub struct SystemMonitorPane {
22 pub x: i32,
23 pub y: i32,
24 pub width: u32,
25 pub height: u32,
26 pub last_update_ticks: usize,
27 pub sort_col: SortColumn,
28 pub sort_asc: bool,
29 pub selected_idx: Option<usize>,
30 pub focused: bool,
31 pub last_mouse_btn: bool,
32 pub last_click_ms: u64,
33 pub wants_power_menu: bool,
34 pub core_loads: [u32; 4],
35 pub last_core_update_ticks: usize,
36 pub dhcp_status: String,
37}
38
39impl SystemMonitorPane {
40 pub fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
41 Self {
42 x,
43 y,
44 width: w,
45 height: h,
46 last_update_ticks: 0,
47 sort_col: SortColumn::Name,
48 sort_asc: true,
49 selected_idx: None,
50 focused: false,
51 last_mouse_btn: false,
52 last_click_ms: 0,
53 wants_power_menu: false,
54 core_loads: [0; 4],
55 last_core_update_ticks: 0,
56 dhcp_status: String::new(),
57 }
58 }
59
60 pub fn draw(&mut self, screen: &Screen) {
62 let ux0 = self.x.max(0) as u32;
63 let uy0 = self.y.max(0) as u32;
64 let ux1 = (self.x + self.width as i32).max(0) as u32;
65 let uy1 = (self.y + self.height as i32).max(0) as u32;
66 let theme = crate::kernel::config::get_config().theme;
67
68 screen.boxfill(ux0, uy0, ux1, uy1, Color(theme.sysmon_bg));
70
71 let dot_color = Color(theme.sysmon_header_bg);
73 for gy in (uy0..uy1).step_by(30) {
74 for gx in (ux0..ux1).step_by(30) {
75 screen.draw_pixel(gx, gy, dot_color);
76 }
77 }
78
79 screen.boxfill(ux0, uy0, ux1, uy0 + 30, Color(theme.sysmon_header_bg));
81 screen.boxfill(ux0, uy0 + 30, ux1, uy0 + 31, Color(theme.sysmon_accent)); screen.draw_string_ja_size(
83 ux0 + 15,
84 uy0 + 8,
85 "SYSTEM STATS MONITOR",
86 14,
87 Color(theme.sysmon_accent),
88 );
89
90 static ICON_POWER: &[u8] = include_bytes!("../../icons/ic_fluent_power_20_regular.svg");
92 let pwr_x = ux1.saturating_sub(45);
93 let pwr_y = uy0 + 5;
94 screen.boxfill(pwr_x, pwr_y, pwr_x + 35, pwr_y + 20, Color(theme.focus_highlight)); screen.draw_svg_icon(pwr_x + 8, pwr_y + 1, ICON_POWER, 18, Color(crate::kernel::config::get_config().theme.mouse_cursor));
96
97 let text_color = Color(theme.sysmon_text);
99 let val_color = Color(theme.sysmon_value);
100
101 screen.draw_string_ja_size(ux0 + 15, uy0 + 45, "OS Kernel :", 14, text_color);
103 screen.draw_string_ja_size(ux0 + 120, uy0 + 45, "AtmOS v3 (SMP)", 14, val_color);
104 screen.draw_string_ja_size(ux0 + 15, uy0 + 63, "CPU Cores :", 14, text_color);
105 screen.draw_string_ja_size(ux0 + 120, uy0 + 63, "4 Cores (SMP)", 14, val_color);
106
107 let uptime_ticks = timer::get_ticks();
108
109 screen.boxfill(ux0 + 10, uy0 + 82, ux1 - 10, uy0 + 83, Color(theme.sysmon_border));
111 screen.draw_string_ja_size(
112 ux0 + 15,
113 uy0 + 95,
114 "--- Network Interface ---",
115 14,
116 Color(theme.sysmon_accent),
117 );
118
119 let net_info = {
120 let _lock = crate::kernel::net::NET_LOCK.lock();
121 let st = crate::kernel::net::status();
122 if st.link_up {
123 let backend = crate::kernel::net::nic_backend_name(st.nic_backend);
124 let mac_str = format!(
125 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
126 st.mac[0], st.mac[1], st.mac[2], st.mac[3], st.mac[4], st.mac[5]
127 );
128 let ip_str = format!("{}.{}.{}.{}", st.ip[0], st.ip[1], st.ip[2], st.ip[3]);
129 Some((backend, mac_str, ip_str))
130 } else {
131 None
132 }
133 };
134
135 if let Some((backend, mac, ip)) = net_info {
136 let backend_line = format!("Backend : {}", backend);
137 let mac_line = format!("MAC : {}", mac);
138 let ip_line = format!("IP : {}", ip);
139 screen.draw_string_ja_size(ux0 + 15, uy0 + 115, &backend_line, 14, Color(theme.sysmon_accent));
140 screen.draw_string_ja_size(ux0 + 15, uy0 + 131, &mac_line, 14, Color(theme.sysmon_accent));
141 screen.draw_string_ja_size(ux0 + 15, uy0 + 147, &ip_line, 14, Color(theme.sysmon_accent));
142 } else {
143 let dhcp_status = crate::kernel::net::dhcp_status_line();
145 screen.draw_string_ja_size(ux0 + 15, uy0 + 115, &dhcp_status, 14, Color(theme.focus_highlight));
146 screen.draw_string_ja_size(ux0 + 15, uy0 + 131, "MAC : -", 14, text_color);
147 screen.draw_string_ja_size(ux0 + 15, uy0 + 147, "IP : -", 14, text_color);
148 }
149
150 let mem_y0 = uy0 + 165;
152 let (used, total) = crate::kernel::allocator::get_heap_stats();
153 let used_mb = used as f32 / (1024.0 * 1024.0);
154 let total_mb = total as f32 / (1024.0 * 1024.0);
155 let usage_pct = if total > 0 {
156 (used as f32 / total as f32) * 100.0
157 } else {
158 0.0
159 };
160
161 screen.boxfill(ux0 + 10, mem_y0, ux1 - 10, mem_y0 + 1, Color(theme.sysmon_border));
162 screen.draw_string_ja_size(
163 ux0 + 15,
164 mem_y0 + 15,
165 "--- Memory Heap ---",
166 14,
167 Color(theme.sysmon_accent),
168 );
169
170 let size_str = format!("{:.2} MB / {:.2} MB", used_mb, total_mb);
171 screen.draw_string_ja_size(ux0 + 15, mem_y0 + 37, "Usage Info :", 14, text_color);
172 screen.draw_string_ja_size(ux0 + 120, mem_y0 + 37, &size_str, 14, val_color);
173
174 let bar_x0 = ux0 + 15;
176 let bar_y0 = mem_y0 + 55;
177 let bar_x1 = ux1 - 15;
178 let bar_y1 = bar_y0 + 14; let bar_w = bar_x1 - bar_x0;
180 let fill_w = (bar_w as f32 * (usage_pct / 100.0)) as u32;
181
182 screen.boxfill(bar_x0, bar_y0, bar_x1, bar_y1, Color(theme.sysmon_header_bg)); let meter_color = if usage_pct > 50.0 {
184 Color(theme.focus_highlight) } else {
186 Color(theme.sysmon_accent) };
188 screen.boxfill(bar_x0, bar_y0, bar_x0 + fill_w, bar_y1, meter_color);
189
190 let pct_str = format!("{:.1}% used", usage_pct);
191 screen.draw_string_ja_size(ux0 + 15, mem_y0 + 74, &pct_str, 14, meter_color);
192
193 let cpu_y0 = mem_y0 + 95;
195 screen.boxfill(ux0 + 10, cpu_y0, ux1 - 10, cpu_y0 + 1, Color(theme.sysmon_border));
196 screen.draw_string_ja_size(
197 ux0 + 15,
198 cpu_y0 + 15,
199 "--- Core Activity ---",
200 14,
201 Color(theme.sysmon_accent),
202 );
203
204 if self.last_core_update_ticks == 0
205 || uptime_ticks.saturating_sub(self.last_core_update_ticks) >= 1000
206 {
207 for core in 0..4 {
208 let seed = uptime_ticks.wrapping_add(core * 31);
209 let mut hash = seed ^ (seed >> 15);
210 hash = hash.wrapping_mul(0x85ebca6b);
211 hash ^= hash >> 13;
212 hash = hash.wrapping_mul(0xc2b2ae35);
213 hash ^= hash >> 16;
214 self.core_loads[core] = 10 + (hash % 41) as u32; }
216 self.last_core_update_ticks = uptime_ticks;
217 }
218
219 let meter_bg = Color(theme.sysmon_header_bg);
220 let meter_fg = Color(theme.sysmon_accent);
221 let max_bar_w = (ux1 - ux0).saturating_sub(180).min(150); for core in 0..4 {
223 let core_y = cpu_y0 + 35 + (core as u32 * 22); let label = format!("Core {}:", core);
225 screen.draw_string_ja_size(ux0 + 15, core_y, &label, 14, text_color);
226
227 let load_val = self.core_loads[core];
228
229 let bar_x0 = ux0 + 75;
230 let bar_y0 = core_y + 2;
231 let bar_x1 = bar_x0 + max_bar_w;
232 let bar_y1 = bar_y0 + 10;
233
234 screen.boxfill(bar_x0, bar_y0, bar_x1, bar_y1, meter_bg);
235 let fill_width = (load_val * max_bar_w) / 50;
236 screen.boxfill(bar_x0, bar_y0, bar_x0 + fill_width, bar_y1, meter_fg);
237
238 let load_str = format!("{}%", load_val * 2);
239 screen.draw_string_ja_size(bar_x1 + 8, core_y, &load_str, 14, val_color);
240 }
241
242 self.last_update_ticks = uptime_ticks;
243
244 self.draw_process_list(screen, ux0, uy0 + 395, ux1, uy1);
246 }
247
248 fn draw_process_list(&self, screen: &Screen, ux0: u32, y_start: u32, ux1: u32, uy1: u32) {
249 let theme = crate::kernel::config::get_config().theme;
250 let text_color = Color(theme.sysmon_text);
251 let header_bg = Color(theme.sysmon_header_bg);
252 let list_bg = Color(theme.sysmon_bg);
253 let selected_bg = Color(theme.sysmon_border);
254
255 screen.boxfill(ux0 + 10, y_start, ux1 - 10, y_start + 1, Color(theme.sysmon_border));
256 screen.draw_string_ja_size(
257 ux0 + 15,
258 y_start + 10,
259 "--- Running Processes ---",
260 14,
261 Color(theme.sysmon_accent),
262 );
263
264 let table_y = y_start + 30;
265
266 screen.boxfill(ux0, table_y, ux1, table_y + 20, header_bg);
268
269 let pane_w = ux1 - ux0;
271 let col_ws_x = ux0 + 8;
272 let col_state_x = ux0 + 26;
273 let col_name_x = ux0 + 48;
274 let col_info_x = ux0 + (pane_w * 55 / 100); let get_sort_marker = |col| {
277 if self.sort_col == col {
278 if self.sort_asc {
279 " [^]"
280 } else {
281 " [v]"
282 }
283 } else {
284 ""
285 }
286 };
287
288 screen.draw_string_ja_size(col_ws_x, table_y + 3, "W", 14, Color(theme.sysmon_value));
289 screen.draw_string_ja_size(col_state_x, table_y + 3, "S", 14, Color(theme.sysmon_value));
290 screen.draw_string_ja_size(
291 col_name_x,
292 table_y + 3,
293 &format!("Name{}", get_sort_marker(SortColumn::Name)),
294 14,
295 Color(theme.sysmon_value),
296 );
297 screen.draw_string_ja_size(
298 col_info_x,
299 table_y + 3,
300 &format!("Info{}", get_sort_marker(SortColumn::Info)),
301 14,
302 Color(theme.sysmon_value),
303 );
304
305 let processes = self.build_process_list();
306 let mut row_y = table_y + 20;
307 let row_h = 18;
308
309 for (i, p) in processes.iter().enumerate() {
310 if row_y + row_h > uy1 {
311 break;
312 }
313
314 let bg = if Some(i) == self.selected_idx {
315 selected_bg
316 } else {
317 list_bg
318 };
319 screen.boxfill(ux0, row_y, ux1, row_y + row_h, bg);
320
321 let fg = if Some(i) == self.selected_idx {
322 Color(crate::kernel::config::get_config().theme.mouse_cursor)
323 } else {
324 text_color
325 };
326
327 let ws_str = match p.workspace {
329 Some(ws) => alloc::format!("{}", ws),
330 None => alloc::string::String::from("-"),
331 };
332 screen.draw_string_ja_size(col_ws_x, row_y + 2, &ws_str, 14, fg);
333
334 screen.draw_string_ja_size(col_state_x, row_y + 2, p.state_char, 14, fg);
336
337 let name_str = if p.is_child {
338 p.display_name.clone()
339 } else {
340 format!("> {}", p.display_name)
341 };
342 screen.draw_string_ja_size(col_name_x, row_y + 2, &name_str, 14, fg);
343 screen.draw_string_ja_size(col_info_x, row_y + 2, &p.original_name, 14, fg);
344
345 row_y += row_h;
346 }
347
348 if self.focused {
350 screen.boxfill(ux0, y_start, ux1, y_start + 1, Color(theme.sysmon_accent));
351 screen.boxfill(ux0, uy1 - 1, ux1, uy1, Color(theme.sysmon_accent));
352 screen.boxfill(ux0, y_start, ux0 + 1, uy1, Color(theme.sysmon_accent));
353 screen.boxfill(ux1 - 1, y_start, ux1, uy1, Color(theme.sysmon_accent));
354 }
355 }
356
357 pub fn build_process_list(&self) -> alloc::vec::Vec<ProcessInfo> {
358 let wm = window_mgr::get_instance();
359
360 struct AppGroup {
361 name: String,
362 cpu: u32,
363 ram: u32,
364 instances: alloc::vec::Vec<(Option<usize>, String, u32, u32)>,
365 }
366
367 let mut groups: alloc::vec::Vec<AppGroup> = alloc::vec::Vec::new();
368
369 let term_cpu = 15;
370 let term_ram = 10;
371 groups.push(AppGroup {
372 name: String::from("Terminal"),
373 cpu: term_cpu,
374 ram: term_ram,
375 instances: alloc::vec![(None, String::from("System Shell"), term_cpu, term_ram)],
376 });
377
378 for (i, win) in wm.windows.iter().enumerate() {
379 let name = win.app.name();
380 let info = win.app.instance_info();
381
382 let cpu = 5 + (i as u32 * 3) % 20;
383 let ram = 20 + (i as u32 * 7) % 50;
384
385 if let Some(g) = groups.iter_mut().find(|g| g.name == name) {
386 g.cpu += cpu;
387 g.ram += ram;
388 g.instances.push((Some(i), info, cpu, ram));
389 } else {
390 groups.push(AppGroup {
391 name: String::from(name),
392 cpu,
393 ram,
394 instances: alloc::vec![(Some(i), info, cpu, ram)],
395 });
396 }
397 }
398
399 groups.sort_by(|a, b| {
400 let cmp = match self.sort_col {
401 SortColumn::Name => a.name.cmp(&b.name),
402 SortColumn::Cpu => a.cpu.cmp(&b.cpu),
403 SortColumn::Ram => a.ram.cmp(&b.ram),
404 SortColumn::Info => core::cmp::Ordering::Equal,
405 };
406 if self.sort_asc {
407 cmp
408 } else {
409 cmp.reverse()
410 }
411 });
412
413 let mut flat = alloc::vec::Vec::new();
414 for g in groups {
415 if g.instances.len() == 1 {
416 let inst = &g.instances[0];
417 let display_info = if inst.1.is_empty() {
418 String::from("-")
419 } else {
420 inst.1.clone()
421 };
422
423 let mut ws = None;
424 let mut state = "N";
425 if let Some(win_idx) = inst.0 {
426 let win = &wm.windows[win_idx];
427 ws = Some(win.workspace);
428 state = if win.is_minimized {
429 "m"
430 } else if win.is_fullscreen {
431 "F"
432 } else if win.x == 100 && win.y == 0 {
433 "M"
434 } else {
435 "N"
436 };
437 } else if g.name == "Terminal" {
438 ws = Some(wm.active_workspace);
439 state = "N";
440 }
441
442 flat.push(ProcessInfo {
443 window_idx: inst.0,
444 display_name: g.name.clone(),
445 cpu: inst.2,
446 ram: inst.3,
447 is_child: false,
448 original_name: display_info,
449 workspace: ws,
450 state_char: state,
451 });
452 } else {
453 flat.push(ProcessInfo {
454 window_idx: None,
455 display_name: g.name.clone(),
456 cpu: g.cpu,
457 ram: g.ram,
458 is_child: false,
459 original_name: format!("{} instances", g.instances.len()),
460 workspace: None,
461 state_char: "N",
462 });
463
464 for (idx, inst) in g.instances.into_iter().enumerate() {
465 let display = if inst.1.is_empty() {
466 format!("{}: {}", g.name, idx + 1)
467 } else {
468 inst.1.clone()
469 };
470
471 let mut ws = None;
472 let mut state = "N";
473 if let Some(win_idx) = inst.0 {
474 let win = &wm.windows[win_idx];
475 ws = Some(win.workspace);
476 state = if win.is_minimized {
477 "m"
478 } else if win.is_fullscreen {
479 "F"
480 } else if win.x == 100 && win.y == 0 {
481 "M"
482 } else {
483 "N"
484 };
485 }
486
487 flat.push(ProcessInfo {
488 window_idx: inst.0,
489 display_name: format!(" |- {}", display),
490 cpu: inst.2,
491 ram: inst.3,
492 is_child: true,
493 original_name: display,
494 workspace: ws,
495 state_char: state,
496 });
497 }
498 }
499 }
500
501 flat
502 }
503
504 pub fn handle_mouse(&mut self, mx: i32, my: i32, btn_left: bool) -> bool {
505 let btn_just_pressed = btn_left && !self.last_mouse_btn;
506 self.last_mouse_btn = btn_left;
507
508 if mx < self.x || mx > self.x + self.width as i32 {
509 if btn_just_pressed {
510 self.focused = false;
511 }
512 return false;
513 }
514
515 if btn_just_pressed {
516 self.focused = true;
517
518 let pwr_x = self.x + self.width as i32 - 45;
520 let pwr_y = self.y + 5;
521 if mx >= pwr_x && mx <= pwr_x + 35 && my >= pwr_y && my <= pwr_y + 20 {
522 self.wants_power_menu = true;
523 return true;
524 }
525
526 let now = crate::kernel::timer::get_system_time_ms();
527 let is_double_click = (now - self.last_click_ms) < 400;
528 self.last_click_ms = now;
529
530 let uy0 = self.y.max(0) as u32;
531 let y_start = uy0 + 395;
532 let table_y = y_start + 30;
533
534 if my as u32 >= table_y && (my as u32) < table_y + 20 {
535 let rel_x = mx - self.x;
536 let pane_w = self.width as i32;
537 let new_col = if rel_x < (pane_w * 55 / 100) {
538 SortColumn::Name
539 } else {
540 SortColumn::Info
541 };
542
543 if self.sort_col == new_col {
544 self.sort_asc = !self.sort_asc;
545 } else {
546 self.sort_col = new_col;
547 self.sort_asc = true;
548 }
549 return true;
550 }
551
552 let list_start_y = table_y + 20;
553 if my as u32 >= list_start_y {
554 let row = ((my as u32 - list_start_y) / 18) as usize;
555 let procs = self.build_process_list();
556 if row < procs.len() {
557 self.selected_idx = Some(row);
558 if is_double_click {
559 self.activate_selected();
560 }
561 } else {
562 self.selected_idx = None;
563 }
564 }
565 }
566 true
567 }
568
569 pub fn handle_key(&mut self, keycode: u8, _ascii: Option<char>) -> bool {
570 if !self.focused {
571 return false;
572 }
573 let procs = self.build_process_list();
574 if procs.is_empty() {
575 return false;
576 }
577
578 let handled = match keycode {
579 0x48 => {
580 if let Some(mut idx) = self.selected_idx {
582 idx = idx.saturating_sub(1);
583 self.selected_idx = Some(idx);
584 } else {
585 self.selected_idx = Some(0);
586 }
587 true
588 }
589 0x50 => {
590 if let Some(mut idx) = self.selected_idx {
592 if idx + 1 < procs.len() {
593 idx += 1;
594 }
595 self.selected_idx = Some(idx);
596 } else {
597 self.selected_idx = Some(0);
598 }
599 true
600 }
601 0x1C => {
602 self.activate_selected();
604 true
605 }
606 _ => false,
607 };
608
609 if handled {
610 window_mgr::get_instance().dirty = true;
611 }
612 handled
613 }
614
615 fn activate_selected(&mut self) {
616 if let Some(idx) = self.selected_idx {
617 let procs = self.build_process_list();
618 if idx < procs.len() {
619 let p = &procs[idx];
620 if let Some(win_idx) = p.window_idx {
621 let mut wm = window_mgr::get_instance();
622 wm.windows[win_idx].is_minimized = false;
623 wm.activate_window(win_idx);
624 self.focused = false;
625 } else if p.display_name == "Terminal" || p.display_name == "> Terminal" {
626 let mut wm = window_mgr::get_instance();
627 wm.terminal_focused = true;
628 if let Some(ws) = p.workspace {
629 wm.active_workspace = ws;
630 }
631 self.focused = false;
632 }
633 }
634 }
635 }
636}
637
638pub struct ProcessInfo {
639 pub window_idx: Option<usize>,
640 pub display_name: String,
641 pub cpu: u32,
642 pub ram: u32,
643 pub is_child: bool,
644 pub original_name: String,
645 pub workspace: Option<u8>,
646 pub state_char: &'static str,
647}