2025-03-03 17:54:00 +08:00
|
|
|
mod assistant;
|
2024-12-14 15:38:32 +08:00
|
|
|
mod autostart;
|
2025-02-06 11:45:37 +08:00
|
|
|
mod common;
|
2025-05-30 17:18:52 +08:00
|
|
|
mod extension;
|
2025-02-12 16:15:55 +08:00
|
|
|
mod search;
|
2025-11-27 10:12:49 +08:00
|
|
|
mod selection_monitor;
|
2025-02-06 11:45:37 +08:00
|
|
|
mod server;
|
2025-05-15 09:17:03 +08:00
|
|
|
mod settings;
|
2025-03-03 17:54:00 +08:00
|
|
|
mod setup;
|
2025-01-20 19:38:59 +08:00
|
|
|
mod shortcut;
|
2025-10-19 19:13:09 +08:00
|
|
|
// We need this in main.rs, so it has to be pub
|
|
|
|
|
pub mod util;
|
2025-01-10 14:59:03 +08:00
|
|
|
|
2025-02-07 16:31:05 +08:00
|
|
|
use crate::common::register::SearchSourceRegistry;
|
2025-06-19 20:58:54 +08:00
|
|
|
use crate::common::{CHECK_WINDOW_LABEL, MAIN_WINDOW_LABEL, SETTINGS_WINDOW_LABEL};
|
2025-11-25 10:20:33 +08:00
|
|
|
use crate::server::servers::{
|
|
|
|
|
load_or_insert_default_server, load_servers_token, start_bg_heartbeat_worker,
|
|
|
|
|
};
|
2025-10-19 19:13:09 +08:00
|
|
|
use crate::util::logging::set_up_tauri_logger;
|
2025-08-22 09:19:32 +08:00
|
|
|
use crate::util::prevent_default;
|
2025-08-13 15:33:30 +08:00
|
|
|
use autostart::change_autostart;
|
2025-02-25 16:22:22 +08:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
|
use std::sync::Mutex;
|
2025-05-09 17:54:58 +08:00
|
|
|
use std::sync::OnceLock;
|
2025-11-19 09:39:36 +08:00
|
|
|
use tauri::{
|
|
|
|
|
AppHandle, Emitter, LogicalPosition, Manager, PhysicalPosition, WebviewWindow, WindowEvent,
|
|
|
|
|
};
|
2025-01-20 19:38:59 +08:00
|
|
|
use tauri_plugin_autostart::MacosLauncher;
|
2025-02-06 11:45:37 +08:00
|
|
|
|
|
|
|
|
/// Tauri store name
|
|
|
|
|
pub(crate) const COCO_TAURI_STORE: &str = "coco_tauri_store";
|
2025-11-03 10:52:29 +08:00
|
|
|
pub(crate) const WINDOW_CENTER_BASELINE_HEIGHT: i32 = 590;
|
2024-10-13 17:41:16 +08:00
|
|
|
|
2025-02-25 16:22:22 +08:00
|
|
|
lazy_static! {
|
|
|
|
|
static ref PREVIOUS_MONITOR_NAME: Mutex<Option<String>> = Mutex::new(None);
|
|
|
|
|
}
|
2025-05-09 17:54:58 +08:00
|
|
|
/// To allow us to access tauri's `AppHandle` when its context is inaccessible,
|
|
|
|
|
/// store it globally. It will be set in `init()`.
|
2025-07-22 14:43:27 +08:00
|
|
|
///
|
|
|
|
|
/// # WARNING
|
|
|
|
|
///
|
|
|
|
|
/// You may find this work, but the usage is discouraged and should be generally
|
|
|
|
|
/// avoided. If you do need it, always be careful that it may not be set() when
|
|
|
|
|
/// you access it.
|
2025-05-09 17:54:58 +08:00
|
|
|
pub(crate) static GLOBAL_TAURI_APP_HANDLE: OnceLock<AppHandle> = OnceLock::new();
|
|
|
|
|
|
2024-11-30 15:29:00 +08:00
|
|
|
#[tauri::command]
|
2025-03-24 14:39:08 +08:00
|
|
|
async fn change_window_height(handle: AppHandle, height: u32) {
|
2025-02-08 07:38:02 +08:00
|
|
|
let window: WebviewWindow = handle.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
|
2024-11-30 15:29:00 +08:00
|
|
|
|
|
|
|
|
let mut size = window.outer_size().unwrap();
|
|
|
|
|
size.height = height;
|
|
|
|
|
window.set_size(size).unwrap();
|
2025-11-03 10:52:29 +08:00
|
|
|
|
|
|
|
|
// Center the window horizontally and vertically based on the baseline height of 590
|
|
|
|
|
let monitor = window.primary_monitor().ok().flatten().or_else(|| {
|
|
|
|
|
window
|
|
|
|
|
.available_monitors()
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|ms| ms.into_iter().next())
|
|
|
|
|
});
|
|
|
|
|
if let Some(monitor) = monitor {
|
|
|
|
|
let monitor_position = monitor.position();
|
|
|
|
|
let monitor_size = monitor.size();
|
|
|
|
|
|
2025-12-05 10:46:37 +08:00
|
|
|
let outer_size = window.outer_size().unwrap();
|
|
|
|
|
let window_width = outer_size.width as i32;
|
|
|
|
|
|
2025-11-03 10:52:29 +08:00
|
|
|
let x = monitor_position.x + (monitor_size.width as i32 - window_width) / 2;
|
|
|
|
|
|
|
|
|
|
let y =
|
|
|
|
|
monitor_position.y + (monitor_size.height as i32 - WINDOW_CENTER_BASELINE_HEIGHT) / 2;
|
|
|
|
|
|
|
|
|
|
let _ = window.set_position(PhysicalPosition::new(x, y));
|
|
|
|
|
}
|
2024-11-30 15:29:00 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-27 10:12:49 +08:00
|
|
|
// Removed unused Payload to avoid unnecessary serde derive macro invocations
|
2025-01-10 14:59:03 +08:00
|
|
|
|
2024-10-13 17:41:16 +08:00
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
|
|
|
pub fn run() {
|
2025-03-24 14:55:35 +08:00
|
|
|
let ctx = tauri::generate_context!();
|
2025-02-23 13:09:38 +08:00
|
|
|
|
2025-11-27 10:12:49 +08:00
|
|
|
let mut app_builder = tauri::Builder::default().plugin(tauri_plugin_clipboard_manager::init());
|
2025-06-09 14:46:06 +08:00
|
|
|
// Set up logger first
|
|
|
|
|
app_builder = app_builder.plugin(set_up_tauri_logger());
|
2025-02-19 13:02:22 +08:00
|
|
|
|
|
|
|
|
#[cfg(desktop)]
|
|
|
|
|
{
|
2025-08-03 14:38:47 +08:00
|
|
|
app_builder =
|
|
|
|
|
app_builder.plugin(tauri_plugin_single_instance::init(|_app, _argv, _cwd| {}));
|
2025-02-19 13:02:22 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-21 18:57:32 +08:00
|
|
|
app_builder = app_builder
|
|
|
|
|
.plugin(tauri_plugin_http::init())
|
2024-10-13 17:41:16 +08:00
|
|
|
.plugin(tauri_plugin_shell::init())
|
2024-12-11 10:45:54 +08:00
|
|
|
.plugin(tauri_plugin_autostart::init(
|
2025-06-09 14:46:06 +08:00
|
|
|
MacosLauncher::LaunchAgent,
|
2024-12-11 10:45:54 +08:00
|
|
|
None,
|
|
|
|
|
))
|
2025-01-10 14:59:03 +08:00
|
|
|
.plugin(tauri_plugin_deep_link::init())
|
2025-03-03 17:54:00 +08:00
|
|
|
.plugin(tauri_plugin_store::Builder::default().build())
|
|
|
|
|
.plugin(tauri_plugin_dialog::init())
|
2025-03-05 12:22:33 +08:00
|
|
|
.plugin(tauri_plugin_fs_pro::init())
|
|
|
|
|
.plugin(tauri_plugin_macos_permissions::init())
|
2025-03-07 12:42:55 +08:00
|
|
|
.plugin(tauri_plugin_screenshots::init())
|
2025-03-11 10:36:42 +08:00
|
|
|
.plugin(tauri_plugin_process::init())
|
2025-07-24 16:05:36 +08:00
|
|
|
.plugin(
|
|
|
|
|
tauri_plugin_updater::Builder::new()
|
2025-10-27 10:04:15 +08:00
|
|
|
.default_version_comparator(crate::util::version::custom_version_comparator)
|
2025-07-24 16:05:36 +08:00
|
|
|
.build(),
|
|
|
|
|
)
|
2025-06-10 17:26:19 +08:00
|
|
|
.plugin(tauri_plugin_windows_version::init())
|
2025-08-22 09:19:32 +08:00
|
|
|
.plugin(tauri_plugin_opener::init())
|
2025-11-28 17:29:36 +08:00
|
|
|
.plugin(tauri_plugin_zustand::init())
|
2025-08-22 09:19:32 +08:00
|
|
|
.plugin(prevent_default::init());
|
2025-02-18 08:26:52 +08:00
|
|
|
|
|
|
|
|
// Conditional compilation for macOS
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
{
|
|
|
|
|
app_builder = app_builder.plugin(tauri_nspanel::init());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let app = app_builder
|
2024-11-30 15:29:00 +08:00
|
|
|
.invoke_handler(tauri::generate_handler![
|
|
|
|
|
change_window_height,
|
2025-01-20 19:38:59 +08:00
|
|
|
shortcut::change_shortcut,
|
|
|
|
|
shortcut::unregister_shortcut,
|
|
|
|
|
shortcut::get_current_shortcut,
|
2024-12-14 15:38:32 +08:00
|
|
|
change_autostart,
|
2025-03-07 12:42:55 +08:00
|
|
|
show_coco,
|
2024-12-24 18:24:53 +08:00
|
|
|
hide_coco,
|
2025-03-07 12:42:55 +08:00
|
|
|
show_settings,
|
2025-06-19 20:58:54 +08:00
|
|
|
show_check,
|
|
|
|
|
hide_check,
|
2025-02-06 11:45:37 +08:00
|
|
|
server::servers::add_coco_server,
|
|
|
|
|
server::servers::remove_coco_server,
|
|
|
|
|
server::servers::list_coco_servers,
|
|
|
|
|
server::servers::logout_coco_server,
|
|
|
|
|
server::servers::refresh_coco_server_info,
|
2025-02-24 18:14:43 +08:00
|
|
|
server::servers::enable_server,
|
|
|
|
|
server::servers::disable_server,
|
2025-02-06 11:45:37 +08:00
|
|
|
server::auth::handle_sso_callback,
|
|
|
|
|
server::profile::get_user_profiles,
|
2025-04-24 19:00:16 +08:00
|
|
|
server::datasource::datasource_search,
|
|
|
|
|
server::datasource::mcp_server_search,
|
2025-02-06 11:45:37 +08:00
|
|
|
server::connector::get_connectors_by_server,
|
2025-02-07 16:31:05 +08:00
|
|
|
search::query_coco_fusion,
|
2025-02-25 15:01:32 +08:00
|
|
|
assistant::chat_history,
|
2025-07-07 19:41:29 +08:00
|
|
|
assistant::chat_create,
|
|
|
|
|
assistant::chat_chat,
|
2025-02-25 15:01:32 +08:00
|
|
|
assistant::session_chat_history,
|
|
|
|
|
assistant::open_session_chat,
|
|
|
|
|
assistant::close_session_chat,
|
|
|
|
|
assistant::cancel_session_chat,
|
2025-04-02 14:03:40 +08:00
|
|
|
assistant::delete_session_chat,
|
|
|
|
|
assistant::update_session_chat,
|
2025-04-20 21:27:25 +08:00
|
|
|
assistant::assistant_search,
|
2025-05-27 16:29:43 +08:00
|
|
|
assistant::assistant_get,
|
|
|
|
|
assistant::assistant_get_multi,
|
2025-02-06 11:45:37 +08:00
|
|
|
// server::get_coco_server_datasources,
|
2025-02-21 18:57:32 +08:00
|
|
|
// server::get_coco_server_connectors,
|
2025-03-28 13:50:14 +08:00
|
|
|
get_app_search_source,
|
|
|
|
|
server::attachment::upload_attachment,
|
2025-07-31 15:36:03 +08:00
|
|
|
server::attachment::get_attachment_by_ids,
|
2025-03-28 13:50:14 +08:00
|
|
|
server::attachment::delete_attachment,
|
2025-04-11 11:48:48 +08:00
|
|
|
server::transcription::transcription,
|
2025-05-07 18:09:19 +08:00
|
|
|
server::system_settings::get_system_settings,
|
2025-05-30 17:18:52 +08:00
|
|
|
extension::built_in::application::get_app_list,
|
|
|
|
|
extension::built_in::application::get_app_search_path,
|
|
|
|
|
extension::built_in::application::get_app_metadata,
|
|
|
|
|
extension::built_in::application::add_app_search_path,
|
|
|
|
|
extension::built_in::application::remove_app_search_path,
|
2025-06-29 10:27:02 +08:00
|
|
|
extension::built_in::application::reindex_applications,
|
2025-07-29 16:30:12 +08:00
|
|
|
extension::quicklink_link_arguments,
|
2025-05-30 17:18:52 +08:00
|
|
|
extension::list_extensions,
|
|
|
|
|
extension::enable_extension,
|
|
|
|
|
extension::disable_extension,
|
|
|
|
|
extension::set_extension_alias,
|
|
|
|
|
extension::register_extension_hotkey,
|
|
|
|
|
extension::unregister_extension_hotkey,
|
|
|
|
|
extension::is_extension_enabled,
|
2025-07-29 10:26:47 +08:00
|
|
|
extension::third_party::install::store::search_extension,
|
2025-08-05 18:08:00 +08:00
|
|
|
extension::third_party::install::store::extension_detail,
|
2025-07-29 10:26:47 +08:00
|
|
|
extension::third_party::install::store::install_extension_from_store,
|
|
|
|
|
extension::third_party::install::local_extension::install_local_extension,
|
2025-07-09 16:28:59 +08:00
|
|
|
extension::third_party::uninstall_extension,
|
2025-11-02 10:59:29 +08:00
|
|
|
extension::is_extension_compatible,
|
feat: new extension type View (#894)
This commit introduces a new extension type View, which enables developers
to implement extensions with GUI. It is implemented using iframe, developers
can specify the path to the HTML file in the `Extension.page` field, then
Coco will load and render that page when the extension gets opened.
coco-api
We provide a TypeScript library [1] that will contain the APIs developers
need to make the experience better.
We start from file system APIs. Since the embedded HTML page will be loaded
by WebView, which has no access to the local file system, we provide APIs
to bridge that gap. Currently, `fs:read_dir()` is the only API we implemented, more
will come soon.
Permission
As View extensions run user-provided code, we introduce a permision
mechanism to sandbox the code. Developers must manually specify the
permission their extension needs in the "plugin.json" file, e,g.:
"permissions": {
"fs": [
{ "path": "/Users/foo/Downloads", "access": ["read", "write"] },
{ "path": "/Users/foo/Documents", "access": ["read"] }
],
"http": [
{ "host": "api.github.com" }
],
"api": ["fs:read_dir"]
}
Currently, both fs and api permissions are implemented. Permission checks
apply only to View extensions for now; Command extensions will support
them in the future.
[1]: https://github.com/infinilabs/coco-api
2025-09-25 11:12:29 +08:00
|
|
|
extension::api::apis,
|
|
|
|
|
extension::api::fs::read_dir,
|
2025-05-15 09:17:03 +08:00
|
|
|
settings::set_allow_self_signature,
|
|
|
|
|
settings::get_allow_self_signature,
|
2025-11-17 18:35:30 +08:00
|
|
|
settings::set_local_query_source_weight,
|
|
|
|
|
settings::get_local_query_source_weight,
|
2025-05-30 17:18:52 +08:00
|
|
|
assistant::ask_ai,
|
|
|
|
|
crate::common::document::open,
|
2025-07-16 09:11:53 +08:00
|
|
|
extension::built_in::file_search::config::get_file_system_config,
|
|
|
|
|
extension::built_in::file_search::config::set_file_system_config,
|
2025-07-10 10:16:51 +08:00
|
|
|
server::synthesize::synthesize,
|
2025-07-21 20:39:16 +08:00
|
|
|
util::file::get_file_icon,
|
2025-08-18 15:38:13 +08:00
|
|
|
setup::backend_setup,
|
|
|
|
|
util::app_lang::update_app_lang,
|
feat: new extension type View (#894)
This commit introduces a new extension type View, which enables developers
to implement extensions with GUI. It is implemented using iframe, developers
can specify the path to the HTML file in the `Extension.page` field, then
Coco will load and render that page when the extension gets opened.
coco-api
We provide a TypeScript library [1] that will contain the APIs developers
need to make the experience better.
We start from file system APIs. Since the embedded HTML page will be loaded
by WebView, which has no access to the local file system, we provide APIs
to bridge that gap. Currently, `fs:read_dir()` is the only API we implemented, more
will come soon.
Permission
As View extensions run user-provided code, we introduce a permision
mechanism to sandbox the code. Developers must manually specify the
permission their extension needs in the "plugin.json" file, e,g.:
"permissions": {
"fs": [
{ "path": "/Users/foo/Downloads", "access": ["read", "write"] },
{ "path": "/Users/foo/Documents", "access": ["read"] }
],
"http": [
{ "host": "api.github.com" }
],
"api": ["fs:read_dir"]
}
Currently, both fs and api permissions are implemented. Permission checks
apply only to View extensions for now; Command extensions will support
them in the future.
[1]: https://github.com/infinilabs/coco-api
2025-09-25 11:12:29 +08:00
|
|
|
util::path::path_absolute,
|
2025-11-27 10:12:49 +08:00
|
|
|
util::logging::app_log_dir,
|
|
|
|
|
selection_monitor::set_selection_enabled,
|
|
|
|
|
selection_monitor::get_selection_enabled,
|
2024-11-30 15:29:00 +08:00
|
|
|
])
|
|
|
|
|
.setup(|app| {
|
2025-06-09 14:46:06 +08:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
{
|
|
|
|
|
log::trace!("hiding Dock icon on macOS");
|
|
|
|
|
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
|
|
|
|
log::trace!("Dock icon should be hidden now");
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 15:33:30 +08:00
|
|
|
/* ----------- This code must be executed on the main thread and must not be relocated. ----------- */
|
|
|
|
|
let app_handle = app.app_handle();
|
|
|
|
|
let main_window = app_handle.get_webview_window(MAIN_WINDOW_LABEL).unwrap();
|
|
|
|
|
let settings_window = app_handle
|
|
|
|
|
.get_webview_window(SETTINGS_WINDOW_LABEL)
|
|
|
|
|
.unwrap();
|
|
|
|
|
let check_window = app_handle.get_webview_window(CHECK_WINDOW_LABEL).unwrap();
|
2025-06-19 20:58:54 +08:00
|
|
|
setup::default(
|
2025-08-13 15:33:30 +08:00
|
|
|
app_handle,
|
2025-06-19 20:58:54 +08:00
|
|
|
main_window.clone(),
|
|
|
|
|
settings_window.clone(),
|
|
|
|
|
check_window.clone(),
|
|
|
|
|
);
|
2025-08-13 15:33:30 +08:00
|
|
|
/* ----------- This code must be executed on the main thread and must not be relocated. ----------- */
|
2025-02-06 11:45:37 +08:00
|
|
|
|
2024-11-30 15:29:00 +08:00
|
|
|
Ok(())
|
2025-02-12 16:15:55 +08:00
|
|
|
})
|
|
|
|
|
.on_window_event(|window, event| match event {
|
|
|
|
|
WindowEvent::CloseRequested { api, .. } => {
|
2025-05-17 12:01:18 +08:00
|
|
|
//dbg!("Close requested event received");
|
2025-02-12 16:15:55 +08:00
|
|
|
window.hide().unwrap();
|
|
|
|
|
api.prevent_close();
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
})
|
|
|
|
|
.build(ctx)
|
2024-10-13 17:41:16 +08:00
|
|
|
.expect("error while running tauri application");
|
2025-05-15 09:17:03 +08:00
|
|
|
|
2025-02-09 21:51:03 +08:00
|
|
|
app.run(|app_handle, event| match event {
|
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
|
tauri::RunEvent::Reopen {
|
|
|
|
|
has_visible_windows,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2025-05-17 12:01:18 +08:00
|
|
|
// dbg!(
|
|
|
|
|
// "Reopen event received: has_visible_windows = {}",
|
|
|
|
|
// has_visible_windows
|
|
|
|
|
// );
|
2025-02-09 21:51:03 +08:00
|
|
|
if has_visible_windows {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
let _ = app_handle;
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-10-13 17:41:16 +08:00
|
|
|
}
|
2024-11-30 15:29:00 +08:00
|
|
|
|
2025-07-29 21:55:03 +08:00
|
|
|
pub async fn init(app_handle: &AppHandle) {
|
2025-02-06 11:45:37 +08:00
|
|
|
// Await the async functions to load the servers and tokens
|
|
|
|
|
if let Err(err) = load_or_insert_default_server(app_handle).await {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to load servers: {}", err);
|
2025-02-06 11:45:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Err(err) = load_servers_token(app_handle).await {
|
2025-05-20 15:10:27 +08:00
|
|
|
log::error!("Failed to load server tokens: {}", err);
|
2025-02-06 11:45:37 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-21 20:39:16 +08:00
|
|
|
let coco_servers = server::servers::get_all_servers().await;
|
2025-02-07 16:31:05 +08:00
|
|
|
|
|
|
|
|
// Get the registry from Tauri's state
|
2025-03-24 14:55:35 +08:00
|
|
|
// let registry: State<SearchSourceRegistry> = app_handle.state::<SearchSourceRegistry>();
|
2025-02-07 16:31:05 +08:00
|
|
|
|
|
|
|
|
for server in coco_servers {
|
2025-03-11 10:36:42 +08:00
|
|
|
crate::server::servers::try_register_server_to_search_source(app_handle.clone(), &server)
|
|
|
|
|
.await;
|
2025-02-07 16:31:05 +08:00
|
|
|
}
|
2025-03-06 20:11:17 +08:00
|
|
|
|
2025-11-25 10:20:33 +08:00
|
|
|
/*
|
|
|
|
|
* Start the background heartbeat worker here after setting up Coco server
|
|
|
|
|
* storage and SearchSourceRegistry.
|
|
|
|
|
*/
|
|
|
|
|
start_bg_heartbeat_worker(app_handle.clone());
|
|
|
|
|
|
2025-06-09 14:46:06 +08:00
|
|
|
extension::built_in::pizza_engine_runtime::start_pizza_engine_runtime().await;
|
2025-01-02 14:41:54 +08:00
|
|
|
}
|
2024-11-30 15:29:00 +08:00
|
|
|
|
2025-03-07 12:42:55 +08:00
|
|
|
#[tauri::command]
|
2025-07-29 21:55:03 +08:00
|
|
|
async fn show_coco(app_handle: AppHandle) {
|
2025-06-13 18:06:21 +08:00
|
|
|
if let Some(window) = app_handle.get_webview_window(MAIN_WINDOW_LABEL) {
|
2025-03-24 17:19:19 +08:00
|
|
|
move_window_to_active_monitor(&window);
|
|
|
|
|
|
2025-10-20 15:53:48 +08:00
|
|
|
cfg_if::cfg_if! {
|
|
|
|
|
if #[cfg(target_os = "macos")] {
|
|
|
|
|
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).unwrap();
|
|
|
|
|
|
|
|
|
|
panel.show_and_make_key();
|
|
|
|
|
}).unwrap();
|
|
|
|
|
} else {
|
|
|
|
|
let _ = window.show();
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-05-07 18:09:19 +08:00
|
|
|
|
|
|
|
|
let _ = app_handle.emit("show-coco", ());
|
2025-03-24 17:19:19 +08:00
|
|
|
}
|
2024-12-24 18:24:53 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-24 17:19:19 +08:00
|
|
|
#[tauri::command]
|
2025-10-20 15:53:48 +08:00
|
|
|
async fn hide_coco(app_handle: AppHandle) {
|
|
|
|
|
cfg_if::cfg_if! {
|
|
|
|
|
if #[cfg(target_os = "macos")] {
|
|
|
|
|
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();
|
2025-03-24 17:19:19 +08:00
|
|
|
} else {
|
2025-10-20 15:53:48 +08:00
|
|
|
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.");
|
|
|
|
|
}
|
2025-03-24 17:19:19 +08:00
|
|
|
}
|
2025-10-20 15:53:48 +08:00
|
|
|
};
|
2024-12-17 11:20:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-29 21:55:03 +08:00
|
|
|
fn move_window_to_active_monitor(window: &WebviewWindow) {
|
2025-11-19 09:39:36 +08:00
|
|
|
let scale_factor = window.scale_factor().unwrap();
|
|
|
|
|
|
|
|
|
|
let point = window.cursor_position().unwrap();
|
|
|
|
|
|
|
|
|
|
let LogicalPosition { x, y } = point.to_logical(scale_factor);
|
|
|
|
|
|
|
|
|
|
match window.monitor_from_point(x, y) {
|
|
|
|
|
Ok(Some(monitor)) => {
|
|
|
|
|
if let Some(name) = monitor.name() {
|
|
|
|
|
let previous_monitor_name = PREVIOUS_MONITOR_NAME.lock().unwrap();
|
|
|
|
|
if let Some(ref prev_name) = *previous_monitor_name {
|
|
|
|
|
if name.to_string() == *prev_name {
|
|
|
|
|
log::debug!("Currently on the same monitor");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-11 14:53:46 +08:00
|
|
|
|
|
|
|
|
let monitor_position = monitor.position();
|
|
|
|
|
let monitor_size = monitor.size();
|
|
|
|
|
|
2025-11-19 09:39:36 +08:00
|
|
|
// Current window size for horizontal centering
|
|
|
|
|
let window_size = match window.inner_size() {
|
|
|
|
|
Ok(size) => size,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
log::error!("Failed to get window size: {}", e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let window_width = window_size.width as i32;
|
|
|
|
|
|
|
|
|
|
// Horizontal center uses actual width, vertical center uses 590 baseline
|
|
|
|
|
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_CENTER_BASELINE_HEIGHT) / 2;
|
|
|
|
|
|
|
|
|
|
if let Err(e) = window.set_position(PhysicalPosition::new(window_x, window_y)) {
|
|
|
|
|
log::error!("Failed to move window: {}", e);
|
|
|
|
|
}
|
2025-02-11 14:53:46 +08:00
|
|
|
|
2025-11-19 09:39:36 +08:00
|
|
|
if let Some(name) = monitor.name() {
|
|
|
|
|
log::debug!("Window moved to monitor: {}", name);
|
|
|
|
|
let mut previous_monitor = PREVIOUS_MONITOR_NAME.lock().unwrap();
|
|
|
|
|
*previous_monitor = Some(name.to_string());
|
2025-02-25 16:22:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-19 09:39:36 +08:00
|
|
|
Ok(None) => {
|
|
|
|
|
log::error!("No monitor found at the specified point");
|
|
|
|
|
}
|
2025-02-11 14:53:46 +08:00
|
|
|
Err(e) => {
|
2025-11-19 09:39:36 +08:00
|
|
|
log::error!("Failed to get monitor from point: {}", e);
|
2025-02-11 14:53:46 +08:00
|
|
|
}
|
2025-02-25 16:22:22 +08:00
|
|
|
}
|
2025-02-11 14:53:46 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-06 17:50:44 +08:00
|
|
|
#[tauri::command]
|
2025-07-22 14:43:27 +08:00
|
|
|
async fn get_app_search_source(app_handle: AppHandle) -> Result<(), String> {
|
2025-03-06 17:50:44 +08:00
|
|
|
let _ = server::connector::refresh_all_connectors(&app_handle).await;
|
|
|
|
|
let _ = server::datasource::refresh_all_datasources(&app_handle).await;
|
2025-03-06 20:11:17 +08:00
|
|
|
|
|
|
|
|
Ok(())
|
2025-03-06 17:50:44 +08:00
|
|
|
}
|
2025-03-07 12:42:55 +08:00
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn show_settings(app_handle: AppHandle) {
|
2025-06-13 18:06:21 +08:00
|
|
|
log::debug!("settings menu item was clicked");
|
|
|
|
|
let window = app_handle
|
|
|
|
|
.get_webview_window(SETTINGS_WINDOW_LABEL)
|
|
|
|
|
.expect("we have a settings window");
|
|
|
|
|
|
|
|
|
|
window.show().unwrap();
|
|
|
|
|
window.unminimize().unwrap();
|
|
|
|
|
window.set_focus().unwrap();
|
2025-04-16 19:06:31 +08:00
|
|
|
}
|
2025-05-07 18:09:19 +08:00
|
|
|
|
2025-06-19 20:58:54 +08:00
|
|
|
#[tauri::command]
|
|
|
|
|
async fn show_check(app_handle: AppHandle) {
|
|
|
|
|
log::debug!("check menu item was clicked");
|
|
|
|
|
let window = app_handle
|
|
|
|
|
.get_webview_window(CHECK_WINDOW_LABEL)
|
|
|
|
|
.expect("we have a check window");
|
|
|
|
|
|
|
|
|
|
window.show().unwrap();
|
|
|
|
|
window.unminimize().unwrap();
|
|
|
|
|
window.set_focus().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
async fn hide_check(app_handle: AppHandle) {
|
|
|
|
|
log::debug!("check window was closed");
|
|
|
|
|
let window = &app_handle
|
|
|
|
|
.get_webview_window(CHECK_WINDOW_LABEL)
|
|
|
|
|
.expect("we have a check window");
|
|
|
|
|
|
|
|
|
|
window.hide().unwrap();
|
|
|
|
|
}
|