tauri_plugin_taskbar/
lib.rs1mod commands;
4mod desktop;
5mod error;
6mod models;
7
8use std::{collections::HashSet, sync::Mutex};
9
10use tauri::{
11 plugin::{Builder, TauriPlugin},
12 Manager, RunEvent, Runtime, WebviewWindow, WindowEvent,
13};
14
15pub use error::{Error, Result};
16pub use models::{Config, EventNames, IconPaths, Tooltips};
17
18pub struct Taskbar<R: Runtime> {
20 _marker: std::marker::PhantomData<fn() -> R>,
22 config: Config,
23 attached_windows: Mutex<HashSet<String>>,
24}
25
26impl<R: Runtime> Taskbar<R> {
27 fn new(config: Config) -> Self {
28 Self {
29 _marker: std::marker::PhantomData::<fn() -> R>,
30 config,
31 attached_windows: Mutex::new(HashSet::new()),
32 }
33 }
34
35 pub fn config(&self) -> &Config {
37 &self.config
38 }
39
40 fn ensure_attached_slot(&self, window_label: &str) -> bool {
41 self.attached_windows
42 .lock()
43 .expect("taskbar plugin state mutex poisoned")
44 .insert(window_label.to_string())
45 }
46
47 fn rollback_attached_slot(&self, window_label: &str) {
48 self.attached_windows
49 .lock()
50 .expect("taskbar plugin state mutex poisoned")
51 .remove(window_label);
52 }
53
54 fn remove_attached_slot(&self, window_label: &str) {
55 self.attached_windows
56 .lock()
57 .expect("taskbar plugin state mutex poisoned")
58 .remove(window_label);
59 }
60
61 fn ensure_attached(&self, window: &WebviewWindow<R>) -> Result<()> {
62 let label = window.label().to_string();
63
64 if !self.ensure_attached_slot(&label) {
65 return Ok(());
66 }
67
68 if let Err(err) = desktop::attach(window, &self.config) {
69 self.rollback_attached_slot(&label);
70 return Err(err);
71 }
72
73 Ok(())
74 }
75
76 pub fn initialize(&self, window: &WebviewWindow<R>) -> Result<()> {
78 self.ensure_attached(window)
79 }
80
81 pub fn set_playback_state(&self, window: &WebviewWindow<R>, is_playing: bool) -> Result<()> {
83 self.ensure_attached(window)?;
84 desktop::update_playback_state(window, is_playing)
85 }
86
87 pub fn set_navigation_enabled(
89 &self,
90 window: &WebviewWindow<R>,
91 previous_enabled: bool,
92 next_enabled: bool,
93 ) -> Result<()> {
94 self.ensure_attached(window)?;
95 desktop::update_navigation_enabled(window, previous_enabled, next_enabled)
96 }
97
98 pub const fn is_supported() -> bool {
100 cfg!(target_os = "windows")
101 }
102}
103
104pub trait TaskbarExt<R: Runtime> {
106 fn taskbar(&self) -> &Taskbar<R>;
107}
108
109impl<R: Runtime, T: Manager<R>> TaskbarExt<R> for T {
110 fn taskbar(&self) -> &Taskbar<R> {
111 self.state::<Taskbar<R>>().inner()
112 }
113}
114
115pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
116 Builder::<R, Option<Config>>::new("taskbar")
117 .setup(|app, api| {
118 let config = api.config().clone().unwrap_or_default();
119 let taskbar = Taskbar::<R>::new(config.clone());
120
121 if config.auto_attach {
122 if let Some(window) = app.get_webview_window(&config.window_label) {
123 if let Err(err) = taskbar.initialize(&window) {
124 log::error!("failed to auto-attach taskbar plugin in setup: {err}");
125 }
126 }
127 }
128
129 app.manage(taskbar);
130
131 Ok(())
132 })
133 .on_event(|app, event| {
134 if let RunEvent::WindowEvent {
135 label,
136 event: WindowEvent::Destroyed,
137 ..
138 } = event
139 {
140 if let Some(taskbar) = app.try_state::<Taskbar<R>>() {
141 taskbar.remove_attached_slot(label);
142 }
143 }
144 })
145 .invoke_handler(tauri::generate_handler![
146 commands::initialize,
147 commands::set_playback_state,
148 commands::set_navigation_enabled,
149 commands::is_supported,
150 ])
151 .build()
152}