1use 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, Submenu, PredefinedMenuItem},
8 TrayIconBuilder, Icon,
9};
10use winit::event_loop::{ControlFlow, EventLoop};
11
12pub const AUTO_OPTIMIZE_THRESHOLD: u32 = 75;
14pub const AUTO_OPTIMIZE_INTERVAL: u64 = 60;
16
17const GITHUB_URL: &str = "https://github.com/ruvnet/optimizer";
19const VERSION: &str = env!("CARGO_PKG_VERSION");
21
22pub struct TrayApp {
23 running: Arc<AtomicBool>,
24}
25
26impl TrayApp {
27 pub fn new() -> Self {
28 Self {
29 running: Arc::new(AtomicBool::new(true)),
30 }
31 }
32
33 pub fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
34 let event_loop = EventLoop::new()?;
35
36 let status_text = get_memory_status_text();
38
39 let menu = Menu::new();
41
42 let status_item = MenuItem::new(&status_text, false, None);
44 let version_item = MenuItem::new(&format!("v{}", VERSION), false, None);
45
46 let auto_item = CheckMenuItem::new("Auto-Optimize (60s)", true, true, None);
48 let optimize_item = MenuItem::new("Optimize Now", true, None);
49 let aggressive_item = MenuItem::new("Deep Clean", true, None);
50
51 let ai_menu = Submenu::new("AI Mode", true);
53 let game_mode_item = CheckMenuItem::new("Game Mode Auto-Detect", true, true, None);
54 let focus_mode_item = CheckMenuItem::new("Focus Mode Auto-Detect", true, true, None);
55 let thermal_item = CheckMenuItem::new("Thermal Prediction", true, true, None);
56 let preload_item = CheckMenuItem::new("Predictive Preloading", true, true, None);
57 ai_menu.append(&game_mode_item)?;
58 ai_menu.append(&focus_mode_item)?;
59 ai_menu.append(&PredefinedMenuItem::separator())?;
60 ai_menu.append(&thermal_item)?;
61 ai_menu.append(&preload_item)?;
62
63 let settings_menu = Submenu::new("Settings", true);
65 let threshold_75 = CheckMenuItem::new("Threshold: 75%", true, true, None);
66 let threshold_80 = CheckMenuItem::new("Threshold: 80%", true, false, None);
67 let threshold_85 = CheckMenuItem::new("Threshold: 85%", true, false, None);
68 let threshold_90 = CheckMenuItem::new("Threshold: 90%", true, false, None);
69 settings_menu.append(&threshold_75)?;
70 settings_menu.append(&threshold_80)?;
71 settings_menu.append(&threshold_85)?;
72 settings_menu.append(&threshold_90)?;
73
74 let cpu_item = MenuItem::new("System Info", true, None);
76 let github_item = MenuItem::new("GitHub Repository", true, None);
77 let quit_item = MenuItem::new("Quit", true, None);
78
79 menu.append(&status_item)?;
81 menu.append(&version_item)?;
82 menu.append(&PredefinedMenuItem::separator())?;
83 menu.append(&auto_item)?;
84 menu.append(&optimize_item)?;
85 menu.append(&aggressive_item)?;
86 menu.append(&PredefinedMenuItem::separator())?;
87 menu.append(&ai_menu)?;
88 menu.append(&settings_menu)?;
89 menu.append(&PredefinedMenuItem::separator())?;
90 menu.append(&cpu_item)?;
91 menu.append(&github_item)?;
92 menu.append(&PredefinedMenuItem::separator())?;
93 menu.append(&quit_item)?;
94
95 let initial_usage = WindowsMemoryOptimizer::get_memory_status()
97 .map(|s| s.memory_load_percent)
98 .unwrap_or(50);
99
100 let icon_data = create_icon_with_usage(initial_usage);
102 let icon = Icon::from_rgba(icon_data, 32, 32)?;
103
104 let tray_icon = TrayIconBuilder::new()
105 .with_menu(Box::new(menu))
106 .with_tooltip(&format!("RuVector MemOpt v{} - Auto-optimizing", VERSION))
107 .with_icon(icon)
108 .build()?;
109
110 let optimize_id = optimize_item.id().clone();
112 let aggressive_id = aggressive_item.id().clone();
113 let cpu_id = cpu_item.id().clone();
114 let quit_id = quit_item.id().clone();
115 let auto_id = auto_item.id().clone();
116 let github_id = github_item.id().clone();
117 let game_mode_id = game_mode_item.id().clone();
118 let focus_mode_id = focus_mode_item.id().clone();
119 let thermal_id = thermal_item.id().clone();
120 let preload_id = preload_item.id().clone();
121 let threshold_75_id = threshold_75.id().clone();
122 let threshold_80_id = threshold_80.id().clone();
123 let threshold_85_id = threshold_85.id().clone();
124 let threshold_90_id = threshold_90.id().clone();
125
126 let running = self.running.clone();
127 let mut last_update = std::time::Instant::now();
128 let mut last_auto_optimize = std::time::Instant::now();
129 let auto_enabled = Arc::new(AtomicBool::new(true));
130 let game_mode_enabled = Arc::new(AtomicBool::new(true));
131 let focus_mode_enabled = Arc::new(AtomicBool::new(true));
132 let thermal_enabled = Arc::new(AtomicBool::new(true));
133 let preload_enabled = Arc::new(AtomicBool::new(true));
134 let current_threshold = Arc::new(AtomicU32::new(75));
135 let last_usage = Arc::new(AtomicU32::new(initial_usage));
136 let total_freed = Arc::new(AtomicU32::new(0));
137
138 #[allow(deprecated)]
140 event_loop.run(move |_event, event_loop| {
141 event_loop.set_control_flow(ControlFlow::WaitUntil(
142 std::time::Instant::now() + std::time::Duration::from_secs(1)
143 ));
144
145 if last_update.elapsed() > std::time::Duration::from_secs(5) {
147 if let Ok(status) = WindowsMemoryOptimizer::get_memory_status() {
148 let usage = status.memory_load_percent;
149 last_usage.store(usage, Ordering::SeqCst);
150
151 let freed = total_freed.load(Ordering::SeqCst);
153 let threshold = current_threshold.load(Ordering::SeqCst);
154 let text = if freed > 0 {
155 format!(
156 "Memory: {:.0}% ({:.1}/{:.1} GB) | Freed: {} MB",
157 usage,
158 status.used_physical_mb() / 1024.0,
159 status.total_physical_mb / 1024.0,
160 freed
161 )
162 } else {
163 format!(
164 "Memory: {:.0}% ({:.1}/{:.1} GB)",
165 usage,
166 status.used_physical_mb() / 1024.0,
167 status.total_physical_mb / 1024.0
168 )
169 };
170 let _ = status_item.set_text(&text);
171
172 let icon_data = create_icon_with_usage(usage);
174 if let Ok(new_icon) = Icon::from_rgba(icon_data, 32, 32) {
175 let _ = tray_icon.set_icon(Some(new_icon));
176 }
177
178 let modes = build_mode_string(
180 game_mode_enabled.load(Ordering::SeqCst),
181 focus_mode_enabled.load(Ordering::SeqCst),
182 );
183 let tooltip = if auto_enabled.load(Ordering::SeqCst) {
184 format!("RuVector v{} - {}% | Auto @{}%{}", VERSION, usage, threshold, modes)
185 } else {
186 format!("RuVector v{} - {}% | Manual{}", VERSION, usage, modes)
187 };
188 let _ = tray_icon.set_tooltip(Some(tooltip));
189
190 if auto_enabled.load(Ordering::SeqCst)
192 && usage > threshold
193 && last_auto_optimize.elapsed() > std::time::Duration::from_secs(AUTO_OPTIMIZE_INTERVAL)
194 {
195 let total_freed_clone = total_freed.clone();
196 std::thread::spawn(move || {
197 let optimizer = WindowsMemoryOptimizer::new();
198 if let Ok(result) = optimizer.optimize(false) {
199 if result.freed_mb > 100.0 {
200 let current = total_freed_clone.load(Ordering::SeqCst);
201 total_freed_clone.store(current + result.freed_mb as u32, Ordering::SeqCst);
202 }
203 }
204 });
205 last_auto_optimize = std::time::Instant::now();
206 }
207 }
208 last_update = std::time::Instant::now();
209 }
210
211 if let Ok(event) = MenuEvent::receiver().try_recv() {
213 if event.id == quit_id {
214 running.store(false, Ordering::SeqCst);
215 event_loop.exit();
216 } else if event.id == optimize_id {
217 let total_freed_clone = total_freed.clone();
218 run_optimization(false, total_freed_clone);
219 } else if event.id == aggressive_id {
220 let total_freed_clone = total_freed.clone();
221 run_optimization(true, total_freed_clone);
222 } else if event.id == cpu_id {
223 show_cpu_info();
224 } else if event.id == github_id {
225 open_github();
226 } else if event.id == auto_id {
227 let current = auto_enabled.load(Ordering::SeqCst);
228 auto_enabled.store(!current, Ordering::SeqCst);
229 let _ = auto_item.set_checked(!current);
230 } else if event.id == game_mode_id {
231 let current = game_mode_enabled.load(Ordering::SeqCst);
232 game_mode_enabled.store(!current, Ordering::SeqCst);
233 let _ = game_mode_item.set_checked(!current);
234 } else if event.id == focus_mode_id {
235 let current = focus_mode_enabled.load(Ordering::SeqCst);
236 focus_mode_enabled.store(!current, Ordering::SeqCst);
237 let _ = focus_mode_item.set_checked(!current);
238 } else if event.id == thermal_id {
239 let current = thermal_enabled.load(Ordering::SeqCst);
240 thermal_enabled.store(!current, Ordering::SeqCst);
241 let _ = thermal_item.set_checked(!current);
242 } else if event.id == preload_id {
243 let current = preload_enabled.load(Ordering::SeqCst);
244 preload_enabled.store(!current, Ordering::SeqCst);
245 let _ = preload_item.set_checked(!current);
246 } else if event.id == threshold_75_id {
247 current_threshold.store(75, Ordering::SeqCst);
248 let _ = threshold_75.set_checked(true);
249 let _ = threshold_80.set_checked(false);
250 let _ = threshold_85.set_checked(false);
251 let _ = threshold_90.set_checked(false);
252 } else if event.id == threshold_80_id {
253 current_threshold.store(80, Ordering::SeqCst);
254 let _ = threshold_75.set_checked(false);
255 let _ = threshold_80.set_checked(true);
256 let _ = threshold_85.set_checked(false);
257 let _ = threshold_90.set_checked(false);
258 } else if event.id == threshold_85_id {
259 current_threshold.store(85, Ordering::SeqCst);
260 let _ = threshold_75.set_checked(false);
261 let _ = threshold_80.set_checked(false);
262 let _ = threshold_85.set_checked(true);
263 let _ = threshold_90.set_checked(false);
264 } else if event.id == threshold_90_id {
265 current_threshold.store(90, Ordering::SeqCst);
266 let _ = threshold_75.set_checked(false);
267 let _ = threshold_80.set_checked(false);
268 let _ = threshold_85.set_checked(false);
269 let _ = threshold_90.set_checked(true);
270 }
271 }
272 })?;
273
274 Ok(())
275 }
276}
277
278fn build_mode_string(game: bool, focus: bool) -> String {
279 let mut modes = Vec::new();
280 if game { modes.push("Game"); }
281 if focus { modes.push("Focus"); }
282 if modes.is_empty() {
283 String::new()
284 } else {
285 format!(" | {}", modes.join("+"))
286 }
287}
288
289fn get_memory_status_text() -> String {
290 if let Ok(status) = WindowsMemoryOptimizer::get_memory_status() {
291 format!(
292 "Memory: {:.0}% ({:.1}/{:.1} GB)",
293 status.memory_load_percent,
294 status.used_physical_mb() / 1024.0,
295 status.total_physical_mb / 1024.0
296 )
297 } else {
298 "Memory: Unknown".to_string()
299 }
300}
301
302fn run_optimization(aggressive: bool, total_freed: Arc<AtomicU32>) {
303 std::thread::spawn(move || {
304 let optimizer = WindowsMemoryOptimizer::new();
305 match optimizer.optimize(aggressive) {
306 Ok(result) => {
307 let current = total_freed.load(Ordering::SeqCst);
308 total_freed.store(current + result.freed_mb as u32, Ordering::SeqCst);
309
310 let msg = format!(
311 "Optimization Complete!\n\nFreed: {:.1} MB\nProcesses: {}\nTime: {} ms",
312 result.freed_mb, result.processes_trimmed, result.duration_ms
313 );
314 show_message_box("RuVector Optimizer", &msg);
315 }
316 Err(e) => {
317 show_message_box("RuVector Optimizer", &format!("Error: {}", e));
318 }
319 }
320 });
321}
322
323fn show_cpu_info() {
324 let caps = CpuCapabilities::detect();
325 let msg = format!(
326 "RuVector Memory Optimizer v{}\n\n\
327 CPU: {}\n\n\
328 Cores: {}\n\
329 AVX2: {}\n\
330 AVX-512: {}\n\
331 AVX-VNNI: {}\n\
332 Intel NPU: {}\n\n\
333 Estimated SIMD Speedup: {:.1}x\n\n\
334 GitHub: {}",
335 VERSION,
336 caps.model,
337 caps.core_count,
338 if caps.has_avx2 { "Yes" } else { "No" },
339 if caps.has_avx512 { "Yes" } else { "No" },
340 if caps.has_avx_vnni { "Yes" } else { "No" },
341 if caps.has_npu { "Yes" } else { "No" },
342 caps.estimated_speedup(),
343 GITHUB_URL
344 );
345 show_message_box("System Information", &msg);
346}
347
348fn open_github() {
349 #[cfg(windows)]
350 {
351 let _ = std::process::Command::new("cmd")
352 .args(["/C", "start", GITHUB_URL])
353 .spawn();
354 }
355}
356
357fn show_message_box(title: &str, message: &str) {
358 #[cfg(windows)]
359 {
360 use std::ffi::OsStr;
361 use std::os::windows::ffi::OsStrExt;
362 use std::ptr;
363
364 fn to_wide(s: &str) -> Vec<u16> {
365 OsStr::new(s).encode_wide().chain(Some(0)).collect()
366 }
367
368 let title = to_wide(title);
369 let message = to_wide(message);
370
371 unsafe {
372 windows::Win32::UI::WindowsAndMessaging::MessageBoxW(
373 windows::Win32::Foundation::HWND(ptr::null_mut()),
374 windows::core::PCWSTR(message.as_ptr()),
375 windows::core::PCWSTR(title.as_ptr()),
376 windows::Win32::UI::WindowsAndMessaging::MB_OK |
377 windows::Win32::UI::WindowsAndMessaging::MB_ICONINFORMATION,
378 );
379 }
380 }
381}
382
383fn create_icon_with_usage(usage_percent: u32) -> Vec<u8> {
385 let mut data = Vec::with_capacity(32 * 32 * 4);
386
387 let (r, g, b) = if usage_percent < 60 {
389 (0x00u8, 0xC8u8, 0x50u8) } else if usage_percent < 80 {
391 (0xFFu8, 0xA5u8, 0x00u8) } else {
393 (0xE0u8, 0x30u8, 0x30u8) };
395
396 let (border_r, border_g, border_b) = if usage_percent < 60 {
397 (0x00u8, 0x80u8, 0x30u8)
398 } else if usage_percent < 80 {
399 (0xCCu8, 0x80u8, 0x00u8)
400 } else {
401 (0xA0u8, 0x20u8, 0x20u8)
402 };
403
404 for y in 0..32 {
405 for x in 0..32 {
406 let in_body = x >= 4 && x < 28 && y >= 2 && y < 30;
408 let in_notch = x >= 12 && x < 20 && y < 4;
409 let in_chip = in_body && !in_notch;
410
411 let left_pin = x < 4 && (y == 8 || y == 14 || y == 20 || y == 26);
413 let right_pin = x >= 28 && (y == 8 || y == 14 || y == 20 || y == 26);
414 let is_pin = left_pin || right_pin;
415
416 let is_border = in_chip && (x == 4 || x == 27 || y == 2 || y == 29 ||
418 (y == 3 && (x < 12 || x >= 20)));
419
420 let fill_height = 28 - ((usage_percent as i32 * 26) / 100);
422 let is_filled = in_chip && !is_border && (y as i32) >= fill_height;
423
424 if is_pin {
425 data.push(border_r);
427 data.push(border_g);
428 data.push(border_b);
429 data.push(0xFF);
430 } else if is_border {
431 data.push(border_r);
433 data.push(border_g);
434 data.push(border_b);
435 data.push(0xFF);
436 } else if is_filled {
437 data.push(r);
439 data.push(g);
440 data.push(b);
441 data.push(0xFF);
442 } else if in_chip {
443 data.push(r / 3);
445 data.push(g / 3);
446 data.push(b / 3);
447 data.push(0xFF);
448 } else {
449 data.push(0x00);
451 data.push(0x00);
452 data.push(0x00);
453 data.push(0x00);
454 }
455 }
456 }
457
458 data
459}
460
461impl Default for TrayApp {
462 fn default() -> Self {
463 Self::new()
464 }
465}