1use std::{
4 any::{Any, TypeId},
5 collections::HashMap,
6 error::Error,
7 sync::{Arc, OnceLock},
8};
9
10use parking_lot::RwLock;
11use tracing::{error, warn};
12use winit::window::Window;
13
14#[cfg(target_os = "android")]
15use winit::platform::android::activity::AndroidApp;
16
17pub type PluginResult = Result<(), Box<dyn Error + Send + Sync>>;
19
20type DesktopWakeHandler = Arc<dyn Fn() + Send + Sync>;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum DesktopWindowAction {
25 Minimize,
27 Maximize,
29 ToggleMaximize,
31 Close,
33}
34
35impl DesktopWindowAction {
36 pub(crate) fn merge_pending(current: Option<Self>, new: Self) -> Self {
37 match (current, new) {
38 (Some(Self::Close), _) | (_, Self::Close) => Self::Close,
39 (_, new) => new,
40 }
41 }
42}
43
44#[derive(Clone)]
46pub struct DesktopPlatformContext {
47 window: Arc<Window>,
48 pending_action: Arc<RwLock<Option<DesktopWindowAction>>>,
49 wake_handler: DesktopWakeHandler,
50}
51
52impl DesktopPlatformContext {
53 pub fn window(&self) -> &Window {
55 &self.window
56 }
57
58 pub fn window_handle(&self) -> Arc<Window> {
60 self.window.clone()
61 }
62
63 pub fn minimize(&self) {
65 self.request_action(DesktopWindowAction::Minimize);
66 }
67
68 pub fn maximize(&self) {
70 self.request_action(DesktopWindowAction::Maximize);
71 }
72
73 pub fn toggle_maximize(&self) {
75 self.request_action(DesktopWindowAction::ToggleMaximize);
76 }
77
78 pub fn request_close(&self) {
80 self.request_action(DesktopWindowAction::Close);
81 }
82
83 fn request_action(&self, action: DesktopWindowAction) {
84 let mut pending_action = self.pending_action.write();
85 let next_action = DesktopWindowAction::merge_pending(*pending_action, action);
86 let changed = *pending_action != Some(next_action);
87 *pending_action = Some(next_action);
88 drop(pending_action);
89
90 if changed {
91 (self.wake_handler)();
92 }
93 }
94
95 pub(crate) fn new(
96 window: Arc<Window>,
97 pending_action: Arc<RwLock<Option<DesktopWindowAction>>>,
98 wake_handler: DesktopWakeHandler,
99 ) -> Self {
100 Self {
101 window,
102 pending_action,
103 wake_handler,
104 }
105 }
106}
107
108pub trait Plugin: Send + Sync + 'static {
110 fn name(&self) -> &'static str {
112 std::any::type_name::<Self>()
113 }
114
115 fn on_resumed(&mut self, _context: &PluginContext) -> PluginResult {
117 Ok(())
118 }
119
120 fn on_suspended(&mut self, _context: &PluginContext) -> PluginResult {
122 Ok(())
123 }
124
125 fn on_shutdown(&mut self, _context: &PluginContext) -> PluginResult {
127 Ok(())
128 }
129}
130
131trait PluginEntry: Send + Sync {
132 fn name(&self) -> &'static str;
133 fn resumed(&self, context: &PluginContext) -> PluginResult;
134 fn suspended(&self, context: &PluginContext) -> PluginResult;
135 fn shutdown(&self, context: &PluginContext) -> PluginResult;
136}
137
138struct PluginSlot<P: Plugin> {
139 inner: Arc<RwLock<P>>,
140}
141
142impl<P: Plugin> PluginSlot<P> {
143 fn new(inner: Arc<RwLock<P>>) -> Self {
144 Self { inner }
145 }
146}
147
148impl<P: Plugin> PluginEntry for PluginSlot<P> {
149 fn name(&self) -> &'static str {
150 self.inner.read().name()
151 }
152
153 fn resumed(&self, context: &PluginContext) -> PluginResult {
154 self.inner.write().on_resumed(context)
155 }
156
157 fn suspended(&self, context: &PluginContext) -> PluginResult {
158 self.inner.write().on_suspended(context)
159 }
160
161 fn shutdown(&self, context: &PluginContext) -> PluginResult {
162 self.inner.write().on_shutdown(context)
163 }
164}
165
166#[derive(Clone)]
168pub struct PluginContext {
169 desktop: DesktopPlatformContext,
170 #[cfg(target_os = "android")]
171 android_app: AndroidApp,
172}
173
174impl PluginContext {
175 pub fn desktop(&self) -> &DesktopPlatformContext {
177 &self.desktop
178 }
179
180 pub fn window(&self) -> &Window {
182 self.desktop.window()
183 }
184
185 pub fn window_handle(&self) -> Arc<Window> {
187 self.desktop.window_handle()
188 }
189
190 #[cfg(target_os = "android")]
192 pub fn android_app(&self) -> &AndroidApp {
193 &self.android_app
194 }
195
196 #[cfg(target_os = "android")]
197 pub(crate) fn new(desktop: DesktopPlatformContext, android_app: AndroidApp) -> Self {
198 Self {
199 desktop,
200 android_app,
201 }
202 }
203
204 #[cfg(not(target_os = "android"))]
205 pub(crate) fn new(desktop: DesktopPlatformContext) -> Self {
206 Self { desktop }
207 }
208}
209
210pub fn register_plugin<P: Plugin>(plugin: P) {
212 register_plugin_arc(Arc::new(RwLock::new(plugin)));
213}
214
215pub fn register_plugin_boxed<P: Plugin>(plugin: Arc<RwLock<P>>) {
217 register_plugin_arc(plugin);
218}
219
220pub fn with_plugin<T, R>(f: impl FnOnce(&T) -> R) -> R
226where
227 T: Plugin + 'static,
228{
229 let plugin = plugin_instance::<T>();
230 let guard = plugin.read();
231 f(&*guard)
232}
233
234pub fn with_plugin_mut<T, R>(f: impl FnOnce(&mut T) -> R) -> R
240where
241 T: Plugin + 'static,
242{
243 let plugin = plugin_instance::<T>();
244 let mut guard = plugin.write();
245 f(&mut *guard)
246}
247
248pub(crate) struct PluginHost {
249 plugins: Vec<Arc<dyn PluginEntry>>,
250 shutdown_called: bool,
251}
252
253impl PluginHost {
254 pub(crate) fn new() -> Self {
255 Self {
256 plugins: registered_plugins(),
257 shutdown_called: false,
258 }
259 }
260
261 pub(crate) fn resumed(&self, context: &PluginContext) {
262 self.dispatch("resumed", context, |plugin, ctx| plugin.resumed(ctx));
263 }
264
265 pub(crate) fn suspended(&self, context: &PluginContext) {
266 self.dispatch("suspended", context, |plugin, ctx| plugin.suspended(ctx));
267 }
268
269 pub(crate) fn shutdown(&mut self, context: &PluginContext) {
270 if self.shutdown_called {
271 return;
272 }
273 self.shutdown_called = true;
274 self.dispatch("shutdown", context, |plugin, ctx| plugin.shutdown(ctx));
275 }
276
277 fn dispatch<F>(&self, stage: &'static str, context: &PluginContext, mut handler: F)
278 where
279 F: FnMut(&dyn PluginEntry, &PluginContext) -> PluginResult,
280 {
281 for plugin in &self.plugins {
282 if let Err(err) = handler(plugin.as_ref(), context) {
283 error!("Plugin '{}' {} hook failed: {}", plugin.name(), stage, err);
284 }
285 }
286 }
287}
288
289fn plugin_registry() -> &'static RwLock<Vec<Arc<dyn PluginEntry>>> {
290 static REGISTRY: OnceLock<RwLock<Vec<Arc<dyn PluginEntry>>>> = OnceLock::new();
291 REGISTRY.get_or_init(|| RwLock::new(Vec::new()))
292}
293
294fn registered_plugins() -> Vec<Arc<dyn PluginEntry>> {
295 plugin_registry().read().clone()
296}
297
298fn register_plugin_arc<P: Plugin>(plugin: Arc<RwLock<P>>) {
299 let plugin_entry = Arc::new(PluginSlot::new(plugin.clone())) as Arc<dyn PluginEntry>;
300 let mut instances = plugin_instance_registry().write();
301 let type_id = TypeId::of::<P>();
302 if instances.contains_key(&type_id) {
303 warn!(
304 "Plugin '{}' was registered more than once; keeping the first instance",
305 std::any::type_name::<P>()
306 );
307 return;
308 }
309 instances.insert(type_id, plugin as Arc<dyn Any + Send + Sync>);
310 drop(instances);
311
312 let mut registry = plugin_registry().write();
313 registry.push(plugin_entry);
314}
315
316fn plugin_instance_registry() -> &'static RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>> {
317 static REGISTRY: OnceLock<RwLock<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>> =
318 OnceLock::new();
319 REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
320}
321
322fn plugin_instance<T: Plugin>() -> Arc<RwLock<T>> {
323 let registry = plugin_instance_registry().read();
324 let type_id = TypeId::of::<T>();
325 let Some(plugin) = registry.get(&type_id) else {
326 panic!("Plugin '{}' is not registered", std::any::type_name::<T>());
327 };
328 let plugin = plugin.clone();
329 drop(registry);
330 match Arc::downcast::<RwLock<T>>(plugin) {
331 Ok(plugin) => plugin,
332 Err(_) => panic!(
333 "Plugin '{}' has a mismatched type",
334 std::any::type_name::<T>()
335 ),
336 }
337}