tray_wrapper/
tray_wrapper.rs1use crate::{
2 event_loop::UserEvent,
3 menu_state::MenuState,
4 server_generator::{ContinueRunning, ServerGenerator},
5 server_status::ServerStatus,
6};
7use image::ImageError;
8use std::time::Duration;
9use take_once::TakeOnce;
10use thiserror::Error;
11use tokio::runtime::Runtime;
12use tray_icon::{BadIcon, Icon};
13use winit::{application::ApplicationHandler, event_loop::EventLoopProxy};
14
15pub struct TrayWrapper {
17 icon: Icon,
18 version: Option<String>,
19 menu_state: Option<MenuState>,
20 runtime: Option<Runtime>,
21 event_loop_proxy: EventLoopProxy<UserEvent>,
22 server_generator: TakeOnce<ServerGenerator>,
23}
24
25impl TrayWrapper {
26 pub fn new(
29 icon_data: &[u8],
30 version: Option<String>,
31 event_loop_proxy: EventLoopProxy<UserEvent>,
32 server_gen: ServerGenerator,
33 ) -> Result<Self, TrayWrapperError> {
34 let image = image::load_from_memory(icon_data)?.into_rgba8();
35
36 let (width, height) = image.dimensions();
37 let rgba = image.into_raw();
38 let icon = Icon::from_rgba(rgba, width, height)?;
39 let server_generator = TakeOnce::new_with(server_gen);
40
41 Ok(TrayWrapper {
42 icon,
43 version,
44 menu_state: None,
45 runtime: Some(Runtime::new()?),
46
47 event_loop_proxy,
48 server_generator,
49 })
50 }
51}
52
53impl ApplicationHandler<UserEvent> for TrayWrapper {
55 fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {}
56
57 fn window_event(
58 &mut self,
59 _event_loop: &winit::event_loop::ActiveEventLoop,
60 _window_id: winit::window::WindowId,
61 _event: winit::event::WindowEvent,
62 ) {
63 }
64
65 fn new_events(
66 &mut self,
67 _event_loop: &winit::event_loop::ActiveEventLoop,
68 cause: winit::event::StartCause,
69 ) {
70 if winit::event::StartCause::Init == cause {
73 let Ok(mut ms) = MenuState::new(self.icon.clone(), self.version.clone()) else {
74 return _event_loop.exit();
75 };
76 ms.update_tray_icon(ServerStatus::StartUp); self.menu_state = Some(ms);
78
79 let Some(rt) = &self.runtime else {
81 return _event_loop.exit();
82 };
83
84 let sg = self
85 .server_generator
86 .take()
87 .expect("Unable to take generator function");
88 let elp = self.event_loop_proxy.clone();
89 rt.spawn(async move {
90 let sg_fn = sg;
91 loop {
92 let next_run = sg_fn();
93 elp.send_event(UserEvent::ServerStatus(ServerStatus::Running))
94 .expect("Event Loop Closed!");
95 match next_run.await {
96 ContinueRunning::Continue => {
97 elp.send_event(UserEvent::ServerStatus(ServerStatus::Stopped(
98 "Server Exited, will start again".to_string(),
99 )))
100 .expect("Event Loop Closed!");
101 continue;
102 }
103 ContinueRunning::Exit => {
104 elp.send_event(UserEvent::ServerExit)
105 .expect("Event Loop Closed!");
106 break;
107 }
108 ContinueRunning::ExitWithError(e) => {
109 elp.send_event(UserEvent::ServerStatus(ServerStatus::Error(
110 e.to_string(),
111 )))
112 .expect("Event Loop Closed!");
113 break;
114 }
115 }
116 }
117 });
118 }
119
120 #[cfg(target_os = "macos")]
123 {
124 use objc2_core_foundation::CFRunLoop;
125 let rl = CFRunLoop::main().unwrap();
126 CFRunLoop::wake_up(&rl);
127 }
128 }
129
130 fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) {
131 if let UserEvent::ServerExit = event {
132 if let Some(rt) = self.runtime.take() {
133 rt.shutdown_timeout(Duration::from_secs(10));
134 }
135 _event_loop.exit();
136 }
137
138 if let Some(ms) = &mut self.menu_state {
139 if ms.quit_matches(&event)
140 && let Some(rt) = self.runtime.take()
141 {
142 rt.shutdown_timeout(Duration::from_secs(10));
143 _event_loop.exit();
144 return;
145 }
146
147 if let UserEvent::ServerStatus(s_stat) = event {
148 ms.update_tray_icon(s_stat);
149 }
150 }
151 }
152}
153
154#[derive(Error, Debug)]
155pub enum TrayWrapperError {
156 #[error("Unable to load the icon from buffer")]
157 IconLoad(#[from] ImageError),
158 #[error("Tray Icon Bad Icon")]
159 BadIcon(#[from] BadIcon),
160 #[error("Failure to pre-create menu")]
161 MenuError(#[from] tray_icon::menu::Error),
162 #[error(transparent)]
163 RunTime(#[from] std::io::Error),
164}