Skip to content

Commit

Permalink
feat(macOS): Implement tray icon template (#2322)
Browse files Browse the repository at this point in the history
  • Loading branch information
lemarier committed Jul 29, 2021
1 parent 8808085 commit 426a6b4
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 4 deletions.
9 changes: 9 additions & 0 deletions .changes/tauri-macos-tray-icon-template.md
@@ -0,0 +1,9 @@
---
"tauri": patch
"tauri-runtime": patch
"tauri-runtime-wry": patch
---

- Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.

- 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.
22 changes: 22 additions & 0 deletions core/tauri-runtime-wry/src/lib.rs
Expand Up @@ -25,6 +25,8 @@ use tauri_runtime::{SystemTray, SystemTrayEvent};
use winapi::shared::windef::HWND;
#[cfg(target_os = "macos")]
use wry::application::platform::macos::WindowExtMacOS;
#[cfg(all(feature = "system-tray", target_os = "macos"))]
use wry::application::platform::macos::{SystemTrayBuilderExtMacOS, SystemTrayExtMacOS};
#[cfg(target_os = "linux")]
use wry::application::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
#[cfg(windows)]
Expand Down Expand Up @@ -725,6 +727,8 @@ enum WebviewEvent {
pub(crate) enum TrayMessage {
UpdateItem(u16, menu::MenuUpdate),
UpdateIcon(Icon),
#[cfg(target_os = "macos")]
UpdateIconAsTemplate(bool),
}

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

let mut items = HashMap::new();

#[cfg(target_os = "macos")]
let tray = SystemTrayBuilder::new(
icon,
system_tray
.menu
.map(|menu| to_wry_context_menu(&mut items, menu)),
)
.with_icon_as_template(system_tray.icon_as_template)
.build(&self.event_loop)
.map_err(|e| Error::SystemTray(Box::new(e)))?;

#[cfg(not(target_os = "macos"))]
let tray = SystemTrayBuilder::new(
icon,
system_tray
Expand Down Expand Up @@ -1940,6 +1956,12 @@ fn handle_event_loop(
tray.lock().unwrap().set_icon(icon.into_tray_icon());
}
}
#[cfg(target_os = "macos")]
TrayMessage::UpdateIconAsTemplate(is_template) => {
if let Some(tray) = &*tray_context.tray.lock().unwrap() {
tray.lock().unwrap().set_icon_as_template(is_template);
}
}
},
Message::GlobalShortcut(message) => match message {
GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx
Expand Down
9 changes: 9 additions & 0 deletions core/tauri-runtime-wry/src/menu.rs
Expand Up @@ -75,6 +75,15 @@ impl TrayHandle for SystemTrayHandle {
.send_event(Message::Tray(TrayMessage::UpdateItem(id, update)))
.map_err(|_| Error::FailedToSendMessage)
}
#[cfg(target_os = "macos")]
fn set_icon_as_template(&self, is_template: bool) -> tauri_runtime::Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateIconAsTemplate(
is_template,
)))
.map_err(|_| Error::FailedToSendMessage)
}
}

#[cfg(target_os = "macos")]
Expand Down
11 changes: 11 additions & 0 deletions core/tauri-runtime/src/lib.rs
Expand Up @@ -34,6 +34,8 @@ use window::{
pub struct SystemTray {
pub icon: Option<Icon>,
pub menu: Option<menu::SystemTrayMenu>,
#[cfg(target_os = "macos")]
pub icon_as_template: bool,
}

#[cfg(feature = "system-tray")]
Expand All @@ -42,6 +44,8 @@ impl Default for SystemTray {
Self {
icon: None,
menu: None,
#[cfg(target_os = "macos")]
icon_as_template: false,
}
}
}
Expand All @@ -63,6 +67,13 @@ impl SystemTray {
self
}

/// Sets the tray icon as template.
#[cfg(target_os = "macos")]
pub fn with_icon_as_template(mut self, is_template: bool) -> Self {
self.icon_as_template = is_template;
self
}

/// Sets the menu to show when the system tray is right clicked.
pub fn with_menu(mut self, menu: menu::SystemTrayMenu) -> Self {
self.menu.replace(menu);
Expand Down
2 changes: 2 additions & 0 deletions core/tauri-runtime/src/menu.rs
Expand Up @@ -148,6 +148,8 @@ pub enum MenuUpdate {
pub trait TrayHandle {
fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
#[cfg(target_os = "macos")]
fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()>;
}

/// A window menu.
Expand Down
7 changes: 6 additions & 1 deletion core/tauri-utils/src/config.rs
Expand Up @@ -209,6 +209,9 @@ pub struct SystemTrayConfig {
/// Path to the icon to use on the system tray.
/// Automatically set to be an `.png` on macOS and Linux, and `.ico` on Windows.
pub icon_path: PathBuf,
/// 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.
#[serde(default)]
pub icon_as_template: bool,
}

/// A CLI argument definition
Expand Down Expand Up @@ -391,6 +394,7 @@ pub struct TauriConfig {
#[serde(default)]
pub security: SecurityConfig,
/// System tray configuration.
#[serde(default)]
pub system_tray: Option<SystemTrayConfig>,
}

Expand Down Expand Up @@ -866,8 +870,9 @@ mod build {

impl ToTokens for SystemTrayConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let icon_as_template = self.icon_as_template;
let icon_path = path_buf_lit(&self.icon_path);
literal_struct!(tokens, SystemTrayConfig, icon_path);
literal_struct!(tokens, SystemTrayConfig, icon_path, icon_as_template);
}
}

Expand Down
29 changes: 29 additions & 0 deletions core/tauri/src/app.rs
Expand Up @@ -759,6 +759,15 @@ impl<R: Runtime> Builder<R> {
icon
};

#[cfg(all(feature = "system-tray", target_os = "macos"))]
let system_tray_icon_as_template = context
.config
.tauri
.system_tray
.as_ref()
.map(|t| t.icon_as_template)
.unwrap_or_default();

let manager = WindowManager::with_handlers(
context,
self.plugins,
Expand Down Expand Up @@ -844,6 +853,8 @@ impl<R: Runtime> Builder<R> {
if let Some(menu) = system_tray.menu {
tray = tray.with_menu(menu);
}

#[cfg(not(target_os = "macos"))]
let tray_handler = app
.runtime
.as_ref()
Expand All @@ -857,6 +868,24 @@ impl<R: Runtime> Builder<R> {
),
)
.expect("failed to run tray");

#[cfg(target_os = "macos")]
let tray_handler = app
.runtime
.as_ref()
.unwrap()
.system_tray(
tray
.with_icon(
system_tray
.icon
.or(system_tray_icon)
.expect("tray icon not found; please configure it on tauri.conf.json"),
)
.with_icon_as_template(system_tray_icon_as_template),
)
.expect("failed to run tray");

let tray_handle = tray::SystemTrayHandle {
ids: Arc::new(ids.clone()),
inner: tray_handler,
Expand Down
9 changes: 9 additions & 0 deletions core/tauri/src/app/tray.rs
Expand Up @@ -126,6 +126,15 @@ impl<R: Runtime> SystemTrayHandle<R> {
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
self.inner.set_icon(icon).map_err(Into::into)
}

/// Support [macOS tray icon template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc) to adjust automatically based on taskbar color.
#[cfg(target_os = "macos")]
pub fn set_icon_as_template(&self, is_template: bool) -> crate::Result<()> {
self
.inner
.set_icon_as_template(is_template)
.map_err(Into::into)
}
}

impl<R: Runtime> SystemTrayMenuItemHandle<R> {
Expand Down
6 changes: 5 additions & 1 deletion docs/usage/guides/visual/system-tray.md
Expand Up @@ -12,14 +12,18 @@ Configure the `systemTray` object on `tauri.conf.json`:
{
"tauri": {
"systemTray": {
"iconPath": "icons/icon.png"
"iconPath": "icons/icon.png",
"iconAsTemplate": true,
}
}
}
```

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

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.


### Creating a system tray

To create a native system tray, import the `SystemTray` type:
Expand Down
Binary file added examples/.icons/tray_icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/.icons/tray_icon_with_transparency.ico
Binary file not shown.
Binary file added examples/.icons/tray_icon_with_transparency.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 56 additions & 1 deletion examples/api/src-tauri/src/main.rs
Expand Up @@ -14,6 +14,9 @@
mod cmd;
mod menu;

#[cfg(target_os = "linux")]
use std::path::PathBuf;

use serde::Serialize;
use tauri::{
CustomMenuItem, Event, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder,
Expand Down Expand Up @@ -53,7 +56,9 @@ fn main() {
SystemTray::new().with_menu(
SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window")),
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")),
),
)
.on_system_tray_event(|app, event| match event {
Expand Down Expand Up @@ -89,6 +94,56 @@ fn main() {
},
)
.unwrap(),
#[cfg(target_os = "macos")]
"icon_1" => {
app.tray_handle().set_icon_as_template(true).unwrap();

app
.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
))
.unwrap();
}
#[cfg(target_os = "macos")]
"icon_2" => {
app.tray_handle().set_icon_as_template(true).unwrap();

app
.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/tray_icon.png").to_vec(),
))
.unwrap();
}
#[cfg(target_os = "linux")]
"icon_1" => app
.tray_handle()
.set_icon(tauri::Icon::File(PathBuf::from(
"../../../.icons/tray_icon_with_transparency.png",
)))
.unwrap(),
#[cfg(target_os = "linux")]
"icon_2" => app
.tray_handle()
.set_icon(tauri::Icon::File(PathBuf::from(
"../../../.icons/tray_icon.png",
)))
.unwrap(),
#[cfg(target_os = "windows")]
"icon_1" => app
.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/tray_icon_with_transparency.ico").to_vec(),
))
.unwrap(),
#[cfg(target_os = "windows")]
"icon_2" => app
.tray_handle()
.set_icon(tauri::Icon::Raw(
include_bytes!("../../../.icons/icon.ico").to_vec(),
))
.unwrap(),
_ => {}
}
}
Expand Down
3 changes: 2 additions & 1 deletion examples/api/src-tauri/tauri.conf.json
Expand Up @@ -78,7 +78,8 @@
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: asset: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
},
"systemTray": {
"iconPath": "../../.icons/icon.png"
"iconPath": "../../.icons/tray_icon_with_transparency.png",
"iconAsTemplate": true
}
}
}
3 changes: 3 additions & 0 deletions tooling/cli.rs/config_definition.rs
Expand Up @@ -623,6 +623,9 @@ pub struct SystemTrayConfig {
///
/// It is forced to be a `.png` file on Linux and macOS, and a `.ico` file on Windows.
pub icon_path: PathBuf,
/// 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.
#[serde(default)]
pub icon_as_template: bool,
}

// We enable the unnecessary_wraps because we need
Expand Down
5 changes: 5 additions & 0 deletions tooling/cli.rs/schema.json
Expand Up @@ -902,6 +902,11 @@
"iconPath"
],
"properties": {
"iconAsTemplate": {
"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.",
"default": false,
"type": "boolean"
},
"iconPath": {
"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.",
"type": "string"
Expand Down

0 comments on commit 426a6b4

Please sign in to comment.