Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): use Icon for tray icons #4342

Merged
merged 6 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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