Skip to content

Commit 020ea05

Browse files
authored
feat: Implement Badging API (#11661)
1 parent a09e48e commit 020ea05

File tree

14 files changed

+345
-7
lines changed

14 files changed

+345
-7
lines changed

.changes/overlay.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"tauri": minor:feat
3+
"tauri-runtime-wry": minor:feat
4+
"tauri-runtime": minor:feat
5+
'@tauri-apps/api': minor:feat
6+
---
7+
8+
Add badging APIs:
9+
- `Window/WebviewWindow::set_badge_count` for Linux, macOS and IOS.
10+
- `Window/WebviewWindow::set_overlay_icon` for Windows Only.
11+
- `Window/WebviewWindow::set_badge_label`for macOS Only.

crates/tauri-runtime-wry/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ wry = { version = "0.47", default-features = false, features = [
2323
"os-webview",
2424
"linux-body",
2525
] }
26-
tao = { version = "0.30.6", default-features = false, features = ["rwh_06"] }
26+
tao = { version = "0.30.8", default-features = false, features = ["rwh_06"] }
2727
tauri-runtime = { version = "2.2.0", path = "../tauri-runtime" }
2828
tauri-utils = { version = "2.1.0", path = "../tauri-utils" }
2929
raw-window-handle = "0.6"

crates/tauri-runtime-wry/src/lib.rs

+57-1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ use wry::{
9595
)))]
9696
use wry::{WebViewBuilderExtUnix, WebViewExtUnix};
9797

98+
#[cfg(target_os = "ios")]
99+
pub use tao::platform::ios::WindowExtIOS;
98100
#[cfg(target_os = "macos")]
99101
pub use tao::platform::macos::{
100102
ActivationPolicy as TaoActivationPolicy, EventLoopExtMacOS, WindowExtMacOS,
@@ -1263,6 +1265,9 @@ pub enum WindowMessage {
12631265
SetCursorIcon(CursorIcon),
12641266
SetCursorPosition(Position),
12651267
SetIgnoreCursorEvents(bool),
1268+
SetBadgeCount(Option<i64>, Option<String>),
1269+
SetBadgeLabel(Option<String>),
1270+
SetOverlayIcon(Option<TaoIcon>),
12661271
SetProgressBar(ProgressBarState),
12671272
SetTitleBarStyle(tauri_utils::TitleBarStyle),
12681273
SetTheme(Option<Theme>),
@@ -2125,6 +2130,32 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
21252130
)
21262131
}
21272132

2133+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()> {
2134+
send_user_message(
2135+
&self.context,
2136+
Message::Window(
2137+
self.window_id,
2138+
WindowMessage::SetBadgeCount(count, desktop_filename),
2139+
),
2140+
)
2141+
}
2142+
2143+
fn set_badge_label(&self, label: Option<String>) -> Result<()> {
2144+
send_user_message(
2145+
&self.context,
2146+
Message::Window(self.window_id, WindowMessage::SetBadgeLabel(label)),
2147+
)
2148+
}
2149+
2150+
fn set_overlay_icon(&self, icon: Option<Icon>) -> Result<()> {
2151+
let icon: Result<Option<TaoIcon>> = icon.map_or(Ok(None), |x| Ok(Some(TaoIcon::try_from(x)?)));
2152+
2153+
send_user_message(
2154+
&self.context,
2155+
Message::Window(self.window_id, WindowMessage::SetOverlayIcon(icon?)),
2156+
)
2157+
}
2158+
21282159
fn set_progress_bar(&self, progress_state: ProgressBarState) -> Result<()> {
21292160
send_user_message(
21302161
&self.context,
@@ -3096,6 +3127,32 @@ fn handle_user_message<T: UserEvent>(
30963127
WindowMessage::RequestRedraw => {
30973128
window.request_redraw();
30983129
}
3130+
WindowMessage::SetBadgeCount(_count, _desktop_filename) => {
3131+
#[cfg(target_os = "ios")]
3132+
window.set_badge_count(
3133+
_count.map_or(0, |x| x.clamp(i32::MIN as i64, i32::MAX as i64) as i32),
3134+
);
3135+
3136+
#[cfg(target_os = "macos")]
3137+
window.set_badge_label(_count.map(|x| x.to_string()));
3138+
3139+
#[cfg(any(
3140+
target_os = "linux",
3141+
target_os = "dragonfly",
3142+
target_os = "freebsd",
3143+
target_os = "netbsd",
3144+
target_os = "openbsd"
3145+
))]
3146+
window.set_badge_count(_count, _desktop_filename);
3147+
}
3148+
WindowMessage::SetBadgeLabel(_label) => {
3149+
#[cfg(target_os = "macos")]
3150+
window.set_badge_label(_label);
3151+
}
3152+
WindowMessage::SetOverlayIcon(_icon) => {
3153+
#[cfg(windows)]
3154+
window.set_overlay_icon(_icon.map(|x| x.0).as_ref());
3155+
}
30993156
WindowMessage::SetProgressBar(progress_state) => {
31003157
window.set_progress_bar(ProgressBarStateWrapper::from(progress_state).0);
31013158
}
@@ -3431,7 +3488,6 @@ fn handle_user_message<T: UserEvent>(
34313488
}
34323489
#[cfg(target_os = "ios")]
34333490
{
3434-
use tao::platform::ios::WindowExtIOS;
34353491
use wry::WebViewExtIOS;
34363492

34373493
f(Webview {

crates/tauri-runtime/src/lib.rs

+18
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,24 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
816816
/// Starts resize-dragging the window.
817817
fn start_resize_dragging(&self, direction: ResizeDirection) -> Result<()>;
818818

819+
/// Sets the badge count on the taskbar
820+
/// The badge count appears as a whole for the application
821+
/// Using `0` or using `None` will remove the badge
822+
///
823+
/// ## Platform-specific
824+
/// - **Windows:** Unsupported, use [`WindowDispatch::set_overlay_icon`] instead.
825+
/// - **Android:** Unsupported.
826+
/// - **iOS:** iOS expects i32, if the value is larger than i32::MAX, it will be clamped to i32::MAX.
827+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()>;
828+
829+
/// Sets the badge count on the taskbar **macOS only**. Using `None` will remove the badge
830+
fn set_badge_label(&self, label: Option<String>) -> Result<()>;
831+
832+
/// Sets the overlay icon on the taskbar **Windows only**. Using `None` will remove the icon
833+
///
834+
/// The overlay icon can be unique for each window.
835+
fn set_overlay_icon(&self, icon: Option<Icon>) -> Result<()>;
836+
819837
/// Sets the taskbar progress state.
820838
///
821839
/// ## Platform-specific

crates/tauri/build.rs

+3
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
105105
("start_dragging", false),
106106
("start_resize_dragging", false),
107107
("set_progress_bar", false),
108+
("set_badge_count", false),
109+
("set_overlay_icon", false),
110+
("set_badge_label", false),
108111
("set_icon", false),
109112
("set_title_bar_style", false),
110113
("set_theme", false),

crates/tauri/permissions/window/autogenerated/reference.md

+78
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,58 @@ Denies the set_always_on_top command without any pre-configured scope.
898898
<tr>
899899
<td>
900900

901+
`core:window:allow-set-badge-count`
902+
903+
</td>
904+
<td>
905+
906+
Enables the set_badge_count command without any pre-configured scope.
907+
908+
</td>
909+
</tr>
910+
911+
<tr>
912+
<td>
913+
914+
`core:window:deny-set-badge-count`
915+
916+
</td>
917+
<td>
918+
919+
Denies the set_badge_count command without any pre-configured scope.
920+
921+
</td>
922+
</tr>
923+
924+
<tr>
925+
<td>
926+
927+
`core:window:allow-set-badge-label`
928+
929+
</td>
930+
<td>
931+
932+
Enables the set_badge_label command without any pre-configured scope.
933+
934+
</td>
935+
</tr>
936+
937+
<tr>
938+
<td>
939+
940+
`core:window:deny-set-badge-label`
941+
942+
</td>
943+
<td>
944+
945+
Denies the set_badge_label command without any pre-configured scope.
946+
947+
</td>
948+
</tr>
949+
950+
<tr>
951+
<td>
952+
901953
`core:window:allow-set-closable`
902954

903955
</td>
@@ -1340,6 +1392,32 @@ Denies the set_minimizable command without any pre-configured scope.
13401392
<tr>
13411393
<td>
13421394

1395+
`core:window:allow-set-overlay-icon`
1396+
1397+
</td>
1398+
<td>
1399+
1400+
Enables the set_overlay_icon command without any pre-configured scope.
1401+
1402+
</td>
1403+
</tr>
1404+
1405+
<tr>
1406+
<td>
1407+
1408+
`core:window:deny-set-overlay-icon`
1409+
1410+
</td>
1411+
<td>
1412+
1413+
Denies the set_overlay_icon command without any pre-configured scope.
1414+
1415+
</td>
1416+
</tr>
1417+
1418+
<tr>
1419+
<td>
1420+
13431421
`core:window:allow-set-position`
13441422

13451423
</td>

crates/tauri/scripts/bundle.global.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tauri/src/test/mock_runtime.rs

+12
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,18 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
977977
Ok(())
978978
}
979979

980+
fn set_badge_count(&self, count: Option<i64>, desktop_filename: Option<String>) -> Result<()> {
981+
Ok(())
982+
}
983+
984+
fn set_badge_label(&self, label: Option<String>) -> Result<()> {
985+
Ok(())
986+
}
987+
988+
fn set_overlay_icon(&self, icon: Option<Icon<'_>>) -> Result<()> {
989+
Ok(())
990+
}
991+
980992
fn set_title_bar_style(&self, style: tauri_utils::TitleBarStyle) -> Result<()> {
981993
Ok(())
982994
}

crates/tauri/src/webview/webview_window.rs

+26
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,32 @@ impl<R: Runtime> WebviewWindow<R> {
17161716
self.window.start_dragging()
17171717
}
17181718

1719+
/// Sets the overlay icon on the taskbar **Windows only**. Using `None` will remove the icon
1720+
///
1721+
/// The overlay icon can be unique for each window.
1722+
#[cfg(target_os = "windows")]
1723+
#[cfg_attr(docsrs, doc(cfg(target_os = "windows")))]
1724+
pub fn set_overlay_icon(&self, icon: Option<Image<'_>>) -> crate::Result<()> {
1725+
self.window.set_overlay_icon(icon)
1726+
}
1727+
1728+
/// Sets the taskbar badge count. Using `0` or `None` will remove the badge
1729+
///
1730+
/// ## Platform-specific
1731+
/// - **Windows:** Unsupported, use [`WebviewWindow::set_overlay_icon`] instead.
1732+
/// - **iOS:** iOS expects i32, the value will be clamped to i32::MIN, i32::MAX.
1733+
/// - **Android:** Unsupported.
1734+
pub fn set_badge_count(&self, count: Option<i64>) -> crate::Result<()> {
1735+
self.window.set_badge_count(count)
1736+
}
1737+
1738+
/// Sets the taskbar badge label **macOS only**. Using `None` will remove the badge
1739+
#[cfg(target_os = "macos")]
1740+
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
1741+
pub fn set_badge_label(&self, label: Option<String>) -> crate::Result<()> {
1742+
self.window.set_badge_label(label)
1743+
}
1744+
17191745
/// Sets the taskbar progress state.
17201746
///
17211747
/// ## Platform-specific

crates/tauri/src/window/mod.rs

+38
Original file line numberDiff line numberDiff line change
@@ -2014,6 +2014,44 @@ tauri::Builder::default()
20142014
.map_err(Into::into)
20152015
}
20162016

2017+
/// Sets the overlay icon on the taskbar **Windows only**. Using `None` to remove the overlay icon
2018+
///
2019+
/// The overlay icon can be unique for each window.
2020+
#[cfg(target_os = "windows")]
2021+
#[cfg_attr(docsrs, doc(cfg(target_os = "windows")))]
2022+
pub fn set_overlay_icon(&self, icon: Option<Image<'_>>) -> crate::Result<()> {
2023+
self
2024+
.window
2025+
.dispatcher
2026+
.set_overlay_icon(icon.map(|x| x.into()))
2027+
.map_err(Into::into)
2028+
}
2029+
2030+
/// Sets the taskbar badge count. Using `0` or `None` will remove the badge
2031+
///
2032+
/// ## Platform-specific
2033+
/// - **Windows:** Unsupported, use [`Window::set_overlay_icon`] instead.
2034+
/// - **iOS:** iOS expects i32, the value will be clamped to i32::MIN, i32::MAX.
2035+
/// - **Android:** Unsupported.
2036+
pub fn set_badge_count(&self, count: Option<i64>) -> crate::Result<()> {
2037+
self
2038+
.window
2039+
.dispatcher
2040+
.set_badge_count(count, Some(format!("{}.desktop", self.package_info().name)))
2041+
.map_err(Into::into)
2042+
}
2043+
2044+
/// Sets the taskbar badge label **macOS only**. Using `None` will remove the badge
2045+
#[cfg(target_os = "macos")]
2046+
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
2047+
pub fn set_badge_label(&self, label: Option<String>) -> crate::Result<()> {
2048+
self
2049+
.window
2050+
.dispatcher
2051+
.set_badge_label(label)
2052+
.map_err(Into::into)
2053+
}
2054+
20172055
/// Sets the taskbar progress state.
20182056
///
20192057
/// ## Platform-specific

crates/tauri/src/window/plugin.rs

+27
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,34 @@ mod desktop_commands {
138138
setter!(start_dragging);
139139
setter!(start_resize_dragging, ResizeDirection);
140140
setter!(set_progress_bar, ProgressBarState);
141+
setter!(set_badge_count, Option<i64>);
142+
#[cfg(target_os = "macos")]
143+
setter!(set_badge_label, Option<String>);
141144
setter!(set_visible_on_all_workspaces, bool);
142145
setter!(set_title_bar_style, TitleBarStyle);
143146
setter!(set_size_constraints, WindowSizeConstraints);
144147
setter!(set_theme, Option<Theme>);
145148
setter!(set_enabled, bool);
146149

150+
#[command(root = "crate")]
151+
#[cfg(target_os = "windows")]
152+
pub async fn set_overlay_icon<R: Runtime>(
153+
webview: Webview<R>,
154+
window: Window<R>,
155+
label: Option<String>,
156+
value: Option<crate::image::JsImage>,
157+
) -> crate::Result<()> {
158+
let window = get_window(window, label)?;
159+
let resources_table = webview.resources_table();
160+
161+
let value = match value {
162+
Some(value) => Some(value.into_img(&resources_table)?.as_ref().clone()),
163+
None => None,
164+
};
165+
166+
window.set_overlay_icon(value).map_err(Into::into)
167+
}
168+
147169
#[command(root = "crate")]
148170
pub async fn set_icon<R: Runtime>(
149171
webview: Webview<R>,
@@ -290,7 +312,12 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
290312
desktop_commands::set_ignore_cursor_events,
291313
desktop_commands::start_dragging,
292314
desktop_commands::start_resize_dragging,
315+
desktop_commands::set_badge_count,
316+
#[cfg(target_os = "macos")]
317+
desktop_commands::set_badge_label,
293318
desktop_commands::set_progress_bar,
319+
#[cfg(target_os = "windows")]
320+
desktop_commands::set_overlay_icon,
294321
desktop_commands::set_icon,
295322
desktop_commands::set_visible_on_all_workspaces,
296323
desktop_commands::set_background_color,

packages/api/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
"globals": "^15.4.0",
5757
"rollup": "4.27.3",
5858
"tslib": "^2.6.3",
59-
"typescript": "^5.4.5",
6059
"typescript-eslint": "^8.1.0"
6160
}
6261
}

0 commit comments

Comments
 (0)