Skip to main content

tauri_plugin_taskbar/
lib.rs

1//! Windows taskbar thumbnail toolbar controls for Tauri applications.
2
3mod 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
18/// Taskbar plugin state and high-level Rust API.
19pub struct Taskbar<R: Runtime> {
20    // keep runtime generic without introducing extra trait bounds on R
21    _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    /// Returns the active plugin configuration.
36    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    /// Explicitly initializes taskbar thumbnail controls for the provided webview window.
77    pub fn initialize(&self, window: &WebviewWindow<R>) -> Result<()> {
78        self.ensure_attached(window)
79    }
80
81    /// Updates play/pause visual state on taskbar thumbnail buttons.
82    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    /// Updates previous/next enabled state on taskbar thumbnail buttons.
88    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    /// Returns whether native taskbar thumbnail behavior is supported.
99    pub const fn is_supported() -> bool {
100        cfg!(target_os = "windows")
101    }
102}
103
104/// Extensions to access the taskbar plugin APIs from app-level managers.
105pub 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}