タスクバーでウィンドウを点滅させる

requestUserAttention()(権限 core:window:allow-request-user-attention)でタスクバーや Dock のアイコンを点滅させる。種類ごとの OS の違い、止め方、効かない条件も示す。

ウィンドウ 対象: Tauri 2.x 更新日: 読了目安: 約7分 win-020
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 2. バックエンドから実装する (Rust)
  4. 動作確認
  5. よくあるエラーと対処法
  6. OS ごとの違いと注意点
  7. 関連レシピ

時間のかかる書き出しが終わった、チャットにメッセージが届いた、というときに、別の作業をしているユーザーの手を止めずに「こちらを見て」と伝えるのが requestUserAttention() です。Windows ではタスクバーのボタンが点滅し、macOS では Dock のアイコンが跳ねます。手前に出してフォーカスを奪う setFocus() と違い、入力を横取りしません。JS の requestUserAttention() か Rust の request_user_attention() を使い、プラグインは不要です。

前提条件

権限 core:window:allow-request-user-attention は core:default に含まれないので追加します。呼ぶ前に確かめる isFocused() と、フォーカスの変化を受ける onFocusChanged() は core:default で使えます。Rust から呼ぶだけなら追加は要りません。

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

種類は UserAttentionType の 2 つで、見え方は OS ごとに決まっています。

指定WindowsmacOSLinux
Criticalウィンドウとタスクバーのボタンが、アプリにフォーカスが来るまで点滅Dock のアイコンが、アプリにフォーカスが来るまで跳ね続ける2 種類の違いはない
Informationalタスクバーのボタンが、アプリにフォーカスが来るまで点滅Dock のアイコンが 1 回跳ねる同上
null取り消す効果なし取り消す

どの OS でも、アプリにフォーカスがあるときに呼んでも何も起きません。

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

処理が終わった時点でユーザーが別のアプリを見ているときだけ知らせ、戻ってきたら取り消します。種類は数値の 1 / 2 ではなく UserAttentionType で指定します。フォーカスが戻っても点滅が自動で止まるとは限らないので、onFocusChanged() で null を渡して止めます。処理を中止した、別の端末で既読になった、など知らせる必要がなくなったときも同じ関数で取り消します。

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

const win = getCurrentWindow();
let requested = false;

// 見ていないときだけ知らせる
export async function notifyDone(critical = false) {
  const focused = await win.isFocused();
  console.log('job done, focused:', focused);
  if (focused) return;
  const kind = critical ? UserAttentionType.Critical : UserAttentionType.Informational;
  await win.requestUserAttention(kind); // core:window:allow-request-user-attention が必要
  requested = true;
  console.log('attention requested:', UserAttentionType[kind]);
}

// 用が済んだら取り消す(macOS では効果なし)
export async function cancelAttention() {
  if (!requested) return;
  requested = false;
  await win.requestUserAttention(null);
  console.log('attention cleared');
}

// フォーカスが戻ったら止める
await win.onFocusChanged(({ payload: focused }) => {
  if (focused) void cancelAttention();
});

// 例: 5 秒かかる処理のあとで知らせる
export async function runJob() {
  await new Promise((resolve) => setTimeout(resolve, 5000));
  await notifyDone();
}

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

処理そのものを Rust で動かしているなら、終わった時点で Rust から知らせれば JS の権限も要りません。フォーカスが戻ったときの取り消しは、on_window_event で WindowEvent::Focused(true) を受けて行います。

use std::time::Duration;
use tauri::{UserAttentionType, WebviewWindow, WindowEvent};

/// アプリを見ていないときだけ知らせる
fn notify_done(window: &WebviewWindow, critical: bool) -> tauri::Result<()> {
    if window.is_focused()? {
        return Ok(());
    }
    let kind = if critical {
        UserAttentionType::Critical
    } else {
        UserAttentionType::Informational
    };
    window.request_user_attention(Some(kind))
}

#[tauri::command]
async fn export_file(window: WebviewWindow) -> Result<(), String> {
    // 実際の重い処理の代わりに 5 秒待つ(async のスレッドを直接ふさがない)
    tauri::async_runtime::spawn_blocking(|| std::thread::sleep(Duration::from_secs(5)))
        .await
        .map_err(|e| e.to_string())?;
    notify_done(&window, false).map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .on_window_event(|window, event| {
            // フォーカスが戻ったら取り消す(自動で止まらない環境向け)
            if let WindowEvent::Focused(true) = event {
                let _ = window.request_user_attention(None);
            }
        })
        .invoke_handler(tauri::generate_handler![export_file])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';

await invoke('export_file'); // 5 秒後、別のアプリを見ていれば点滅する

動作確認

npm run tauri dev で起動し、runJob() を呼んだらすぐに別のアプリをクリックします。5 秒後にタスクバーのボタンが点滅し(macOS は Dock のアイコンが 1 回跳ねる)、クリックして戻るとコンソールに次のように出ます。ウィンドウを見たまま待つと、フォーカスがあるので何も起きません。

job done, focused: false
attention requested: Informational
attention cleared

よくあるエラーと対処法

  • 「window.request_user_attention not allowed. Permissions associated with this command: core:window:allow-request-user-attention」: 権限の追加漏れです。リリースビルドでは「Command plugin:window|request_user_attention not allowed by ACL」だけになります。
  • 呼んでも何も起きない: アプリにフォーカスがあります。ボタンのクリックで直接呼ぶと必ずこの状態になるので、試すときはタイマーで遅らせて別のアプリへ切り替えます。
  • 引数を省くと型エラーになる: 引数は必須で、省くと「Expected 1 arguments, but got 0.」になります。型チェックのない JS で省くと取り消し(null)と同じ扱いになり、やはり何も起きません。
  • 隠したウィンドウでは点滅が見えない: hide() したウィンドウにはタスクバーのボタンがありません(ウィンドウを表示・非表示にする)。トレイに格納 している間は 通知 で知らせます。最小化ならボタンが残るので点滅は見えます。
  • macOS で止まらない: macOS では null に効果がなく、Critical はアプリにフォーカスが来るまで跳ね続けます。取り消す可能性がある知らせは Informational にします。

OS ごとの違いと注意点

  • Windows: Critical はタスクバーのボタンだけでなくウィンドウ自体も点滅します。どちらの種類もアプリにフォーカスが来ると止まります。
  • macOS: 跳ねるのはアプリの Dock アイコンで、ウィンドウごとの区別はありません。
  • Linux: 2 種類の見え方は同じです。
  • ほかの知らせ方との使い分け: 点滅は「気付かせるだけ」です。内容まで伝える、隠れていても届ける、状態を出し続ける、といった目的には次を使います。
手段向いている場面
requestUserAttention()ウィンドウがタスクバーにある(最小化を含む)とき、気付かせるだけ
通知(dlg-009)隠している・トレイ常駐中、内容も伝えたい
タイトルに件数を出す(win-002)未読数など、状態を出し続けたい
setBadgeCount() / setOverlayIcon()アイコンに数やマークを付けたい(前者は Windows 非対応、後者は Windows 専用)

関連レシピ

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

Web Ninja

この記事を書いた人

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

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

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

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