浏览代码

feat(core): set macOS app icon in development (#4385)

Lucas Fernandes Nogueira 3 年之前
父节点
当前提交
307c2ebfb6

+ 7 - 0
.changes/dev-dock-icon.md

@@ -0,0 +1,7 @@
+---
+"tauri-codegen": patch
+"tauri-macros": patch
+"tauri": patch
+---
+
+Set the application icon in development mode on macOS.

+ 59 - 5
core/tauri-codegen/src/context.rs

@@ -192,21 +192,23 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   // handle default window icons for Windows targets
   // handle default window icons for Windows targets
   #[cfg(windows)]
   #[cfg(windows)]
   let default_window_icon = {
   let default_window_icon = {
-    let mut icon_path = find_icon(
+    let icon_path = find_icon(
       &config,
       &config,
       &config_parent,
       &config_parent,
       |i| i.ends_with(".ico"),
       |i| i.ends_with(".ico"),
       "icons/icon.ico",
       "icons/icon.ico",
     );
     );
-    if !icon_path.exists() {
-      icon_path = find_icon(
+    if icon_path.exists() {
+      ico_icon(&root, &out_dir, icon_path)?
+    } else {
+      let icon_path = find_icon(
         &config,
         &config,
         &config_parent,
         &config_parent,
         |i| i.ends_with(".png"),
         |i| i.ends_with(".png"),
         "icons/icon.png",
         "icons/icon.png",
       );
       );
+      png_icon(&root, &out_dir, icon_path)?
     }
     }
-    ico_icon(&root, &out_dir, icon_path)?
   };
   };
   #[cfg(target_os = "linux")]
   #[cfg(target_os = "linux")]
   let default_window_icon = {
   let default_window_icon = {
@@ -221,6 +223,29 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   #[cfg(not(any(windows, target_os = "linux")))]
   #[cfg(not(any(windows, target_os = "linux")))]
   let default_window_icon = quote!(None);
   let default_window_icon = quote!(None);
 
 
+  #[cfg(target_os = "macos")]
+  let app_icon = if dev {
+    let mut icon_path = find_icon(
+      &config,
+      &config_parent,
+      |i| i.ends_with(".icns"),
+      "icons/icon.png",
+    );
+    if !icon_path.exists() {
+      icon_path = find_icon(
+        &config,
+        &config_parent,
+        |i| i.ends_with(".png"),
+        "icons/icon.png",
+      );
+    }
+    raw_icon(&out_dir, icon_path)?
+  } else {
+    quote!(None)
+  };
+  #[cfg(not(target_os = "macos"))]
+  let app_icon = quote!(None);
+
   let package_name = if let Some(product_name) = &config.package.product_name {
   let package_name = if let Some(product_name) = &config.package.product_name {
     quote!(#product_name.to_string())
     quote!(#product_name.to_string())
   } else {
   } else {
@@ -353,6 +378,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
     #config,
     #config,
     ::std::sync::Arc::new(#assets),
     ::std::sync::Arc::new(#assets),
     #default_window_icon,
     #default_window_icon,
+    #app_icon,
     #system_tray_icon,
     #system_tray_icon,
     #package_info,
     #package_info,
     #info_plist,
     #info_plist,
@@ -403,6 +429,35 @@ fn ico_icon<P: AsRef<Path>>(
   Ok(icon)
   Ok(icon)
 }
 }
 
 
+#[cfg(target_os = "macos")]
+fn raw_icon<P: AsRef<Path>>(out_dir: &Path, path: P) -> Result<TokenStream, EmbeddedAssetsError> {
+  use std::fs::File;
+  use std::io::Write;
+
+  let path = path.as_ref();
+  let bytes = std::fs::read(&path)
+    .unwrap_or_else(|_| panic!("failed to read icon {}", path.display()))
+    .to_vec();
+
+  let out_path = out_dir.join(path.file_name().unwrap());
+  let mut out_file = File::create(&out_path).map_err(|error| EmbeddedAssetsError::AssetWrite {
+    path: out_path.clone(),
+    error,
+  })?;
+
+  out_file
+    .write_all(&bytes)
+    .map_err(|error| EmbeddedAssetsError::AssetWrite {
+      path: path.to_owned(),
+      error,
+    })?;
+
+  let out_path = out_path.display().to_string();
+
+  let icon = quote!(Some(include_bytes!(#out_path).to_vec()));
+  Ok(icon)
+}
+
 fn png_icon<P: AsRef<Path>>(
 fn png_icon<P: AsRef<Path>>(
   root: &TokenStream,
   root: &TokenStream,
   out_dir: &Path,
   out_dir: &Path,
@@ -445,7 +500,6 @@ fn png_icon<P: AsRef<Path>>(
   Ok(icon)
   Ok(icon)
 }
 }
 
 
-#[cfg(any(windows, target_os = "linux"))]
 fn find_icon<F: Fn(&&String) -> bool>(
 fn find_icon<F: Fn(&&String) -> bool>(
   config: &Config,
   config: &Config,
   config_parent: &Path,
   config_parent: &Path,

+ 23 - 1
core/tauri/src/app.rs

@@ -1490,7 +1490,29 @@ fn on_event_loop_event<R: Runtime, F: FnMut(&AppHandle<R>, RunEvent) + 'static>(
       label,
       label,
       event: event.into(),
       event: event.into(),
     },
     },
-    RuntimeRunEvent::Ready => RunEvent::Ready,
+    RuntimeRunEvent::Ready => {
+      // set the app icon in development
+      #[cfg(all(dev, target_os = "macos"))]
+      unsafe {
+        use cocoa::{
+          appkit::NSImage,
+          base::{id, nil},
+          foundation::NSData,
+        };
+        use objc::*;
+        if let Some(icon) = app_handle.manager.inner.app_icon.clone() {
+          let ns_app: id = msg_send![class!(NSApplication), sharedApplication];
+          let data = NSData::dataWithBytes_length_(
+            nil,
+            icon.as_ptr() as *const std::os::raw::c_void,
+            icon.len() as u64,
+          );
+          let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data);
+          let _: () = msg_send![ns_app, setApplicationIconImage: app_icon];
+        }
+      }
+      RunEvent::Ready
+    }
     RuntimeRunEvent::Resumed => RunEvent::Resumed,
     RuntimeRunEvent::Resumed => RunEvent::Resumed,
     RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,
     RuntimeRunEvent::MainEventsCleared => RunEvent::MainEventsCleared,
     RuntimeRunEvent::UserEvent(t) => t.into(),
     RuntimeRunEvent::UserEvent(t) => t.into(),

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

@@ -445,6 +445,7 @@ pub struct Context<A: Assets> {
   pub(crate) config: Config,
   pub(crate) config: Config,
   pub(crate) assets: Arc<A>,
   pub(crate) assets: Arc<A>,
   pub(crate) default_window_icon: Option<Icon>,
   pub(crate) default_window_icon: Option<Icon>,
+  pub(crate) app_icon: Option<Vec<u8>>,
   pub(crate) system_tray_icon: Option<Icon>,
   pub(crate) system_tray_icon: Option<Icon>,
   pub(crate) package_info: PackageInfo,
   pub(crate) package_info: PackageInfo,
   pub(crate) _info_plist: (),
   pub(crate) _info_plist: (),
@@ -458,6 +459,7 @@ impl<A: Assets> fmt::Debug for Context<A> {
     let mut d = f.debug_struct("Context");
     let mut d = f.debug_struct("Context");
     d.field("config", &self.config)
     d.field("config", &self.config)
       .field("default_window_icon", &self.default_window_icon)
       .field("default_window_icon", &self.default_window_icon)
+      .field("app_icon", &self.app_icon)
       .field("system_tray_icon", &self.system_tray_icon)
       .field("system_tray_icon", &self.system_tray_icon)
       .field("package_info", &self.package_info)
       .field("package_info", &self.package_info)
       .field("pattern", &self.pattern);
       .field("pattern", &self.pattern);
@@ -548,6 +550,7 @@ impl<A: Assets> Context<A> {
     config: Config,
     config: Config,
     assets: Arc<A>,
     assets: Arc<A>,
     default_window_icon: Option<Icon>,
     default_window_icon: Option<Icon>,
+    app_icon: Option<Vec<u8>>,
     system_tray_icon: Option<Icon>,
     system_tray_icon: Option<Icon>,
     package_info: PackageInfo,
     package_info: PackageInfo,
     info_plist: (),
     info_plist: (),
@@ -558,6 +561,7 @@ impl<A: Assets> Context<A> {
       config,
       config,
       assets,
       assets,
       default_window_icon,
       default_window_icon,
+      app_icon,
       system_tray_icon,
       system_tray_icon,
       package_info,
       package_info,
       _info_plist: info_plist,
       _info_plist: info_plist,

+ 3 - 0
core/tauri/src/manager.rs

@@ -206,6 +206,7 @@ pub struct InnerWindowManager<R: Runtime> {
   config: Arc<Config>,
   config: Arc<Config>,
   assets: Arc<dyn Assets>,
   assets: Arc<dyn Assets>,
   default_window_icon: Option<Icon>,
   default_window_icon: Option<Icon>,
+  pub(crate) app_icon: Option<Vec<u8>>,
 
 
   package_info: PackageInfo,
   package_info: PackageInfo,
   /// The webview protocols protocols available to all windows.
   /// The webview protocols protocols available to all windows.
@@ -231,6 +232,7 @@ impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
       .field("state", &self.state)
       .field("state", &self.state)
       .field("config", &self.config)
       .field("config", &self.config)
       .field("default_window_icon", &self.default_window_icon)
       .field("default_window_icon", &self.default_window_icon)
+      .field("app_icon", &self.app_icon)
       .field("package_info", &self.package_info)
       .field("package_info", &self.package_info)
       .field("menu", &self.menu)
       .field("menu", &self.menu)
       .field("pattern", &self.pattern)
       .field("pattern", &self.pattern)
@@ -303,6 +305,7 @@ impl<R: Runtime> WindowManager<R> {
         config: Arc::new(context.config),
         config: Arc::new(context.config),
         assets: context.assets,
         assets: context.assets,
         default_window_icon: context.default_window_icon,
         default_window_icon: context.default_window_icon,
+        app_icon: context.app_icon,
         package_info: context.package_info,
         package_info: context.package_info,
         pattern: context.pattern,
         pattern: context.pattern,
         uri_scheme_protocols,
         uri_scheme_protocols,

+ 1 - 0
core/tauri/src/test/mod.rs

@@ -67,6 +67,7 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
     },
     },
     assets: Arc::new(assets),
     assets: Arc::new(assets),
     default_window_icon: None,
     default_window_icon: None,
+    app_icon: None,
     system_tray_icon: None,
     system_tray_icon: None,
     package_info: crate::PackageInfo {
     package_info: crate::PackageInfo {
       name: "test".into(),
       name: "test".into(),