ruvector_memopt/tray/
mod.rs

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