Skip to content

Commit

Permalink
refactor(core): use Icon for tray icons (#4342)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog authored Jun 14, 2022
1 parent 6e16679 commit 4ce8e22
Show file tree
Hide file tree
Showing 25 changed files with 451 additions and 330 deletions.
5 changes: 5 additions & 0 deletions .changes/build-do-not-copy-tray-icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-build": patch
---

Do not copy the tray icon to the output directory on Linux since it is embedded in the binary.
7 changes: 7 additions & 0 deletions .changes/core-remove-tray-icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"tauri": patch
"tauri-codegen": patch
---

**Breaking change:** The `TrayIcon` enum has been removed and now `Icon` is used instead.
This allows you to use more image formats and use embedded icons on Linux.
6 changes: 6 additions & 0 deletions .changes/debian-remove-tray-icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"cli.rs": patch
"cli.js": patch
---

Removed the tray icon from the Debian and AppImage bundles since they are embedded in the binary now.
6 changes: 6 additions & 0 deletions .changes/runtime-icon-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-runtime": patch
"tauri-runtime-wry": patch
---

Removed `TrayIcon` and renamed `WindowIcon` to `Icon`, a shared type for both icons.
6 changes: 1 addition & 5 deletions core/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,8 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
)?;
}

#[allow(unused_mut)]
#[allow(unused_mut, clippy::redundant_clone)]
let mut resources = config.tauri.bundle.resources.clone().unwrap_or_default();
#[cfg(target_os = "linux")]
if let Some(tray) = config.tauri.system_tray {
resources.push(tray.icon_path.display().to_string());
}
#[cfg(windows)]
if let Some(fixed_webview2_runtime_path) = &config.tauri.bundle.windows.webview_fixed_runtime_path
{
Expand Down
4 changes: 0 additions & 4 deletions core/tauri-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ brotli = { version = "3", optional = true, default-features = false, features =
regex = { version = "1.5.6", optional = true }
uuid = { version = "1", features = [ "v4" ] }
semver = "1"

[target."cfg(windows)".dependencies]
ico = "0.1"

[target."cfg(target_os = \"linux\")".dependencies]
png = "0.17"

[features]
Expand Down
72 changes: 25 additions & 47 deletions core/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
_ => unimplemented!(),
};

#[cfg(any(windows, target_os = "linux"))]
let out_dir = {
let out_dir = std::env::var("OUT_DIR")
.map_err(|_| EmbeddedAssetsError::OutDir)
Expand All @@ -193,12 +192,20 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
// handle default window icons for Windows targets
#[cfg(windows)]
let default_window_icon = {
let icon_path = find_icon(
let mut icon_path = find_icon(
&config,
&config_parent,
|i| i.ends_with(".ico"),
"icons/icon.ico",
);
if !icon_path.exists() {
icon_path = find_icon(
&config,
&config_parent,
|i| i.ends_with(".png"),
"icons/icon.png",
);
}
ico_icon(&root, &out_dir, icon_path)?
};
#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -234,49 +241,22 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
}
);

#[cfg(target_os = "linux")]
let system_tray_icon = if let Some(tray) = &config.tauri.system_tray {
let mut system_tray_icon_path = tray.icon_path.clone();
system_tray_icon_path.set_extension("png");
if dev {
let system_tray_icon_path = config_parent
.join(system_tray_icon_path)
.display()
.to_string();
quote!(Some(#root::TrayIcon::File(::std::path::PathBuf::from(#system_tray_icon_path))))
let system_tray_icon_path = tray.icon_path.clone();
let ext = system_tray_icon_path.extension();
if ext.map_or(false, |e| e == "ico") {
ico_icon(&root, &out_dir, system_tray_icon_path)?
} else if ext.map_or(false, |e| e == "png") {
png_icon(&root, &out_dir, system_tray_icon_path)?
} else {
let system_tray_icon_file_path = system_tray_icon_path.to_string_lossy().to_string();
quote!(
Some(
#root::TrayIcon::File(
#root::api::path::resolve_path(
&#config,
&#package_info,
&Default::default(),
#system_tray_icon_file_path,
Some(#root::api::path::BaseDirectory::Resource)
).expect("failed to resolve resource dir")
)
)
)
quote!(compile_error!(
"The tray icon extension must be either `.ico` or `.png`."
))
}
} else {
quote!(None)
};

#[cfg(not(target_os = "linux"))]
let system_tray_icon = if let Some(tray) = &config.tauri.system_tray {
let mut system_tray_icon_path = tray.icon_path.clone();
system_tray_icon_path.set_extension(if cfg!(windows) { "ico" } else { "png" });
let system_tray_icon_path = config_parent
.join(system_tray_icon_path)
.display()
.to_string();
quote!(Some(#root::TrayIcon::Raw(include_bytes!(#system_tray_icon_path).to_vec())))
} else {
quote!(None)
};

#[cfg(target_os = "macos")]
let info_plist = {
if dev {
Expand Down Expand Up @@ -367,7 +347,6 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
)))
}

#[cfg(windows)]
fn ico_icon<P: AsRef<Path>>(
root: &TokenStream,
out_dir: &Path,
Expand All @@ -378,14 +357,14 @@ fn ico_icon<P: AsRef<Path>>(

let path = path.as_ref();
let bytes = std::fs::read(&path)
.unwrap_or_else(|_| panic!("failed to read window icon {}", path.display()))
.unwrap_or_else(|_| panic!("failed to read icon {}", path.display()))
.to_vec();
let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))
.unwrap_or_else(|_| panic!("failed to parse window icon {}", path.display()));
.unwrap_or_else(|_| panic!("failed to parse icon {}", path.display()));
let entry = &icon_dir.entries()[0];
let rgba = entry
.decode()
.unwrap_or_else(|_| panic!("failed to decode window icon {}", path.display()))
.unwrap_or_else(|_| panic!("failed to decode icon {}", path.display()))
.rgba_data()
.to_vec();
let width = entry.width();
Expand All @@ -410,7 +389,6 @@ fn ico_icon<P: AsRef<Path>>(
Ok(icon)
}

#[cfg(target_os = "linux")]
fn png_icon<P: AsRef<Path>>(
root: &TokenStream,
out_dir: &Path,
Expand All @@ -421,12 +399,12 @@ fn png_icon<P: AsRef<Path>>(

let path = path.as_ref();
let bytes = std::fs::read(&path)
.unwrap_or_else(|_| panic!("failed to read window icon {}", path.display()))
.unwrap_or_else(|_| panic!("failed to read icon {}", path.display()))
.to_vec();
let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
let mut reader = decoder
.read_info()
.unwrap_or_else(|_| panic!("failed to read window icon {}", path.display()));
.unwrap_or_else(|_| panic!("failed to read icon {}", path.display()));
let mut buffer: Vec<u8> = Vec::new();
while let Ok(Some(row)) = reader.next_row() {
buffer.extend(row.data());
Expand Down Expand Up @@ -459,7 +437,7 @@ fn find_icon<F: Fn(&&String) -> bool>(
config_parent: &Path,
predicate: F,
default: &str,
) -> String {
) -> PathBuf {
let icon_path = config
.tauri
.bundle
Expand All @@ -468,7 +446,7 @@ fn find_icon<F: Fn(&&String) -> bool>(
.find(|i| predicate(i))
.cloned()
.unwrap_or_else(|| default.to_string());
config_parent.join(icon_path).display().to_string()
config_parent.join(icon_path)
}

#[cfg(feature = "shell-scope")]
Expand Down
2 changes: 1 addition & 1 deletion core/tauri-runtime-wry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exclude = [ ".license_template", "CHANGELOG.md", "/target" ]
readme = "README.md"

[dependencies]
wry = { version = "0.18.1", default-features = false, features = [ "file-drop", "protocol" ] }
wry = { version = "0.18.3", default-features = false, features = [ "file-drop", "protocol" ] }
tauri-runtime = { version = "0.7.0", path = "../tauri-runtime" }
tauri-utils = { version = "1.0.0-rc.9", path = "../tauri-utils" }
uuid = { version = "1", features = [ "v4" ] }
Expand Down
51 changes: 23 additions & 28 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use tauri_runtime::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
CursorIcon, DetachedWindow, FileDropEvent, JsEventListenerKey, PendingWindow, WindowEvent,
},
Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Result, RunEvent, RunIteration,
Runtime, RuntimeHandle, UserAttentionType, UserEvent, WindowIcon,
Dispatch, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent, RunIteration,
Runtime, RuntimeHandle, UserAttentionType, UserEvent,
};

use tauri_runtime::window::MenuEvent;
Expand Down Expand Up @@ -486,9 +486,9 @@ fn icon_err<E: std::error::Error + Send + Sync + 'static>(e: E) -> Error {
Error::InvalidIcon(Box::new(e))
}

impl TryFrom<WindowIcon> for WryIcon {
impl TryFrom<Icon> for WryIcon {
type Error = Error;
fn try_from(icon: WindowIcon) -> std::result::Result<Self, Self::Error> {
fn try_from(icon: Icon) -> std::result::Result<Self, Self::Error> {
WryWindowIcon::from_rgba(icon.rgba, icon.width, icon.height)
.map(Self)
.map_err(icon_err)
Expand Down Expand Up @@ -885,7 +885,7 @@ impl WindowBuilder for WindowBuilderWrapper {
self
}

fn icon(mut self, icon: WindowIcon) -> Result<Self> {
fn icon(mut self, icon: Icon) -> Result<Self> {
self.inner = self
.inner
.with_window_icon(Some(WryIcon::try_from(icon)?.0));
Expand Down Expand Up @@ -1092,7 +1092,7 @@ pub enum WebviewEvent {
pub enum TrayMessage {
UpdateItem(u16, MenuUpdate),
UpdateMenu(SystemTrayMenu),
UpdateIcon(TrayIcon),
UpdateIcon(Icon),
#[cfg(target_os = "macos")]
UpdateIconAsTemplate(bool),
Close,
Expand Down Expand Up @@ -1480,7 +1480,7 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
)
}

fn set_icon(&self, icon: WindowIcon) -> Result<()> {
fn set_icon(&self, icon: Icon) -> Result<()> {
send_user_message(
&self.context,
Message::Window(
Expand Down Expand Up @@ -1953,33 +1953,26 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {

#[cfg(feature = "system-tray")]
fn system_tray(&self, system_tray: SystemTray) -> Result<Self::TrayHandler> {
let icon = system_tray
.icon
.expect("tray icon not set")
.into_platform_icon();
let icon = TrayIcon::try_from(system_tray.icon.expect("tray icon not set"))?;

let mut items = HashMap::new();

#[cfg(target_os = "macos")]
let tray = SystemTrayBuilder::new(
icon,
#[allow(unused_mut)]
let mut tray_builder = SystemTrayBuilder::new(
icon.0,
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
.menu
.map(|menu| to_wry_context_menu(&mut items, menu)),
)
.build(&self.event_loop)
.map_err(|e| Error::SystemTray(Box::new(e)))?;
#[cfg(target_os = "macos")]
{
tray_builder = tray_builder.with_icon_as_template(system_tray.icon_as_template);
}

let tray = tray_builder
.build(&self.event_loop)
.map_err(|e| Error::SystemTray(Box::new(e)))?;

*self.tray_context.items.lock().unwrap() = items;
*self.tray_context.tray.lock().unwrap() = Some(Arc::new(Mutex::new(tray)));
Expand Down Expand Up @@ -2530,7 +2523,9 @@ fn handle_user_message<T: UserEvent>(
}
TrayMessage::UpdateIcon(icon) => {
if let Some(tray) = &*tray_context.tray.lock().unwrap() {
tray.lock().unwrap().set_icon(icon.into_platform_icon());
if let Ok(icon) = TrayIcon::try_from(icon) {
tray.lock().unwrap().set_icon(icon.0);
}
}
}
#[cfg(target_os = "macos")]
Expand Down
17 changes: 15 additions & 2 deletions core/tauri-runtime-wry/src/system_tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ pub use tauri_runtime::{
Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry,
SystemTrayMenuItem, TrayHandle,
},
SystemTrayEvent, TrayIcon,
Icon, SystemTrayEvent,
};
pub use wry::application::{
event::TrayEvent,
event_loop::EventLoopProxy,
menu::{
ContextMenu as WryContextMenu, CustomMenuItem as WryCustomMenuItem, MenuItem as WryMenuItem,
},
system_tray::Icon as WryTrayIcon,
};

#[cfg(target_os = "macos")]
Expand All @@ -35,13 +36,25 @@ pub type SystemTrayEventHandler = Box<dyn Fn(&SystemTrayEvent) + Send>;
pub type SystemTrayEventListeners = Arc<Mutex<HashMap<Uuid, Arc<SystemTrayEventHandler>>>>;
pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;

/// Wrapper around a [`wry::application::system_tray::Icon`] that can be created from an [`WindowIcon`].
pub struct TrayIcon(pub(crate) WryTrayIcon);

impl TryFrom<Icon> for TrayIcon {
type Error = Error;
fn try_from(icon: Icon) -> std::result::Result<Self, Self::Error> {
WryTrayIcon::from_rgba(icon.rgba, icon.width, icon.height)
.map(Self)
.map_err(crate::icon_err)
}
}

#[derive(Debug, Clone)]
pub struct SystemTrayHandle<T: UserEvent> {
pub(crate) proxy: EventLoopProxy<super::Message<T>>,
}

impl<T: UserEvent> TrayHandle for SystemTrayHandle<T> {
fn set_icon(&self, icon: TrayIcon) -> Result<()> {
fn set_icon(&self, icon: Icon) -> Result<()> {
self
.proxy
.send_event(Message::Tray(TrayMessage::UpdateIcon(icon)))
Expand Down
Loading

0 comments on commit 4ce8e22

Please sign in to comment.