自作タイトルバーのボタンや「起動時は最大化」「最小化中は更新を止める」といった処理では、ウィンドウの最大化・最小化をコードから扱います。フロントエンドでは @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="最小化">–</button>
<button id="btn-max" title="最大化">□</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を返します。最大化・最小化の要求は非同期に処理されるので、状態はイベント経由で確かめます。
