Karabiner-Elementsで⌘⇧[だけ効かない問題をlazy修飾キーで直す

はじめに

macOS上のOrcaとVS Codeで、次のタブへ進む⌘⇧]は動くのに、前のタブへ戻る⌘⇧[だけが反応しなくなりました。

キーボードはUS配列です。Shift + [で{は入力でき、Karabiner-EventViewerにもCommand、Shift、open_bracketの3イベントが正しく表示されます。それでもアプリでは何も起きませんでした。

原因は、左右のCommandキーを単独で押したときに英数・かなへ切り替えるKarabiner-Elementsのルールでした。Commandを通常の修飾キーとして即時出力していたため、後続の⌘⇧[用ルールと干渉していました。Commandの出力に"lazy": trueを付けることで、英数・かなの単押し操作を残したまま解決できました。

この記事では、原因を特定した手順と最終的な設定をまとめます。

症状

発生していた症状は次の通りです。

  • Orcaで⌘⇧]は動くが、⌘⇧[は動かない
  • VS Codeでも同じ方向だけ動かない
  • Shift + [による{の入力はできる
  • 左右どちらのCommandとShiftを使っても再現する
  • Karabiner-EventViewerはcommand + shift + open_bracketを認識する

複数のアプリで同じ現象が起きたため、各アプリのキーバインドより手前にあるmacOSまたはKarabiner-Elementsを疑いました。

最初に確認したこと

物理キーは認識されているか

Karabiner-EventViewerでは、実際に次の順序でイベントが届いていました。

right_command down
right_shift down
open_bracket down
open_bracket up
right_command up
right_shift up

open_bracketが欠けているわけではないため、キーボードの故障やUS/JIS配列の取り違えではありません。

macOSがショートカットを奪っていないか

macOSのシステムショートカットや常駐アプリを止めても変化はありませんでした。

ここまでの切り分けはこちらで行いましたが、原因は分かりませんでした。そこでAIに調査を続けてもらったところ、macOSのCarbon APIにあるRegisterEventHotKeyを使った検証プログラムを作ってくれました。このプログラムで、⌘⇧[のイベントがmacOSから届くかを確認しています。Carbonは、macOSに古くからあるC APIです。

コードは次のようなものです。US配列の[に対応するキーコード33と、Command、Shiftを登録してイベントを1回待ちます。

#include <Carbon/Carbon.h>

EventHotKeyRef hotKey = NULL;
EventHotKeyID id = { 'TEST', 1 };
EventTypeSpec type = { kEventClassKeyboard, kEventHotKeyPressed };

OSStatus status = RegisterEventHotKey(
    33,
    cmdKey | shiftKey,
    id,
    GetApplicationEventTarget(),
    0,
    &hotKey
);

EventRef event = NULL;
OSStatus received = ReceiveNextEvent(1, &type, 30.0, true, &event);

登録とイベント受信の両方に成功したため、少なくともmacOSは⌘⇧[を認識し、テストプログラムまで届けられています。ただし、これだけで他のアプリとの競合がないとまでは断定できません。

なお、Appleは現在のアプリ開発では、Carbon APIからAppKitやFoundationなどへ移行するよう案内しています。

Karabinerのルールを外すとどうなるか

次の「Command単押しで英数・かな」ルールを一時的に外すと、⌘⇧[から別キーへの変換が動きました。

{
  "from": {
    "key_code": "left_command",
    "modifiers": { "optional": ["any"] }
  },
  "to": [{ "key_code": "left_command" }],
  "to_if_alone": [{ "key_code": "japanese_eisuu" }],
  "type": "basic"
}

fromがCommandそのものを広く受け取り、toがCommandを即座に出力する構成です。このルールが⌘⇧[の認識を妨げていました。

lazy修飾キーとは

Karabiner-Elementsのto.lazy公式ドキュメントによると、lazyを付けた修飾キーは、別のキーが一緒に押されるまで自身のキーイベントを送信しません。

また、to_if_aloneは、対象キーが単独で押されて離されたときにイベントを送ります。公式ドキュメントでも、修飾キーの単押しとキーコンビネーションを両立させる用途でlazyとの組み合わせが紹介されています。

今回必要だった動きは次の通りです。

  • 左Commandだけを押して離す: 英数キーを送る
  • 右Commandだけを押して離す: かなキーを送る
  • Commandを押しながら別のキーを押す: 通常のCommand修飾キーとして扱う
  • ⌘⇧[を押す: 前のタブ用の別ショートカットへ変換する

これはlazyとto_if_aloneの用途に一致します。

Command単押しルールをlazy化する

左Commandのtoに"lazy": trueを追加します。

{
  "description": "Left Command: Command / Eisu when tapped",
  "type": "basic",
  "from": {
    "key_code": "left_command",
    "modifiers": { "optional": ["any"] }
  },
  "to": [
    {
      "key_code": "left_command",
      "lazy": true
    }
  ],
  "to_if_alone": [{ "key_code": "japanese_eisuu" }]
}

右Command側も同様です。

{
  "description": "Right Command: Command / Kana when tapped",
  "type": "basic",
  "from": {
    "key_code": "right_command",
    "modifiers": { "optional": ["any"] }
  },
  "to": [
    {
      "key_code": "right_command",
      "lazy": true
    }
  ],
  "to_if_alone": [{ "key_code": "japanese_kana" }]
}

これにより、Command単押しでは従来通り英数・かなが送られ、他のキーと組み合わせたときだけCommandが修飾キーとして出力されます。

⌘⇧[をアプリが受け取れるキーへ変換する

今回の環境では、元の⌘⇧[を直接アプリへ渡すだけでは改善しなかったため、Karabiner-ElementsでControl + F18へ変換しました。

{
  "description": "Cmd+Shift+[ to Control+F18 in Orca and VS Code",
  "type": "basic",
  "from": {
    "key_code": "open_bracket",
    "modifiers": {
      "mandatory": ["command", "shift"],
      "optional": ["any"]
    }
  },
  "to": [
    {
      "key_code": "f18",
      "modifiers": ["left_control"]
    }
  ],
  "conditions": [
    {
      "type": "frontmost_application_if",
      "bundle_identifiers": [
        "^com\\.stablyai\\.orca$",
        "^com\\.microsoft\\.VSCode$"
      ]
    }
  ]
}

Karabiner-Elementsでは、from.modifiersのcommandとshiftはそれぞれ左右どちらの修飾キーにも一致します。詳しい仕様はfrom.modifiersの公式ドキュメントで確認できます。

この変換ルールは、Command単押しルールより前に配置します。また、frontmost_application_ifでOrcaとVS Codeだけに限定し、他のアプリの⌘⇧[には影響させません。

Orca側の設定

Orcaの~/.orca/keybindings.jsonでは、前のタブをControl + F18へ割り当てました。

{
  "version": 1,
  "keybindings": {},
  "platforms": {
    "darwin": {
      "tab.previousAllTypes": ["Ctrl+F18"]
    },
    "linux": {},
    "win32": {}
  }
}

最初はF18単体を割り当てましたが、Orcaは修飾キーのないショートカットを受け付けず、Include at least one modifier key.と表示しました。そのため、中継キーをControl + F18にしています。

ファイルを編集した後は、Orcaの「設定 → ショートカット → キーバインドファイルメニュー → ディスクから再読み込み」を実行します。設定画面でModified 1、Conflicts 0になれば読み込みは成功です。

VS Code側の設定

VS Codeのkeybindings.jsonにも同じ中継キーを登録します。

{
  "key": "ctrl+f18",
  "command": "workbench.action.previousEditor"
}

これで、物理キーボードでは今まで通り⌘⇧[を押しつつ、アプリにはControl + F18が届き、前のタブへ移動します。

切り分けで役立った順序

今回のような問題では、次の順序で確認すると原因を絞りやすくなります。

  1. 文字単体を入力できるか確認する
  2. Karabiner-EventViewerで物理キーの入力を確認する
  3. 複数アプリで再現するか確認する
  4. macOSがそのキーをグローバルホットキーとして配送できるか確認する
  5. KarabinerのComplex Modificationsを一つずつ外す
  6. to_if_aloneを持つ修飾キールールにlazyがあるか確認する
  7. Karabinerの出力キーとアプリ側の受信キーを分けて検証する

EventViewerに元のキーが見えているだけでは、すべてのComplex Modificationsを通過した後のイベントが期待通りとは限りません。入力、Karabinerによる変換、アプリのキーバインドという3段階に分けることが重要でした。

おわりに

⌘⇧[だけが効かないため、最初はキーボード配列やmacOSの予約ショートカットを疑いました。しかし実際には、日常的に使っていたCommand単押しの英数・かな切り替えルールが、別のCommandショートカットへ影響していました。

to_if_aloneで修飾キーに別の役割を持たせている場合、to側をlazy修飾キーにすることで単押しとキーコンビネーションをきれいに両立できます。特定のCommandショートカットだけが複数アプリで反応しないときは、アプリの設定だけでなく、Karabiner-Elementsの修飾キールールも確認してみてください。