feat: update dependencies and refactor macOS icon handling

This commit is contained in:
2026-07-31 17:07:25 -05:00
parent 62a3526f33
commit 075a051f74
12 changed files with 124 additions and 49 deletions
Generated
+85 -8
View File
@@ -1405,10 +1405,12 @@ version = "0.1.0"
dependencies = [ dependencies = [
"arboard", "arboard",
"base64", "base64",
"cocoa",
"env_logger", "env_logger",
"log", "log",
"muda", "muda",
"objc2 0.6.4",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
"png 0.18.1", "png 0.18.1",
"rfd", "rfd",
"serde", "serde",
@@ -2556,10 +2558,10 @@ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"libc", "libc",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-core-data", "objc2-core-data 0.2.2",
"objc2-core-image", "objc2-core-image 0.2.2",
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
"objc2-quartz-core", "objc2-quartz-core 0.2.2",
] ]
[[package]] [[package]]
@@ -2570,10 +2572,17 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.1",
"block2 0.6.2", "block2 0.6.2",
"libc",
"objc2 0.6.4", "objc2 0.6.4",
"objc2-cloud-kit 0.3.2",
"objc2-core-data 0.3.2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "objc2-core-graphics",
"objc2-core-image 0.3.2",
"objc2-core-text",
"objc2-core-video",
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"objc2-quartz-core 0.3.2",
] ]
[[package]] [[package]]
@@ -2589,6 +2598,17 @@ dependencies = [
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
] ]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags 2.13.1",
"objc2 0.6.4",
"objc2-foundation 0.3.2",
]
[[package]] [[package]]
name = "objc2-contacts" name = "objc2-contacts"
version = "0.2.2" version = "0.2.2"
@@ -2612,6 +2632,17 @@ dependencies = [
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
] ]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"bitflags 2.13.1",
"objc2 0.6.4",
"objc2-foundation 0.3.2",
]
[[package]] [[package]]
name = "objc2-core-foundation" name = "objc2-core-foundation"
version = "0.3.2" version = "0.3.2"
@@ -2648,6 +2679,16 @@ dependencies = [
"objc2-metal", "objc2-metal",
] ]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2 0.6.4",
"objc2-foundation 0.3.2",
]
[[package]] [[package]]
name = "objc2-core-location" name = "objc2-core-location"
version = "0.2.2" version = "0.2.2"
@@ -2660,6 +2701,31 @@ dependencies = [
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
] ]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags 2.13.1",
"objc2 0.6.4",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-core-video"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
dependencies = [
"bitflags 2.13.1",
"objc2 0.6.4",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-io-surface",
]
[[package]] [[package]]
name = "objc2-encode" name = "objc2-encode"
version = "4.1.0" version = "4.1.0"
@@ -2740,6 +2806,17 @@ dependencies = [
"objc2-metal", "objc2-metal",
] ]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags 2.13.1",
"objc2 0.6.4",
"objc2-foundation 0.3.2",
]
[[package]] [[package]]
name = "objc2-symbols" name = "objc2-symbols"
version = "0.2.2" version = "0.2.2"
@@ -2759,13 +2836,13 @@ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.1",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-cloud-kit", "objc2-cloud-kit 0.2.2",
"objc2-core-data", "objc2-core-data 0.2.2",
"objc2-core-image", "objc2-core-image 0.2.2",
"objc2-core-location", "objc2-core-location",
"objc2-foundation 0.2.2", "objc2-foundation 0.2.2",
"objc2-link-presentation", "objc2-link-presentation",
"objc2-quartz-core", "objc2-quartz-core 0.2.2",
"objc2-symbols", "objc2-symbols",
"objc2-uniform-type-identifiers", "objc2-uniform-type-identifiers",
"objc2-user-notifications", "objc2-user-notifications",
+3 -1
View File
@@ -25,4 +25,6 @@ base64 = "0.22"
url = { workspace = true } url = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.26" objc2 = "0.6"
objc2-app-kit = "0.3"
objc2-foundation = "0.3"
+19 -23
View File
@@ -951,24 +951,21 @@ fn find_app_icon_file(app_path: &std::path::Path) -> Option<PathBuf> {
fn apply_dock_icon(icon: &DecodedIcon) { fn apply_dock_icon(icon: &DecodedIcon) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
use cocoa::appkit::{NSApplication, NSImage}; use objc2::AnyThread;
use cocoa::base::{id, nil}; use objc2_app_kit::{NSApplication, NSImage};
use cocoa::foundation::NSData; use objc2_foundation::{MainThreadMarker, NSData};
let img_bytes = if !icon.rgba.is_empty() { let img_bytes = if !icon.rgba.is_empty() {
rgba_to_png(&icon.rgba, icon.width, icon.height).unwrap_or_else(|| icon.raw.clone()) rgba_to_png(&icon.rgba, icon.width, icon.height).unwrap_or_else(|| icon.raw.clone())
} else { } else {
icon.raw.clone() icon.raw.clone()
}; };
if let Some(mtm) = MainThreadMarker::new() {
let data = NSData::with_bytes(&img_bytes);
if let Some(image) = NSImage::initWithData(NSImage::alloc(), &data) {
let app = NSApplication::sharedApplication(mtm);
unsafe { unsafe {
let data = NSData::dataWithBytes_length_( app.setApplicationIconImage(Some(&image));
nil, }
img_bytes.as_ptr() as *const std::ffi::c_void,
img_bytes.len() as u64,
);
let image: id = NSImage::initWithData_(NSImage::alloc(nil), data);
if image != nil {
let app = NSApplication::sharedApplication(nil);
let _: () = app.setApplicationIconImage_(image);
} }
} }
} }
@@ -1035,15 +1032,16 @@ fn detect_and_apply_icon(app_path: &std::path::Path, st: &mut AppState) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn apply_dock_icon_from_path(path: &std::path::Path) { fn apply_dock_icon_from_path(path: &std::path::Path) {
use cocoa::appkit::{NSApplication, NSImage}; use objc2::AnyThread;
use cocoa::base::{id, nil}; use objc2_app_kit::{NSApplication, NSImage};
use cocoa::foundation::NSString; use objc2_foundation::{MainThreadMarker, NSString};
if let Some(mtm) = MainThreadMarker::new() {
let ns_path = NSString::from_str(&path.display().to_string());
if let Some(image) = NSImage::initWithContentsOfFile(NSImage::alloc(), &ns_path) {
let app = NSApplication::sharedApplication(mtm);
unsafe { unsafe {
let ns_path = NSString::alloc(nil).init_str(&path.display().to_string()); app.setApplicationIconImage(Some(&image));
let image: id = NSImage::initWithContentsOfFile_(NSImage::alloc(nil), ns_path); }
if image != nil {
let app = NSApplication::sharedApplication(nil);
let _: () = app.setApplicationIconImage_(image);
} }
} }
} }
@@ -1291,7 +1289,6 @@ fn handle_to_rust(
if let Ok(mut c) = arboard::Clipboard::new() { if let Ok(mut c) = arboard::Clipboard::new() {
use base64::Engine; use base64::Engine;
if let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(&data) { if let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(&data) {
use std::io::Read;
let cursor = std::io::Cursor::new(&bytes[..]); let cursor = std::io::Cursor::new(&bytes[..]);
let decoder = png::Decoder::new(cursor); let decoder = png::Decoder::new(cursor);
if let Ok(mut reader) = decoder.read_info() { if let Ok(mut reader) = decoder.read_info() {
@@ -1589,7 +1586,6 @@ fn handle_to_rust(
pair.window.set_maximized(true); pair.window.set_maximized(true);
} }
} }
_ => {}
} }
} }
@@ -1625,7 +1621,7 @@ mod tests {
bmp[8..12].copy_from_slice(&(w as i32).to_le_bytes()); bmp[8..12].copy_from_slice(&(w as i32).to_le_bytes());
bmp[14..16].copy_from_slice(&32u16.to_le_bytes()); bmp[14..16].copy_from_slice(&32u16.to_le_bytes());
for y in (0..w).rev() { for y in (0..w).rev() {
for x in 0..w { for _x in 0..w {
let p = pixels[y]; let p = pixels[y];
bmp.extend_from_slice(&[p[2], p[1], p[0], p[3]]); bmp.extend_from_slice(&[p[2], p[1], p[0], p[3]]);
} }
+2 -6
View File
@@ -12,7 +12,6 @@ static IPC_LISTENERS: once_cell::sync::Lazy<Mutex<HashMap<String, Vec<IpcListene
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct IpcListener { struct IpcListener {
id: u32, id: u32,
channel: String,
} }
static LISTENER_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(1); static LISTENER_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(1);
@@ -51,7 +50,7 @@ pub fn ipc_main_on(channel: String, _callback: JsFunction) -> Result<i64> {
listeners listeners
.entry(channel.clone()) .entry(channel.clone())
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(IpcListener { id, channel }); .push(IpcListener { id });
Ok(id as i64) Ok(id as i64)
} }
@@ -146,10 +145,7 @@ pub fn ipc_renderer_on(channel: String, _callback: JsFunction) -> Result<i64> {
listeners listeners
.entry(format!("renderer:{}", channel)) .entry(format!("renderer:{}", channel))
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(IpcListener { .push(IpcListener { id });
id,
channel: channel.clone(),
});
log::debug!("ipcRenderer.on registered for channel: {}", &channel); log::debug!("ipcRenderer.on registered for channel: {}", &channel);
Ok(id as i64) Ok(id as i64)
} }
+1
View File
@@ -62,6 +62,7 @@ pub fn tray_set_pressed_image(_tray_id: u32, _image_json: Option<String>) -> Res
} }
#[napi] #[napi]
#[allow(non_snake_case)]
pub fn tray_setContextMenu(tray_id: u32, menu_id: Option<u32>) -> Result<()> { pub fn tray_setContextMenu(tray_id: u32, menu_id: Option<u32>) -> Result<()> {
log::info!("Tray {} context menu set to {:?}", tray_id, menu_id); log::info!("Tray {} context menu set to {:?}", tray_id, menu_id);
Ok(()) Ok(())
@@ -111,12 +111,14 @@ pub fn web_contents_get_session_id(window_id: u32) -> Result<String> {
} }
#[napi] #[napi]
#[allow(non_snake_case)]
pub fn web_contents_set_windowOpenHandler(window_id: u32) -> Result<()> { pub fn web_contents_set_windowOpenHandler(window_id: u32) -> Result<()> {
log::debug!("Window {} set window open handler", window_id); log::debug!("Window {} set window open handler", window_id);
Ok(()) Ok(())
} }
#[napi] #[napi]
#[allow(non_snake_case)]
pub fn web_contents_set_permissionRequestHandler(window_id: u32) -> Result<()> { pub fn web_contents_set_permissionRequestHandler(window_id: u32) -> Result<()> {
log::debug!("Window {} set permission request handler", window_id); log::debug!("Window {} set permission request handler", window_id);
Ok(()) Ok(())
@@ -146,6 +148,7 @@ pub fn web_contents_set_ignore_menu_shortcuts(window_id: u32, ignore: bool) -> R
} }
#[napi] #[napi]
#[allow(non_snake_case)]
pub fn web_contents_setAudioMuted(window_id: u32, muted: bool) -> Result<()> { pub fn web_contents_setAudioMuted(window_id: u32, muted: bool) -> Result<()> {
log::debug!("Window {} audio muted={}", window_id, muted); log::debug!("Window {} audio muted={}", window_id, muted);
Ok(()) Ok(())
+1 -1
View File
@@ -335,7 +335,7 @@ impl InnerWebView {
Ok(()) Ok(())
} }
pub fn id(&self) -> crate::WebViewId { pub fn id(&self) -> crate::WebViewId<'_> {
&self.id &self.id
} }
+1 -1
View File
@@ -1558,7 +1558,7 @@ impl WebView {
} }
/// Returns the id of this webview. /// Returns the id of this webview.
pub fn id(&self) -> WebViewId { pub fn id(&self) -> WebViewId<'_> {
self.webview.id() self.webview.id()
} }
+1 -1
View File
@@ -568,7 +568,7 @@ impl InnerWebView {
is_inspector_open is_inspector_open
} }
pub fn id(&self) -> crate::WebViewId { pub fn id(&self) -> crate::WebViewId<'_> {
&self.id &self.id
} }
+1 -1
View File
@@ -1263,7 +1263,7 @@ impl InnerWebView {
/// Public APIs /// Public APIs
impl InnerWebView { impl InnerWebView {
pub fn id(&self) -> crate::WebViewId { pub fn id(&self) -> crate::WebViewId<'_> {
&self.id &self.id
} }
+2 -2
View File
@@ -87,13 +87,13 @@ pub(crate) fn download_did_finish(this: &WryDownloadDelegate, download: &WKDownl
pub(crate) fn download_did_fail( pub(crate) fn download_did_fail(
this: &WryDownloadDelegate, this: &WryDownloadDelegate,
download: &WKDownload, download: &WKDownload,
error: &NSError, _error: &NSError,
_resume_data: &NSData, _resume_data: &NSData,
) { ) {
unsafe { unsafe {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
let description = error.localizedDescription().to_string(); let description = _error.localizedDescription().to_string();
eprintln!("Download failed with error: {}", description); eprintln!("Download failed with error: {}", description);
} }
+3 -3
View File
@@ -34,7 +34,7 @@ use dpi::{LogicalPosition, LogicalSize};
use objc2::runtime::Bool; use objc2::runtime::Bool;
use objc2::{ use objc2::{
rc::Retained, rc::Retained,
runtime::{AnyObject, NSObject, ProtocolObject}, runtime::{AnyObject, ProtocolObject},
ClassType, DeclaredClass, ClassType, DeclaredClass,
}; };
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -387,7 +387,7 @@ impl InnerWebView {
#[cfg(any(debug_assertions, feature = "devtools"))] #[cfg(any(debug_assertions, feature = "devtools"))]
if attributes.devtools { if attributes.devtools {
let has_inspectable_property: bool = let has_inspectable_property: bool =
NSObject::respondsToSelector(&webview, objc2::sel!(setInspectable:)); objc2::runtime::NSObject::respondsToSelector(&webview, objc2::sel!(setInspectable:));
if has_inspectable_property { if has_inspectable_property {
webview.setInspectable(true); webview.setInspectable(true);
} }
@@ -538,7 +538,7 @@ r#"Object.defineProperty(window, 'ipc', {
} }
} }
pub fn id(&self) -> crate::WebViewId { pub fn id(&self) -> crate::WebViewId<'_> {
&self.id &self.id
} }