tauriでタスクトレイにアイコンを表示する

# tauriでタスクトレイにアイコンを表示する
[公式ドキュメント](https://v2.tauri.app/learn/system-tray/#add-a-menu)のコードをそのまま貼り付けるといくらか警告が出たので修正したものをこちらにメモしておきます．

TypeScript（JavaScript）でも同様のことができるみたいですが，ここではRustで実装します．

## 環境
執筆時の環境は以下の通りです．

| 項目 | 値 |
--- | ---
| OS | Mac OS Seqoia 15.4|
| RAM | 16GB |
| cargo | 1.85.1 |

~~Mac OS なのでタスクトレイではなくメニューバーかもしれない．~~

## 準備
まずは`src-tauri/Cargo.toml`の`dependencies`に`tray-icon`を追加します．
```diff_toml:Cargo.toml
[dependencies]
+tauri = { version = "2.0.0", features = [ "tray-icon" ] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```

## アイコンをタスクトレイに表示する
`src-tauri/src/lib.rs`を編集します．
`use`と`tauri::Builder::default()`に`setup`を追加します．
`setup`の中の`_tray`は，ドキュメントでは`tray`として宣言していますが，使用しない変数だと警告が出るので頭に`_`を付けています．

```diff_rust:lib.rs
+use tauri::{
+   tray::TrayIconBuilder,
+};

// いろいろ

pub fn run() {
    tauri::Builder::default()
+       .setup(|app| {
+           let _tray = TrayIconBuilder::new()
+               .icon(app.default_window_icon().unwrap().clone())
+               .build(app)?;
+           Ok(())
+       })
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

## タスクトレイのアイコンにメニューをつける
useと`setup`にそれぞれ追加します．
ドキュメントでは`setup`の中に`.menu_on_left_click(true)`を追加していますが，現在は非推奨のメソッド（deprecated method）らしいので`.show_menu_on_left_click(true)`を使います．
```diff_rust:lib.rs
use tauri::{
    menu::{Menu, MenuItem},
+   tray::TrayIconBuilder,
};

// いろいろ

pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
+            let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
+            let menu = Menu::with_items(app, &[&quit_i])?;
             let _tray = TrayIconBuilder::new()
                .icon(app.default_window_icon().unwrap().clone())
+               .menu(&menu)
+               .show_menu_on_left_click(true)
                .build(app)?;
            Ok(())
        })
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

## メニューからプログラムを終了できるようにする
編集するのは`run`の中の`setup`だけです．
ここはドキュメント通りです．

```diff_rust:lib.rs
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
            let menu = Menu::with_items(app, &[&quit_i])?;
            let _tray = TrayIconBuilder::new()
                .icon(app.default_window_icon().unwrap().clone())
                .menu(&menu)
                .show_menu_on_left_click(true)
+               .on_menu_event(|app, event| match event.id.as_ref() {
+                   "quit" => {
+                       println!("quit menu item was clicked");
+                       app.exit(0);
+                   }
+                   _ => {
+                       println!("menu item {:?} not handled", event.id);
+                   }
+               })
                .build(app)?;
            Ok(())
        })
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

```

## 任意のアイコンに変更する
アイコンを表示したとき，以下の命令を追加していました．
```diff_rust:lib.rust

let _tray = TrayIconBuilder::new()
+   .icon(app.default_window_icon().unwrap().clone())
```
ここで，[TrayIconBuilder構造体のドキュメント](https://docs.rs/tauri/2.4.1/tauri/tray/struct.TrayIconBuilder.html)を見てみましょう（3つたどるので結果だけ知りたい人は飛ばしてください）．
ドキュメントで`icon`は以下のように定義されています．
```rust
pub fn icon(self, icon: Image<'_>) -> Self
```
[Image構造体のドキュメント](https://docs.rs/tauri/2.4.1/tauri/image/struct.Image.html)では，`Path`から`Image`を生成するメソッドが挙げられています．
```rust
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self>
```
[Path構造体のドキュメント](https://doc.rust-lang.org/nightly/std/path/struct.Path.html)では，文字列から`Path`を生成するメソッドが挙げられています．
```rust
let path = Path::new("./foo/bar.txt");
```
したがって，上記を組み合わせれば任意のアイコンに変更できます．

なお，`Image`構造体は`ico`と`png`形式のみサポートしているようです．この点には注意しましょう．
[from_pathメソッドの実装](https://docs.rs/tauri/2.4.1/src/tauri/image/mod.rs.html#72-75)では，以下のような定義があります．
```rs:tauri/image/mod.rs
#[cfg(any(feature = "image-ico", feature = "image-png"))]
```
そこで，`features = [ "image-png" ]`を追加します．
```diff_toml:Cargo.toml
[dependencies]
+tauri = { version = "2.0.0", features = [ "tray-icon", "image-png" ] }tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```

`Cargo.toml`にfeaturesを追記したら，アイコンの設定をします．
パスの基準は`Cargo.toml`があるディレクトリです．ここでは`src-tauri`に`hoge.png`があることを前提とします．

```
.
├── Cargo.lock
⋮
├── hoge.png

```

```diff_rust:lib.rs
+use std::path::Path;
use tauri::{
    image::Image,
    menu::{Menu, MenuItem},
+   tray::TrayIconBuilder,
};

// いろいろ

pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
            let menu = Menu::with_items(app, &[&quit_i])?;
+           let path = Path::new("./hoge.png");
+           let image = Image::from_path(path).unwrap();
            let _tray = TrayIconBuilder::new()
-           .icon(app.default_window_icon().unwrap().clone())
+               .icon(image)
                .menu(&menu)
                .show_menu_on_left_click(true)
                .on_menu_event(|app, event| match event.id.as_ref() {
                    "quit" => {
                        println!("quit menu item was clicked");
                        app.exit(0);
                    }
                    _ => {
                        println!("menu item {:?} not handled", event.id);
                    }
                })
                .build(app)?;
            Ok(())
        })
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

これでタスクトレイのアイコンが変わります．
ちなみに，画像をリサイズして長方形にするとアイコンが表示されなくなりました．
用意する画像は正方形にしましょう．