tauriのstateと終了処理についてのメモ

tauriアプリを作るときのstateの管理と、終了の制御

tauri

バージョンは以下:

  • rustc 1.69.0
  • tauri 1.4.1

tauriはrustでGUIアプリを作れるフレームワーク。マルチプラットフォーム向けに作ることも可能。

この記事は、tauriを使うときに、mainエントリポイントに書くことが多いであろうtauri::Builderを使ったtauriアプリの初期化について、いくつかメモを残しておきたくて書いたものだ。

具体的には、

  • state(ステート)を追加するmanageメソッド
  • 2通りある終了制御の使用に関する一例

について実装例を記述している。

tauriにstateを追加する

stateとして持たせると、フロント側から呼び出した関数のなかでその構造体Tを取り出すことができる(詳しくは下部リンクを参考)。

tauriで、ある構造体Tをstateとして持たせたい場合、

  • Builder.manage<T>(self, state: T)を使う方法
  • Builder.setup<F>(self, setup: F)の中でsetupの引数のAppインスタンスのmanage<T>(&self, state: T)を呼び出す方法

の2通りの方法がある:

// ...

fn main() {
    tauri::Builder::default()
        // way1. tauriのstateに追加。初期化にtauriのAPIが不要な時はこちらが楽。
        .manage(MyAppManager::new())
        .setup(|app| {
            // tauriのAPIを使ってコンフィグファイルをロードする関数をコール
            let appconfig = match AppConfig::load_config(&app.app_handle()) {
                Ok(ac) => ac,
                Err(e) => {
                    // ファイルが存在しなければデフォルト値を使う
                    eprintln!("{}", e);
                    AppConfig::default()
                }
            };
            // way2. tauriのstateに追加
            app.manage(AppConfigWrapper::from(appconfig));
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            /* some handlers here */
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

AppConfigはアプリの設定を保持している構造体で、jsonファイルとリンクしている。ファイルが存在しなければ、デフォルトの値で構築しておく。ここでは関係性が薄いが&app.app_handle()を利用してtauri推奨のファイルのパスを取得している。

AppConfigWrapperAppConfigを保持するラッパーでMutex<AppConfig>として持つ。これは内部可変性を持たせるため。AppConfigのフィールドをMutexにしてもいいのだが、ファイルのロード・セーブのデシリアライズ/シリアライズの都合で、全体をラップする方が簡単なので、このようにしている。

上記のように、stateで管理したい構造体でtauriのAPIを利用して初期化を行いたい場合は、setupの中で行うのがいいだろう。

tauriのstateについての公式記述は少ない気もするが、この辺りに一部分がある:

Calling Rust from the frontend | Tauri Apps
Tauri provides a simple yet powerful command system for calling Rust functions from your web app.
Manager in tauri - Rust
Manages a running application.

終了時の制御

tauriアプリは特に何もしなければ、ウィンドウを閉じれば自動的にアプリも終了するようになっている。制御コードを追加すれば、この振る舞いを変更できる。

tauriのバックグラウンド処理化の制御については、GUIを隠すか、破棄するかの2通りを選択できるようだ。

ここではウィンドウを閉じる選択をしたとき、ウィンドウのGUIを隠すだけだが、システムトレイから終了するときはバックグラウンド処理を停止・終了させてからアプリを完全に終了させるようにする:

// ...

fn main() {
    tauri::Builder::default()
        // trayモジュールでシステムトレイの初期化・ハンドリングを行っている
        .system_tray(tray::init_system_tray())
        .on_system_tray_event(tray::handle_system_tray_event)
        .on_window_event(|event| match event.event() {
            tauri::WindowEvent::CloseRequested { api, .. } => {
                let manager = event.window().state::<MyAppManager>();
                match manager.get_state() {
                    ManagerState::Running(..) => {
                        // この状態のときは、アプリの終了はせず、タスクトレイにアイコンを残す
                        event.window().hide().unwrap();
                        api.prevent_close();
                        // システムトレイアイコンは既存なのでここでは何もしていない
                    }
                    _ => {}
                }
            }
            _ => {}
        })
        .invoke_handler(tauri::generate_handler![
            /* some handlers here */
        ])
        // runではなく、build
        .build(tauri::generate_context!())
        .expect("error while building tauri application")
        .run(|app_handle, event| match event {
            tauri::RunEvent::ExitRequested { .. } => {
                // app終了時のクリーンアップ。終了時、必ずここは呼ばれる。
                // バックグラウンド処理が動いていれば、on_window_eventの方でGUIが隠され、保持され、ここは呼ばれない。
                let manager = app_handle.state::<MyAppManager>();
                // 停止する
                manager.stop();
                // システムトレイを取得し、破棄する
                match tray::get_system_tray(&app_handle) {
                    Some(tray) => tray.destroy().expect("failed to destroy system tray"),
                    None => {}
                };
            }
            _ => {}
        });
}

MyAppManagerはバックグラウンド処理の管理などを行っていて、その状態はManagerStateで確認できるようにしている。

tauri::WindowEvent::CloseRequestedがウィンドウを閉じたときに呼ばれる。条件によってアプリの終了を抑制している。

tauri::RunEvent::ExitRequestedがアプリの終了開始の直前で呼ばれる。これが呼ばれた時点ですべてのGUIは破棄されているはず。GUIを破棄した状態でバックグラウンド処理をしたいなら、api.prevent_exit();をここで使うようにする(詳しくは下の参考にて)。

公式のガイドは片方ずつ紹介しているが、どちらも併用できるようだ:

System Tray | Tauri Apps
Native application system tray.

おわり

tauriは最終的なアプリサイズが小さくなるから結構好きかもしれない。しかし、開発中のサイズは普通に大きくなるので油断してはいけない。

rustを書くのも結構楽しいし、これは良い。

以上です。

タイトルとURLをコピーしました