OS 起動時にアプリを自動起動させる

Autostart プラグインの enable() / disable() / isEnabled() でログイン時の自動起動を切り替える。起動引数で自動起動を見分けて画面を出さずに常駐させる方法と、開発ビルドの落とし穴も示す。

システム情報 対象: Tauri 2.x 更新日: 読了目安: 約9分 sys-022
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 2. バックエンドから実装する (Rust)
  4. 起動引数を付けて登録し、自動起動を見分ける
  5. 動作確認
  6. よくあるエラーと対処法
  7. OS ごとの違いと注意点
  8. 関連レシピ

チャットや同期ツールのように裏で動いていてほしいアプリには、設定画面に「ログイン時に起動する」のスイッチを用意するのが定番です。Tauri では公式の Autostart プラグインを使い、JS の enable() / disable() / isEnabled() か、Rust の app.autolaunch() で登録を切り替えます。登録はユーザーごとで、正確には OS の起動時ではなくそのユーザーがログインしたときに起動し、管理者権限は要りません。自動起動のときだけ画面を出さずに常駐させる方法と、開発中に試すときの落とし穴も説明します。

前提条件

プラグインを追加します。Windows / macOS / Linux 用で、iOS / Android では使えません。

npm run tauri add autostart

依存の追加に加えて、lib.rs へのプラグインの登録と、デスクトップ向けの capability ファイル(capabilities/desktop.json)への autostart:default の追加まで行われます。autostart:default は autostart:allow-enable / allow-disable / allow-is-enabled の 3 つをまとめたもので、core:default には含まれません。自分で書く場合は次のようにします。

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "desktop-capability",
  "platforms": ["macOS", "windows", "linux"],
  "windows": ["main"],
  "permissions": ["autostart:default"]
}

platforms でデスクトップに限るのは、モバイル向けのビルドにはこのプラグインが含まれないためです。2 章のように Rust のコマンドで切り替えるなら、JS 側の権限は無しにもできます。

設定ファイルで自動起動を有効にする項目はありません。ユーザーの環境を変える操作なので、初回起動時に黙って enable() せず、ユーザーが選んだときに呼びます。

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

設定画面のチェックボックスに結び付ける例です。表示の元にするのはアプリ側に保存した値ではなく、毎回 isEnabled() で読んだ OS 側の状態です。ユーザーが OS の設定画面から自動起動を外すことがあるためです。失敗したときも見た目が実際とずれないよう、最後に読み直します。

import { enable, disable, isEnabled } from '@tauri-apps/plugin-autostart';

// <input type="checkbox" id="autostart"> を想定
export async function bindAutostartToggle() {
  const toggle = document.querySelector<HTMLInputElement>('#autostart');
  if (!toggle) return;

  toggle.checked = await isEnabled(); // 画面を開くたびに OS 側の状態を読む
  toggle.addEventListener('change', async () => {
    toggle.disabled = true;
    try {
      if (toggle.checked) {
        await enable();
      } else if (await isEnabled()) {
        await disable(); // 登録が無いときに呼ぶと失敗する環境があるので確かめてから
      }
    } catch (e) {
      console.error('自動起動を切り替えられませんでした:', e);
    } finally {
      toggle.checked = await isEnabled(); // 失敗しても実際の状態に合わせ直す
      toggle.disabled = false;
    }
  });
}

2. バックエンドから実装する (Rust)

起動引数を付けて登録し、自動起動を見分ける

「ログイン直後は画面を出さずにトレイに常駐し、自分で起動したときは画面を出す」には、Builder の args() で自動起動のときだけ付く引数を登録し、起動時にその有無を見ます。メインウィンドウは "visible": false にしておき、手動で起動したときだけ show() します。

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

tauri add が lib.rs に書き足した登録の行は消して、次のコードに置き換えます。二重に登録すると先に登録した側の設定が使われ、args() の引数が付かないまま OS に登録されます。

use tauri::Manager;
use tauri_plugin_autostart::ManagerExt;

/// 自動起動のときだけ付ける引数(登録と判定で同じ値を使う)
const AUTOSTART_FLAG: &str = "--autostart";

fn started_by_autostart() -> bool {
    std::env::args().any(|a| a == AUTOSTART_FLAG)
}

/// 画面から「自動起動で立ち上がったか」を問い合わせる
#[tauri::command]
fn launched_by_autostart() -> bool {
    started_by_autostart()
}

/// 自動起動を切り替え、切り替え後の実際の状態を返す
#[tauri::command]
fn set_autostart(app: tauri::AppHandle, enabled: bool) -> Result<bool, String> {
    let autostart = app.autolaunch();
    let current = autostart.is_enabled().map_err(|e| e.to_string())?;
    if enabled && !current {
        autostart.enable().map_err(|e| e.to_string())?;
    } else if !enabled && current {
        autostart.disable().map_err(|e| e.to_string())?;
    }
    autostart.is_enabled().map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            // デスクトップ専用のプラグインなので cfg(desktop) の中で登録する
            #[cfg(desktop)]
            app.handle().plugin(
                tauri_plugin_autostart::Builder::new()
                    .args([AUTOSTART_FLAG])
                    .build(),
            )?;

            // 手動で起動したときだけ画面を出す(自動起動ならトレイに常駐させておく)
            if !started_by_autostart() {
                if let Some(main) = app.get_webview_window("main") {
                    main.show()?;
                }
            }
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![set_autostart, launched_by_autostart])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Rust での入口は app.autolaunch() です(autostart ではありません)。enable() などは JS と同じ働きで、トレイのメニューから切り替えるときに使えます。引数の読み方は sys-013、トレイは menu-009 を参照してください。裏で動いている間にユーザーがアイコンから起動すると 2 つ目のプロセスが立ち上がるので、Single Instance プラグイン で既存のウィンドウを出すのが定番です。

macOS の登録方式(後述)を変えるときは tauri_plugin_autostart::init(MacosLauncher::AppleScript, Some(vec!["--autostart"])) を使います。Builder の macos_launcher() は macOS 向けのビルドにしか無く、Windows や Linux ではコンパイルエラーになります。

JS からは次のように呼びます。この形なら JS 側に autostart の権限は要りません。

import { invoke } from '@tauri-apps/api/core';

const enabled = await invoke<boolean>('set_autostart', { enabled: true });
console.log(`自動起動: ${enabled ? '有効' : '無効'}`);

if (await invoke<boolean>('launched_by_autostart')) {
  console.log('ログイン時に自動で起動しました'); // 起動直後のお知らせを出さない、など
}

動作確認

npm run tauri dev で起動し、チェックボックスをオンにすると isEnabled() が true になり、Windows ならタスクマネージャーの「スタートアップ アプリ」に項目が現れます。自動起動したときの動きは、引数を付けて起動すれば再ログインせずに確かめられます。npm run と tauri dev(cargo 用とアプリ用)がそれぞれ -- を区切りに使うので、3 つ重ねます。

npm run tauri dev -- -- -- --autostart

ウィンドウが表示されず、launched_by_autostart が true を返せば成功です。本当にログイン時に起動するかは、インストーラーで入れたリリースビルドでオンにし、サインアウトしてサインインし直して確かめます。

よくあるエラーと対処法

  • 「autostart.enable not allowed. Permissions associated with this command: autostart:allow-enable, autostart:default」: 呼び出したウィンドウに権限がありません。リリースビルドでは「Command plugin:autostart|enable not allowed by ACL」だけになります。
  • 「state() called before manage() for」で始まるパニック: プラグインを登録していないか、登録より先に app.autolaunch() を呼んでいます。
  • 自動起動したのに --autostart が付いていない: プラグインを二重に登録しています。また、引数は enable() の時点で OS に書き込まれるので、コードの引数を変えたら一度オフにしてからオンにし直します。
  • ログイン時に起動したが画面が真っ白、または接続できない趣旨の表示になる: tauri dev 中にオンにしたため、開発用の実行ファイル(src-tauri/target/debug の中)が登録されています。開発用のビルドは開発サーバーからページを読むため、サーバーが動いていないと表示できません。試した後はオフに戻します。
  • disable() がエラーになる: 登録が無い状態で呼ぶと、環境によっては見つからない趣旨のエラーになります。isEnabled() で確かめてから呼びます。

OS ごとの違いと注意点

  • Windows: 登録されるのは起動中の実行ファイルのパスで、インストール先が変わったらオンにし直す必要があります。NSIS のインストーラーはアンインストール時に productName と同じ名前の登録も消しますが、app_name() で名前を変えると対象外になり、MSI にはこの処理がありません。
  • macOS: 既定の MacosLauncher::LaunchAgent は実行ファイルを直接起動する登録で、args() の引数も渡ります。MacosLauncher::AppleScript はアプリ(.app)をログイン項目に加える方式で、起動引数では見分けられません。
  • Linux: AppImage で動かしているときは AppImage ファイルのパスが登録されます。ファイルを移動すると起動しなくなるので、置き場所を変えたらオンにし直します。
  • iOS / Android: 非対応です。モバイル向けにもビルドするなら、プラグインを使う use やコマンドも #[cfg(desktop)] で囲みます。
  • 共通: 登録名は既定で productName(無ければ Cargo のパッケージ名)です。後から変えると古い名前の登録が残り、isEnabled() は false を返すので、名前は途中で変えない方が無難です。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する