ruvector_memopt/tray/
mod.rs1use 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 let status_text = get_memory_status_text();
28
29 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 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 #[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 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
168fn create_icon_data() -> Vec<u8> {
173 create_icon_with_usage(50) }
175
176fn create_icon_with_usage(usage_percent: u32) -> Vec<u8> {
178 let mut data = Vec::with_capacity(32 * 32 * 4);
179
180 let (r, g, b) = if usage_percent < 60 {
182 (0x00u8, 0xC8u8, 0x50u8) } else if usage_percent < 80 {
184 (0xFFu8, 0xA5u8, 0x00u8) } else {
186 (0xE0u8, 0x30u8, 0x30u8) };
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 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 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 let is_border = in_chip && (x == 4 || x == 27 || y == 2 || y == 29 ||
211 (y == 3 && (x < 12 || x >= 20)));
212
213 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 data.push(border_r);
220 data.push(border_g);
221 data.push(border_b);
222 data.push(0xFF);
223 } else if is_border {
224 data.push(border_r);
226 data.push(border_g);
227 data.push(border_b);
228 data.push(0xFF);
229 } else if is_filled {
230 data.push(r);
232 data.push(g);
233 data.push(b);
234 data.push(0xFF);
235 } else if in_chip {
236 data.push(r / 3);
238 data.push(g / 3);
239 data.push(b / 3);
240 data.push(0xFF);
241 } else {
242 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}