From 457722dc3a6251b1f8f6e76750a41530795c47bb Mon Sep 17 00:00:00 2001
From: Hikari
Date: Sun, 25 Jan 2026 13:38:30 -0800
Subject: [PATCH] feat: add minimize to system tray option
Add ability to minimize Hikari to the system tray when closing the window
instead of fully exiting. When enabled, clicking the close button hides
the window and shows a tray icon with "Show Hikari" and "Quit" options.
- Add tray module with system tray setup and menu handling
- Add minimize_to_tray config option in settings
- Handle window close event to hide instead of close when enabled
- Add tray icon click handler to restore window
---
src-tauri/Cargo.lock | 1 +
src-tauri/Cargo.toml | 2 +-
src-tauri/capabilities/default.json | 3 +-
src-tauri/src/config.rs | 6 +++
src-tauri/src/lib.rs | 24 +++++++++
src-tauri/src/tray.rs | 68 +++++++++++++++++++++++++
src-tauri/tauri.conf.json | 6 +++
src/lib/components/ConfigSidebar.svelte | 16 ++++++
src/lib/components/StatusBar.svelte | 1 +
src/lib/stores/config.ts | 2 +
10 files changed, 127 insertions(+), 2 deletions(-)
create mode 100644 src-tauri/src/tray.rs
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::(&content) {
+ return config.minimize_to_tray;
+ }
+ }
+ }
+
+ false
+}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index b529bcc..368ea33 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -22,6 +22,12 @@
],
"security": {
"csp": null
+ },
+ "trayIcon": {
+ "id": "main",
+ "iconPath": "icons/32x32.png",
+ "iconAsTemplate": false,
+ "tooltip": "Hikari - Claude Code Assistant"
}
},
"bundle": {
diff --git a/src/lib/components/ConfigSidebar.svelte b/src/lib/components/ConfigSidebar.svelte
index 8981690..22ce8c1 100644
--- a/src/lib/components/ConfigSidebar.svelte
+++ b/src/lib/components/ConfigSidebar.svelte
@@ -23,6 +23,7 @@
notifications_enabled: true,
notification_volume: 0.7,
always_on_top: false,
+ minimize_to_tray: false,
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
@@ -477,6 +478,21 @@
+
+
+
+
+ Hide to tray instead of closing when you click the X button
+
+
+