Tauri でフレームレスウィンドウのアプリケーションを作成する

# はじめに

Tauri でウィンドウを作るのは結構簡単にできる。
では、見た目をもっと製品っぽくリッチにするには？となるとやはりフレームレスウィンドウを作るという選択肢になってくると思う。
また、常駐系のアプリケーションを作りたい場合、「閉じる」ボタンはいらないし、許可したくないといった場合もカスタムが必要になってくる。

こうした条件に対してフレームレスウィンドウを作っていくことになるが、ちょこっと調べる必要があったので、それをまとめる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/1b8912b2-1ae6-0821-32b1-696d9e99fba7.png)

Tauri は起動も速くて、軽量で素敵ですね。

![output-palette.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/1c6e3b1e-4b4a-fffa-c1c0-851825e515b7.gif)


## 前提: 環境

* Windows 11
* Rust v1.72.0 (rustc, cargo)
* Tauri v1.4
* Svelte v4.2


# フレームレスウィンドウを作る

デフォルトの状態は次のような装飾になっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/888161d5-80cc-028d-ef2c-5776750978b9.png)

これを、こうする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/ae3ba6be-4764-e947-ab3f-f25694f23551.png)


## コンフィグを設定する

フレームレスウィンドウを設定する上で、以下のことを考慮する必要がある。

* デフォルトの Window の装飾設定を切る。
* フロントエンド側からウィンドウ操作を許可する。

これの設定には、 **tauri.conf.json** を編集する。

### デフォルトの Window の装飾設定を切る

デフォルトでついているウィンドウ装飾を一切切ってしまう設定。
ウィンドウ上部のウィンドウコントロール部分もだが、影も消えている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/b057790a-16d0-ff59-b2dc-3515f3c7f122.png)


```json:src-tauri/tauri.conf.json
{
    "tauri": {
        "windows": {
            "decorations": false
        }
    }
}
```

### フロントエンド側からウィンドウ操作を許可する

特にドラッグ部分の `startDragging: true` に注意する。
他には、ウィンドウに行わせたい挙動に合わせてウィンドウ操作への権限を付与する。

```json:src-tauri/tauri.conf.json
{
    "tauri": {
        "allowlist": {
            "window": {
                "maximize": true,
                "minimize": true,
                "hide": true,
                "startDragging": true,
                "show": true,
                "unmaximize": true,
                "unminimize": true
            }
        }
    }
}
```

:::note info
もし、自らの管理が行き届く範囲での操作を行う場合は「すべて許可」する方法もある。

```json:src-tauri/tauri.conf.json
{
    "tauri": {
        "allowlist": {
            "window": {
                "all": true,
            }
        }
    }
}
```
:::

これらの許可項目については以下のドキュメントを参照。

https://tauri.app/v1/api/js/window

## フロントエンド側の処理を書く

画像は、フロントエンドに Svelte (SvelteKit) を使用している。普段は React 多め。
(Tauri は[軽量・速度が魅力なのを謡っている](https://tauri.app/v1/references/benchmarks)ので、どうせならフロントエンドでも[その位置に立っている](https://svelte.dev) Svelte を使っていきたいのはある。)

また、以下のライブラリも併用している。ここら辺は好みなので、特に必須ではない。

```json:pacakge.json
{
    "devDependencies": {
        "postcss": "^8.4.29",
        "tailwindcss": "^3.3.3",
		"lucide-svelte": "^0.274.0"
    }
}
```

まずは、全体のコード。

```svelte:src/routes/+layout.svelte
<script lang="ts">
    import '../app.css'
    import { appWindow } from "@tauri-apps/api/window"
    import { X, Minus, Maximize, AlignHorizontalJustifyCenter } from 'lucide-svelte'

    function minimize() {
        appWindow.minimize()
    }

    async function maximize() {
        let maximizeState = await appWindow.isMaximized()

        if (!maximizeState) {
            appWindow.maximize()
        } else {
            appWindow.unmaximize()
        }
    }

    function hide() {
        appWindow.hide()
    }
</script>

<div>
    <header class="bg-zinc-800 h-12 select-none bg-fixe">
        <nav data-tauri-drag-region class="mx-auto flex items-center justify-between" aria-label="Global">
            <div class="mx-1　p-3">
                <AlignHorizontalJustifyCenter class="square-4" />
            </div>
            <ul class="flex gap-x-1">
                <li>
                    <button on:click={minimize} class="window-control-button">
                        <Minus size="16" />
                    </button>
                </li>
                <li>
                    <button on:click={maximize} class="window-control-button">
                        <Maximize size="16" />                         
                    </button>
                </li>
                <li>
                    <button on:click={hide} class="window-control-button">
                        <X size="16" />
                    </button>
                </li>
            </ul>
        </nav>
    </header>
    <div class="bg-zinc-900 h-[1px]"></div>
    <main class="bg-zinc-800 h-screen">
        <slot />
    </main>
</div>

<style lang="postcss">
    :global(body) {
        overflow: hidden;
        user-select: none;
        color: white;
    }

    .window-control-button {
        @apply hover:bg-zinc-600 p-4;
    }
</style>
```

#### data-tauri-drag-region 属性を適切につける

![output-palette.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/0e8c7a9a-6264-2630-d73e-12d1a05f5307.gif)


この属性がドラッグ可能エリアを示している。
上記コードでは `<nav></nav>` タグ部分をドラッグ可能部分に指定しているが、もし別の部分もまとめて行いたい場合は、以下のように設定する。

```svelte
<div data-tauri-drag-region="input, a, textarea"> 特定のタグ要素をドラッグ対象に入れない </div>
// もしくは
<div data-tauri-drag-region="default"> デフォルトでいくつかのタグ要素をドラッグ対象に入れない </div>
```

https://github.com/tauri-apps/tauri/issues/1656#issuecomment-1161495124

:::note warn
例えば、上記のような場合、 `<div></div>` の中にウィンドウ操作のナビメニューを設定した `<nav></nav>` タグが用意されているが、これを誤って親の `<div></div>` タグの属性として付与してしまうと**動作しない**。
**一番上のレイヤーの要素が何か**に注目して、 `data-tauri-drag-region` 属性を付けるように注意する。
:::

# Window のシャドウをつける

デフォルトでウィンドウ装飾を `false` にしてしまうと、Windows 側で用意されたシャドウが消えてしまう。
また、角ばってしまった印象になって格好悪い。

これをシャドウを付けつつ、丸みを帯びた感じの Windows らしい装飾デザインにしようというもの。
また、これは同様に MacOS 側のデザインにも対応する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/086bf005-2ccf-a245-5244-c7b6e902c31b.png)

これを実現するには、[window-shadows](https://github.com/tauri-apps/window-shadows) というプラグインを使用する。

https://github.com/tauri-apps/window-shadows

```toml:src-tauri/Cargo.toml
[dependencies]
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
```

そしてこれを Window 生成時のセットアップ処理に追加する。

```diff_rust:src_tauri/src/main.rs
+ use tauri::Manager; // クロージャー内の型付けなどをサポートしてくれる。
+ use window_shadows::set_shadow;

fn main() {
    tauri::Builder::default()
+        .setup(|app| {
+            // "main" ウィンドウの取得
+            let main_window = app.get_window("main").unwrap();
+
+            // ウィンドウに window-shadows の装飾を適用
+            // Windows, macOS で有効
+            #[cfg(any(windows, target_os = "macos"))]
+            set_shadow(main_window, true).unwrap();
+
+            Ok(())
+        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/1c0cb34e-47f6-9c23-5677-2a495b730262.png)

# Tauri v2 での対応

:::note info
Tauri v2 での作成方法は、こちらでも別途まとめています。
[「Tauri v2 x Svelte 5 で、アプリケーション開発をはじめる！」](https://qiita.com/takavfx/items/248250daacde5d400bd9)
:::

## セキュリティへの対応

Tauri v2 ではセキュリティコントロールがより厳密かつ、広範囲的にコントロールできるようになった。
半面、かなりとっつきづらきなっている。
（多分慣れれば結構コントロールがしやすくていいなとは直感では感じているが、またここら辺は別途まとめたい。

ただ、**とりあえず**としては次のように、**src-tauri/capabilities/default.json** に書き加えれば良い。

```diff_json:src-tauri/capabilities/default.json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "shell:allow-open",
+   "core:window:allow-hide",
+   "core:window:allow-minimize",
+   "core:window:allow-maximize",
+   "core:window:allow-toggle-maximize",
+   "core:window:allow-start-dragging"
  ]
}
```

:::note info
**どうやって調べる？**
実のところ、この **capabilities** や **permissions** について詳しいドキュメントが**無い** :scream:
ではななぜ分かったのか？
僕のエディタ環境 (NeoVim /w LazyVim) だと、候補がでてくるのでそれっぽいものを選択した！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/4e4b89ff-9593-2e5d-b35c-c17ff9fb73cf.png)

とまぁ、このまま何何故？が分からないとまずいなとも思うので、またセキュリティ周りについては追調査の後にまとめようと思う。
:::

::: note info
**Tauri v2 でのセキュリティ設定について**
Taruvi v2 ではセキュリティ設定への理解がある程度必要になってくるので、関連したポストを書きました。
「[アプリ開発者のにとっての Tauri v2 のセキュリティ構造を読み解く](https://qiita.com/takavfx/items/314a4a79afc38cb4e174)」にて参照してください。
:::

::: note info
Tauri v2 + Svelte 5 での作成の仕方の記事を作成しています。

:::


## 影落ちへの対応

Tauri v2 では、なんと、装飾部分 (タイトルバー) を削除してもウィンドウの影落ちや角の丸みの保持が可能になった！！
Tauri v1 では、装飾を「全部切る」ことしかできなかったため、別途影落ち用のプラグインを入れるなどしていたが、これらの設定が分離され、 **「タイトルバーだけ消す」** といったことが可能になった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/b963533f-30f8-97e9-8835-e037cee1706a.png)

```diff_json:src-tauri/tauri.conf.json
{
  "app": {
    "windows": [
      {
        "title": "for-qiita",
        "width": 800,
        "height": 600,
+       "decorations": false
      }
    ],
    "security": {
      "csp": null
    }
  },
}
```

:::note info
**あえて影落ちも消したい場合**

あえて影落ちも消したい場合は、`shadow` を切る。
しかしながら、Windows 11 などの場合は特有の角丸も取れてしまうので注意。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/4ce1a7ae-9783-3ec2-cd18-87ec8c021a98.png)

```diff_json:src-tauri/tauri.conf.json
{
  "app": {
    "windows": [
      {
        "title": "for-qiita",
        "width": 800,
        "height": 600,
+       "decorations": false,
+       "shadow": false
      }
    ],
    "security": {
      "csp": null
    }
  },
}
```
:::


# まとめ

少々、ここまでもっていくのにスタックがあったものの、おおむねスムーズに構築できたし、何より起動も速い。
Tauri + Rust + Svelte 環境はもう少し深堀していってみようと思う。