Sfoglia il codice sorgente

refactor(core): move `png` and `ico` behind Cargo features (#3588)

Lucas Fernandes Nogueira 3 anni fa
parent
commit
8c9358725a

+ 6 - 0
.changes/icon-compile-time.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch
+"tauri-codegen": patch
+---
+
+Parse window icons at compile time.

+ 7 - 0
.changes/icon-feature.md

@@ -0,0 +1,7 @@
+---
+"tauri": patch
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+**Breaking change:** Move `ico` and `png` parsing behind `icon-ico` and `icon-png` Cargo features.

+ 6 - 0
core/tauri-codegen/Cargo.toml

@@ -27,6 +27,12 @@ zstd = { version = "0.10", optional = true }
 regex = { version = "1", optional = true }
 uuid = { version = "0.8", features = [ "v4" ] }
 
+[target."cfg(windows)".dependencies]
+ico = "0.1"
+
+[target."cfg(not(windows))".dependencies]
+png = "0.16"
+
 [features]
 default = [ "compression" ]
 compression = [ "zstd", "tauri-utils/compression" ]

+ 52 - 9
core/tauri-codegen/src/context.rs

@@ -165,25 +165,28 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   };
 
   // handle default window icons for Windows targets
-  let default_window_icon = if cfg!(windows) {
+  #[cfg(windows)]
+  let default_window_icon = {
     let icon_path = find_icon(
       &config,
       &config_parent,
       |i| i.ends_with(".ico"),
       "icons/icon.ico",
     );
-    quote!(Some(include_bytes!(#icon_path).to_vec()))
-  } else if cfg!(target_os = "linux") {
+    ico_icon(&root, icon_path)
+  };
+  #[cfg(target_os = "linux")]
+  let default_window_icon = {
     let icon_path = find_icon(
       &config,
       &config_parent,
       |i| i.ends_with(".png"),
       "icons/icon.png",
     );
-    quote!(Some(include_bytes!(#icon_path).to_vec()))
-  } else {
-    quote!(None)
+    png_icon(&root, icon_path)
   };
+  #[cfg(not(any(windows, target_os = "linux")))]
+  let default_window_icon = quote!(None);
 
   let package_name = if let Some(product_name) = &config.package.product_name {
     quote!(#product_name.to_string())
@@ -213,12 +216,12 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
         .join(system_tray_icon_path)
         .display()
         .to_string();
-      quote!(Some(#root::Icon::File(::std::path::PathBuf::from(#system_tray_icon_path))))
+      quote!(Some(#root::TrayIcon::File(::std::path::PathBuf::from(#system_tray_icon_path))))
     } else {
       let system_tray_icon_file_path = system_tray_icon_path.to_string_lossy().to_string();
       quote!(
         Some(
-          #root::Icon::File(
+          #root::TrayIcon::File(
             #root::api::path::resolve_path(
               &#config,
               &#package_info,
@@ -242,7 +245,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
       .join(system_tray_icon_path)
       .display()
       .to_string();
-    quote!(Some(#root::Icon::Raw(include_bytes!(#system_tray_icon_path).to_vec())))
+    quote!(Some(#root::TrayIcon::Raw(include_bytes!(#system_tray_icon_path).to_vec())))
   } else {
     quote!(None)
   };
@@ -337,6 +340,46 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   )))
 }
 
+#[cfg(windows)]
+fn ico_icon<P: AsRef<Path>>(root: &TokenStream, path: P) -> TokenStream {
+  let path = path.as_ref();
+  let bytes = std::fs::read(&path)
+    .unwrap_or_else(|_| panic!("failed to read window 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()));
+  let entry = &icon_dir.entries()[0];
+  let rgba = entry
+    .decode()
+    .unwrap_or_else(|_| panic!("failed to decode window icon {}", path.display()))
+    .rgba_data()
+    .to_vec();
+  let width = entry.width();
+  let height = entry.height();
+  let rgba_items = rgba.into_iter();
+  quote!(Some(#root::Icon::Rgba { rgba: vec![#(#rgba_items),*], width: #width, height: #height }))
+}
+
+#[cfg(not(windows))]
+fn png_icon<P: AsRef<Path>>(root: &TokenStream, path: P) -> TokenStream {
+  let path = path.as_ref();
+  let bytes = std::fs::read(&path)
+    .unwrap_or_else(|_| panic!("failed to read window icon {}", path.display()))
+    .to_vec();
+  let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
+  let (info, mut reader) = decoder
+    .read_info()
+    .unwrap_or_else(|_| panic!("failed to read window icon {}", path.display()));
+  let mut buffer: Vec<u8> = Vec::new();
+  while let Ok(Some(row)) = reader.next_row() {
+    buffer.extend(row);
+  }
+  let rgba_items = buffer.into_iter();
+  let width = info.width;
+  let height = info.height;
+  quote!(Some(#root::Icon::Rgba { rgba: vec![#(#rgba_items),*], width: #width, height: #height }))
+}
+
 fn find_icon<F: Fn(&&String) -> bool>(
   config: &Config,
   config_parent: &Path,

+ 0 - 3
core/tauri-runtime-wry/Cargo.toml

@@ -17,10 +17,8 @@ wry = { version = "0.13.3", default-features = false, features = [ "file-drop",
 tauri-runtime = { version = "0.3.2", path = "../tauri-runtime" }
 tauri-utils = { version = "1.0.0-rc.2", path = "../tauri-utils" }
 uuid = { version = "0.8.2", features = [ "v4" ] }
-infer = "0.4"
 
 [target."cfg(windows)".dependencies]
-ico = "0.1"
 webview2-com = "0.13.0"
 
   [target."cfg(windows)".dependencies.windows]
@@ -28,7 +26,6 @@ webview2-com = "0.13.0"
   features = [ "Win32_Foundation" ]
 
 [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
-png = "0.16"
 gtk = { version = "0.15", features = [ "v3_20" ] }
 
 [features]

+ 15 - 50
core/tauri-runtime-wry/src/lib.rs

@@ -16,8 +16,8 @@ use tauri_runtime::{
     dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
     DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
   },
-  ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Icon, Result,
-  RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType,
+  ClipboardManager, Dispatch, Error, ExitRequestedEventAction, GlobalShortcutManager, Result,
+  RunEvent, RunIteration, Runtime, RuntimeHandle, UserAttentionType, WindowIcon,
 };
 
 use tauri_runtime::window::MenuEvent;
@@ -56,7 +56,7 @@ use wry::{
       MenuItemAttributes as WryMenuItemAttributes, MenuType,
     },
     monitor::MonitorHandle,
-    window::{Fullscreen, Icon as WindowIcon, UserAttentionType as WryUserAttentionType},
+    window::{Fullscreen, Icon as WryWindowIcon, UserAttentionType as WryUserAttentionType},
   },
   http::{
     Request as WryHttpRequest, RequestParts as WryRequestParts, Response as WryHttpResponse,
@@ -84,7 +84,6 @@ use std::{
     HashMap, HashSet,
   },
   fmt,
-  fs::read,
   ops::Deref,
   path::PathBuf,
   sync::{
@@ -482,52 +481,18 @@ impl ClipboardManager for ClipboardManagerWrapper {
 }
 
 /// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`].
-pub struct WryIcon(WindowIcon);
+pub struct WryIcon(WryWindowIcon);
 
 fn icon_err<E: std::error::Error + Send + 'static>(e: E) -> Error {
   Error::InvalidIcon(Box::new(e))
 }
 
-impl TryFrom<Icon> for WryIcon {
+impl TryFrom<WindowIcon> for WryIcon {
   type Error = Error;
-  fn try_from(icon: Icon) -> std::result::Result<Self, Self::Error> {
-    let image_bytes = match icon {
-      Icon::File(path) => read(path).map_err(icon_err)?,
-      Icon::Raw(raw) => raw,
-      _ => unimplemented!(),
-    };
-    let extension = infer::get(&image_bytes)
-      .expect("could not determine icon extension")
-      .extension();
-    match extension {
-      #[cfg(windows)]
-      "ico" => {
-        let icon_dir = ico::IconDir::read(std::io::Cursor::new(image_bytes)).map_err(icon_err)?;
-        let entry = &icon_dir.entries()[0];
-        let icon = WindowIcon::from_rgba(
-          entry.decode().map_err(icon_err)?.rgba_data().to_vec(),
-          entry.width(),
-          entry.height(),
-        )
-        .map_err(icon_err)?;
-        Ok(Self(icon))
-      }
-      #[cfg(target_os = "linux")]
-      "png" => {
-        let decoder = png::Decoder::new(std::io::Cursor::new(image_bytes));
-        let (info, mut reader) = decoder.read_info().map_err(icon_err)?;
-        let mut buffer = Vec::new();
-        while let Ok(Some(row)) = reader.next_row() {
-          buffer.extend(row);
-        }
-        let icon = WindowIcon::from_rgba(buffer, info.width, info.height).map_err(icon_err)?;
-        Ok(Self(icon))
-      }
-      _ => panic!(
-        "image `{}` extension not supported; please file a Tauri feature request",
-        extension
-      ),
-    }
+  fn try_from(icon: WindowIcon) -> std::result::Result<Self, Self::Error> {
+    WryWindowIcon::from_rgba(icon.rgba, icon.width, icon.height)
+      .map(Self)
+      .map_err(icon_err)
   }
 }
 
@@ -851,7 +816,7 @@ impl WindowBuilder for WindowBuilderWrapper {
     self
   }
 
-  fn icon(mut self, icon: Icon) -> Result<Self> {
+  fn icon(mut self, icon: WindowIcon) -> Result<Self> {
     self.inner = self
       .inner
       .with_window_icon(Some(WryIcon::try_from(icon)?.0));
@@ -975,7 +940,7 @@ pub enum WindowMessage {
   SetPosition(Position),
   SetFullscreen(bool),
   SetFocus,
-  SetIcon(WindowIcon),
+  SetIcon(WryWindowIcon),
   SetSkipTaskbar(bool),
   DragWindow,
   UpdateMenuItem(u16, MenuUpdate),
@@ -1001,7 +966,7 @@ pub enum WebviewEvent {
 pub enum TrayMessage {
   UpdateItem(u16, MenuUpdate),
   UpdateMenu(SystemTrayMenu),
-  UpdateIcon(Icon),
+  UpdateIcon(TrayIcon),
   #[cfg(target_os = "macos")]
   UpdateIconAsTemplate(bool),
   Close,
@@ -1392,7 +1357,7 @@ impl Dispatch for WryDispatcher {
     )
   }
 
-  fn set_icon(&self, icon: Icon) -> Result<()> {
+  fn set_icon(&self, icon: WindowIcon) -> Result<()> {
     send_user_message(
       &self.context,
       Message::Window(
@@ -1790,7 +1755,7 @@ impl Runtime for Wry {
     let icon = system_tray
       .icon
       .expect("tray icon not set")
-      .into_tray_icon();
+      .into_platform_icon();
 
     let mut items = HashMap::new();
 
@@ -2208,7 +2173,7 @@ fn handle_user_message(
       }
       TrayMessage::UpdateIcon(icon) => {
         if let Some(tray) = &*tray_context.tray.lock().unwrap() {
-          tray.lock().unwrap().set_icon(icon.into_tray_icon());
+          tray.lock().unwrap().set_icon(icon.into_platform_icon());
         }
       }
       #[cfg(target_os = "macos")]

+ 2 - 2
core/tauri-runtime-wry/src/system_tray.rs

@@ -7,7 +7,7 @@ pub use tauri_runtime::{
     Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry,
     SystemTrayMenuItem, TrayHandle,
   },
-  Icon, SystemTrayEvent,
+  SystemTrayEvent, TrayIcon,
 };
 pub use wry::application::{
   event::TrayEvent,
@@ -41,7 +41,7 @@ pub struct SystemTrayHandle {
 }
 
 impl TrayHandle for SystemTrayHandle {
-  fn set_icon(&self, icon: Icon) -> Result<()> {
+  fn set_icon(&self, icon: TrayIcon) -> Result<()> {
     self
       .proxy
       .send_event(Message::Tray(TrayMessage::UpdateIcon(icon)))

+ 26 - 15
core/tauri-runtime/src/lib.rs

@@ -39,7 +39,7 @@ use crate::http::{
 #[non_exhaustive]
 #[derive(Debug, Default)]
 pub struct SystemTray {
-  pub icon: Option<Icon>,
+  pub icon: Option<TrayIcon>,
   pub menu: Option<menu::SystemTrayMenu>,
   #[cfg(target_os = "macos")]
   pub icon_as_template: bool,
@@ -56,9 +56,9 @@ impl SystemTray {
     self.menu.as_ref()
   }
 
-  /// Sets the tray icon. Must be a [`Icon::File`] on Linux and a [`Icon::Raw`] on Windows and macOS.
+  /// Sets the tray icon. Must be a [`TrayIcon::File`] on Linux and a [`TrayIcon::Raw`] on Windows and macOS.
   #[must_use]
-  pub fn with_icon(mut self, icon: Icon) -> Self {
+  pub fn with_icon(mut self, icon: TrayIcon) -> Self {
     self.icon.replace(icon);
     self
   }
@@ -145,25 +145,36 @@ pub enum Error {
 /// Result type.
 pub type Result<T> = std::result::Result<T, Error>;
 
+/// Window icon.
+#[derive(Debug, Clone)]
+pub struct WindowIcon {
+  /// RGBA bytes of the icon.
+  pub rgba: Vec<u8>,
+  /// Icon width.
+  pub width: u32,
+  /// Icon height.
+  pub height: u32,
+}
+
 /// A icon definition.
 #[derive(Debug, Clone)]
 #[non_exhaustive]
-pub enum Icon {
+pub enum TrayIcon {
   /// Icon from file path.
   File(PathBuf),
-  /// Icon from raw bytes.
+  /// Icon from raw file bytes.
   Raw(Vec<u8>),
 }
 
-impl Icon {
+impl TrayIcon {
   /// Converts the icon to a the expected system tray format.
   /// We expect the code that passes the Icon enum to have already checked the platform.
   #[cfg(target_os = "linux")]
-  pub fn into_tray_icon(self) -> PathBuf {
+  pub fn into_platform_icon(self) -> PathBuf {
     match self {
-      Icon::File(path) => path,
-      Icon::Raw(_) => {
-        panic!("linux requires the system menu icon to be a file path, not bytes.")
+      Self::File(path) => path,
+      Self::Raw(_) => {
+        panic!("linux requires the system menu icon to be a file path, not raw bytes.")
       }
     }
   }
@@ -171,11 +182,11 @@ impl Icon {
   /// Converts the icon to a the expected system tray format.
   /// We expect the code that passes the Icon enum to have already checked the platform.
   #[cfg(not(target_os = "linux"))]
-  pub fn into_tray_icon(self) -> Vec<u8> {
+  pub fn into_platform_icon(self) -> Vec<u8> {
     match self {
-      Icon::Raw(bytes) => bytes,
-      Icon::File(_) => {
-        panic!("non-linux system menu icons must be bytes, not a file path.")
+      Self::Raw(r) => r,
+      Self::File(_) => {
+        panic!("non-linux system menu icons must be raw bytes, not a file path.")
       }
     }
   }
@@ -522,7 +533,7 @@ pub trait Dispatch: Debug + Clone + Send + Sync + Sized + 'static {
   fn set_focus(&self) -> crate::Result<()>;
 
   /// Updates the window icon.
-  fn set_icon(&self, icon: Icon) -> crate::Result<()>;
+  fn set_icon(&self, icon: WindowIcon) -> crate::Result<()>;
 
   /// Whether to show the window icon in the task bar or not.
   fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()>;

+ 1 - 1
core/tauri-runtime/src/menu.rs

@@ -147,7 +147,7 @@ pub enum MenuUpdate {
 }
 
 pub trait TrayHandle: fmt::Debug + Clone + Send + Sync {
-  fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
+  fn set_icon(&self, icon: crate::TrayIcon) -> crate::Result<()>;
   fn set_menu(&self, menu: crate::menu::SystemTrayMenu) -> crate::Result<()>;
   fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
   #[cfg(target_os = "macos")]

+ 2 - 2
core/tauri-runtime/src/webview.rs

@@ -4,7 +4,7 @@
 
 //! Items specific to the [`Runtime`](crate::Runtime)'s webview.
 
-use crate::{menu::Menu, window::DetachedWindow, Icon};
+use crate::{menu::Menu, window::DetachedWindow, WindowIcon};
 
 use tauri_utils::config::{WindowConfig, WindowUrl};
 
@@ -150,7 +150,7 @@ pub trait WindowBuilder: WindowBuilderBase {
   fn always_on_top(self, always_on_top: bool) -> Self;
 
   /// Sets the window icon.
-  fn icon(self, icon: Icon) -> crate::Result<Self>;
+  fn icon(self, icon: WindowIcon) -> crate::Result<Self>;
 
   /// Sets whether or not the window icon should be added to the taskbar.
   #[must_use]

+ 5 - 0
core/tauri/Cargo.toml

@@ -89,6 +89,9 @@ regex = { version = "1.5", optional = true }
 glob = "0.3"
 data-url = { version = "0.1", optional = true }
 serialize-to-javascript = "=0.1.1"
+infer = { version = "0.4", optional = true }
+png = { version = "0.16", optional = true }
+ico = { version = "0.1", optional = true }
 
 [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
 gtk = { version = "0.15", features = [ "v3_20" ] }
@@ -249,6 +252,8 @@ window-set-skip-taskbar = [ ]
 window-start-dragging = [ ]
 window-print = [ ]
 config-json5 = [ "tauri-macros/config-json5" ]
+icon-ico = [ "infer", "ico" ]
+icon-png = [ "infer", "png" ]
 
 [[example]]
 name = "commands"

+ 3 - 3
core/tauri/src/app.rs

@@ -42,7 +42,7 @@ use crate::runtime::menu::{Menu, MenuId, MenuIdRef};
 
 use crate::runtime::RuntimeHandle;
 #[cfg(feature = "system-tray")]
-use crate::runtime::{Icon, SystemTrayEvent as RuntimeSystemTrayEvent};
+use crate::runtime::{SystemTrayEvent as RuntimeSystemTrayEvent, TrayIcon};
 
 #[cfg(feature = "updater")]
 use crate::updater;
@@ -1075,7 +1075,7 @@ impl<R: Runtime> Builder<R> {
       if self.system_tray.is_some() {
         use std::io::{Error, ErrorKind};
         #[cfg(target_os = "linux")]
-        if let Some(Icon::Raw(_)) = icon {
+        if let Some(TrayIcon::Raw(..)) = icon {
           return Err(crate::Error::InvalidIcon(Box::new(Error::new(
             ErrorKind::InvalidInput,
             "system tray icons on linux must be a file path",
@@ -1083,7 +1083,7 @@ impl<R: Runtime> Builder<R> {
         }
 
         #[cfg(not(target_os = "linux"))]
-        if let Some(Icon::File(_)) = icon {
+        if let Some(TrayIcon::File(_)) = icon {
           return Err(crate::Error::InvalidIcon(Box::new(Error::new(
             ErrorKind::InvalidInput,
             "system tray icons on non-linux platforms must be the raw bytes",

+ 3 - 3
core/tauri/src/app/tray.rs

@@ -7,7 +7,7 @@ pub use crate::runtime::{
     MenuHash, MenuId, MenuIdRef, MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, TrayHandle,
   },
   window::dpi::{PhysicalPosition, PhysicalSize},
-  Icon, Runtime, SystemTray,
+  Runtime, SystemTray, TrayIcon,
 };
 
 use tauri_macros::default_runtime;
@@ -127,8 +127,8 @@ impl<R: Runtime> SystemTrayHandle<R> {
     panic!("item id not found")
   }
 
-  /// Updates the tray icon. Must be a [`Icon::File`] on Linux and a [`Icon::Raw`] on Windows and macOS.
-  pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
+  /// Updates the tray icon. Must be a [`TrayIcon::File`] on Linux and a [`TrayIcon::Raw`] on Windows and macOS.
+  pub fn set_icon(&self, icon: TrayIcon) -> crate::Result<()> {
     self.inner.set_icon(icon).map_err(Into::into)
   }
 

+ 20 - 5
core/tauri/src/endpoints/window.rs

@@ -11,26 +11,41 @@ use crate::{
     Runtime, UserAttentionType,
   },
   utils::config::WindowConfig,
-  Manager,
+  Icon, Manager,
 };
 use serde::Deserialize;
 use tauri_macros::{module_command_handler, CommandModule};
 
-use crate::runtime::Icon;
-use std::path::PathBuf;
-
 #[derive(Deserialize)]
 #[serde(untagged)]
 pub enum IconDto {
-  File(PathBuf),
+  #[cfg(any(feature = "icon-png", feature = "icon-ico"))]
+  File(std::path::PathBuf),
+  #[cfg(any(feature = "icon-png", feature = "icon-ico"))]
   Raw(Vec<u8>),
+  Rgba {
+    rgba: Vec<u8>,
+    width: u32,
+    height: u32,
+  },
 }
 
 impl From<IconDto> for Icon {
   fn from(icon: IconDto) -> Self {
     match icon {
+      #[cfg(any(feature = "icon-png", feature = "icon-ico"))]
       IconDto::File(path) => Self::File(path),
+      #[cfg(any(feature = "icon-png", feature = "icon-ico"))]
       IconDto::Raw(raw) => Self::Raw(raw),
+      IconDto::Rgba {
+        rgba,
+        width,
+        height,
+      } => Self::Rgba {
+        rgba,
+        width,
+        height,
+      },
     }
   }
 }

+ 4 - 0
core/tauri/src/error.rs

@@ -110,6 +110,10 @@ pub enum Error {
   /// Invalid glob pattern.
   #[error("invalid glob pattern: {0}")]
   GlobPattern(#[from] glob::PatternError),
+  /// Error decoding PNG image.
+  #[cfg(feature = "icon-png")]
+  #[error("failed to decode PNG: {0}")]
+  PngDecode(#[from] png::DecodingError),
 }
 
 pub(crate) fn into_anyhow<T: std::fmt::Display>(err: T) -> anyhow::Error {

+ 97 - 10
core/tauri/src/lib.rs

@@ -216,7 +216,7 @@ pub use {
       dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel, Position, Size},
       WindowEvent,
     },
-    ClipboardManager, GlobalShortcutManager, Icon, RunIteration, Runtime, UserAttentionType,
+    ClipboardManager, GlobalShortcutManager, RunIteration, Runtime, TrayIcon, UserAttentionType,
   },
   self::state::{State, StateManager},
   self::utils::{
@@ -264,6 +264,93 @@ macro_rules! tauri_build_context {
 
 pub use pattern::Pattern;
 
+/// A icon definition.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum Icon {
+  /// Icon from file path.
+  #[cfg(any(feature = "icon-ico", feature = "icon-png"))]
+  #[cfg_attr(doc_cfg, doc(cfg(any(feature = "icon-ico", feature = "icon-png"))))]
+  File(std::path::PathBuf),
+  /// Icon from raw RGBA bytes. Width and height is parsed at runtime.
+  #[cfg(any(feature = "icon-ico", feature = "icon-png"))]
+  #[cfg_attr(doc_cfg, doc(cfg(any(feature = "icon-ico", feature = "icon-png"))))]
+  Raw(Vec<u8>),
+  /// Icon from raw RGBA bytes.
+  Rgba {
+    /// RGBA byes of the icon image.
+    rgba: Vec<u8>,
+    /// Icon width.
+    width: u32,
+    /// Icon height.
+    height: u32,
+  },
+}
+
+impl TryFrom<Icon> for runtime::WindowIcon {
+  type Error = Error;
+
+  fn try_from(icon: Icon) -> Result<Self> {
+    #[allow(irrefutable_let_patterns)]
+    if let Icon::Rgba {
+      rgba,
+      width,
+      height,
+    } = icon
+    {
+      Ok(Self {
+        rgba,
+        width,
+        height,
+      })
+    } else {
+      #[cfg(not(any(feature = "icon-ico", feature = "icon-png")))]
+      panic!("unexpected Icon variant");
+      #[cfg(any(feature = "icon-ico", feature = "icon-png"))]
+      {
+        let bytes = match icon {
+          Icon::File(p) => std::fs::read(p)?,
+          Icon::Raw(r) => r,
+          Icon::Rgba { .. } => unreachable!(),
+        };
+        let extension = infer::get(&bytes)
+          .expect("could not determine icon extension")
+          .extension();
+        match extension {
+        #[cfg(feature = "icon-ico")]
+        "ico" => {
+          let icon_dir = ico::IconDir::read(std::io::Cursor::new(bytes))?;
+          let entry = &icon_dir.entries()[0];
+          Ok(Self {
+            rgba: entry.decode()?.rgba_data().to_vec(),
+            width: entry.width(),
+            height: entry.height(),
+          })
+        }
+        #[cfg(feature = "icon-png")]
+        "png" => {
+          let decoder = png::Decoder::new(std::io::Cursor::new(bytes));
+          let (info, mut reader) = decoder.read_info()?;
+          let mut buffer = Vec::new();
+          while let Ok(Some(row)) = reader.next_row() {
+            buffer.extend(row);
+          }
+          Ok(Self {
+            rgba: buffer,
+            width: info.width,
+            height: info.height,
+          })
+        }
+        _ => panic!(
+          "image `{}` extension not supported; please file a Tauri feature request. `png` or `ico` icons are supported with the `icon-png` and `icon-ico` feature flags",
+          extension
+        ),
+      }
+      }
+    }
+  }
+}
+
 /// User supplied data required inside of a Tauri application.
 ///
 /// # Stability
@@ -272,8 +359,8 @@ pub use pattern::Pattern;
 pub struct Context<A: Assets> {
   pub(crate) config: Config,
   pub(crate) assets: Arc<A>,
-  pub(crate) default_window_icon: Option<Vec<u8>>,
-  pub(crate) system_tray_icon: Option<Icon>,
+  pub(crate) default_window_icon: Option<Icon>,
+  pub(crate) system_tray_icon: Option<TrayIcon>,
   pub(crate) package_info: PackageInfo,
   pub(crate) _info_plist: (),
   pub(crate) pattern: Pattern,
@@ -322,25 +409,25 @@ impl<A: Assets> Context<A> {
 
   /// The default window icon Tauri should use when creating windows.
   #[inline(always)]
-  pub fn default_window_icon(&self) -> Option<&[u8]> {
-    self.default_window_icon.as_deref()
+  pub fn default_window_icon(&self) -> Option<&Icon> {
+    self.default_window_icon.as_ref()
   }
 
   /// A mutable reference to the default window icon Tauri should use when creating windows.
   #[inline(always)]
-  pub fn default_window_icon_mut(&mut self) -> &mut Option<Vec<u8>> {
+  pub fn default_window_icon_mut(&mut self) -> &mut Option<Icon> {
     &mut self.default_window_icon
   }
 
   /// The icon to use on the system tray UI.
   #[inline(always)]
-  pub fn system_tray_icon(&self) -> Option<&Icon> {
+  pub fn system_tray_icon(&self) -> Option<&TrayIcon> {
     self.system_tray_icon.as_ref()
   }
 
   /// A mutable reference to the icon to use on the system tray UI.
   #[inline(always)]
-  pub fn system_tray_icon_mut(&mut self) -> &mut Option<Icon> {
+  pub fn system_tray_icon_mut(&mut self) -> &mut Option<TrayIcon> {
     &mut self.system_tray_icon
   }
 
@@ -375,8 +462,8 @@ impl<A: Assets> Context<A> {
   pub fn new(
     config: Config,
     assets: Arc<A>,
-    default_window_icon: Option<Vec<u8>>,
-    system_tray_icon: Option<Icon>,
+    default_window_icon: Option<Icon>,
+    system_tray_icon: Option<TrayIcon>,
     package_info: PackageInfo,
     info_plist: (),
     pattern: Pattern,

+ 7 - 6
core/tauri/src/manager.rs

@@ -41,14 +41,14 @@ use crate::{
     },
     webview::{FileDropEvent, FileDropHandler, WebviewIpcHandler, WindowBuilder},
     window::{dpi::PhysicalSize, DetachedWindow, PendingWindow, WindowEvent},
-    Icon, Runtime,
+    Runtime,
   },
   utils::{
     assets::Assets,
     config::{AppUrl, Config, WindowUrl},
     PackageInfo,
   },
-  Context, Invoke, Manager, Pattern, Scopes, StateManager, Window,
+  Context, Icon, Invoke, Manager, Pattern, Scopes, StateManager, Window,
 };
 
 #[cfg(any(target_os = "linux", target_os = "windows"))]
@@ -195,7 +195,7 @@ pub struct InnerWindowManager<R: Runtime> {
 
   config: Arc<Config>,
   assets: Arc<dyn Assets>,
-  default_window_icon: Option<Vec<u8>>,
+  default_window_icon: Option<Icon>,
 
   package_info: PackageInfo,
   /// The webview protocols protocols available to all windows.
@@ -445,9 +445,10 @@ impl<R: Runtime> WindowManager<R> {
     pending.webview_attributes = webview_attributes;
 
     if !pending.window_builder.has_icon() {
-      if let Some(default_window_icon) = &self.inner.default_window_icon {
-        let icon = Icon::Raw(default_window_icon.clone());
-        pending.window_builder = pending.window_builder.icon(icon)?;
+      if let Some(default_window_icon) = self.inner.default_window_icon.clone() {
+        pending.window_builder = pending
+          .window_builder
+          .icon(default_window_icon.try_into()?)?;
       }
     }
 

+ 12 - 7
core/tauri/src/test/mock_runtime.rs

@@ -5,18 +5,21 @@
 #![allow(dead_code)]
 
 use tauri_runtime::{
-  menu::{Menu, MenuUpdate, SystemTrayMenu, TrayHandle},
+  menu::{Menu, MenuUpdate},
   monitor::Monitor,
   webview::{WindowBuilder, WindowBuilderBase},
   window::{
     dpi::{PhysicalPosition, PhysicalSize, Position, Size},
     DetachedWindow, MenuEvent, PendingWindow, WindowEvent,
   },
-  ClipboardManager, Dispatch, GlobalShortcutManager, Icon, Result, RunEvent, Runtime,
-  RuntimeHandle, UserAttentionType,
+  ClipboardManager, Dispatch, GlobalShortcutManager, Result, RunEvent, Runtime, RuntimeHandle,
+  UserAttentionType, WindowIcon,
 };
 #[cfg(feature = "system-tray")]
-use tauri_runtime::{SystemTray, SystemTrayEvent};
+use tauri_runtime::{
+  menu::{SystemTrayMenu, TrayHandle},
+  SystemTray, SystemTrayEvent, TrayIcon,
+};
 use tauri_utils::config::WindowConfig;
 use uuid::Uuid;
 
@@ -217,7 +220,7 @@ impl WindowBuilder for MockWindowBuilder {
     self
   }
 
-  fn icon(self, icon: Icon) -> Result<Self> {
+  fn icon(self, icon: WindowIcon) -> Result<Self> {
     Ok(self)
   }
 
@@ -442,7 +445,7 @@ impl Dispatch for MockDispatcher {
     Ok(())
   }
 
-  fn set_icon(&self, icon: Icon) -> Result<()> {
+  fn set_icon(&self, icon: WindowIcon) -> Result<()> {
     Ok(())
   }
 
@@ -463,13 +466,15 @@ impl Dispatch for MockDispatcher {
   }
 }
 
+#[cfg(feature = "system-tray")]
 #[derive(Debug, Clone)]
 pub struct MockTrayHandler {
   context: RuntimeContext,
 }
 
+#[cfg(feature = "system-tray")]
 impl TrayHandle for MockTrayHandler {
-  fn set_icon(&self, icon: Icon) -> Result<()> {
+  fn set_icon(&self, icon: TrayIcon) -> Result<()> {
     Ok(())
   }
   fn set_menu(&self, menu: SystemTrayMenu) -> Result<()> {

+ 8 - 4
core/tauri/src/window.rs

@@ -22,12 +22,12 @@ use crate::{
       dpi::{PhysicalPosition, PhysicalSize, Position, Size},
       DetachedWindow, JsEventListenerKey, PendingWindow, WindowEvent,
     },
-    Dispatch, Icon, Runtime, UserAttentionType,
+    Dispatch, Runtime, UserAttentionType,
   },
   sealed::ManagerBase,
   sealed::RuntimeOrDispatch,
   utils::config::WindowUrl,
-  Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, PageLoadPayload,
+  Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager, PageLoadPayload,
 };
 
 use serde::Serialize;
@@ -268,7 +268,7 @@ impl<R: Runtime> WindowBuilder<R> {
 
   /// Sets the window icon.
   pub fn icon(mut self, icon: Icon) -> crate::Result<Self> {
-    self.window_builder = self.window_builder.icon(icon)?;
+    self.window_builder = self.window_builder.icon(icon.try_into()?)?;
     Ok(self)
   }
 
@@ -999,7 +999,11 @@ impl<R: Runtime> Window<R> {
 
   /// Sets this window' icon.
   pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
-    self.window.dispatcher.set_icon(icon).map_err(Into::into)
+    self
+      .window
+      .dispatcher
+      .set_icon(icon.try_into()?)
+      .map_err(Into::into)
   }
 
   /// Whether to show the window icon in the task bar or not.

+ 2 - 3
core/tauri/tests/restart/Cargo.lock

@@ -2554,6 +2554,8 @@ version = "1.0.0-rc.2"
 dependencies = [
  "base64",
  "blake3",
+ "ico",
+ "png 0.16.8",
  "proc-macro2",
  "quote",
  "serde",
@@ -2600,9 +2602,6 @@ name = "tauri-runtime-wry"
 version = "0.3.2"
 dependencies = [
  "gtk",
- "ico",
- "infer",
- "png 0.16.8",
  "tauri-runtime",
  "tauri-utils",
  "uuid",

+ 5 - 3
examples/api/src-tauri/Cargo.lock

@@ -3319,7 +3319,9 @@ dependencies = [
  "glob",
  "gtk",
  "http",
+ "ico",
  "ignore",
+ "infer",
  "memchr",
  "minisign-verify",
  "notify-rust",
@@ -3328,6 +3330,7 @@ dependencies = [
  "os_info",
  "os_pipe",
  "percent-encoding",
+ "png 0.16.8",
  "rand 0.8.5",
  "raw-window-handle",
  "regex",
@@ -3371,6 +3374,8 @@ version = "1.0.0-rc.2"
 dependencies = [
  "base64",
  "blake3",
+ "ico",
+ "png 0.16.8",
  "proc-macro2",
  "quote",
  "regex",
@@ -3418,9 +3423,6 @@ name = "tauri-runtime-wry"
 version = "0.3.2"
 dependencies = [
  "gtk",
- "ico",
- "infer",
- "png 0.16.8",
  "tauri-runtime",
  "tauri-utils",
  "uuid",

+ 1 - 1
examples/api/src-tauri/Cargo.toml

@@ -12,7 +12,7 @@ tauri-build = { path = "../../../core/tauri-build", features = ["isolation"] }
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "isolation", "macos-private-api", "system-tray", "updater"] }
+tauri = { path = "../../../core/tauri", features = ["api-all", "cli", "icon-ico", "icon-png", "isolation", "macos-private-api", "system-tray", "updater"] }
 
 [features]
 default = [ "custom-protocol" ]

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

@@ -140,7 +140,7 @@ fn main() {
 
             app
               .tray_handle()
-              .set_icon(tauri::Icon::Raw(
+              .set_icon(tauri::TrayIcon::Raw(
                 include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(),
               ))
               .unwrap();
@@ -151,36 +151,36 @@ fn main() {
 
             app
               .tray_handle()
-              .set_icon(tauri::Icon::Raw(
-                include_bytes!("../../../.icons/tray_icon.png").to_vec(),
+              .set_icon(tauri::TrayIcon::Raw(
+                include_bytes!("../../../.icons/tray_icon_with.png").to_vec(),
               ))
               .unwrap();
           }
           #[cfg(target_os = "linux")]
           "icon_1" => app
             .tray_handle()
-            .set_icon(tauri::Icon::File(PathBuf::from(
+            .set_icon(tauri::TrayIcon::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(
+            .set_icon(tauri::TrayIcon::File(PathBuf::from(
               "../../../.icons/tray_icon.png",
             )))
             .unwrap(),
           #[cfg(target_os = "windows")]
           "icon_1" => app
             .tray_handle()
-            .set_icon(tauri::Icon::Raw(
+            .set_icon(tauri::TrayIcon::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(
+            .set_icon(tauri::TrayIcon::Raw(
               include_bytes!("../../../.icons/icon.ico").to_vec(),
             ))
             .unwrap(),

+ 7 - 0
tooling/api/src/window.ts

@@ -1022,6 +1022,13 @@ class WindowManager extends WebviewWindowHandle {
   /**
    * Sets the window icon.
    *
+   * Note that you need the `icon-ico` or `icon-png` Cargo features to use this API.
+   * To enable it, change your Cargo.toml file:
+   * ```toml
+   * [dependencies]
+   * tauri = { version = "...", features = ["...", "icon-png"] }
+   * ```
+   *
    * @param icon Icon bytes or path to the icon file.
    * @returns A promise indicating the success or failure of the operation.
    */