ruvector_memopt/tray/
mod.rs

1//! System tray icon and menu
2
3use crate::windows::memory::WindowsMemoryOptimizer;
4use crate::accel::CpuCapabilities;
5use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
6use tray_icon::{
7    menu::{Menu, MenuEvent, MenuItem, PredefinedMenuItem},
8    TrayIconBuilder, Icon,
9};
10use winit::event_loop::{ControlFlow, EventLoop};
11
12pub struct TrayApp {
13    running: Arc<AtomicBool>,
14}
15
16impl TrayApp {
17    pub fn new() -> Self {
18        Self {
19            running: Arc::new(AtomicBool::new(true)),
20        }
21    }
22
23    pub fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
24        let event_loop = EventLoop::new()?;
25
26        // Get initial memory status
27        let status_text = get_memory_status_text();
28
29        // Create menu items
30        let menu = Menu::new();
31        let status_item = MenuItem::new(&status_text, false, None);
32        let optimize_item = MenuItem::new("Optimize Now", true, None);
33        let aggressive_item = MenuItem::new("Aggressive Optimize", true, None);
34        let cpu_item = MenuItem::new("CPU Info", true, None);
35        let quit_item = MenuItem::new("Quit", true, None);
36
37        menu.append(&status_item)?;
38        menu.append(&PredefinedMenuItem::separator())?;
39        menu.append(&optimize_item)?;
40        menu.append(&aggressive_item)?;
41        menu.append(&PredefinedMenuItem::separator())?;
42        menu.append(&cpu_item)?;
43        menu.append(&PredefinedMenuItem::separator())?;
44        menu.append(&quit_item)?;
45
46        // Create tray icon
47        let icon_data = create_icon_data();
48        let icon = Icon::from_rgba(icon_data, 32, 32)?;
49
50        let _tray_icon = TrayIconBuilder::new()
51            .with_menu(Box::new(menu))
52            .with_tooltip("RuVector Memory Optimizer")
53            .with_icon(icon)
54            .build()?;
55
56        let optimize_id = optimize_item.id().clone();
57        let aggressive_id = aggressive_item.id().clone();
58        let cpu_id = cpu_item.id().clone();
59        let quit_id = quit_item.id().clone();
60
61        let running = self.running.clone();
62        let mut last_update = std::time::Instant::now();
63
64        // Run event loop
65        #[allow(deprecated)]
66        event_loop.run(move |_event, event_loop| {
67            event_loop.set_control_flow(ControlFlow::WaitUntil(
68                std::time::Instant::now() + std::time::Duration::from_secs(1)
69            ));
70
71            // Update status text periodically
72            if last_update.elapsed() > std::time::Duration::from_secs(5) {
73                let text = get_memory_status_text();
74                let _ = status_item.set_text(&text);
75                last_update = std::time::Instant::now();
76            }
77
78            if let Ok(event) = MenuEvent::receiver().try_recv() {
79                if event.id == quit_id {
80                    running.store(false, Ordering::SeqCst);
81                    event_loop.exit();
82                } else if event.id == optimize_id {
83                    run_optimization(false);
84                } else if event.id == aggressive_id {
85                    run_optimization(true);
86                } else if event.id == cpu_id {
87                    show_cpu_info();
88                }
89            }
90        })?;
91
92        Ok(())
93    }
94}
95
96fn get_memory_status_text() -> String {
97    if let Ok(status) = WindowsMemoryOptimizer::get_memory_status() {
98        format!(
99            "Memory: {:.0}% ({:.1}/{:.1} GB)",
100            status.memory_load_percent,
101            status.used_physical_mb() / 1024.0,
102            status.total_physical_mb / 1024.0
103        )
104    } else {
105        "Memory: Unknown".to_string()
106    }
107}
108
109fn run_optimization(aggressive: bool) {
110    std::thread::spawn(move || {
111        let optimizer = WindowsMemoryOptimizer::new();
112        match optimizer.optimize(aggressive) {
113            Ok(result) => {
114                let msg = format!(
115                    "Optimization Complete!\n\nFreed: {:.1} MB\nProcesses: {}\nTime: {} ms",
116                    result.freed_mb, result.processes_trimmed, result.duration_ms
117                );
118                show_message_box("RuVector Optimizer", &msg);
119            }
120            Err(e) => {
121                show_message_box("RuVector Optimizer", &format!("Error: {}", e));
122            }
123        }
124    });
125}
126
127fn show_cpu_info() {
128    let caps = CpuCapabilities::detect();
129    let msg = format!(
130        "CPU: {}\n\nCores: {}\nAVX2: {}\nAVX-512: {}\nAVX-VNNI: {}\nIntel NPU: {}\n\nEstimated SIMD Speedup: {:.0}x",
131        caps.model,
132        caps.core_count,
133        if caps.has_avx2 { "Yes" } else { "No" },
134        if caps.has_avx512 { "Yes" } else { "No" },
135        if caps.has_avx_vnni { "Yes" } else { "No" },
136        if caps.has_npu { "Yes" } else { "No" },
137        caps.estimated_speedup()
138    );
139    show_message_box("CPU Information", &msg);
140}
141
142fn show_message_box(title: &str, message: &str) {
143    #[cfg(windows)]
144    {
145        use std::ffi::OsStr;
146        use std::os::windows::ffi::OsStrExt;
147        use std::ptr;
148
149        fn to_wide(s: &str) -> Vec<u16> {
150            OsStr::new(s).encode_wide().chain(Some(0)).collect()
151        }
152
153        let title = to_wide(title);
154        let message = to_wide(message);
155
156        unsafe {
157            windows::Win32::UI::WindowsAndMessaging::MessageBoxW(
158                windows::Win32::Foundation::HWND(ptr::null_mut()),
159                windows::core::PCWSTR(message.as_ptr()),
160                windows::core::PCWSTR(title.as_ptr()),
161                windows::Win32::UI::WindowsAndMessaging::MB_OK |
162                windows::Win32::UI::WindowsAndMessaging::MB_ICONINFORMATION,
163            );
164        }
165    }
166}
167
168/// Create a memory-chip shaped icon with color based on usage
169/// - Green: < 60% usage (healthy)
170/// - Orange: 60-80% usage (moderate)
171/// - Red: > 80% usage (critical)
172fn create_icon_data() -> Vec<u8> {
173    create_icon_with_usage(50) // Default to green
174}
175
176/// Create icon with specific memory usage percentage for color coding
177fn create_icon_with_usage(usage_percent: u32) -> Vec<u8> {
178    let mut data = Vec::with_capacity(32 * 32 * 4);
179
180    // Color based on memory usage
181    let (r, g, b) = if usage_percent < 60 {
182        (0x00u8, 0xC8u8, 0x50u8) // Green
183    } else if usage_percent < 80 {
184        (0xFFu8, 0xA5u8, 0x00u8) // Orange
185    } else {
186        (0xE0u8, 0x30u8, 0x30u8) // Red
187    };
188
189    let (border_r, border_g, border_b) = if usage_percent < 60 {
190        (0x00u8, 0x80u8, 0x30u8)
191    } else if usage_percent < 80 {
192        (0xCCu8, 0x80u8, 0x00u8)
193    } else {
194        (0xA0u8, 0x20u8, 0x20u8)
195    };
196
197    for y in 0..32 {
198        for x in 0..32 {
199            // Memory chip shape: rounded rectangle with notch
200            let in_body = x >= 4 && x < 28 && y >= 2 && y < 30;
201            let in_notch = x >= 12 && x < 20 && y < 4;
202            let in_chip = in_body && !in_notch;
203
204            // Pin markers on sides
205            let left_pin = x < 4 && (y == 8 || y == 14 || y == 20 || y == 26);
206            let right_pin = x >= 28 && (y == 8 || y == 14 || y == 20 || y == 26);
207            let is_pin = left_pin || right_pin;
208
209            // Border detection
210            let is_border = in_chip && (x == 4 || x == 27 || y == 2 || y == 29 ||
211                                       (y == 3 && (x < 12 || x >= 20)));
212
213            // Fill level indicator (shows usage as filled portion)
214            let fill_height = 28 - ((usage_percent as i32 * 26) / 100);
215            let is_filled = in_chip && !is_border && (y as i32) >= fill_height;
216
217            if is_pin {
218                // Pins in border color
219                data.push(border_r);
220                data.push(border_g);
221                data.push(border_b);
222                data.push(0xFF);
223            } else if is_border {
224                // Border
225                data.push(border_r);
226                data.push(border_g);
227                data.push(border_b);
228                data.push(0xFF);
229            } else if is_filled {
230                // Filled portion (based on usage)
231                data.push(r);
232                data.push(g);
233                data.push(b);
234                data.push(0xFF);
235            } else if in_chip {
236                // Empty portion (darker)
237                data.push(r / 3);
238                data.push(g / 3);
239                data.push(b / 3);
240                data.push(0xFF);
241            } else {
242                // Transparent
243                data.push(0x00);
244                data.push(0x00);
245                data.push(0x00);
246                data.push(0x00);
247            }
248        }
249    }
250
251    data
252}
253
254impl Default for TrayApp {
255    fn default() -> Self {
256        Self::new()
257    }
258}