1use tray_icon::{menu::{Menu, MenuItem, PredefinedMenuItem, MenuEvent, MenuId}, TrayIconBuilder,TrayIconEventReceiver };
8use std::path::PathBuf;
9use tray_icon::menu::MenuEventReceiver;
10use sharing::VERSION;
11use winit::event_loop::{EventLoopBuilder, EventLoop};
12use parking_lot::RwLock;
13use std::io::{Read, Write};
14use std::net::{TcpStream};
15use std::sync::Arc;
16use easy_trace::instruments::tracing::{error, info};
17use sharing::service::{Action, DriverStatus};
18use crate::tray_utils::{load_icon, open_window, ICON_ON_BYTES, ICON_OFF_BYTES, ICON_ALERT_BYTES};
19use std::str::FromStr;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::thread::sleep;
22use std::time::Duration;
23use std::sync::mpsc;
24
25pub type TrayIconResult<T> = sharing::PisPasResult<T>;
26use tray_icon::TrayIconEvent;
27#[derive(Clone)]
28pub struct PisPasTrayIcon {
29 try_icon_on: tray_icon::Icon,
30 try_icon_off: tray_icon::Icon,
31 try_icon_alert: tray_icon::Icon,
32 tray_icon: tray_icon::TrayIcon,
33 configurator: MenuItem,
34 about: MenuItem,
35 start: MenuItem,
36 restart: MenuItem,
37 stop: MenuItem,
38 check_update: MenuItem,
39 menu_status: MenuItem,
40 quit: MenuItem,
41 tray_status: Arc<RwLock<DriverStatus>>,
42 stream: Option<Arc<RwLock<TcpStream>>>,
43}
44
45
46unsafe impl Send for PisPasTrayIcon {}
47unsafe impl Sync for PisPasTrayIcon {}
48impl PisPasTrayIcon {
49 pub fn get_id_configurator(&self) -> &MenuId { self.configurator.id() }
50 pub fn get_id_about(&self) -> &MenuId {
51 self.about.id()
52 }
53 pub fn get_id_start(&self) -> &MenuId {
54 self.start.id()
55 }
56 pub fn get_id_stop(&self) -> &MenuId {
57 self.stop.id()
58 }
59 pub fn get_id_quit(&self) -> &MenuId {
60 self.quit.id()
61 }
62 pub fn get_id_check_update(&self) -> &MenuId {
63 self.check_update.id()
64 }
65 pub fn get_id_restart(&self) -> &MenuId {
66 self.restart.id()
67 }
68 pub fn build_event_loop() -> (&'static MenuEventReceiver, &'static TrayIconEventReceiver, EventLoop<()>) {
69 let menu_channel = MenuEvent::receiver();
70 let tray_channel = TrayIconEvent::receiver();
71 match EventLoopBuilder::new().build() {
72 Ok(event_loop) => {
73 info!("Event loop created");
74 return (menu_channel, tray_channel, event_loop);
75 }
76 Err(e) => {
77 panic!("Error creating event loop: {}", e);
78 }
79 }
80 }
81 pub fn get_stream(&self) -> Option<Arc<RwLock<TcpStream>>> {
82 self.stream.clone()
83 }
84
85 pub fn tray_connect(&mut self) -> TrayIconResult<()>{
86 match TcpStream::connect(sharing::CHANNEL_NAME) {
87 #[allow(unused_mut)]
88 Ok(mut stream) => {
89 info!("Connected to pispas-channel on INIT");
90 match stream.set_nonblocking(true) {
91 Ok(_) => {
92 info!("Socket set to nonblocking");
93 }
94 Err(e) => {
95 error!("Error setting socket to nonblocking: {}", e);
96 }
97 }
98 self.stream = Some(Arc::new(RwLock::new(stream)));
99 return Ok(());
100 }
101 Err(e) => {
102 error!("Error connecting to pispas-channel: {}", e);
103 }
104 }
105
106 Err(anyhow::anyhow!("Error connecting to pispas-channel"))
107 }
108
109 fn new(try_icon_on: tray_icon::Icon,
110 try_icon_off: tray_icon::Icon,
111 try_icon_alert: tray_icon::Icon,
112 tray_icon: tray_icon::TrayIcon,
113 configurator: MenuItem,
114 about: MenuItem,
115 start: MenuItem,
116 restart: MenuItem,
117 stop: MenuItem,
118 check_update: MenuItem,
119 menu_status: MenuItem,
120 quit: MenuItem
121
122 ) -> Self {
123 Self {
124 try_icon_on,
125 try_icon_off,
126 try_icon_alert,
127 tray_icon,
128 configurator,
129 about,
130 start,
131 restart,
132 stop,
133 check_update,
134 menu_status,
135 quit,
136 tray_status: Arc::new(RwLock::new(DriverStatus::Stopped)),
137 stream: None,
138 }
139 }
140
141 pub fn build() -> TrayIconResult<Self>
142 {
143 let icon = load_icon(ICON_ON_BYTES)?;
144 let icon_off = load_icon(ICON_OFF_BYTES)?;
145 let icon_alert = load_icon(ICON_ALERT_BYTES)?;
146 let menu = Menu::new();
147 let configurator = MenuItem::new("Configurator", true, None);
148 let about = MenuItem::new("About", true, None);
149 let start= MenuItem::new("Start", true, None);
150 let restart= MenuItem::new("Restart", true, None);
151 let stop = MenuItem::new("Stop", true, None);
152 let check_update = MenuItem::new("Check Updates", true, None);
153 let menu_status = MenuItem::new("STATUS: OFF", false, None);
154 let quit = MenuItem::new("Quit", true, None);
155 let separator = &PredefinedMenuItem::separator();
156 let _ = menu.append_items(&[
157 &configurator,
158 &about,
159 separator,
160 &start,
161 &stop,
162 &check_update,
163 &restart,
164 separator,
165 &menu_status,
166 separator,
167 &quit,
168 ]);
169 let box_icon = Box::new(menu);
170 let tray_builder = TrayIconBuilder::new()
171 .with_menu(box_icon)
172 .with_tooltip("pispas service")
173 .with_icon(icon_off.clone())
174 .build()?;
175
176 Ok(Self::new(icon, icon_off, icon_alert, tray_builder, configurator, about, start, restart, stop, check_update, menu_status, quit))
177 }
178
179 pub fn run_suscriber(&mut self, cancel: Arc<AtomicBool>) {
180 loop {
181 sleep(Duration::from_millis(300));
182 if let Some(stream) = self.get_stream() {
183 let message = self.read_channel(stream.clone(), cancel.clone());
184 info!("Message received from Subscription: {}", message);
185 if !message.is_empty() {
186 if message.starts_with("MessageBox:") {
187 let msg = message.trim_start_matches("MessageBox:").trim();
188 if !msg.is_empty() {
189 sharing::natives::api::show_message_box_non_blocking(msg);
190 } else {
191 info!("No message provided for MessageBox.");
192 }
193 } else {
194 match self.change_status(DriverStatus::from_str(message.as_str()).unwrap_or(DriverStatus::Stopped)) {
195 Ok(_) => {
196 info!("Status changed to: {}", message);
197 }
198 Err(error) => {
199 error!("Error changing status: {}", error);
200 }
201 }
202 }
203 }
204 } else {
205 match self.tray_connect() {
206 Ok(_) => {
207 info!("Connected to tray");
208 self.subscribe_service();
209 }
210 Err(error) => {
211 error!("Error connecting to tray: {}", error);
212 sleep(Duration::from_millis(1000));
213 }
214 }
215 }
216 if cancel.load(Ordering::Relaxed) {
217 if let Some(stream) = self.get_stream() {
218 match stream.write().write_all(Action::Close.to_string().as_bytes()) {
219 Ok(_) => {
220 info!("run_suscriber Message sent: {} CLOSE", Action::Close.to_string());
221 }
222 Err(e) => {
223 error!("Error writing to stream CLOSE: {:?}", e);
224 }
225 }
226 }
227 break;
228 }
229 }
230 }
231
232 pub fn change_status(&mut self, status: DriverStatus) -> TrayIconResult<()>{
233 match status {
234 DriverStatus::Running => self.running()?,
235 DriverStatus::Stopped => self.stopped()?,
236 DriverStatus::Launched => self.launched()?,
237 _ => self.warning()?,
238 }
239 let mut write_status = self.tray_status.write();
240 *write_status = status;
241 Ok(())
242 }
243 pub fn change_status_main_thread(&mut self, status: DriverStatus) -> TrayIconResult<()> {
244 self.change_status(status)
245 }
246 fn read_channel_background(&self,
247 stream: Arc<RwLock<TcpStream>>,
248 cancel: Arc<AtomicBool>) -> String {
249 let mut buffer = Vec::new();
250
251 loop {
252 sleep(Duration::from_millis(100));
253 let mut partial_buffer = [0; 1024];
254
255 match stream.write().read(&mut partial_buffer) {
256 Ok(size) => {
257 if size == 0 {
258 if cancel.load(Ordering::Relaxed) {
259 info!("Closing socket");
260 break;
261 }
262 continue;
263 }
264 buffer.extend_from_slice(&partial_buffer[..size]);
265 if size < partial_buffer.len() {
266 break;
267 }
268 }
269 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
270 continue;
271 }
272 Err(e) => {
273 error!("Failed to read from socket in background thread: {}", e);
274 break;
275 }
276 }
277
278 if cancel.load(Ordering::Relaxed) {
279 break;
280 }
281 }
282 let message = String::from_utf8_lossy(&buffer).to_string();
283 info!("Message received: {} on read_channel_background", message);
284 message
285 }
286 pub fn launched(&mut self) -> TrayIconResult<()> {
287 self.start.set_enabled(false);
288 self.stop.set_enabled(true);
289 self.menu_status.set_text("STATUS: STARTING");
290 self.tray_icon.set_icon(Some(self.try_icon_alert.clone()))?;
291 Ok(())
292 }
293 fn subscribe_service_background(&self, stream: &Arc<RwLock<TcpStream>>) {
294 match stream.write().write_all(Action::Subscribe.to_string().as_bytes()) {
295 Ok(_) => {
296 info!("Message sent: {} SUBSCRIBE (background)", Action::Subscribe.to_string());
297 }
298 Err(e) => {
299 error!("Error writing to stream SUBSCRIBE: {:?}", e);
300 }
301 }
302 }
303 pub fn run_subscriber_background(&self,
305 cancel: Arc<AtomicBool>,
306 status_sender: mpsc::Sender<DriverStatus>) {
307 let mut stream_holder: Option<Arc<RwLock<TcpStream>>> = None;
308
309 loop {
310 sleep(Duration::from_millis(300));
311
312 if let Some(stream) = &stream_holder {
313 let message = self.read_channel_background(stream.clone(), cancel.clone());
314 info!("Message received from Subscription: {}", message);
315
316 if !message.is_empty() {
317 if message.starts_with("MessageBox:") {
318 let msg = message.trim_start_matches("MessageBox:").trim();
319 if !msg.is_empty() {
320 sharing::natives::api::show_message_box_non_blocking(msg);
321 }
322 } else {
323 let new_status = DriverStatus::from_str(message.as_str())
324 .unwrap_or(DriverStatus::Stopped);
325
326 if let Err(e) = status_sender.send(new_status) {
328 error!("Error sending status update: {}", e);
329 break; }
331 }
332 }
333 } else {
334 match self.tray_connect_background() {
336 Ok(stream) => {
337 info!("Connected to tray in background");
338 stream_holder = Some(stream.clone());
339 self.subscribe_service_background(&stream);
340 }
341 Err(error) => {
342 error!("Error connecting to tray: {}", error);
343 sleep(Duration::from_millis(1000));
344 }
345 }
346 }
347
348 if cancel.load(Ordering::Relaxed) {
349 if let Some(stream) = &stream_holder {
350 let _ = self.send_close_background(stream);
351 }
352 break;
353 }
354 }
355 }
356 fn send_close_background(&self, stream: &Arc<RwLock<TcpStream>>) -> TrayIconResult<()> {
358 match stream.write().write_all(Action::Close.to_string().as_bytes()) {
359 Ok(_) => {
360 info!("Background thread sent CLOSE message");
361 Ok(())
362 }
363 Err(e) => {
364 error!("Error writing CLOSE to stream: {:?}", e);
365 Err(anyhow::anyhow!("Error writing CLOSE: {}", e))
366 }
367 }
368 }
369 fn tray_connect_background(&self) -> TrayIconResult<Arc<RwLock<TcpStream>>> {
370 match TcpStream::connect(sharing::CHANNEL_NAME) {
371 Ok(stream) => {
372 info!("Connected to pispas-channel on INIT (background)");
373 match stream.set_nonblocking(true) {
374 Ok(_) => {
375 info!("Socket set to nonblocking");
376 }
377 Err(e) => {
378 error!("Error setting socket to nonblocking: {}", e);
379 }
380 }
381 Ok(Arc::new(RwLock::new(stream)))
382 }
383 Err(e) => {
384 error!("Error connecting to pispas-channel: {}", e);
385 Err(anyhow::anyhow!("Error connecting to pispas-channel"))
386 }
387 }
388 }
389 pub fn running(&mut self) -> TrayIconResult<()>{
390 self.start.set_enabled(false);
391 self.stop.set_enabled(true);
392 self.menu_status.set_text("STATUS: RUNNING");
393 self.tray_icon.set_icon(Some(self.try_icon_on.clone()))?;
394 Ok(())
395 }
396
397 pub fn stopped(&mut self) ->TrayIconResult<()>{
398 self.start.set_enabled(true);
399 self.stop.set_enabled(false);
400 self.menu_status.set_text("STATUS: STOPPED");
401 self.tray_icon.set_icon(Some(self.try_icon_off.clone()))?;
402 Ok(())
403 }
404
405 pub fn warning(&mut self) -> TrayIconResult<()>{
406 self.start.set_enabled(false);
407 self.stop.set_enabled(true);
408 self.menu_status.set_text(self.tray_status.read().to_string());
409 self.tray_icon.set_icon(Some(self.try_icon_alert.clone()))?;
410 Ok(())
411 }
412
413 pub fn log_dir() -> PathBuf {
414 sharing::paths::HOME_DIR.join(crate::TRY_ICON_LOG_FILE)
415 }
416
417 pub fn about(&self) {
418 open_window("About", format!("pispas service\nService Versión {VERSION}\n\nAuthors: GEKKOTECH S.L.").as_str());
419 }
420 pub fn open_configurator(&mut self) {
421 let p = sharing::paths::configurator_path().display().to_string();
422 info!("Opening configurator: {}", p);
423 std::thread::spawn(move || {
424 match std::process::Command::new(&p).status() {
425 Ok(status) => {
426 info!("Configurator exited with status: {:?}", status);
427 },
428 Err(e) => {
429 error!("Error spawning configurator: {:?}", e);
430 }
431 }
432 });
433 }
434
435 pub fn download_drivers(&mut self) {
436 let path = sharing::paths::elevator_path().display().to_string();
437 let output = std::process::Command::new("cmd")
438 .args(&["/C", &path])
439 .output()
440 .expect("Failed to execute command");
441
442 if !output.status.success() {
443 error!("Command failed with output: {:?}", output);
444 }
445
446 }
447 pub fn subscribe_service(&mut self){
448 if let Some(stream) = &self.stream {
449 match stream.write().write_all(Action::Subscribe.to_string().as_bytes()){
450 Ok(_) => {
451 info!("Message sent: {} SUSCRIBE", Action::Subscribe.to_string());
452 }
453 Err(e) => {
454 error!("Error writing to stream SUSCRIBE: {:?}", e);
455 }
456 }
457 }
458 }
459
460 pub fn send_action(&mut self, action: Action) -> sharing::PisPasResult<()> {
467 match self.send_string(action.to_string().as_str()) {
468 Ok(_) => {
469 info!("Message sent: {}", action.to_string());
470 }
471 Err(e) => {
472 error!("Error sending stop command: {}", e);
473 }
474 }
475 Ok(())
476 }
477 pub fn check_updates(&mut self) -> sharing::PisPasResult<()>{
478 self.send_action(Action::Update)
479 }
480 pub fn stop_service(&mut self) -> sharing::PisPasResult<()> {
481 self.send_action(Action::Stop)
482 }
483 pub fn start_service(&mut self) -> sharing::PisPasResult<()> {
484 self.send_action(Action::Start)
485 }
486 pub fn restart(&mut self) -> sharing::PisPasResult<()> {
487 self.send_action(Action::Restart)
488 }
489 pub fn close_socket(&mut self) -> sharing::PisPasResult<()> {
490 self.send_action(Action::Close)
491 }
492 pub fn read_channel(&mut self, stream: Arc<RwLock<TcpStream>>, cancel: Arc<AtomicBool>) -> String {
493 let mut buffer = Vec::new(); loop {
496 sleep(Duration::from_millis(100));
497 let mut partial_buffer = [0; 1024];
498
499 match stream.write().read(&mut partial_buffer) {
500 Ok(size) => {
501 if size == 0 {
502 if cancel.load(Ordering::Relaxed) {
503 info!("Closing socket");
504 break;
505 }
506 continue;
507 }
508 buffer.extend_from_slice(&partial_buffer[..size]);
509 if size < partial_buffer.len() {
510 break; }
512 }
513 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
514 continue;
516 }
517 Err(e) => {
518 error!("Failed to read from socket in dedicated thread: {}", e);
519 self.stream = None;
520 break;
521 }
522 }
523
524 if cancel.load(Ordering::Relaxed) {
525 break;
526 }
527 }
528
529 let message = String::from_utf8_lossy(&buffer).to_string();
530 info!("Message received: {} on read_channel", message);
531
532 message
533 }
534
535 fn send_string(&mut self, message: &str) -> sharing::PisPasResult<()> {
536 match TcpStream::connect(sharing::CHANNEL_NAME) {
537 Ok(mut streamsocket) => {
538 info!("Connected to pispas-channel, message send: {}", message);
539 match streamsocket.write_all(message.as_bytes()) {
541 Ok(_) => {
542 info!("Message sent: {}", message);
543 return Ok(());
544 }
545 Err(e) => {
546 error!("Failed to write to socket in dedicated thread {}", e);
547 return Err(anyhow::anyhow!("Failed to write to socket in dedicated thread {}", e));
548 }
549 }
550
551 }
552 Err(e) => {
553 error!("Error connecting to pispas-channel: {}", e);
554 }
555 }
556
557 Err(anyhow::anyhow!("No socket available"))
558 }
559
560}
561
562