fix: ensure app is always on top and visible on all workspaces (#127)

This commit is contained in:
Medcl
2025-02-08 07:38:02 +08:00
committed by GitHub
parent 08142ca15e
commit 60e43ddfb8
12 changed files with 204 additions and 38 deletions

52
src-tauri/Cargo.lock generated
View File

@@ -625,11 +625,11 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
"tauri-nspanel",
"tauri-plugin-autostart",
"tauri-plugin-deep-link",
"tauri-plugin-global-shortcut",
"tauri-plugin-http",
"tauri-plugin-oauth",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
"tauri-plugin-store",
@@ -2652,6 +2652,17 @@ dependencies = [
"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]]
name = "objc-sys"
version = "0.3.5"
@@ -2870,6 +2881,15 @@ dependencies = [
"objc2-foundation",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "object"
version = "0.36.5"
@@ -4463,6 +4483,22 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-nspanel"
version = "2.0.1"
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2#9b5deee48ccadc8e3f3b44c7e05ca1908a534034"
dependencies = [
"bitflags 2.6.0",
"block",
"cocoa",
"core-foundation 0.10.0",
"core-graphics",
"objc",
"objc-foundation",
"objc_id",
"tauri",
]
[[package]]
name = "tauri-plugin"
version = "2.0.1"
@@ -4572,20 +4608,6 @@ dependencies = [
"urlpattern",
]
[[package]]
name = "tauri-plugin-oauth"
version = "2.0.0"
source = "git+https://github.com/FabianLars/tauri-plugin-oauth?branch=v2#daf24f3b3f34fd471f561750a7cc00dc9930850b"
dependencies = [
"httparse",
"log",
"serde",
"tauri",
"tauri-plugin",
"thiserror 1.0.64",
"url",
]
[[package]]
name = "tauri-plugin-shell"
version = "2.0.1"

View File

@@ -27,8 +27,7 @@ tauri-plugin-http = "2"
tauri-plugin-websocket = "2"
tauri-plugin-theme = "2.1.2"
tauri-plugin-oauth = { git = "https://github.com/FabianLars/tauri-plugin-oauth", branch = "v2" }
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" }
tauri-plugin-deep-link = "2.0.0"
tauri-plugin-single-instance = "2.0.0"
tauri-plugin-store = "2.2.0"

View File

@@ -13,6 +13,10 @@
<key>NSPrefPaneIconLabel</key>
<string>coco-ai</string>
<key>LSUIElement</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>

View File

@@ -2,7 +2,11 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main", "chat", "settings"],
"windows": [
"main",
"chat",
"settings"
],
"permissions": [
"core:default",
"core:event:allow-emit",
@@ -45,8 +49,7 @@
"global-shortcut:allow-unregister",
"global-shortcut:allow-unregister-all",
"theme:default",
"oauth:allow-start",
"deep-link:allow-get-current",
"deep-link:allow-get-current",
"deep-link:default",
"deep-link:allow-register",
{
@@ -57,9 +60,6 @@
},
{
"url": "https://coco.infini.cloud"
},
{
"url": "http://infini.tpddns.cn:27200"
}
],
"deny": []

View File

@@ -8,3 +8,6 @@ pub mod search;
pub mod document;
pub mod traits;
pub mod register;
pub static MAIN_WINDOW_LABEL: &str = "main";
pub static SETTINGS_WINDOW_LABEL: &str = "settings";

View File

@@ -6,8 +6,11 @@ mod util;
mod local;
mod search;
mod setup;
use crate::common::register::SearchSourceRegistry;
use crate::common::traits::SearchSource;
use crate::common::{MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL};
use crate::server::search::CocoSearchSource;
use crate::server::servers::{load_or_insert_default_server, load_servers_token};
use autostart::{change_autostart, enable_autostart};
@@ -19,15 +22,13 @@ use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow};
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use tokio::runtime::Runtime as RT;
// Add this import
// Add this import
/// Tauri store name
pub(crate) const COCO_TAURI_STORE: &str = "coco_tauri_store";
#[tauri::command]
fn change_window_height(handle: AppHandle, height: u32) {
let window: WebviewWindow = handle.get_webview_window("main").unwrap();
let window: WebviewWindow = handle.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
let mut size = window.outer_size().unwrap();
size.height = height;
@@ -73,8 +74,7 @@ pub fn run() {
let mut ctx = tauri::generate_context!();
tauri::Builder::default()
// .plugin(tauri_nspanel::init())
.plugin(tauri_plugin_oauth::init())
.plugin(tauri_nspanel::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_autostart::init(
@@ -153,6 +153,9 @@ pub fn run() {
dbg!(event.urls());
});
let main_window = app.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
let settings_window = app.get_webview_window(SETTINGS_WINDOW_LABEL).unwrap();
setup::default(app, main_window.clone(), settings_window.clone());
Ok(())
})
@@ -226,7 +229,9 @@ pub async fn init<R: Runtime>(app_handle: &AppHandle<R>) {
#[tauri::command]
fn hide_coco(app: tauri::AppHandle) {
if let Some(window) = app.get_window("main") {
dbg!("Hide Coco menu clicked!");
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
match window.is_visible() {
Ok(true) => {
if let Err(err) = window.hide() {
@@ -250,8 +255,10 @@ fn hide_coco(app: tauri::AppHandle) {
fn handle_open_coco(app: &AppHandle) {
// println!("Open Coco menu clicked!");
if let Some(window) = app.get_window("main") {
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
window.show().unwrap();
window.set_visible_on_all_workspaces(true).unwrap();
window.set_always_on_top(true).unwrap();
window.set_focus().unwrap();
} else {
eprintln!("Failed to get main window.");
@@ -261,7 +268,7 @@ fn handle_open_coco(app: &AppHandle) {
fn handle_hide_coco(app: &AppHandle) {
// println!("Hide Coco menu clicked!");
if let Some(window) = app.get_window("main") {
if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) {
if let Err(err) = window.hide() {
eprintln!("Failed to hide the window: {}", err);
} else {

View File

@@ -0,0 +1,3 @@
use tauri::{App, WebviewWindow};
pub fn platform(_app: &mut App, _main_window: WebviewWindow, _settings_window: WebviewWindow) {}

View File

@@ -0,0 +1,58 @@
//credits to: https://github.com/ayangweb/ayangweb-EcoPaste/blob/169323dbe6365ffe4abb64d867439ed2ea84c6d1/src-tauri/src/core/setup/mac.rs
use tauri::{ActivationPolicy, App, Emitter, Manager, WebviewWindow};
use tauri_nspanel::{
cocoa::appkit::{NSMainMenuWindowLevel, NSWindowCollectionBehavior},
panel_delegate, WebviewWindowExt,
};
#[allow(non_upper_case_globals)]
const NSWindowStyleMaskNonActivatingPanel: i32 = 1 << 7;
#[allow(non_upper_case_globals)]
const NSResizableWindowMask: i32 = 1 << 3;
const MACOS_PANEL_FOCUS: &str = "macos-panel-focus";
pub fn platform(app: &mut App, main_window: WebviewWindow, _settings_window: WebviewWindow) {
let app_handle = app.app_handle().clone();
app.set_activation_policy(ActivationPolicy::Accessory);
// Convert ns_window to ns_panel
let panel = main_window.to_panel().unwrap();
// Make the window above the dock
panel.set_level(NSMainMenuWindowLevel + 1);
// Do not steal focus from other windows and support resizing
panel.set_style_mask(NSWindowStyleMaskNonActivatingPanel | NSResizableWindowMask);
// Share the window across all desktop spaces and full screen
panel.set_collection_behaviour(
NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary,
);
// Define the panel's delegate to listen to panel window events
let delegate = panel_delegate!(EcoPanelDelegate {
window_did_become_key,
window_did_resign_key
});
// Set event listeners for the delegate
delegate.set_listener(Box::new(move |delegate_name: String| {
match delegate_name.as_str() {
// Called when the window gains keyboard focus
"window_did_become_key" => {
app_handle.emit(MACOS_PANEL_FOCUS, true).unwrap();
}
// Called when the window loses keyboard focus
"window_did_resign_key" => {
app_handle.emit(MACOS_PANEL_FOCUS, false).unwrap();
}
_ => (),
}
}));
// Set the delegate object for the window to handle window events
panel.set_delegate(delegate);
}

View File

@@ -0,0 +1,25 @@
use tauri::{App, WebviewWindow};
#[cfg(target_os = "macos")]
mod mac;
#[cfg(target_os = "windows")]
mod win;
#[cfg(target_os = "linux")]
mod linux;
mod linux;
mod windows;
#[cfg(target_os = "macos")]
pub use mac::*;
#[cfg(target_os = "windows")]
pub use win::*;
#[cfg(target_os = "linux")]
pub use linux::*;
pub fn default(app: &mut App, main_window: WebviewWindow, settings_window: WebviewWindow) {
platform(app, main_window.clone(), settings_window.clone());
}

View File

@@ -0,0 +1,3 @@
use tauri::{App, WebviewWindow};
pub fn platform(_app: &mut App, _main_window: WebviewWindow, _settings_window: WebviewWindow) {}

View File

@@ -108,6 +108,8 @@ fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
main_window.hide().unwrap();
} else {
main_window.show().unwrap();
main_window.set_visible_on_all_workspaces(true).unwrap();
main_window.set_always_on_top(true).unwrap();
main_window.set_focus().unwrap();
}
}
@@ -117,19 +119,24 @@ fn _register_shortcut<R: Runtime>(app: &AppHandle<R>, shortcut: Shortcut) {
.unwrap();
}
use crate::common::MAIN_WINDOW_LABEL;
/// Helper function to register a shortcut, used to set up the shortcut up App's first start.
fn _register_shortcut_upon_start(app: &App, shortcut: Shortcut) {
let window = app.get_webview_window("main").unwrap();
let window = app.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
app.handle()
.plugin(
tauri_plugin_global_shortcut::Builder::new()
.with_handler(move |_app, scut, event| {
.with_handler(move |app, scut, event| {
if scut == &shortcut {
if let ShortcutState::Pressed = event.state() {
if window.is_visible().unwrap() {
window.hide().unwrap();
} else {
dbg!("showing window");
window.show().unwrap();
window.set_visible_on_all_workspaces(true).unwrap();
window.set_always_on_top(true).unwrap();
window.set_focus().unwrap();
}
}

View File

@@ -13,6 +13,7 @@
"macOSPrivateApi": true,
"windows": [
{
"label": "main",
"title": "Coco AI",
"url": "/ui",
"height": 590,
@@ -22,6 +23,8 @@
"maximizable": false,
"skipTaskbar": true,
"resizable": false,
"alwaysOnTop": false,
"acceptFirstMouse": true,
"shadow": true,
"transparent": true,
"fullscreen": false,
@@ -30,6 +33,24 @@
"effects": [],
"radius": 12
}
},
{
"label": "settings",
"url": "/ui/settings",
"width": 1000,
"height": 700,
"center": true,
"transparent": true,
"maximizable": false,
"skipTaskbar": false,
"dragDropEnabled": true,
"visible": false,
"windowEffects": {
"effects": [
"sidebar"
],
"state": "active"
}
}
],
"security": {
@@ -75,11 +96,16 @@
}
}
},
"resources": ["assets", "icons"]
"resources": [
"assets",
"icons"
]
},
"plugins": {
"features": {
"protocol": ["all"]
"protocol": [
"all"
]
},
"window": {},
"websocket": {},
@@ -88,11 +114,20 @@
"deep-link": {
"schema": "coco",
"mobile": [
{ "host": "app.infini.cloud", "pathPrefix": ["/open"] },
{ "host": "localhost:9000" }
{
"host": "app.infini.cloud",
"pathPrefix": [
"/open"
]
},
{
"host": "localhost:9000"
}
],
"desktop": {
"schemes": ["coco"]
"schemes": [
"coco"
]
}
}
}