Skip to main content

atmos/kernel/
window.rs

1// src/window.rs - システムモニター用右側固定ペインの描画管理
2#![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    /// システムモニターペインのプレミアムな描画処理 (チラつき防止のため右側領域に部分限定描画)
61    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        // 1. 右側ペイン背景のクリア
69        screen.boxfill(ux0, uy0, ux1, uy1, Color(theme.sysmon_bg));
70
71        // ドットの背景装飾 (プレミアム感の向上)
72        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        // 装飾ヘッダ
80        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)); // アクセントライン
82        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        // Power ボタン (右上) - Fluent UI System Icons (MIT)
91        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)); // 電源ボタン背景
95        screen.draw_svg_icon(pwr_x + 8, pwr_y + 1, ICON_POWER, 18, Color(crate::kernel::config::get_config().theme.mouse_cursor));
96
97        // 2. 統計情報の描画
98        let text_color = Color(theme.sysmon_text);
99        let val_color = Color(theme.sysmon_value);
100
101        // 基本情報 (Uptimeは非表示)
102        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        // 3. ネットワーク情報 (接続時は Backend, MAC, IP のみ、未接続時はその旨を表示)
110        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            // DHCP ステータスを取得して表示。IP が未取得の場合はここに来る。
144            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        // 4. メモリ使用状況
151        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        // 使用率インジケーター(メーター)の高さを拡張 (上下の潰れを解消)
175        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; // 高さ14pxに変更
179        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)); // メーター背景
183        let meter_color = if usage_pct > 50.0 {
184            Color(theme.focus_highlight) // 警告
185        } else {
186            Color(theme.sysmon_accent) // 通常
187        };
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        // 5. 各CPUコアロード表示
194        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; // 10% - 50%
215            }
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); // 画面幅に応じてメーターの最大幅を制限
222        for core in 0..4 {
223            let core_y = cpu_y0 + 35 + (core as u32 * 22); // 行の高さを 22px に詰める
224            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        // 6. プロセスリスト (タスクマネージャ) 領域 (開始位置を 395 に引き上げ)
245        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        // ヘッダ描画
267        screen.boxfill(ux0, table_y, ux1, table_y + 20, header_bg);
268
269        // 列の座標(パーセンテージによる動的レイアウト)
270        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); // 55%
275
276        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            // WS列
328            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            // State列
335            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        // フォーカス枠
349        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            // PWRボタンの判定
519            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                // Up
581                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                // Down
591                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                // Enter
603                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}