ウィンドウを持たない常駐アプリを作る

Recipe ID: menu-014

起動時にウィンドウを表示せず、システムトレイ常駐型のアプリケーションとして動作させる方法を解説します。

Tauri の JavaScript API を利用するためには、WebView(ウィンドウ)が存在している必要があります。そのため、「ウィンドウを表示しない(Hidden)」状態で起動し、JavaScript でトレイアイコンを制御する方法が最も簡単で柔軟です。

前提条件

プラグインの導入

アプリ終了 (exit) コマンドを使用するため、process プラグインを導入します。

npm run tauri add process

src-tauri/src/lib.rs:

.plugin(tauri_plugin_process::init()) を追加します。process プラグインをインストールすると、自動的に挿入されるはずですので、確認してください。

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_process::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Cargo.toml の設定

Tauri v2 ではトレイ機能は Core (本体) に統合されているため、追加のプラグイン(tauri-plugin-tray など)のインストールは不要です。
ただし、src-tauri/Cargo.tomltray-icon 機能を有効にする必要があります。

[dependencies]
tauri = { version = "2", features = ["tray-icon", "image-png"] }

Permissions (権限) の設定

src-tauri/capabilities/default.json に以下を追加します。

{
  "permissions": [
    ...,
    "core:window:allow-hide",
    "core:window:allow-is-visible",
    "core:window:allow-set-focus",
    "core:window:allow-show",
    "core:tray:default",
    "core:menu:default",
    "core:app:allow-default-window-icon",
    "process:default"
  ]
}

設定

tauri.conf.json で非表示設定

src-tauri/tauri.conf.jsonapp.windows 設定で、メインウィンドウの visible プロパティを false に設定します。

{
  "app": {
    "windows": [
      {
        "title": "My Hidden App",
        "label": "main",
        "visible": false 
      }
    ]
  }
}

1. フロントエンドで実装する (TypeScript)

アプリ起動時(JavaScript ロード時)にトレイアイコンを作成します。

import { TrayIcon } from '@tauri-apps/api/tray';
import { Menu } from '@tauri-apps/api/menu';
import { exit } from '@tauri-apps/plugin-process';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { defaultWindowIcon } from '@tauri-apps/api/app';

async function initTray() {
  const icon = await defaultWindowIcon();
  if (!icon) return;

  // メニュー作成
  const menu = await Menu.new({
    items: [
      {
         text: 'ダッシュボードを表示',
         action: async () => {
             const win = getCurrentWindow();
             await win.show();
             await win.setFocus();
         }
      },
      { item: 'Separator' },
      {
         text: '終了',
         action: async () => {
             await exit(0);
         }
      }
    ]
  });

  // トレイ作成
  await TrayIcon.new({
    icon: icon,
    tooltip: '常駐アプリ実行中',
    menu: menu,
    action: async (e) => {
        if (e.type === 'Click' && e.button === 'Left') {
            const win = getCurrentWindow();
            const isVisible = await win.isVisible();
            if (isVisible) {
                await win.hide();
            } else {
                await win.show();
                await win.setFocus();
            }
        }
    }
  });
  
  console.log('Tray initialized');
}

initTray();

2. バックエンドで実装する (Rustのみ)

WebView を使用せず、メモリ使用量を極限まで抑えたい場合は、tauri.conf.jsonwindows を空([])にし、Rustコードのみでトレイを作成します。

use tauri::tray::TrayIconBuilder;
use tauri::menu::{MenuBuilder, MenuItem};

pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            // トレイメニュー
            let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
            let menu = MenuBuilder::new(app)
                .item(&quit_i)
                .build()?;

            let _tray = TrayIconBuilder::with_id("tray")
                .icon(app.default_window_icon().unwrap().clone())
                .menu(&menu)
                .on_menu_event(|app, event| {
                    if event.id() == "quit" {
                        app.exit(0);
                    }
                })
                .build(app)?;

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

補足

* JavaScript API の使用: バックエンドのみの実装(WebViewなし)では、JavaScript API は一切使用できません。
* イベントループ: Rust コードの run メソッドがイベントループを開始するため、ウィンドウがなくてもアプリは終了せず常駐し続けます(tauri-plugin-process 等で明示的に終了しない限り)。