「ウィンドウの閉じるボタン(×ボタン)を押してもアプリを終了せず、トレイに格納(ウィンドウを非表示)する」という、常駐型アプリでよくある挙動を実装する方法を解説します。
Tauri v2 では、JavaScript API (Window.onCloseRequested) を使用して、閉じるイベントをキャンセルし、代わりにウィンドウを隠す処理を記述できます。
前提条件
Cargo.toml の設定
Tauri v2 ではトレイ機能は Core (本体) に統合されているため、追加のプラグイン(tauri-plugin-tray など)のインストールは不要です。
ただし、src-tauri/Cargo.toml で tray-icon 機能を有効にする必要があります。
[dependencies]
tauri = { version = "2", features = ["tray-icon", "image-png"] }
Permissions (権限) の設定
src-tauri/capabilities/default.json に以下の権限を追加します。
{
"permissions": [
...,
"core:window:default",
"core:window:allow-hide",
"core:window:allow-show",
"core:tray:default",
"core:app:allow-default-window-icon"
]
}
1. フロントエンドで実装する (TypeScript)
getCurrentWindow() で現在のウィンドウを取得し、onCloseRequested イベントをリッスンします。
イベント内で event.preventDefault() を呼び出すことで、デフォルトの閉じる動作(アプリ終了/ウィンドウ破棄)を防ぎます。
import { getCurrentWindow } from '@tauri-apps/api/window';
import { TrayIcon } from '@tauri-apps/api/tray';
import { defaultWindowIcon } from '@tauri-apps/api/app';
const appWindow = getCurrentWindow();
// 1. トレイアイコンを作成
const icon = await defaultWindowIcon();
if (icon) {
const tray = await TrayIcon.new({
tooltip: 'My App',
icon: icon, // アプリのデフォルトアイコンを使用
action: async (event) => {
// トレイアイコンクリックでウィンドウの表示/非表示を切り替え
if (event.type === 'Click') {
const visible = await appWindow.isVisible();
if (visible) {
await appWindow.hide();
} else {
await appWindow.show();
await appWindow.setFocus();
}
}
},
});
}
// 2. ウィンドウの「閉じる」ボタンの挙動をオーバーライド
await appWindow.onCloseRequested(async (event) => {
// デフォルトの閉じる動作(アプリ終了/ウィンドウ破棄)をキャンセル
event.preventDefault();
// ウィンドウを非表示にする(トレイに格納されたように見える)
await appWindow.hide();
console.log('Window hidden to tray');
});
2. バックエンドで実装する (Rust) - 推奨
Rust 側では on_window_event を使用して、CloseRequested イベントを捕捉します。フロントエンドよりも確実に動作を保証できます。
use tauri::{
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
Manager, WindowEvent,
};
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let _tray = TrayIconBuilder::with_id("tray")
.icon(app.default_window_icon().unwrap().clone())
.on_tray_icon_event(|tray, event| match event {
TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Up,
..
} => {
let app = tray.app_handle();
if let Some(window) = app.get_webview_window("main") {
if window.is_visible().unwrap_or(false) {
let _ = window.hide();
} else {
let _ = window.show();
let _ = window.set_focus();
}
}
}
_ => {}
})
.build(app)?;
Ok(())
})
.on_window_event(|window, event| match event {
WindowEvent::CloseRequested { api, .. } => {
// デフォルトの閉じる動作を防止
api.prevent_close();
// ウィンドウを隠す
window.hide().unwrap();
}
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
補足
* アプリの完全終了: この実装を行うと、ウィンドウの×ボタンでアプリが終了しなくなります。トレイメニューなどに必ず「終了(Quit)」項目を用意し、そこで process.exit() や app.exit() を呼び出すようにしてください。