メニューにショートカットキーを割り当てる

メニュー項目の accelerator に CmdOrCtrl+S などを指定し、キー操作でもクリックと同じ処理を呼ぶ。書式の規則、書き間違いが黙って無視される落とし穴、実行中の変更も示す。

メニュー・トレイ 対象: Tauri 2.x 更新日: 読了目安: 約9分 menu-004
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. キーの書き方(Rust も同じ)
  4. 項目に付ける
  5. 実行中に変える
  6. 2. バックエンドから実装する (Rust)
  7. 動作確認
  8. よくあるエラーと対処法
  9. OS ごとの違いと注意点
  10. 関連レシピ

メニュー項目に「Ctrl+S」のようなショートカットキー(アクセラレータ)を付けると、項目の右にキーが表示され、アプリを使っている間はキーを押すだけでクリックと同じイベントが届きます。JS では accelerator、Rust では MenuItemBuilder::accelerator() に文字列で指定し、CmdOrCtrl と書けば macOS では Command、Windows と Linux では Ctrl になります。つまずきやすいのは、書式を間違えてもエラーにならず、ショートカットが付かないだけで終わる点です。メニューの作り方は menu-001、届いたイベントの振り分けは menu-005 で説明しています。

前提条件

プラグインは不要です。accelerator は項目を作るときのオプションなので、メニューを作る権限(core:menu:default)のほかは要りません。実行中に変える setAccelerator() の権限 core:menu:allow-set-accelerator も core:menu:default に含まれます(どちらも core:default に含まれる)。

メニューのショートカットはアプリが前面にあるときだけ効きます。ほかのアプリを使っている間も反応させるには グローバルショートカットを登録する の方法を使います。

メニューのショートカットグローバルショートカット
効く場面アプリが前面にあるときほかのアプリを使っている間も
メニューへの表示項目の右に出る出ない
必要なもの追加なしglobal-shortcut プラグイン
処理を書く場所クリックと同じ(action / on_menu_event)プラグインのハンドラー

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

キーの書き方(Rust も同じ)

修飾キーを先に並べ、最後にキーを 1 つだけ置いて + でつなぎます。大文字・小文字は区別されません。

修飾キーの書き方macOSWindows / Linux
CmdOrCtrl(CommandOrControl)CommandCtrl
Ctrl(Control)ControlCtrl
ShiftShiftShift
Alt(Option)OptionAlt
Cmd(Command / Super)CommandWindows キー(Super)

キーには A〜Z、0〜9、F1〜F24、Enter、Space、Tab、Backspace、Delete、Esc、Home、End、PageUp、PageDown、Insert、矢印の Up / Down / Left / Right、記号の , . / ; - = などが使えます。+ そのものはキーとして使えません。記号キーはキーボード配列によって位置が違い、日本語配列では思ったキーにならないことがあるので、英字・数字・ファンクションキーを選ぶと確実です。

項目に付ける

Menu.new() に渡す項目に accelerator を足します。キーで選ばれたときも、クリックと同じ action が同じ id で呼ばれます。「ファイル」などの見出し(サブメニュー)には付けられないので、見出しは & のアクセスキーで開けるようにします。チェック付きの項目(menu-003)にも同じように付けられます。

import { Menu } from '@tauri-apps/api/menu';

// クリックでもキー操作でも、同じ id でここに来る(振り分けは menu-005)
const run = (id: string) => console.log('menu:', id);

const menu = await Menu.new({
  items: [
    {
      text: 'ファイル(&F)', // 見出しには accelerator を付けられない
      items: [
        { id: 'file:new', text: '新規作成(&N)', accelerator: 'CmdOrCtrl+N', action: run },
        { id: 'file:save', text: '保存(&S)', accelerator: 'CmdOrCtrl+S', action: run },
        { id: 'file:save-as', text: '名前を付けて保存(&A)...', accelerator: 'CmdOrCtrl+Shift+S', action: run },
      ],
    },
    {
      text: 'ヘルプ(&H)',
      items: [{ id: 'help:docs', text: 'ヘルプを表示', accelerator: 'F1', action: run }],
    },
  ],
});
await menu.setAsAppMenu();

コピー・貼り付けなどの標準項目には CmdOrCtrl+C などのキーがあらかじめ付いていて、変える API はありません。自前の項目に同じキーを付けないようにします(標準項目は コピー・貼り付けなどの標準メニューを入れる)。

実行中に変える

ユーザーがキーを選べるようにするなら、項目を MenuItem.new() で作って参照を持っておき、setAccelerator() を呼びます(null で外れる)。今のキーを読み出す API は無く、解釈できない文字列を渡すとエラーにならずに外れるだけなので、自由入力ではなく決まった選択肢から選ばせます。

import { Menu, MenuItem, Submenu } from '@tauri-apps/api/menu';

const save = await MenuItem.new({
  id: 'file:save',
  text: '保存(&S)',
  accelerator: 'CmdOrCtrl+S',
  action: (id) => console.log('menu:', id),
});
const file = await Submenu.new({ text: 'ファイル(&F)', items: [save] });
const menu = await Menu.new({ items: [file] });
await menu.setAsAppMenu();

// 選べるキーは固定にしておく
const SHORTCUTS = { standard: 'CmdOrCtrl+S', alternative: 'CmdOrCtrl+Alt+S', none: null } as const;

export async function applySaveShortcut(choice: keyof typeof SHORTCUTS) {
  await save.setAccelerator(SHORTCUTS[choice]); // null ならショートカットを外す
}

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

Rust では MenuItemBuilder の .accelerator() で指定します(MenuItem::with_id() の最後の引数に Some("CmdOrCtrl+S") を渡しても同じ)。SubmenuBuilder の .text() ではショートカットを付けられないので、別に作って .item() / .items() で足します。実行中に変えるなら、項目を State に持っておき set_accelerator() を呼びます。

use tauri::menu::{MenuBuilder, MenuItem, MenuItemBuilder, SubmenuBuilder};
use tauri::{Manager, State, Wry};

/// 実行中にキーを変えるため、項目を State に入れておく
struct SaveItem(MenuItem<Wry>);

#[tauri::command]
fn set_save_shortcut(item: State<'_, SaveItem>, accelerator: Option<String>) -> Result<(), String> {
    // None なら外す。解釈できない文字列でも外れるので、選択肢はフロント側で固定にする
    item.0.set_accelerator(accelerator).map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .menu(|app| {
            let save = MenuItemBuilder::with_id("file:save", "保存(&S)")
                .accelerator("CmdOrCtrl+S")
                .build(app)?;
            let save_as = MenuItemBuilder::with_id("file:save-as", "名前を付けて保存(&A)...")
                .accelerator("CmdOrCtrl+Shift+S")
                .build(app)?;
            let docs = MenuItemBuilder::with_id("help:docs", "ヘルプを表示")
                .accelerator("F1")
                .build(app)?;
            app.manage(SaveItem(save.clone()));

            let file = SubmenuBuilder::new(app, "ファイル(&F)")
                .text("file:new", "新規作成(&N)") // text() ではショートカットを付けられない
                .items(&[&save, &save_as])
                .build()?;
            let help = SubmenuBuilder::new(app, "ヘルプ(&H)").item(&docs).build()?;
            MenuBuilder::new(app).items(&[&file, &help]).build()
        })
        .on_menu_event(|_app, event| {
            // キーで選ばれても、クリックと同じ id で届く
            println!("menu: {}", event.id().as_ref());
        })
        .invoke_handler(tauri::generate_handler![set_save_shortcut])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';

await invoke('set_save_shortcut', { accelerator: 'CmdOrCtrl+Alt+S' });
await invoke('set_save_shortcut', { accelerator: null }); // 外す

動作確認

npm run tauri dev で起動して「ファイル」を開くと、Windows では「保存(S)」の右に「Ctrl+S」が表示されます(macOS では Command キーの記号)。メニューを閉じたままウィンドウで Ctrl+S、Ctrl+Shift+S を押すと、Rust 版ではターミナルに次のように出ます。JS 版ではコンソールに同じ id が出ます。

menu: file:save
menu: file:save-as

試しに accelerator を 'CmdOrCtrl+Plus' に変えて起動し直すと、キーの表示が消え、押しても何も起きず、エラーも出ません。項目の右にキーが表示されているかどうかが、文字列が正しく解釈されたかの目安です。

よくあるエラーと対処法

  • キーが表示されず、押しても反応しない: 文字列が解釈できていません。+ や Plus をキーにしている、Ctrl+K+S のようにキーを 2 つ並べている、Return(正しくは Enter)のような無い名前を使っている、修飾キーを後ろに書いている、などが原因です。
  • Windows で Cmd+S が Ctrl+S として働かない: Cmd と Super は Windows では Windows キーになります。OS をまたぐなら CmdOrCtrl を使います。
  • ページの keydown と両方で処理が動く: ページ側でも同じキーを処理していると、OS によっては両方が動きます。処理はメニューの id から呼ぶ 1 か所にまとめます。
  • setAccelerator() の後にショートカットが消えた: 解釈できない文字列を渡しています。上の書式の表で確かめます。
  • アプリが後ろにあると効かない: メニューのショートカットの仕様です。グローバルショートカットを使います。

OS ごとの違いと注意点

  • CmdOrCtrl: macOS では Command、Windows と Linux では Ctrl になるので、OS ごとに書き分ける必要はありません。
  • macOS: メニューはアプリ全体で 1 つなので、ショートカットもアプリ単位で効きます。
  • Windows / Linux: メニューはウィンドウごとに付きます。メニューを外したウィンドウや別のメニューを付けたウィンドウでも同じキーを確実に効かせたいなら、そのウィンドウのメニューにも同じ項目を入れるか、ページの keydown で同じ処理を呼びます。Windows では Alt と英字の組み合わせがメニューのアクセスキー((&F) など)と重なるので、ショートカットには使わない方が無難です。
  • iOS / Android: メニューの API はありません。
  • 項目の文言や有効・無効を変える方法は menu-006 で扱います。

関連レシピ

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

Web Ninja

この記事を書いた人

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

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

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

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