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