refactor: bump tauri_nspanel & show_coco/hide_coco now use NSPanel's function on macOS (#933)

This commit:

1. Bump dep tauri_nspenel to v2.1
2. On macOS, our main window is not a window but panel. Previously, we
   were using window.show() and window.hide() in show_coco() and
   hide_coco(). In this commit, we switch from window.show/hide to
   panel.show/hide

Co-authored-by: ayang <473033518@qq.com>
This commit is contained in:
SteveLauC
2025-10-20 15:53:48 +08:00
committed by GitHub
parent 859def21bf
commit 8e49455acf
5 changed files with 101 additions and 136 deletions

View File

@@ -37,6 +37,8 @@ chore: add cross-domain configuration for web component #921
refactor: retry if AXUIElementSetAttributeValue() does not work #924 refactor: retry if AXUIElementSetAttributeValue() does not work #924
refactor(calculator): skip evaluation if expr is in form "num => num" #929 refactor(calculator): skip evaluation if expr is in form "num => num" #929
chore: use a custom log directory #930 chore: use a custom log directory #930
chore: bump tauri_nspanel to v2.1 #933
refactor: show_coco/hide_coco now use NSPanel's function on macOS #933
## 0.8.0 (2025-09-28) ## 0.8.0 (2025-09-28)

65
src-tauri/Cargo.lock generated
View File

@@ -976,7 +976,7 @@ dependencies = [
"block", "block",
"cocoa-foundation", "cocoa-foundation",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-graphics 0.24.0", "core-graphics",
"foreign-types 0.5.0", "foreign-types 0.5.0",
"libc", "libc",
"objc", "objc",
@@ -1126,19 +1126,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "core-graphics"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
dependencies = [
"bitflags 2.9.4",
"core-foundation 0.10.1",
"core-graphics-types",
"foreign-types 0.5.0",
"libc",
]
[[package]] [[package]]
name = "core-graphics-types" name = "core-graphics-types"
version = "0.2.0" version = "0.2.0"
@@ -1561,7 +1548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fd9ae1736d6ebb2e472740fbee86fb2178b8d56feb98a6751411d4c95b7e72" checksum = "67fd9ae1736d6ebb2e472740fbee86fb2178b8d56feb98a6751411d4c95b7e72"
dependencies = [ dependencies = [
"cocoa", "cocoa",
"core-graphics 0.24.0", "core-graphics",
"dunce", "dunce",
"gdk", "gdk",
"gdkx11", "gdkx11",
@@ -1656,7 +1643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cf6f550bbbdd5fe66f39d429cb2604bcdacbf00dca0f5bbe2e9306a0009b7c6" checksum = "0cf6f550bbbdd5fe66f39d429cb2604bcdacbf00dca0f5bbe2e9306a0009b7c6"
dependencies = [ dependencies = [
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-graphics 0.24.0", "core-graphics",
"foreign-types-shared 0.3.1", "foreign-types-shared 0.3.1",
"libc", "libc",
"log", "log",
@@ -4126,17 +4113,6 @@ dependencies = [
"malloc_buf", "malloc_buf",
] ]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]] [[package]]
name = "objc-sys" name = "objc-sys"
version = "0.3.5" version = "0.3.5"
@@ -4479,15 +4455,6 @@ dependencies = [
"objc2-security", "objc2-security",
] ]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.7" version = "0.36.7"
@@ -4709,6 +4676,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
[[package]] [[package]]
name = "path-clean" name = "path-clean"
version = "1.0.1" version = "1.0.1"
@@ -6279,7 +6252,7 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"cfg_aliases", "cfg_aliases",
"core-graphics 0.24.0", "core-graphics",
"foreign-types 0.5.0", "foreign-types 0.5.0",
"js-sys", "js-sys",
"log", "log",
@@ -6527,7 +6500,7 @@ dependencies = [
"bitflags 2.9.4", "bitflags 2.9.4",
"block2 0.6.1", "block2 0.6.1",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-graphics 0.24.0", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
"dispatch", "dispatch",
"dlopen2", "dlopen2",
@@ -6727,17 +6700,13 @@ dependencies = [
[[package]] [[package]]
name = "tauri-nspanel" name = "tauri-nspanel"
version = "2.0.1" version = "2.1.0"
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#18ffb9a201fbf6fedfaa382fd4b92315ea30ab1a" source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2.1#da9c9a8d4eb7f0524a2508988df1a7d9585b4904"
dependencies = [ dependencies = [
"bitflags 2.9.4", "objc2 0.6.2",
"block", "objc2-app-kit 0.3.1",
"cocoa", "objc2-foundation 0.3.1",
"core-foundation 0.10.1", "pastey",
"core-graphics 0.25.0",
"objc",
"objc-foundation",
"objc_id",
"tauri", "tauri",
] ]

View File

@@ -122,7 +122,7 @@ path-clean = "1.0.1"
tempfile = "3.23.0" tempfile = "3.23.0"
[target."cfg(target_os = \"macos\")".dependencies] [target."cfg(target_os = \"macos\")".dependencies]
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" } tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2.1" }
objc2-app-kit = { version = "0.3.1", features = ["NSWindow"] } objc2-app-kit = { version = "0.3.1", features = ["NSWindow"] }
objc2 = "0.6.2" objc2 = "0.6.2"
objc2-core-foundation = {version = "0.3.1", features = ["CFString", "CFCGTypes", "CFArray"] } objc2-core-foundation = {version = "0.3.1", features = ["CFString", "CFCGTypes", "CFArray"] }

View File

@@ -267,35 +267,57 @@ async fn show_coco(app_handle: AppHandle) {
if let Some(window) = app_handle.get_webview_window(MAIN_WINDOW_LABEL) { if let Some(window) = app_handle.get_webview_window(MAIN_WINDOW_LABEL) {
move_window_to_active_monitor(&window); move_window_to_active_monitor(&window);
let _ = window.show(); cfg_if::cfg_if! {
let _ = window.unminimize(); if #[cfg(target_os = "macos")] {
use tauri_nspanel::ManagerExt;
// The Window Management (WM) extension (macOS-only) controls the let app_handle_clone = app_handle.clone();
// frontmost window. Setting focus on macOS makes Coco the frontmost
// window, which means the WM extension would control Coco instead of other app_handle.run_on_main_thread(move || {
// windows, which is not what we want. let panel = app_handle_clone.get_webview_panel(MAIN_WINDOW_LABEL).unwrap();
//
// On Linux/Windows, however, setting focus is a necessity to ensure that panel.show_and_make_key();
// users open Coco's window, then they can start typing, without needing }).unwrap();
// to click on the window. } else {
#[cfg(not(target_os = "macos"))] let _ = window.show();
let _ = window.set_focus(); let _ = window.unminimize();
// The Window Management (WM) extension (macOS-only) controls the
// frontmost window. Setting focus on macOS makes Coco the frontmost
// window, which means the WM extension would control Coco instead of other
// windows, which is not what we want.
//
// On Linux/Windows, however, setting focus is a necessity to ensure that
// users open Coco's window, then they can start typing, without needing
// to click on the window.
let _ = window.set_focus();
}
};
let _ = app_handle.emit("show-coco", ()); let _ = app_handle.emit("show-coco", ());
} }
} }
#[tauri::command] #[tauri::command]
async fn hide_coco(app: AppHandle) { async fn hide_coco(app_handle: AppHandle) {
if let Some(window) = app.get_webview_window(MAIN_WINDOW_LABEL) { cfg_if::cfg_if! {
if let Err(err) = window.hide() { if #[cfg(target_os = "macos")] {
log::error!("Failed to hide the window: {}", err); use tauri_nspanel::ManagerExt;
let app_handle_clone = app_handle.clone();
app_handle.run_on_main_thread(move || {
let panel = app_handle_clone.get_webview_panel(MAIN_WINDOW_LABEL).expect("cannot find the main window/panel");
panel.hide();
}).unwrap();
} else { } else {
log::debug!("Window successfully hidden."); let window = app_handle.get_webview_window(MAIN_WINDOW_LABEL).expect("cannot find the main window");
if let Err(err) = window.hide() {
log::error!("Failed to hide the window: {}", err);
} else {
log::debug!("Window successfully hidden.");
}
} }
} else { };
log::error!("Main window not found.");
}
} }
fn move_window_to_active_monitor(window: &WebviewWindow) { fn move_window_to_active_monitor(window: &WebviewWindow) {

View File

@@ -1,23 +1,26 @@
//! credits to: https://github.com/ayangweb/ayangweb-EcoPaste/blob/169323dbe6365ffe4abb64d867439ed2ea84c6d1/src-tauri/src/core/setup/mac.rs //! credits to: https://github.com/ayangweb/ayangweb-EcoPaste/blob/169323dbe6365ffe4abb64d867439ed2ea84c6d1/src-tauri/src/core/setup/mac.rs
//!
//! # allow(deprecated)
//!
//! This file uses some deprecated interfaces from the `tauri_nspanel` crate. The
//! only way to get rid of them is to bump that crate (v2->v2.1), we are not going
//! to do that because doing that bump requires a re-write of the code in this
//! file and v2 has been working well. So we allow these deprecated interfaces.
use crate::common::MAIN_WINDOW_LABEL; use crate::common::MAIN_WINDOW_LABEL;
use objc2_app_kit::NSNonactivatingPanelMask; use tauri::{AppHandle, Emitter, EventTarget, Manager, WebviewWindow};
use tauri::{AppHandle, Emitter, EventTarget, WebviewWindow}; use tauri_nspanel::{CollectionBehavior, PanelLevel, StyleMask, WebviewWindowExt, tauri_panel};
#[allow(deprecated)]
use tauri_nspanel::cocoa::appkit::NSWindowCollectionBehavior;
use tauri_nspanel::{WebviewWindowExt, panel_delegate};
const WINDOW_FOCUS_EVENT: &str = "tauri://focus"; const WINDOW_FOCUS_EVENT: &str = "tauri://focus";
const WINDOW_BLUR_EVENT: &str = "tauri://blur"; const WINDOW_BLUR_EVENT: &str = "tauri://blur";
const WINDOW_MOVED_EVENT: &str = "tauri://move";
const WINDOW_RESIZED_EVENT: &str = "tauri://resize"; tauri_panel! {
panel!(NsPanel {
config: {
is_floating_panel: true,
can_become_key_window: true,
can_become_main_window: false
}
})
panel_event!(NsPanelEventHandler {
window_did_become_key(notification: &NSNotification) -> (),
window_did_resign_key(notification: &NSNotification) -> (),
})
}
pub fn platform( pub fn platform(
_tauri_app_handle: &AppHandle, _tauri_app_handle: &AppHandle,
@@ -26,70 +29,39 @@ pub fn platform(
_check_window: WebviewWindow, _check_window: WebviewWindow,
) { ) {
// Convert ns_window to ns_panel // Convert ns_window to ns_panel
let panel = main_window.to_panel().unwrap(); let panel = main_window.to_panel::<NsPanel>().unwrap();
// set level
panel.set_level(PanelLevel::Utility.value());
// Do not steal focus from other windows // Do not steal focus from other windows
// panel.set_style_mask(StyleMask::empty().nonactivating_panel().into());
// Cast is safe
panel.set_style_mask(NSNonactivatingPanelMask.0 as i32);
// Set its level to NSFloatingWindowLevel to ensure it appears in front of
// all normal-level windows
//
// NOTE: some Chinese input methods use a level between NSDockWindowLevel (20)
// and NSMainMenuWindowLevel (24), setting our level above NSDockWindowLevel
// would block their window
panel.set_floating_panel(true);
// Open the window in the active workspace and full screen // Open the window in the active workspace and full screen
#[allow(deprecated)] panel.set_collection_behavior(
panel.set_collection_behaviour( CollectionBehavior::new()
NSWindowCollectionBehavior::NSWindowCollectionBehaviorMoveToActiveSpace .stationary()
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary .move_to_active_space()
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary, .full_screen_auxiliary()
.into(),
); );
// Define the panel's delegate to listen to panel window events let handler = NsPanelEventHandler::new();
#[allow(deprecated)]
let delegate = panel_delegate!(EcoPanelDelegate {
window_did_become_key,
window_did_resign_key,
window_did_resize,
window_did_move
});
// Set event listeners for the delegate let window = main_window.clone();
delegate.set_listener(Box::new(move |delegate_name: String| { handler.window_did_become_key(move |_| {
let target = EventTarget::labeled(MAIN_WINDOW_LABEL); let target = EventTarget::labeled(MAIN_WINDOW_LABEL);
let window_move_event = || { let _ = window.emit_to(target, WINDOW_FOCUS_EVENT, true);
if let Ok(position) = main_window.outer_position() { });
let _ = main_window.emit_to(target.clone(), WINDOW_MOVED_EVENT, position);
}
};
match delegate_name.as_str() { let window = main_window.clone();
// Called when the window gets keyboard focus handler.window_did_resign_key(move |_| {
"window_did_become_key" => { let target = EventTarget::labeled(MAIN_WINDOW_LABEL);
let _ = main_window.emit_to(target, WINDOW_FOCUS_EVENT, true);
}
// Called when the window loses keyboard focus
"window_did_resign_key" => {
let _ = main_window.emit_to(target, WINDOW_BLUR_EVENT, true);
}
// Called when the window size changes
"window_did_resize" => {
window_move_event();
if let Ok(size) = main_window.inner_size() { let _ = window.emit_to(target, WINDOW_BLUR_EVENT, true);
let _ = main_window.emit_to(target, WINDOW_RESIZED_EVENT, size); });
}
}
// Called when the window position changes
"window_did_move" => window_move_event(),
_ => (),
}
}));
// Set the delegate object for the window to handle window events // Set the delegate object for the window to handle window events
panel.set_delegate(delegate); panel.set_event_handler(Some(handler.as_ref()));
} }