メニュー項目に「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 つだけ置いて + でつなぎます。大文字・小文字は区別されません。
| 修飾キーの書き方 | macOS | Windows / Linux |
|---|---|---|
CmdOrCtrl(CommandOrControl) | Command | Ctrl |
Ctrl(Control) | Control | Ctrl |
Shift | Shift | Shift |
Alt(Option) | Option | Alt |
Cmd(Command / Super) | Command | Windows キー(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 で扱います。
