ウィンドウを最大化・最小化する

toggleMaximize・minimize で最大化・最小化し、isMaximized で状態を取得する。toggle 専用の権限、onResized での状態同期、自作タイトルバーのボタン、Rust での検知も扱う。

ウィンドウ 対象: Tauri 2.x 更新日: 読了目安: 約12分 win-003
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 操作と状態の取得
  4. 自作タイトルバーのボタンを状態に追従させる
  5. 最小化も含めて状態を監視する
  6. 最大化・最小化ボタンを無効にする
  7. 2. バックエンドから実装する (Rust)
  8. 動作確認
  9. よくあるエラーと対処法
  10. OS ごとの違いと注意点
  11. 関連レシピ

自作タイトルバーのボタンや「起動時は最大化」「最小化中は更新を止める」といった処理では、ウィンドウの最大化・最小化をコードから扱います。フロントエンドでは @tauri-apps/api/window の maximize() / unmaximize() / toggleMaximize() / minimize() / unminimize() で操作し、isMaximized() / isMinimized() で状態を取得します(Rust にも toggleMaximize() 以外は対応するメソッドがあります)。つまずきやすいのは、操作系の権限が core:window:default に含まれないことと、最大化・最小化を知らせる専用のイベントがないことです。

前提条件

プラグインは不要です。状態の取得は core:default だけで動きますが、操作系のコマンドは 1 つずつ許可します。

使う API権限core:window:default
isMaximized() / isMinimized()core:window:allow-is-maximized / allow-is-minimized含まれる
maximize() / unmaximize()core:window:allow-maximize / allow-unmaximize含まれない
toggleMaximize()core:window:allow-toggle-maximize含まれない
minimize() / unminimize()core:window:allow-minimize / allow-unminimize含まれない
setMaximizable() / setMinimizable()core:window:allow-set-maximizable / allow-set-minimizable含まれない
ドラッグ領域のダブルクリックcore:window:allow-internal-toggle-maximize含まれる

toggleMaximize() は maximize / unmaximize とは別のコマンドなので、その 2 つを許可していても拒否されます。呼ぶ API に合わせて追加します(次はこのレシピの TypeScript 用の src-tauri/capabilities/default.json)。

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "core:window:allow-maximize",
    "core:window:allow-unmaximize",
    "core:window:allow-toggle-maximize",
    "core:window:allow-minimize"
  ]
}

起動時の状態は tauri.conf.json の app.windows[] で決めます。設定ファイルは作成時の初期値で権限は不要、作成後の変更は実行時 API、と分けて考えます。maximized: true でも、解除したときの大きさとして width / height を書いておきます。

{
  "app": {
    "windows": [
      { "label": "main", "width": 1024, "height": 700, "maximized": true }
    ]
  }
}

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

操作と状態の取得

import { getCurrentWindow } from '@tauri-apps/api/window';

const win = getCurrentWindow();

// 最大化 ⇔ 元に戻す(単純に切り替えるだけなら win.toggleMaximize() で済む)
export async function maximizeOrRestore() {
  if (await win.isMaximized()) {
    await win.unmaximize();
  } else {
    await win.maximize();
  }
}

export async function minimizeWindow() {
  await win.minimize();
}

export async function logState() {
  console.log('maximized:', await win.isMaximized(), 'minimized:', await win.isMinimized());
}

toggleMaximize() は「最大化中なら解除、そうでなければ最大化」を 1 回の呼び出しで行います。上のように自分で分岐させるのは、最大化の前に確認を挟みたいときなどです。

また、これらの Promise は最大化の完了ではなく、要求をイベントループに渡した時点で解決します。Linux では最大化自体も非同期なので、直後の isMaximized() が古い値を返すことがあります。画面の表示はイベントから更新します。

自作タイトルバーのボタンを状態に追従させる

decorations: false のウィンドウ(win-009)ではボタンを HTML で作ります。最大化ボタンの表示は、クリック時ではなくサイズが変わったときに更新します。ドラッグ領域のダブルクリック(win-006)や OS 側の操作でも状態は変わるからです。

<header data-tauri-drag-region class="titlebar">
  <span data-tauri-drag-region>My App</span>
  <button id="btn-min" title="最小化">&#x2013;</button>
  <button id="btn-max" title="最大化">&#x25A1;</button>
</header>
import { getCurrentWindow } from '@tauri-apps/api/window';

const win = getCurrentWindow();
const maxBtn = document.getElementById('btn-max') as HTMLButtonElement;

async function syncMaxButton() {
  const maximized = await win.isMaximized();
  maxBtn.textContent = maximized ? '❐' : '□';
  maxBtn.title = maximized ? '元に戻す' : '最大化';
  document.body.classList.toggle('maximized', maximized); // 最大化中は角丸や外周の余白を消す CSS 用
}

document.getElementById('btn-min')!.addEventListener('click', () => win.minimize());
maxBtn.addEventListener('click', () => win.toggleMaximize());

await syncMaxButton();              // 起動直後(maximized: true の場合もある)
await win.onResized(syncMaxButton); // どの経路で最大化・解除されても追従する

onResized は枠のドラッグ中も連続で発火するので、重い処理を載せるなら間引きます。なお、ドラッグ領域のダブルクリックは Tauri 内部で resizable と maximizable を確かめてから最大化しますが、toggleMaximize() / maximize() は確かめません。最大化を禁止したウィンドウでは、自作ボタンも isMaximizable() を見て無効にします。

最小化も含めて状態を監視する

最小化はウィンドウの大きさを変える操作ではないため、環境によってはリサイズイベントが届きません。onFocusChanged も併用し、isMinimized() → isMaximized() の順に問い合わせて、変化したときだけ通知する関数にまとめます。最小化中は定期的な再描画や通信を止める、といった用途に使えます。

import { getCurrentWindow } from '@tauri-apps/api/window';
import type { UnlistenFn } from '@tauri-apps/api/event';

export type WinState = 'normal' | 'maximized' | 'minimized';

export async function watchWindowState(onChange: (s: WinState) => void): Promise<UnlistenFn> {
  const win = getCurrentWindow();
  let last: WinState | undefined;
  const check = async () => {
    const s: WinState = (await win.isMinimized()) ? 'minimized'
      : (await win.isMaximized()) ? 'maximized' : 'normal';
    if (s !== last) { last = s; onChange(s); }
  };
  const offResized = await win.onResized(check);
  const offFocus = await win.onFocusChanged(check);
  await check();
  return () => { offResized(); offFocus(); };
}
// (続き)
await watchWindowState((s) => console.log('window state:', s));

最大化・最小化ボタンを無効にする

標準タイトルバーのボタンは、設定ファイルの "maximizable": false / "minimizable": false で無効にでき、実行中は setMaximizable() / setMinimizable()(要権限)で切り替えます。resizable: false のウィンドウでは maximizable の指定は無視され、最大化ボタンは自動で無効になります(win-010)。

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

Window / WebviewWindow の maximize() / unmaximize() / minimize() / unminimize() / is_maximized() / is_minimized() を使います。toggle_maximize() はないので is_maximized() で分岐します。自作コマンドは、アプリ独自の ACL マニフェストを作っていなければ capabilities に追加しなくても呼べます。状態の検知は on_window_event で WindowEvent::Resized と WindowEvent::Focused を受け、変化したときだけフロントエンドへ知らせます。

use std::sync::Mutex;
use tauri::{Emitter, Manager, WebviewWindow, WindowEvent};

#[derive(Clone, serde::Serialize)]
struct WindowStatePayload {
    maximized: bool,
    minimized: bool,
}

#[tauri::command]
fn window_control(window: WebviewWindow, action: String) -> Result<(), String> {
    let result = match action.as_str() {
        "minimize" => window.minimize(),
        // Rust 側に toggle_maximize() はないので状態を見て分岐する
        "toggle-maximize" => match window.is_maximized() {
            Ok(true) => window.unmaximize(),
            Ok(false) => window.maximize(),
            Err(e) => Err(e),
        },
        // トレイなどから戻すとき: 非表示・最小化のどちらからでも前面に出す
        "restore" => window
            .show()
            .and_then(|_| window.unminimize())
            .and_then(|_| window.set_focus()),
        other => return Err(format!("unknown action: {other}")),
    };
    result.map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            if let Some(main) = app.get_webview_window("main") {
                let win = main.clone();
                let last = Mutex::new(None::<(bool, bool)>);
                main.on_window_event(move |event| {
                    if !matches!(event, WindowEvent::Resized(_) | WindowEvent::Focused(_)) {
                        return;
                    }
                    let now = (
                        win.is_maximized().unwrap_or(false),
                        win.is_minimized().unwrap_or(false),
                    );
                    let mut prev = last.lock().unwrap();
                    if *prev != Some(now) {
                        *prev = Some(now);
                        let payload = WindowStatePayload { maximized: now.0, minimized: now.1 };
                        let _ = win.emit("window-state", payload);
                    }
                });
            }
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![window_control])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';

type WindowStatePayload = { maximized: boolean; minimized: boolean };

await listen<WindowStatePayload>('window-state', (e) => {
  console.log('from rust:', e.payload);
});
await invoke('window_control', { action: 'toggle-maximize' });

動作確認

npm run tauri dev で起動し、自作の最大化ボタンかドラッグ領域のダブルクリックで最大化すると、ボタンが「❐」に変わり、戻すと「□」になります。watchWindowState() を仕込んで「最大化 → 最小化 → タスクバー(macOS は Dock)から復元」と操作したときのログです。

window state: normal
window state: maximized
window state: minimized
window state: maximized

Rust 版でも同じ操作で from rust: {maximized: true, minimized: false} のようなログが出ます(前回値と比べているので、枠のドラッグ中にログは増えません)。

よくあるエラーと対処法

  • window.maximize not allowed. Permissions associated with this command: core:window:allow-maximize: 権限の追加漏れです。これは開発ビルド(tauri dev)の文言で、リリースビルドでは Command plugin:window|maximize not allowed by ACL になります。toggleMaximize() の場合は window.toggle_maximize not allowed. ... と出るので、core:window:allow-toggle-maximize を追加します。
  • ボタンの表示が実際の状態とずれる: maximize() 直後の isMaximized() で表示を決めているのが原因です。完了を待たない Promise と、ダブルクリックなど別経路の変化の取りこぼしが重なるので、表示の更新は onResized に任せます。
  • Linux で最大化しない: Linux の最大化は resizable: true が前提です。また Tauri のダブルクリック処理は、OS を問わず resizable: false のウィンドウでは何もしません。
  • トレイから戻そうとして unminimize() を呼んでも出てこない: hide() で隠したウィンドウは最小化とは別の状態で、isVisible() で判別します。show() → unminimize() → setFocus() の順に呼べば、どちらの状態からでも前面に戻せます(Rust 例の restore。トレイへの格納は menu-013)。

OS ごとの違いと注意点

  • Windows: minimizable / maximizable は標準タイトルバーのボタンにそのまま反映されます。作成時に fullscreen と maximized を両方 true にすると、全画面だけが適用されます(全画面は win-004)。
  • macOS: 最大化は緑の「ズーム」ボタンの動作で、このボタンは全画面への入口も兼ねています。maximizable: false にすると全画面ボタンとしても使えなくなります。独自メニューを設定していなければ、既定メニューの Window に「Minimize」(Cmd + M)と「Zoom」が入ります。decorations: false や resizable: false のウィンドウでは、公式の Window State プラグインが既知の問題の回避として isMaximized() を使わずに処理しているので、この構成で最大化状態に頼る処理は実機で確かめます。
  • Linux: maximizable / minimizable と setMaximizable() / setMinimizable() は非対応で、isMaximizable() / isMinimizable() は常に true を返します。最大化・最小化の要求は非同期に処理されるので、状態はイベント経由で確かめます。

関連レシピ

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

Web Ninja

この記事を書いた人

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

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

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

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