1#![allow(dead_code)]
8
9use alloc::{string::String, vec::Vec};
10
11use crate::sync::once_lock::GlobalState;
12
13const CHAR_W: usize = 8;
19const CHAR_H: usize = 16;
20
21const TRAY_PADDING: usize = 4;
23
24const TRAY_HEIGHT: usize = 24;
26
27const COLOR_NORMAL: u32 = 0xFFBBBBBB;
29
30const COLOR_CPU_LOW: u32 = 0xFF44CC44;
32
33const COLOR_CPU_MED: u32 = 0xFFCCCC44;
35
36const COLOR_CPU_HIGH: u32 = 0xFFCC4444;
38
39const COLOR_SEPARATOR: u32 = 0xFF555555;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum SysTrayItemType {
49 Clock,
51 CpuMonitor,
53 MemoryMonitor,
55 NetworkStatus,
57 BatteryStatus,
59 Volume,
61 Custom,
63}
64
65#[derive(Debug, Clone)]
67pub struct SysTrayItem {
68 pub item_type: SysTrayItemType,
70 pub label: String,
72 pub tooltip: String,
74 pub visible: bool,
76 pub width: usize,
78}
79
80impl SysTrayItem {
81 pub fn new(item_type: SysTrayItemType, label: &str, tooltip: &str) -> Self {
83 let width = label.len() * CHAR_W + TRAY_PADDING * 2;
84 Self {
85 item_type,
86 label: String::from(label),
87 tooltip: String::from(tooltip),
88 visible: true,
89 width,
90 }
91 }
92
93 fn set_label(&mut self, new_label: &str) {
95 self.label.clear();
96 self.label.push_str(new_label);
97 self.width = self.label.len() * CHAR_W + TRAY_PADDING * 2;
98 }
99}
100
101pub struct SystemTray {
103 items: Vec<SysTrayItem>,
106 tray_height: usize,
108 padding: usize,
110}
111
112impl SystemTray {
113 pub fn new() -> Self {
115 let mut tray = Self {
116 items: Vec::new(),
117 tray_height: TRAY_HEIGHT,
118 padding: TRAY_PADDING,
119 };
120
121 tray.items.push(SysTrayItem::new(
123 SysTrayItemType::NetworkStatus,
124 "Net: up",
125 "Network status",
126 ));
127 tray.items.push(SysTrayItem::new(
128 SysTrayItemType::CpuMonitor,
129 "CPU: 0%",
130 "CPU usage",
131 ));
132 tray.items.push(SysTrayItem::new(
133 SysTrayItemType::MemoryMonitor,
134 "MEM: 0/0 MB",
135 "Memory usage",
136 ));
137 tray.items.push(SysTrayItem::new(
138 SysTrayItemType::Volume,
139 "Vol: 75%",
140 "Audio volume",
141 ));
142 tray.items.push(SysTrayItem::new(
143 SysTrayItemType::BatteryStatus,
144 "AC",
145 "Power source",
146 ));
147 tray.items.push(SysTrayItem::new(
148 SysTrayItemType::Clock,
149 "00:00 Jan 01",
150 "System clock",
151 ));
152
153 tray
154 }
155
156 pub fn add_item(&mut self, item: SysTrayItem) {
158 if item.item_type != SysTrayItemType::Custom
160 && self.items.iter().any(|i| i.item_type == item.item_type)
161 {
162 return;
163 }
164 self.items.push(item);
165 }
166
167 pub fn remove_item(&mut self, item_type: SysTrayItemType) {
169 if let Some(pos) = self.items.iter().position(|i| i.item_type == item_type) {
170 self.items.remove(pos);
171 }
172 }
173
174 pub fn update_clock(&mut self, hours: u8, minutes: u8, _seconds: u8, month: u8, day: u8) {
176 let label = format_clock(hours, minutes, month, day);
177 for item in &mut self.items {
178 if item.item_type == SysTrayItemType::Clock {
179 item.set_label(&label);
180 return;
181 }
182 }
183 }
184
185 pub fn update_cpu(&mut self, usage_percent: u8) {
187 let label = format_cpu(usage_percent);
188 for item in &mut self.items {
189 if item.item_type == SysTrayItemType::CpuMonitor {
190 item.set_label(&label);
191 return;
192 }
193 }
194 }
195
196 pub fn update_memory(&mut self, used_mb: u32, total_mb: u32) {
198 let label = format_memory(used_mb, total_mb);
199 for item in &mut self.items {
200 if item.item_type == SysTrayItemType::MemoryMonitor {
201 item.set_label(&label);
202 return;
203 }
204 }
205 }
206
207 pub fn update_network(&mut self, is_up: bool) {
209 let label = if is_up { "Net: up" } else { "Net: --" };
210 for item in &mut self.items {
211 if item.item_type == SysTrayItemType::NetworkStatus {
212 item.set_label(label);
213 return;
214 }
215 }
216 }
217
218 pub fn update_volume(&mut self, volume_percent: u8) {
220 let label = format_volume(volume_percent);
221 for item in &mut self.items {
222 if item.item_type == SysTrayItemType::Volume {
223 item.set_label(&label);
224 return;
225 }
226 }
227 }
228
229 pub fn update_battery(&mut self, label_str: &str) {
231 for item in &mut self.items {
232 if item.item_type == SysTrayItemType::BatteryStatus {
233 item.set_label(label_str);
234 return;
235 }
236 }
237 }
238
239 pub fn total_width(&self) -> usize {
241 let visible: Vec<&SysTrayItem> = self.items.iter().filter(|i| i.visible).collect();
242 if visible.is_empty() {
243 return 0;
244 }
245 let items_width: usize = visible.iter().map(|i| i.width).sum();
246 let separators = if visible.len() > 1 {
247 (visible.len() - 1) * (self.padding + 1 + self.padding)
249 } else {
250 0
251 };
252 items_width + separators
253 }
254
255 pub fn render_to_buffer(
260 &self,
261 buffer: &mut [u32],
262 buf_width: usize,
263 x_start: usize,
264 y_start: usize,
265 ) {
266 let mut cx = x_start;
267
268 for (idx, item) in self.items.iter().filter(|i| i.visible).enumerate() {
269 if idx > 0 {
271 cx += self.padding;
272 let sep_top = y_start + 2;
274 let sep_bot = y_start + self.tray_height.saturating_sub(2);
275 for sy in sep_top..sep_bot {
276 let offset = sy * buf_width + cx;
277 if offset < buffer.len() {
278 buffer[offset] = COLOR_SEPARATOR;
279 }
280 }
281 cx += 1 + self.padding;
282 }
283
284 let color = item_color(item);
286
287 let text_y = y_start + (self.tray_height.saturating_sub(CHAR_H)) / 2;
289
290 let text_x = cx + self.padding;
292 for (ci, &ch) in item.label.as_bytes().iter().enumerate() {
293 render_glyph_u32(buffer, buf_width, text_x + ci * CHAR_W, text_y, ch, color);
294 }
295
296 cx += item.width;
297 }
298 }
299
300 pub fn handle_click(&self, x: usize, _y: usize) -> Option<SysTrayItemType> {
306 let mut cx: usize = 0;
307
308 for (idx, item) in self.items.iter().filter(|i| i.visible).enumerate() {
309 if idx > 0 {
310 cx += self.padding + 1 + self.padding; }
312
313 if x >= cx && x < cx + item.width {
314 return Some(item.item_type);
315 }
316 cx += item.width;
317 }
318
319 None
320 }
321}
322
323fn format_clock(hours: u8, minutes: u8, month: u8, day: u8) -> String {
329 let mut s = String::with_capacity(14);
330 push_2digit(&mut s, hours);
331 s.push(':');
332 push_2digit(&mut s, minutes);
333 s.push(' ');
334 s.push_str(month_abbr(month));
335 s.push(' ');
336 push_2digit(&mut s, day);
337 s
338}
339
340fn format_cpu(usage: u8) -> String {
342 let mut s = String::with_capacity(10);
343 s.push_str("CPU:");
344 push_u8_decimal(&mut s, usage);
345 s.push('%');
346 s
347}
348
349fn format_volume(volume: u8) -> String {
351 let mut s = String::with_capacity(10);
352 s.push_str("Vol:");
353 push_u8_decimal(&mut s, volume);
354 s.push('%');
355 s
356}
357
358fn format_memory(used_mb: u32, total_mb: u32) -> String {
360 let mut s = String::with_capacity(18);
361 s.push_str("MEM:");
362 push_u32_decimal(&mut s, used_mb);
363 s.push('/');
364 push_u32_decimal(&mut s, total_mb);
365 s.push_str("MB");
366 s
367}
368
369fn push_2digit(s: &mut String, v: u8) {
371 let v = v % 100;
372 s.push((b'0' + v / 10) as char);
373 s.push((b'0' + v % 10) as char);
374}
375
376fn push_u8_decimal(s: &mut String, v: u8) {
378 if v >= 100 {
379 s.push((b'0' + v / 100) as char);
380 }
381 if v >= 10 {
382 s.push((b'0' + (v / 10) % 10) as char);
383 }
384 s.push((b'0' + v % 10) as char);
385}
386
387fn push_u32_decimal(s: &mut String, v: u32) {
389 if v == 0 {
390 s.push('0');
391 return;
392 }
393 let mut div = 1u32;
395 let mut tmp = v;
396 while tmp >= 10 {
397 tmp /= 10;
398 div *= 10;
399 }
400 let mut remaining = v;
402 while div > 0 {
403 let digit = remaining / div;
404 s.push((b'0' + digit as u8) as char);
405 remaining %= div;
406 div /= 10;
407 }
408}
409
410fn month_abbr(month: u8) -> &'static str {
412 match month {
413 1 => "Jan",
414 2 => "Feb",
415 3 => "Mar",
416 4 => "Apr",
417 5 => "May",
418 6 => "Jun",
419 7 => "Jul",
420 8 => "Aug",
421 9 => "Sep",
422 10 => "Oct",
423 11 => "Nov",
424 12 => "Dec",
425 _ => "???",
426 }
427}
428
429fn item_color(item: &SysTrayItem) -> u32 {
431 match item.item_type {
432 SysTrayItemType::CpuMonitor => {
433 let usage = parse_cpu_usage(&item.label);
435 if usage >= 80 {
436 COLOR_CPU_HIGH
437 } else if usage >= 50 {
438 COLOR_CPU_MED
439 } else {
440 COLOR_CPU_LOW
441 }
442 }
443 _ => COLOR_NORMAL,
444 }
445}
446
447fn parse_cpu_usage(label: &str) -> u8 {
449 let bytes = label.as_bytes();
451 let mut start = 0;
452 let mut end = bytes.len();
453
454 for (i, &b) in bytes.iter().enumerate() {
455 if b == b':' {
456 start = i + 1;
457 } else if b == b'%' {
458 end = i;
459 break;
460 }
461 }
462
463 let mut result: u8 = 0;
464 for &b in &bytes[start..end] {
465 if b.is_ascii_digit() {
466 result = result.saturating_mul(10).saturating_add(b - b'0');
467 }
468 }
469 result
470}
471
472fn render_glyph_u32(buf: &mut [u32], buf_width: usize, px: usize, py: usize, ch: u8, color: u32) {
478 use crate::graphics::font8x16;
479
480 let glyph = font8x16::glyph(ch);
481 for (row, &bits) in glyph.iter().enumerate() {
482 for col in 0..8 {
483 if (bits >> (7 - col)) & 1 != 0 {
484 let x = px + col;
485 let y = py + row;
486 let offset = y * buf_width + x;
487 if offset < buf.len() {
488 buf[offset] = color;
489 }
490 }
491 }
492 }
493}
494
495static SYSTEM_TRAY: GlobalState<spin::Mutex<SystemTray>> = GlobalState::new();
500
501pub fn init() {
503 let _ = SYSTEM_TRAY.init(spin::Mutex::new(SystemTray::new()));
504 crate::println!("[SYSTRAY] System tray initialized (6 default items)");
505}
506
507pub fn with_system_tray<R, F: FnOnce(&mut SystemTray) -> R>(f: F) -> Option<R> {
509 SYSTEM_TRAY.with(|lock| {
510 let mut tray = lock.lock();
511 f(&mut tray)
512 })
513}
514
515pub fn with_system_tray_ref<R, F: FnOnce(&SystemTray) -> R>(f: F) -> Option<R> {
517 SYSTEM_TRAY.with(|lock| {
518 let tray = lock.lock();
519 f(&tray)
520 })
521}
522
523impl Default for SystemTray {
524 fn default() -> Self {
525 Self::new()
526 }
527}