diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 3ed5bd2..8f4fc69 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -4180,6 +4180,7 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
+ "image",
"jni",
"libc",
"log",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 5ac4de4..ede8d32 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -13,7 +13,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
-tauri = { version = "2", features = [] }
+tauri = { version = "2", features = ["tray-icon", "image-png"] }
tauri-plugin-dialog = "2"
tauri-plugin-opener = "2"
tauri-plugin-shell = "2"
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index 5e067ad..2bf139b 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -15,6 +15,7 @@
"notification:allow-request-permission",
"notification:allow-notify",
"clipboard-manager:default",
- "clipboard-manager:allow-read-image"
+ "clipboard-manager:allow-read-image",
+ "core:tray:default"
]
}
diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs
index d6f6fae..6800346 100644
--- a/src-tauri/src/config.rs
+++ b/src-tauri/src/config.rs
@@ -70,6 +70,9 @@ pub struct HikariConfig {
#[serde(default = "default_font_size")]
pub font_size: u32,
+
+ #[serde(default)]
+ pub minimize_to_tray: bool,
}
impl Default for HikariConfig {
@@ -89,6 +92,7 @@ impl Default for HikariConfig {
update_checks_enabled: true,
character_panel_width: None,
font_size: 14,
+ minimize_to_tray: false,
}
}
}
@@ -140,6 +144,7 @@ mod tests {
assert!(config.update_checks_enabled);
assert!(config.character_panel_width.is_none());
assert_eq!(config.font_size, 14);
+ assert!(!config.minimize_to_tray);
}
#[test]
@@ -159,6 +164,7 @@ mod tests {
update_checks_enabled: true,
character_panel_width: Some(400),
font_size: 16,
+ minimize_to_tray: true,
};
let json = serde_json::to_string(&config).unwrap();
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index f635d4d..8957ffa 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -5,6 +5,7 @@ mod config;
mod notifications;
mod stats;
mod temp_manager;
+mod tray;
mod types;
mod vbs_notification;
mod windows_toast;
@@ -15,7 +16,9 @@ use bridge_manager::create_shared_bridge_manager;
use commands::load_saved_achievements;
use commands::*;
use notifications::*;
+use tauri::Manager;
use temp_manager::create_shared_temp_manager;
+use tray::{setup_tray, should_minimize_to_tray};
use vbs_notification::*;
use windows_toast::*;
use wsl_notifications::*;
@@ -47,6 +50,27 @@ pub fn run() {
}
}
+ // Set up system tray
+ if let Err(e) = setup_tray(app.handle()) {
+ eprintln!("Failed to set up system tray: {}", e);
+ }
+
+ // Handle window close event for minimize to tray
+ let main_window = app.get_webview_window("main").unwrap();
+ main_window.on_window_event({
+ let app_handle = app.handle().clone();
+ move |event| {
+ if let tauri::WindowEvent::CloseRequested { api, .. } = event {
+ if should_minimize_to_tray(&app_handle) {
+ api.prevent_close();
+ if let Some(window) = app_handle.get_webview_window("main") {
+ let _ = window.hide();
+ }
+ }
+ }
+ }
+ });
+
Ok(())
})
.invoke_handler(tauri::generate_handler![
diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs
new file mode 100644
index 0000000..9f87b0b
--- /dev/null
+++ b/src-tauri/src/tray.rs
@@ -0,0 +1,68 @@
+use tauri::{
+ menu::{Menu, MenuItem},
+ tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
+ AppHandle, Manager,
+};
+
+use crate::config::HikariConfig;
+
+pub fn setup_tray(app: &AppHandle) -> tauri::Result<()> {
+ let show_item = MenuItem::with_id(app, "show", "Show Hikari", true, None::<&str>)?;
+ let quit_item = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
+
+ let menu = Menu::with_items(app, &[&show_item, &quit_item])?;
+
+ let _tray = TrayIconBuilder::with_id("main")
+ .icon(app.default_window_icon().unwrap().clone())
+ .menu(&menu)
+ .tooltip("Hikari - Claude Code Assistant")
+ .on_menu_event(|app, event| match event.id.as_ref() {
+ "show" => {
+ if let Some(window) = app.get_webview_window("main") {
+ let _ = window.show();
+ let _ = window.unminimize();
+ let _ = window.set_focus();
+ }
+ }
+ "quit" => {
+ app.exit(0);
+ }
+ _ => {}
+ })
+ .on_tray_icon_event(|tray, event| {
+ if let TrayIconEvent::Click {
+ button: MouseButton::Left,
+ button_state: MouseButtonState::Up,
+ ..
+ } = event
+ {
+ let app = tray.app_handle();
+ if let Some(window) = app.get_webview_window("main") {
+ let _ = window.show();
+ let _ = window.unminimize();
+ let _ = window.set_focus();
+ }
+ }
+ })
+ .build(app)?;
+
+ Ok(())
+}
+
+pub fn should_minimize_to_tray(app: &AppHandle) -> bool {
+ let config_path = app
+ .path()
+ .app_config_dir()
+ .ok()
+ .map(|p| p.join("hikari-config.json"));
+
+ if let Some(path) = config_path {
+ if let Ok(content) = std::fs::read_to_string(&path) {
+ if let Ok(config) = serde_json::from_str::
+ Hide to tray instead of closing when you click the X button +
+