Skip to content

Commit 426a6b4

Browse files
authored
feat(macOS): Implement tray icon template (#2322)
1 parent 8808085 commit 426a6b4

File tree

16 files changed

+168
-4
lines changed

16 files changed

+168
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"tauri": patch
3+
"tauri-runtime": patch
4+
"tauri-runtime-wry": patch
5+
---
6+
7+
- Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.
8+
9+
- Images you mark as template images should consist of only black and clear colors. You can use the alpha channel in the image to adjust the opacity of black content, however.

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

+22
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use tauri_runtime::{SystemTray, SystemTrayEvent};
2525
use winapi::shared::windef::HWND;
2626
#[cfg(target_os = "macos")]
2727
use wry::application::platform::macos::WindowExtMacOS;
28+
#[cfg(all(feature = "system-tray", target_os = "macos"))]
29+
use wry::application::platform::macos::{SystemTrayBuilderExtMacOS, SystemTrayExtMacOS};
2830
#[cfg(target_os = "linux")]
2931
use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
3032
#[cfg(windows)]
@@ -725,6 +727,8 @@ enum WebviewEvent {
725727
pub(crate) enum TrayMessage {
726728
UpdateItem(u16, menu::MenuUpdate),
727729
UpdateIcon(Icon),
730+
#[cfg(target_os = "macos")]
731+
UpdateIconAsTemplate(bool),
728732
}
729733

730734
#[derive(Clone)]
@@ -1448,6 +1452,18 @@ impl Runtime for Wry {
14481452

14491453
let mut items = HashMap::new();
14501454

1455+
#[cfg(target_os = "macos")]
1456+
let tray = SystemTrayBuilder::new(
1457+
icon,
1458+
system_tray
1459+
.menu
1460+
.map(|menu| to_wry_context_menu(&mut items, menu)),
1461+
)
1462+
.with_icon_as_template(system_tray.icon_as_template)
1463+
.build(&self.event_loop)
1464+
.map_err(|e| Error::SystemTray(Box::new(e)))?;
1465+
1466+
#[cfg(not(target_os = "macos"))]
14511467
let tray = SystemTrayBuilder::new(
14521468
icon,
14531469
system_tray
@@ -1940,6 +1956,12 @@ fn handle_event_loop(
19401956
tray.lock().unwrap().set_icon(icon.into_tray_icon());
19411957
}
19421958
}
1959+
#[cfg(target_os = "macos")]
1960+
TrayMessage::UpdateIconAsTemplate(is_template) => {
1961+
if let Some(tray) = &*tray_context.tray.lock().unwrap() {
1962+
tray.lock().unwrap().set_icon_as_template(is_template);
1963+
}
1964+
}
19431965
},
19441966
Message::GlobalShortcut(message) => match message {
19451967
GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx

core/tauri-runtime-wry/src/menu.rs

+9
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ impl TrayHandle for SystemTrayHandle {
7575
.send_event(Message::Tray(TrayMessage::UpdateItem(id, update)))
7676
.map_err(|_| Error::FailedToSendMessage)
7777
}
78+
#[cfg(target_os = "macos")]
79+
fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
80+
self
81+
.proxy
82+
.send_event(Message::Tray(TrayMessage::UpdateIconAsTemplate(
83+
is_template,
84+
)))
85+
.map_err(|_| Error::FailedToSendMessage)
86+
}
7887
}
7988

8089
#[cfg(target_os = "macos")]

core/tauri-runtime/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ use window::{
3434
pub struct SystemTray {
3535
pub icon: Option<Icon>,
3636
pub menu: Option<menu::SystemTrayMenu>,
37+
#[cfg(target_os = "macos")]
38+
pub icon_as_template: bool,
3739
}
3840

3941
#[cfg(feature = "system-tray")]
@@ -42,6 +44,8 @@ impl Default for SystemTray {
4244
Self {
4345
icon: None,
4446
menu: None,
47+
#[cfg(target_os = "macos")]
48+
icon_as_template: false,
4549
}
4650
}
4751
}
@@ -63,6 +67,13 @@ impl SystemTray {
6367
self
6468
}
6569

70+
/// Sets the tray icon as template.
71+
#[cfg(target_os = "macos")]
72+
pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
73+
self.icon_as_template = is_template;
74+
self
75+
}
76+
6677
/// Sets the menu to show when the system tray is right clicked.
6778
pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self {
6879
self.menu.replace(menu);

core/tauri-runtime/src/menu.rs

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ pub enum MenuUpdate {
148148
pub trait TrayHandle {
149149
fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
150150
fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
151+
#[cfg(target_os = "macos")]
152+
fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>;
151153
}
152154

153155
/// A window menu.

core/tauri-utils/src/config.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ pub struct SystemTrayConfig {
209209
/// Path to the icon to use on the system tray.
210210
/// Automatically set to be an `.png` on macOS and Linux, and `.ico` on Windows.
211211
pub icon_path: PathBuf,
212+
/// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
213+
#[serde(default)]
214+
pub icon_as_template: bool,
212215
}
213216

214217
/// A CLI argument definition
@@ -391,6 +394,7 @@ pub struct TauriConfig {
391394
#[serde(default)]
392395
pub security: SecurityConfig,
393396
/// System tray configuration.
397+
#[serde(default)]
394398
pub system_tray: Option<SystemTrayConfig>,
395399
}
396400

@@ -866,8 +870,9 @@ mod build {
866870

867871
impl ToTokens for SystemTrayConfig {
868872
fn to_tokens(&self, tokens: &mut TokenStream) {
873+
let icon_as_template = self.icon_as_template;
869874
let icon_path = path_buf_lit(&self.icon_path);
870-
literal_struct!(tokens, SystemTrayConfig, icon_path);
875+
literal_struct!(tokens, SystemTrayConfig, icon_path, icon_as_template);
871876
}
872877
}
873878

core/tauri/src/app.rs

+29
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,15 @@ impl<R: Runtime> Builder<R> {
759759
icon
760760
};
761761

762+
#[cfg(all(feature = "system-tray", target_os = "macos"))]
763+
let system_tray_icon_as_template = context
764+
.config
765+
.tauri
766+
.system_tray
767+
.as_ref()
768+
.map(|t| t.icon_as_template)
769+
.unwrap_or_default();
770+
762771
let manager = WindowManager::with_handlers(
763772
context,
764773
self.plugins,
@@ -844,6 +853,8 @@ impl<R: Runtime> Builder<R> {
844853
if let Some(menu) = system_tray.menu {
845854
tray = tray.with_menu(menu);
846855
}
856+
857+
#[cfg(not(target_os = "macos"))]
847858
let tray_handler = app
848859
.runtime
849860
.as_ref()
@@ -857,6 +868,24 @@ impl<R: Runtime> Builder<R> {
857868
),
858869
)
859870
.expect("failed to run tray");
871+
872+
#[cfg(target_os = "macos")]
873+
let tray_handler = app
874+
.runtime
875+
.as_ref()
876+
.unwrap()
877+
.system_tray(
878+
tray
879+
.with_icon(
880+
system_tray
881+
.icon
882+
.or(system_tray_icon)
883+
.expect("tray icon not found; please configure it on tauri.conf.json"),
884+
)
885+
.with_icon_as_template(system_tray_icon_as_template),
886+
)
887+
.expect("failed to run tray");
888+
860889
let tray_handle = tray::SystemTrayHandle {
861890
ids: Arc::new(ids.clone()),
862891
inner: tray_handler,

core/tauri/src/app/tray.rs

+9
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ impl<R: Runtime> SystemTrayHandle<R> {
126126
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
127127
self.inner.set_icon(icon).map_err(Into::into)
128128
}
129+
130+
/// Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.
131+
#[cfg(target_os = "macos")]
132+
pub fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()> {
133+
self
134+
.inner
135+
.set_icon_as_template(is_template)
136+
.map_err(Into::into)
137+
}
129138
}
130139

131140
impl<R: Runtime> SystemTrayMenuItemHandle<R> {

docs/usage/guides/visual/system-tray.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ Configure the `systemTray` object on `tauri.conf.json`:
1212
{
1313
"tauri": {
1414
"systemTray": {
15-
"iconPath": "icons/icon.png"
15+
"iconPath": "icons/icon.png",
16+
"iconAsTemplate": true,
1617
}
1718
}
1819
}
1920
```
2021

2122
The `iconPath` is pointed to a PNG file on macOS and Linux, and a `.ico` file must exist for Windows support.
2223

24+
The `iconAsTemplate` is a boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
25+
26+
2327
### Creating a system tray
2428

2529
To create a native system tray, import the `SystemTray` type:

examples/.icons/tray_icon.png

970 Bytes
Loading
4.31 KB
Binary file not shown.
1.03 KB
Loading

examples/api/src-tauri/src/main.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
mod cmd;
1515
mod menu;
1616

17+
#[cfg(target_os = "linux")]
18+
use std::path::PathBuf;
19+
1720
use serde::Serialize;
1821
use tauri::{
1922
CustomMenuItem, Event, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder,
@@ -53,7 +56,9 @@ fn main() {
5356
SystemTray::new().with_menu(
5457
SystemTrayMenu::new()
5558
.add_item(CustomMenuItem::new("toggle", "Toggle"))
56-
.add_item(CustomMenuItem::new("new", "New window")),
59+
.add_item(CustomMenuItem::new("new", "New window"))
60+
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
61+
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")),
5762
),
5863
)
5964
.on_system_tray_event(|app, event| match event {
@@ -89,6 +94,56 @@ fn main() {
8994
},
9095
)
9196
.unwrap(),
97+
#[cfg(target_os = "macos")]
98+
"icon_1" => {
99+
app.tray_handle().set_icon_as_template(true).unwrap();
100+
101+
app
102+
.tray_handle()
103+
.set_icon(tauri::Icon::Raw(
104+
include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
105+
))
106+
.unwrap();
107+
}
108+
#[cfg(target_os = "macos")]
109+
"icon_2" => {
110+
app.tray_handle().set_icon_as_template(true).unwrap();
111+
112+
app
113+
.tray_handle()
114+
.set_icon(tauri::Icon::Raw(
115+
include_bytes!("../../../.icons/tray_icon.png").to_vec(),
116+
))
117+
.unwrap();
118+
}
119+
#[cfg(target_os = "linux")]
120+
"icon_1" => app
121+
.tray_handle()
122+
.set_icon(tauri::Icon::File(PathBuf::from(
123+
"../../../.icons/tray_icon_with_transparency.png",
124+
)))
125+
.unwrap(),
126+
#[cfg(target_os = "linux")]
127+
"icon_2" => app
128+
.tray_handle()
129+
.set_icon(tauri::Icon::File(PathBuf::from(
130+
"../../../.icons/tray_icon.png",
131+
)))
132+
.unwrap(),
133+
#[cfg(target_os = "windows")]
134+
"icon_1" => app
135+
.tray_handle()
136+
.set_icon(tauri::Icon::Raw(
137+
include_bytes!("../../../.icons/tray_icon_with_transparency.ico").to_vec(),
138+
))
139+
.unwrap(),
140+
#[cfg(target_os = "windows")]
141+
"icon_2" => app
142+
.tray_handle()
143+
.set_icon(tauri::Icon::Raw(
144+
include_bytes!("../../../.icons/icon.ico").to_vec(),
145+
))
146+
.unwrap(),
92147
_ => {}
93148
}
94149
}

examples/api/src-tauri/tauri.conf.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@
7878
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: asset: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
7979
},
8080
"systemTray": {
81-
"iconPath": "../../.icons/icon.png"
81+
"iconPath": "../../.icons/tray_icon_with_transparency.png",
82+
"iconAsTemplate": true
8283
}
8384
}
8485
}

tooling/cli.rs/config_definition.rs

+3
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,9 @@ pub struct SystemTrayConfig {
623623
///
624624
/// It is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows.
625625
pub icon_path: PathBuf,
626+
/// A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.
627+
#[serde(default)]
628+
pub icon_as_template: bool,
626629
}
627630

628631
// We enable the unnecessary_wraps because we need

tooling/cli.rs/schema.json

+5
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,11 @@
902902
"iconPath"
903903
],
904904
"properties": {
905+
"iconAsTemplate": {
906+
"description": "A Boolean value that determines whether the image represents a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) image on macOS.",
907+
"default": false,
908+
"type": "boolean"
909+
},
905910
"iconPath": {
906911
"description": "Path to the icon to use on the system tray.\n\nIt is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows.",
907912
"type": "string"

0 commit comments

Comments
 (0)