From e6afce66ea2d3426e55702f1fa5cc811e1d2b241 Mon Sep 17 00:00:00 2001 From: Medcl Date: Tue, 11 Feb 2025 14:53:46 +0800 Subject: [PATCH] feat: allow to launch coco across multi monitors (#135) * feat: allow to launch coco across multi monitors * chore: add missing commits * fix: fix build and default style --- src-tauri/src/lib.rs | 114 +++++++++++++++++++++++++++++++++----- src-tauri/src/main.rs | 1 - src-tauri/src/shortcut.rs | 20 ++++--- src-tauri/tauri.conf.json | 13 +---- src/main.css | 1 + 5 files changed, 118 insertions(+), 31 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0418b8dc..9364ad36 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -18,7 +18,7 @@ use reqwest::Client; use std::path::PathBuf; #[cfg(target_os = "macos")] use tauri::ActivationPolicy; -use tauri::{AppHandle, Emitter, Listener, Manager, Runtime, WebviewWindow, WindowEvent}; +use tauri::{AppHandle, Emitter, Listener, Manager, PhysicalPosition, Runtime, WebviewWindow, Window, WindowEvent}; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_deep_link::DeepLinkExt; use tokio::runtime::Runtime as RT; @@ -106,8 +106,7 @@ pub fn run() { dbg!("Async initialization tasks completed"); }); - - shortcut::enable_shortcut(app); + shortcut::enable_shortcut(&app); enable_tray(app); enable_autostart(app); @@ -181,18 +180,30 @@ pub async fn init(app_handle: &AppHandle) { registry.register_source(source).await; } + // Clone app_handle to move it into the background task + let app_handle_clone = app_handle.clone(); - let dir = vec![ - dirs::home_dir().map(|home| home.join("Applications")), // Resolve `~/Applications` - Some(PathBuf::from("/Applications")), - Some(PathBuf::from("/System/Applications")), - Some(PathBuf::from("/System/Applications/Utilities")), - ]; + // Run the slow application directory search in the background + tokio::spawn(async move { + dbg!("Initializing application search source in background"); + let dir = vec![ + dirs::home_dir().map(|home| home.join("Applications")), // Resolve `~/Applications` + Some(PathBuf::from("/Applications")), + Some(PathBuf::from("/System/Applications")), + Some(PathBuf::from("/System/Applications/Utilities")), + ]; - // Remove any `None` values if `home_dir()` fails - let app_dirs: Vec = dir.into_iter().flatten().collect(); - let application_search = local::application::ApplicationSearchSource::new(1000f64, app_dirs); - registry.register_source(application_search).await; + // Remove any `None` values if `home_dir()` fails + let app_dirs: Vec = dir.into_iter().flatten().collect(); + + let application_search = local::application::ApplicationSearchSource::new(1000f64, app_dirs); + + // Register the application search source + let registry = app_handle_clone.state::(); + registry.register_source(application_search).await; + + dbg!("Application search source initialized in background"); + }); dbg!("Initialization completed"); } @@ -226,6 +237,8 @@ fn handle_open_coco(app: &AppHandle) { println!("Open Coco menu clicked!"); if let Some(window) = app.get_window(MAIN_WINDOW_LABEL) { + move_window_to_active_monitor(&window); + window.show().unwrap(); window.set_visible_on_all_workspaces(true).unwrap(); window.set_always_on_top(true).unwrap(); @@ -235,6 +248,81 @@ fn handle_open_coco(app: &AppHandle) { } } +fn move_window_to_active_monitor(window: &Window) { + dbg!("Moving window to active monitor"); + // Try to get the available monitors, handle failure gracefully + let available_monitors = match window.available_monitors() { + Ok(monitors) => monitors, + Err(e) => { + eprintln!("Failed to get monitors: {}", e); + return; + } + }; + + // Attempt to get the cursor position, handle failure gracefully + let cursor_position = match window.cursor_position() { + Ok(pos) => Some(pos), + Err(e) => { + eprintln!("Failed to get cursor position: {}", e); + None + } + }; + + // Find the monitor that contains the cursor or default to the primary monitor + let target_monitor = if let Some(cursor_position) = cursor_position { + // Convert cursor position to integers + let cursor_x = cursor_position.x.round() as i32; + let cursor_y = cursor_position.y.round() as i32; + + // Find the monitor that contains the cursor + available_monitors.into_iter().find(|monitor| { + let monitor_position = monitor.position(); + let monitor_size = monitor.size(); + + cursor_x >= monitor_position.x + && cursor_x <= monitor_position.x + monitor_size.width as i32 + && cursor_y >= monitor_position.y + && cursor_y <= monitor_position.y + monitor_size.height as i32 + }) + } else { + None + }; + + // Use the target monitor or default to the primary monitor + let monitor = match target_monitor.or_else(|| window.primary_monitor().ok().flatten()) { + Some(monitor) => monitor, + None => { + eprintln!("No monitor found!"); + return; + } + }; + + let monitor_position = monitor.position(); + let monitor_size = monitor.size(); + + // Get the current size of the window + let window_size = match window.inner_size() { + Ok(size) => size, + Err(e) => { + eprintln!("Failed to get window size: {}", e); + return; + } + }; + + let window_width = window_size.width as i32; + let window_height = window_size.height as i32; + + // Calculate the new position to center the window on the monitor + let window_x = monitor_position.x + (monitor_size.width as i32 - window_width) / 2; + let window_y = monitor_position.y + (monitor_size.height as i32 - window_height) / 2; + + // Move the window to the new position + if let Err(e) = window.set_position(PhysicalPosition::new(window_x, window_y)) { + eprintln!("Failed to move window: {}", e); + } +} + + fn handle_hide_coco(app: &AppHandle) { // println!("Hide Coco menu clicked!"); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 29fc301d..566f6c53 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,6 +1,5 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - fn main() { coco_lib::run(); } diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index f3b859f2..fc075aea 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -1,4 +1,4 @@ -use crate::COCO_TAURI_STORE; +use crate::{move_window_to_active_monitor, COCO_TAURI_STORE}; use tauri::App; use tauri::AppHandle; use tauri::Manager; @@ -99,18 +99,22 @@ pub fn change_shortcut( /// Helper function to register a shortcut, used for shortcut updates. fn _register_shortcut(app: &AppHandle, shortcut: Shortcut) { - let main_window = app.get_webview_window("main").unwrap(); app.global_shortcut() - .on_shortcut(shortcut, move |_app, scut, event| { + .on_shortcut(shortcut, move |app, scut, event| { if scut == &shortcut { + dbg!("shortcut pressed"); + let main_window = app.get_window(MAIN_WINDOW_LABEL).unwrap(); if let ShortcutState::Pressed = event.state() { if main_window.is_visible().unwrap() { + dbg!("hiding window"); main_window.hide().unwrap(); } else { - main_window.show().unwrap(); + dbg!("showing window"); + move_window_to_active_monitor(&main_window); main_window.set_visible_on_all_workspaces(true).unwrap(); main_window.set_always_on_top(true).unwrap(); main_window.set_focus().unwrap(); + main_window.show().unwrap(); } } } @@ -123,21 +127,23 @@ 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_WINDOW_LABEL).unwrap(); - app.handle() + let handler = app.app_handle(); + handler .plugin( tauri_plugin_global_shortcut::Builder::new() .with_handler(move |app, scut, event| { if scut == &shortcut { + let window = app.get_window(MAIN_WINDOW_LABEL).unwrap(); if let ShortcutState::Pressed = event.state() { if window.is_visible().unwrap() { window.hide().unwrap(); } else { dbg!("showing window"); - window.show().unwrap(); + move_window_to_active_monitor(&window); window.set_visible_on_all_workspaces(true).unwrap(); window.set_always_on_top(true).unwrap(); window.set_focus().unwrap(); + window.show().unwrap(); } } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 12a301bd..6aa20c64 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -71,14 +71,7 @@ }, "bundle": { "active": true, - "targets": [ - "nsis", - "dmg", - "app", - "appimage", - "deb", - "rpm" - ], + "targets": "all", "shortDescription": "Coco AI", "icon": [ "icons/32x32.png", @@ -103,11 +96,11 @@ "dmg": { "appPosition": { "x": 180, - "y": 140 + "y": 180 }, "applicationFolderPosition": { "x": 480, - "y": 140 + "y": 180 } } }, diff --git a/src/main.css b/src/main.css index 21582852..da0d5e05 100644 --- a/src/main.css +++ b/src/main.css @@ -14,6 +14,7 @@ --background: #ffffff; --foreground: #09090b; --border: #e3e3e7; + --coco-primary-color: rgb(149, 5, 153); } /* Light theme */