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