diff --git a/.github/workflows/template-tauri-build-linux-x64-external.yml b/.github/workflows/template-tauri-build-linux-x64-external.yml index 3a243530e..59c14a3d6 100644 --- a/.github/workflows/template-tauri-build-linux-x64-external.yml +++ b/.github/workflows/template-tauri-build-linux-x64-external.yml @@ -64,7 +64,7 @@ jobs: - name: Install Tauri dependencies run: | sudo apt update - sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 libayatana-appindicator3-dev - name: Update app version run: | diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml index ef9054be1..3b9daebb5 100644 --- a/.github/workflows/template-tauri-build-linux-x64.yml +++ b/.github/workflows/template-tauri-build-linux-x64.yml @@ -101,7 +101,7 @@ jobs: - name: Install Tauri dependencies run: | sudo apt update - sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 libayatana-appindicator3-dev - name: Update app version base public_provider run: | diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 12b1ddaaf..c8fd66b30 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -517,41 +517,41 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5357d&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=fcb200&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/95e2ec1f1213d604730f5c9c381c80840402b00a9649039d1a9754ee3efa13e224e4ca39ea094aab5751f3f2ace1860c7640769e66b191b8c56998fd5f2ba5b9 + checksum: 10c0/603e79794614f861a9cf5693a4bbc480a62242309a6cb94a97c81e31518032c7462b2edad93a4380a18110f817ab15d85dc91a7924fd6e103a3462f6915ee368 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5357d&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=fcb200&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/95e2ec1f1213d604730f5c9c381c80840402b00a9649039d1a9754ee3efa13e224e4ca39ea094aab5751f3f2ace1860c7640769e66b191b8c56998fd5f2ba5b9 + checksum: 10c0/603e79794614f861a9cf5693a4bbc480a62242309a6cb94a97c81e31518032c7462b2edad93a4380a18110f817ab15d85dc91a7924fd6e103a3462f6915ee368 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5357d&locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=fcb200&locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/95e2ec1f1213d604730f5c9c381c80840402b00a9649039d1a9754ee3efa13e224e4ca39ea094aab5751f3f2ace1860c7640769e66b191b8c56998fd5f2ba5b9 + checksum: 10c0/603e79794614f861a9cf5693a4bbc480a62242309a6cb94a97c81e31518032c7462b2edad93a4380a18110f817ab15d85dc91a7924fd6e103a3462f6915ee368 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fllamacpp-extension%40workspace%3Allamacpp-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5357d&locator=%40janhq%2Fllamacpp-extension%40workspace%3Allamacpp-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=fcb200&locator=%40janhq%2Fllamacpp-extension%40workspace%3Allamacpp-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/95e2ec1f1213d604730f5c9c381c80840402b00a9649039d1a9754ee3efa13e224e4ca39ea094aab5751f3f2ace1860c7640769e66b191b8c56998fd5f2ba5b9 + checksum: 10c0/603e79794614f861a9cf5693a4bbc480a62242309a6cb94a97c81e31518032c7462b2edad93a4380a18110f817ab15d85dc91a7924fd6e103a3462f6915ee368 languageName: node linkType: hard diff --git a/src-tauri/.cargo/config.toml b/src-tauri/.cargo/config.toml index 3c45f4de8..830adb1f1 100644 --- a/src-tauri/.cargo/config.toml +++ b/src-tauri/.cargo/config.toml @@ -1,4 +1,5 @@ [env] # workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests # see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864 -__TAURI_WORKSPACE__ = "true" \ No newline at end of file +__TAURI_WORKSPACE__ = "true" +ENABLE_SYSTEM_TRAY_ICON = "false" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index efd69e9bf..353c3efff 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,6 +20,7 @@ default = [ "tauri/x11", "tauri/protocol-asset", "tauri/macos-private-api", + "tauri/tray-icon", "tauri/test", ] test-tauri = [ @@ -27,6 +28,7 @@ test-tauri = [ "tauri/x11", "tauri/protocol-asset", "tauri/macos-private-api", + "tauri/tray-icon", "tauri/test", ] diff --git a/src-tauri/src/core/mcp/commands.rs b/src-tauri/src/core/mcp/commands.rs index 48c7f88a1..3bef12149 100644 --- a/src-tauri/src/core/mcp/commands.rs +++ b/src-tauri/src/core/mcp/commands.rs @@ -296,7 +296,6 @@ pub async fn cancel_tool_call( pub async fn get_mcp_configs(app: AppHandle) -> Result { let mut path = get_jan_data_folder_path(app); path.push("mcp_config.json"); - log::info!("read mcp configs, path: {:?}", path); // Create default empty config if file doesn't exist if !path.exists() { diff --git a/src-tauri/src/core/setup.rs b/src-tauri/src/core/setup.rs index 0168e8e57..c88e62a8d 100644 --- a/src-tauri/src/core/setup.rs +++ b/src-tauri/src/core/setup.rs @@ -5,7 +5,11 @@ use std::{ path::PathBuf, }; use tar::Archive; -use tauri::{App, Emitter, Manager}; +use tauri::{ + menu::{Menu, MenuItem, PredefinedMenuItem}, + tray::{MouseButton, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent}, + App, Emitter, Manager, +}; use tauri_plugin_store::StoreExt; // use tokio::sync::Mutex; // use tokio::time::{sleep, Duration}; // Using tokio::sync::Mutex @@ -82,7 +86,6 @@ pub fn install_extensions(app: tauri::AppHandle, force: bool) -> Result<(), Stri let path = entry.path(); if path.extension().map_or(false, |ext| ext == "tgz") { - log::info!("Installing extension from {:?}", path); let tar_gz = File::open(&path).map_err(|e| e.to_string())?; let gz_decoder = GzDecoder::new(tar_gz); let mut archive = Archive::new(gz_decoder); @@ -207,3 +210,46 @@ pub fn setup_mcp(app: &App) { .unwrap(); }); } + +pub fn setup_tray(app: &App) -> tauri::Result { + let show_i = MenuItem::with_id(app.handle(), "open", "Open Jan", true, None::<&str>)?; + let quit_i = MenuItem::with_id(app.handle(), "quit", "Quit", true, None::<&str>)?; + let separator_i = PredefinedMenuItem::separator(app.handle())?; + let menu = Menu::with_items(app.handle(), &[&show_i, &separator_i, &quit_i])?; + TrayIconBuilder::with_id("tray") + .icon(app.default_window_icon().unwrap().clone()) + .menu(&menu) + .show_menu_on_left_click(false) + .on_tray_icon_event(|tray, event| match event { + TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } => { + // let's show and focus the main window when the tray is clicked + let app = tray.app_handle(); + if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); + let _ = window.show(); + let _ = window.set_focus(); + } + } + _ => { + log::debug!("unhandled event {event:?}"); + } + }) + .on_menu_event(|app, event| match event.id.as_ref() { + "open" => { + let window = app.get_webview_window("main").unwrap(); + window.show().unwrap(); + window.set_focus().unwrap(); + } + "quit" => { + app.exit(0); + } + other => { + println!("menu item {} not handled", other); + } + }) + .build(app) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 10a9d7556..59b1db5c0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,10 +8,12 @@ use core::{ }; use jan_utils::generate_app_token; use std::{collections::HashMap, sync::Arc}; -use tauri::{Emitter, Manager, RunEvent}; +use tauri::{Manager, RunEvent}; use tauri_plugin_llamacpp::cleanup_llama_processes; use tokio::sync::Mutex; +use crate::core::setup::setup_tray; + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let mut builder = tauri::Builder::default(); @@ -108,6 +110,21 @@ pub fn run() { server_handle: Arc::new(Mutex::new(None)), tool_call_cancellations: Arc::new(Mutex::new(HashMap::new())), }) + .on_window_event(|window, event| match event { + tauri::WindowEvent::CloseRequested { api, .. } => { + if option_env!("ENABLE_SYSTEM_TRAY_ICON").unwrap_or("false") == "true" { + #[cfg(target_os = "macos")] + window + .app_handle() + .set_activation_policy(tauri::ActivationPolicy::Accessory) + .unwrap(); + + window.hide().unwrap(); + api.prevent_close(); + } + } + _ => {} + }) .setup(|app| { app.handle().plugin( tauri_plugin_log::Builder::default() @@ -129,6 +146,11 @@ pub fn run() { log::error!("Failed to install extensions: {}", e); } + if option_env!("ENABLE_SYSTEM_TRAY_ICON").unwrap_or("false") == "true" { + log::info!("Enabling system tray icon"); + let _ = setup_tray(app); + } + #[cfg(any(windows, target_os = "linux"))] { use tauri_plugin_deep_link::DeepLinkExt; @@ -146,14 +168,12 @@ pub fn run() { // This is called when the app is actually exiting (e.g., macOS dock quit) // We can't prevent this, so run cleanup quickly let app_handle = app.clone(); + // Hide window immediately + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.hide(); + } tokio::task::block_in_place(|| { tauri::async_runtime::block_on(async { - // Hide window immediately - if let Some(window) = app_handle.get_webview_window("main") { - let _ = window.hide(); - let _ = window.emit("kill-mcp-servers", ()); - } - // Quick cleanup with shorter timeout let state = app_handle.state::(); let _ = clean_up_mcp_servers(state).await;