From 85f72ccc816a2cc2684cec81a5db2195e4471748 Mon Sep 17 00:00:00 2001 From: Alex Shabalin <110031243+alex-sparus@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:45:38 +0200 Subject: [PATCH] refactor(deps): drop cocoa/objc for objc2-app-kit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: tooling flags `block 0.1.6` as unmaintained. Nothing uses it directly; it arrives only through `cocoa 0.26`. `cocoa` and `objc 0.2` are deprecated upstream as well — no soundness fixes, no bindings for new macOS SDKs, and untyped `msg_send!` calls no compiler checks. Solution: move the three NSWindow call sites onto `objc2-app-kit`, already in the tree through Tauri, so the lockfile gains nothing. The two identical panel-setup blocks in `peek.rs` collapse into one helper whose main-thread requirement is asserted rather than assumed, and the hardcoded level literal becomes `NSFloatingWindowLevel`. `cocoa`, `cocoa-foundation`, `objc`, `block`, `malloc_buf` and `core-graphics 0.24` leave `Cargo.lock`. Refs #546 --- Cargo.lock | 71 +---------------------- apps/native/src-tauri/Cargo.toml | 12 +++- apps/native/src-tauri/src/main.rs | 27 ++++----- apps/native/src-tauri/src/peek.rs | 96 ++++++++++++------------------- 4 files changed, 60 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9044a08fc..1ad1752fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,12 +514,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -810,35 +804,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" -[[package]] -name = "cocoa" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" -dependencies = [ - "bitflags 2.11.0", - "block", - "cocoa-foundation", - "core-foundation 0.10.1", - "core-graphics 0.24.0", - "foreign-types 0.5.0", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" -dependencies = [ - "bitflags 2.11.0", - "block", - "core-foundation 0.10.1", - "core-graphics-types", - "objc", -] - [[package]] name = "colorchoice" version = "1.0.4" @@ -992,19 +957,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.10.1", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - [[package]] name = "core-graphics" version = "0.25.0" @@ -3429,15 +3381,6 @@ dependencies = [ "core-foundation-sys", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "markup5ever" version = "0.14.1" @@ -3702,7 +3645,6 @@ dependencies = [ "block2", "chrono", "clap", - "cocoa", "configurable", "core-foundation 0.10.1", "diesel", @@ -3721,8 +3663,8 @@ dependencies = [ "libsqlite3-sys", "log", "nix", - "objc", "objc2", + "objc2-app-kit", "objc2-foundation", "objc2-security", "objc2-service-management", @@ -3909,15 +3851,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc2" version = "0.6.4" @@ -6981,7 +6914,7 @@ dependencies = [ "bitflags 2.11.0", "block2", "core-foundation 0.10.1", - "core-graphics 0.25.0", + "core-graphics", "crossbeam-channel", "dbus", "dispatch2", diff --git a/apps/native/src-tauri/Cargo.toml b/apps/native/src-tauri/Cargo.toml index 3ce9d447f..ea651a492 100644 --- a/apps/native/src-tauri/Cargo.toml +++ b/apps/native/src-tauri/Cargo.toml @@ -138,8 +138,6 @@ tauri-plugin-updater = "2.0.0" url = "2" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.26" -objc = "0.2" window-vibrancy = "0.6" core-foundation = "0.10" security-framework = "3" @@ -186,6 +184,16 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = [ ] } block2 = "0.6" dispatch2 = "0.3" +# Typed `NSWindow` bindings for the floating-panel setup in `peek.rs` and the +# `NIXMAC_E2E_OPAQUE_WINDOW` diagnostic in `main.rs`. Already in the tree +# through Tauri; declared directly with the exact features this code uses, so +# the build does not depend on Tauri's own feature selection. +objc2-app-kit = { version = "0.3.2", default-features = false, features = [ + "std", + "NSResponder", + "NSWindow", + "objc2-core-foundation", +] } [target.'cfg(unix)'.dependencies] nix = { version = "0.31", features = ["user"] } diff --git a/apps/native/src-tauri/src/main.rs b/apps/native/src-tauri/src/main.rs index 6e682b57a..f74952b5f 100644 --- a/apps/native/src-tauri/src/main.rs +++ b/apps/native/src-tauri/src/main.rs @@ -938,26 +938,23 @@ use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial, NSVisualEffectStat #[cfg(target_os = "macos")] if e2e_opaque_window { - use objc::msg_send; - use objc::runtime::Object; - use objc::sel; - use objc::sel_impl; + use objc2_app_kit::NSWindow; match main_window.ns_window() { - Ok(ns_window) => unsafe { - let ns_window = ns_window as *mut Object; - let is_opaque: bool = msg_send![ns_window, isOpaque]; - let alpha_value: f64 = msg_send![ns_window, alphaValue]; - let level: i64 = msg_send![ns_window, level]; - let has_shadow: bool = msg_send![ns_window, hasShadow]; + Ok(ns_window) => { + // SAFETY: `ns_window()` hands back a + // retained-then-autoreleased NSWindow, valid for this + // scope, and `setup()` runs on the main thread, as + // main-thread-only `NSWindow` requires. + let ns_window: &NSWindow = unsafe { &*ns_window.cast() }; log::info!( "NIXMAC_E2E_OPAQUE_WINDOW native window diagnostics: isOpaque={} alphaValue={:.3} level={} hasShadow={}", - is_opaque, - alpha_value, - level, - has_shadow + ns_window.isOpaque(), + ns_window.alphaValue(), + ns_window.level(), + ns_window.hasShadow() ); - }, + } Err(error) => { log::warn!( "NIXMAC_E2E_OPAQUE_WINDOW native window diagnostics unavailable: {}", diff --git a/apps/native/src-tauri/src/peek.rs b/apps/native/src-tauri/src/peek.rs index 63b430c41..7533a6e4a 100644 --- a/apps/native/src-tauri/src/peek.rs +++ b/apps/native/src-tauri/src/peek.rs @@ -472,6 +472,38 @@ pub fn get_preview_indicator_state() -> PreviewIndicatorState { } } +/// Makes `window` an independent floating panel: above regular windows, present +/// on every space without following the active one, and skipped by Cmd+`. +/// +/// Main-thread only, because `NSWindow` is `MainThreadOnly` — call it from +/// `setup()`, not from a command worker thread. +#[cfg(target_os = "macos")] +fn set_floating_panel_behavior(window: &tauri::WebviewWindow) { + use objc2_app_kit::{NSFloatingWindowLevel, NSWindow, NSWindowCollectionBehavior}; + + debug_assert!( + objc2::MainThreadMarker::new().is_some(), + "set_floating_panel_behavior must run on the main thread" + ); + + let _ = window.set_shadow(false); + + let Ok(ns_window) = window.ns_window() else { + return; + }; + // SAFETY: `ns_window()` hands back a retained-then-autoreleased NSWindow, + // valid for this scope. `NSWindow` is main-thread-only, and both callers + // run in `setup()` on the main thread. + let ns_window: &NSWindow = unsafe { &*ns_window.cast() }; + + ns_window.setCollectionBehavior( + NSWindowCollectionBehavior::CanJoinAllSpaces + | NSWindowCollectionBehavior::Stationary + | NSWindowCollectionBehavior::IgnoresCycle, + ); + ns_window.setLevel(NSFloatingWindowLevel); +} + /// Creates the preview indicator window (call once during setup). /// /// The window stays hidden until the preview-indicator React tree has a @@ -530,38 +562,8 @@ pub fn create_preview_indicator_window(app: &AppHandle) -> Result #[cfg(not(target_os = "macos"))] let _ = &window; - // Disable shadow and set as utility panel (prevents grouping with main window) #[cfg(target_os = "macos")] - { - use cocoa::appkit::{NSWindow, NSWindowCollectionBehavior}; - use cocoa::base::id; - use objc::msg_send; - use objc::sel; - use objc::sel_impl; - - let _ = window.set_shadow(false); - - // Get the native NSWindow and configure it as an independent panel - if let Ok(ns_window) = window.ns_window() { - let ns_win = ns_window as id; - unsafe { - // Set collection behavior to be independent (not grouped with other windows) - // NSWindowCollectionBehaviorStationary keeps it from moving with spaces - // NSWindowCollectionBehaviorCanJoinAllSpaces makes it visible on all spaces - // NSWindowCollectionBehaviorIgnoresCycle prevents Cmd+` from cycling to it - ns_win.setCollectionBehavior_( - NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces - | NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary - | NSWindowCollectionBehavior::NSWindowCollectionBehaviorIgnoresCycle, - ); - - // Set as a floating panel level (above regular windows but independent) - // kCGFloatingWindowLevel = 5 (or we can use NSFloatingWindowLevel = 3) - let floating_level: i64 = 3; // NSFloatingWindowLevel - let _: () = msg_send![ns_win, setLevel: floating_level]; - } - } - } + set_floating_panel_behavior(&window); Ok(()) } @@ -669,11 +671,8 @@ const EVOLVE_MASCOT_MARGIN: f64 = 24.0; /// thread). Mirrors `create_preview_indicator_window`: a transparent, /// borderless, always-on-top floating panel, created hidden and shown on demand. /// -/// IMPORTANT: the raw AppKit panel tweaks below are main-thread-only, which is -/// why this runs in `setup()` rather than lazily from a command worker thread. -/// (An earlier lazy variant wrapped in `run_on_main_thread` never delivered its -/// closure, so the window was never created — this matches the proven preview -/// indicator path instead.) +/// IMPORTANT: `set_floating_panel_behavior` is main-thread-only, which is why +/// this runs in `setup()` rather than lazily from a command worker thread. pub fn create_evolve_mascot_window(app: &AppHandle) -> Result<(), String> { if app.get_webview_window("evolve-mascot").is_some() { return Ok(()); @@ -714,31 +713,8 @@ pub fn create_evolve_mascot_window(app: &AppHandle) -> Result<(), #[cfg(not(target_os = "macos"))] let _ = &window; - // Disable shadow and set as an independent floating panel (matches the - // preview indicator so it doesn't group/cycle with the main window). #[cfg(target_os = "macos")] - { - use cocoa::appkit::{NSWindow, NSWindowCollectionBehavior}; - use cocoa::base::id; - use objc::msg_send; - use objc::sel; - use objc::sel_impl; - - let _ = window.set_shadow(false); - - if let Ok(ns_window) = window.ns_window() { - let ns_win = ns_window as id; - unsafe { - ns_win.setCollectionBehavior_( - NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces - | NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary - | NSWindowCollectionBehavior::NSWindowCollectionBehaviorIgnoresCycle, - ); - let floating_level: i64 = 3; // NSFloatingWindowLevel - let _: () = msg_send![ns_win, setLevel: floating_level]; - } - } - } + set_floating_panel_behavior(&window); peek_log!( "🌀 Created evolve mascot indicator window at ({}, {})",