黒宮丈治の備忘録 AUTHOR

ショートカットの誤打を2度打ちで防ぐ。Karabiner-Elementsを使って

作業スピードをアップするのに、キーボードショートカットは欠かせません。しかし、時には誤って入力してしまって、余計なことになってしまうことも珍しくないものです。中でもアプリケー0ションを終了させる「⌘Q」や、ウィンドウを閉じる「⌘W」はその代表選手です。そこで、今回はキーボードカスタマイズアプリのKarabiner-Elementsをつかい、それを予防させたので、その備忘録になります。

そもそも⌘Qと⌘Wの誤打はなぜ多いのか?

安全に作業を進める上で、作業途中で細かく「保存」を行うことはクリエイター業務では必須です。最近は自動保存してくれるアプリが多いとはいえ、自動を信用しすぎると後で「もうやりたくない!」となる、うちのデータどこにいったん?現象が起きるもの。

そこでこまめに保存のショートカット「⌘S」を入力するのですが、キーボードの構造上Sの上にはWとQがいるのです。

また、主に全選択などでつかう「⌘A」もその上にQとWがいて、AとSは頻繁につかうショートカットにもかかわらず、近すぎて「安全のために保存しておこう」と思った民たちを奈落の底に突き落としてしまうのです。

Karabiner-Elementsで2度打ちのルールをつくる

そこで今回はキーボードカスタマイズアプリのKarabiner-Elementsを利用します。こちらのアプリについての詳細は置いておきますが、私はUSキーボードをつかう中で「英数」「かな」キーがない問題を解消するために入れています。そのあたりはまた今度。

Karabiner-Elementsには「Complex Modifications」という機能があり、これはプログラミングを利用してキーボードの細かい挙動などに特殊なルールを設定する機能です。さきほど話した「英数」「かな」キーの件もこれを利用しています。

プログラミングの知識がなくても、様々な人がアップした特殊ルールが公式にアップされていて、それを利用することが可能です。今回あげた誤打の中でも⌘Q問題は、多くの人が悩まされているらしく、すでにこれを解決するために「⌘Qでアプリを終了させたい時は連続して2回入力する」というルールがあります。

⌘Qの問題だけ解決したい人は、これをダウンロードして適用すればそれでOKです。誤って⌘Qを打っても、1回打っただけではなにも反応しません。連続して2回打ってはじめて「⌘Qのショートカット」を作動してくれるわけです。

Karabiner-Elementsでは、ダウンロードしたプログラムを自分の使いやすいように書き直すこともできるようになっています。そこで、これを応用して⌘W版もつくればよいわけです。

今回はChatGPT君に相談しました。単純に元のスクリプトをテキストエディタに貼り付けて、それを添付してこれの「⌘W版もつくって」と指示したのみです。

まず以下がそのコードです

{
"description": "Close window by pressing command-w twice",
"manipulators": [
{
"conditions": [
{
"name": "command-w",
"type": "variable_if",
"value": 1
}
],
"from": {
"key_code": "w",
"modifiers": {
"mandatory": ["command"],
"optional": ["caps_lock"]
}
},
"to": [
{
"key_code": "w",
"modifiers": ["left_command"]
}
],
"type": "basic"
},
{
"from": {
"key_code": "w",
"modifiers": {
"mandatory": ["command"],
"optional": ["caps_lock"]
}
},
"to": [
{
"set_variable": {
"name": "command-w",
"value": 1
}
}
],
"to_delayed_action": {
"to_if_canceled": [
{
"set_variable": {
"name": "command-w",
"value": 0
}
}
],
"to_if_invoked": [
{
"set_variable": {
"name": "command-w",
"value": 0
}
}
]
},
"type": "basic"
}
]
}

そして、以下が設定の解説です。

  1. 条件付きで「W」キーが押された場合の動作:
    • command-w という変数が1である場合のみ、実際の「コマンド + W」を送信してウィンドウを閉じる動作を実行します。
  2. 「W」キーが押されたときの動作:
    • 初回の「コマンド + W」の押下で command-w 変数を1に設定します。
    • この変数が1に設定された状態で再度「コマンド + W」を押すと、ウィンドウを閉じる動作が実行されます。
  3. 遅延アクション:
    • 「コマンド + W」を押してすぐに離した場合、もしくは一定時間内に再度押されなかった場合、変数を0にリセットします。

とてもありがたい。あっと言う間です。LLMなのもあって、⌘Wはウィンドウ閉じる動作だから、その名前つけておくねって感じで勝手にタイトルを付け替えてくれていました。

ストレスだから特定のアプリのみにしたい

GPT君ががんばってくれたのですが、ブラウザで大量にタブを立ち上げた後など、⌘Wを何度も2度打ちするのが地味にストレスになりました。そこで、⌘Wの誤打を防ぎたいアプリを挙げて、そのアプリのみで適用するように書き換えてもらいました。

以下がコードです

{
"description": "Close window by pressing command-w twice (specific app only)",
"manipulators": [
{
"conditions": [
{
"type": "frontmost_application_if",
"bundle_identifiers": [
"^com\\.apple\\.Safari$", // SafariのバンドルID
"^com\\.google\\.Chrome$" // ChromeのバンドルID
]
},
{
"name": "command-w",
"type": "variable_if",
"value": 1
}
],
"from": {
"key_code": "w",
"modifiers": {
"mandatory": ["command"],
"optional": ["caps_lock"]
}
},
"to": [
{
"key_code": "w",
"modifiers": ["left_command"]
}
],
"type": "basic"
},
{
"conditions": [
{
"type": "frontmost_application_if",
"bundle_identifiers": [
"^com\\.apple\\.Safari$", // SafariのバンドルID
"^com\\.google\\.Chrome$" // ChromeのバンドルID
]
}
],
"from": {
"key_code": "w",
"modifiers": {
"mandatory": ["command"],
"optional": ["caps_lock"]
}
},
"to": [
{
"set_variable": {
"name": "command-w",
"value": 1
}
}
],
"to_delayed_action": {
"to_if_canceled": [
{
"set_variable": {
"name": "command-w",
"value": 0
}
}
],
"to_if_invoked": [
{
"set_variable": {
"name": "command-w",
"value": 0
}
}
]
},
"type": "basic"
}
]
}

少々複雑になってきました。今回はログりませんが、特定のアプリを除外させることも可能です。その場合は「frontmost_application_if 」の部分を「frontmost_application_unless」することで指定したアプリを除外させられます。

ベースのコードは書けましたが、自分が除外したいアプリを指定する必要があります。以下が、GPT君からの説明です。

説明

  • frontmost_application_if: ここで指定されたバンドルIDに一致するアプリケーションが前面にある場合のみルールが適用されます。
  • frontmost_application_unless: ここで指定されたバンドルIDに一致するアプリケーションが前面にある場合にはルールが適用されません。

つまりここで「バンドルID」なるものを指定しないといけないわけです。

コードを読む限りでは「"^com\\.apple\\.Safari$", // SafariのバンドルID」なっている部分ですね。

では、この調べ方もGPT君に聞きました。いくつかやり方を教えてもらったのですが、アプリやOSのバージョンなどでうまくいかないこともあるようです。結果として現状としては以下の2つのどちらかで表示されました。

1. ターミナルを使用する

以下の手順を試してみてください。

  1. Terminal を開きます。
  2. 以下のコマンドを入力します。アプリケーションの名前を正確に入力してください。: アプリケーション名が正確でないとエラーが表示されることがあります。
    rustコードをコピーする
    osascript -e 'id of app "Adobe Photoshop 2024"'

このコマンドを実行すると、PhotoshopのバンドルIDが表示されます。例えば、Adobe Photoshopの場合、通常は com.adobe.Photoshop というバンドルIDが表示されます。

2. mdls コマンドを使用する

ターミナルで次のコマンドを使用して、アプリケーションのバンドルIDを取得することができます。この方法はアプリケーション名ではなく、アプリケーションのパスを使用するため、より確実です。

  1. ターミナルを開きます。
  2. 次のコマンドを入力して、アプリケーションのバンドルIDを取得します。
    mdls -name kMDItemCFBundleIdentifier /Applications/Adobe\ Illustrator\ 2024/Adobe\ Illustrator\ 2024.app

    注意: /Applications/Adobe\ Illustrator\ 2024/Adobe\ Illustrator\ 2024.app は、実際にAdobe Illustrator 2024がインストールされている場所に変更してください。スペースはバックスラッシュ \ でエスケープします。

このコマンドを実行すると、バンドルIDが以下の形式で表示されます。

kMDItemCFBundleIdentifier = "com.adobe.illustrator"

上記通り、Photoshopでは1のやり方、Illustratorでは2のやり方で表示されました。基本は1でいいようです。

これでバンドルIDを調べて、除外したいアプリを指定すれば完了です。そのアプリのみ⌘W2度打ちでしか反応しなくなります。2つ以上のアプリを指定する場合は、例にもある通り、バンドルIDの指示が終わるたびに,を入力する必要があります。

とはいっても、プログラミングの知識がない人間がやると、「あれ?そのままいれたら駄目じゃん」ということになります。

"^com\\.apple\\.Safari$",  // SafariのバンドルID
"^com\\.google\\.Chrome$" // ChromeのバンドルID

上記どおり、少しだけ加工が必要です。以下がその説明です

この記述は正規表現(regex)を使っており、特定のアプリケーションのバンドルIDと一致させるためのものです。それぞれの記号には特別な意味があり、以下で詳しく解説します。

正規表現の記号の意味

  1. バックスラッシュ (\\):
    • バックスラッシュはエスケープ文字として使われます。正規表現では、特定の文字を特別な意味から「エスケープ」して、その文字そのものを意味させるために使います。
    • 例えば、. は通常「任意の1文字」を意味しますが、\. とすると「ドット(.)」そのものを意味するようになります。
    • "^com\\.apple\\.Safari$" の場合、\\. は実際には . を意味し、バンドルIDの中にあるドットを表現しています。
  2. ドル記号 ($):
    • ドル記号は、「文字列の終わり」を表します。
    • "^com\\.apple\\.Safari$" の場合、この正規表現は com.apple.Safari という文字列が完全に一致し、文字列の最後が Safari で終わることを意味します。
  3. キャレット (^):
    • キャレットは、「文字列の先頭」を表します。
    • "^com\\.apple\\.Safari$" の場合、この正規表現は文字列が com.apple.Safari で始まることを示しています。

正規表現の意味

したがって、 "^com\\.apple\\.Safari$" は次のように解釈されます:

  • ^com: 文字列が com で始まる。
  • \\.apple\\.: com に続くドット、apple に続くドットを含む。
  • Safari$: 文字列が Safari で終わる。

これにより、文字列全体が com.apple.Safari と正確に一致することを確認します。

ダブルクォート (") とシングルクォート (')

  • ダブルクォート ("):
    • JSONやプログラム内で文字列を定義するために使用されます。JSON形式では、文字列は常にダブルクォートで囲む必要があります。
  • シングルクォート ('):
    • シングルクォートは、スクリプトやコマンドラインで文字列を囲むために使用されます。例えば、ターミナルやスクリプトの中で、外側にシングルクォート、内側にダブルクォートを使うことで、文字列の中にダブルクォートを含むことができます。

なぜダブルクォートで囲んでいるのか

正規表現をJSONやスクリプト内で使用する際、エスケープ文字や特別な文字を含む場合は、その文字列をダブルクォートで囲む必要があります。これにより、正規表現がそのまま適切に解釈されるようにします。

まとめ

  • "^com\\.apple\\.Safari$"com.apple.Safari というバンドルIDと正確に一致させるための正規表現です。
  • バックスラッシュはエスケープ文字として使われ、.$ のような特別な文字がそのままの意味で解釈されるようにしています。
  • ダブルクォートは、JSON内で文字列を定義するために必要です。シングルクォートはターミナルやスクリプト内で使われることがありますが、通常JSONではダブルクォートだけを使用します。

書いていることを読む限りだと1のバックスラッシュは1回だけでも良さそう気がしたので、なぜ2回なのかも聞いてみました。

バックスラッシュを2個使う理由は、エスケープシーケンスが重複して解釈されるのを防ぐためです。以下にその詳細を説明します。

1. エスケープ文字の役割

バックスラッシュ (\) は、特定の文字やシーケンスを「エスケープ」して、それらの特別な意味を無効化し、その文字そのものを指すようにします。例えば、正規表現の中で . は「任意の1文字」を意味しますが、\. と書くことで「ドット」そのものを意味します。

2. JSONやプログラミング言語での解釈

JSONや多くのプログラミング言語では、バックスラッシュはエスケープ文字として使われます。例えば、JSONの文字列内で "(ダブルクォート)を表現したい場合、 \" と書きます。

1個のバックスラッシュの意味

  • "com\.apple\.Safari" という記述をJSONファイル内で使用した場合、バックスラッシュはそのままエスケープシーケンスとして解釈されますが、次の文字が特殊な意味を持たない場合は、無視されることがあります。

2個のバックスラッシュの意味

  • "com\\.apple\\.Safari" のように2つのバックスラッシュを使うことで、最初のバックスラッシュがエスケープシーケンスとして解釈され、結果的に1つのバックスラッシュが残ります。このバックスラッシュが正規表現のドット . をエスケープし、「ドットそのもの」を意味します。

3. 例で説明

  • バックスラッシュ1個:
    "com\.apple\.Safari"
    • これをJSONとして解釈すると、 \. は特別なシーケンスとして処理される可能性があり、意図した通りに動作しない場合があります。
  • バックスラッシュ2個:
    "com\\.apple\\.Safari"
    • これをJSONとして解釈すると、 \\ は単一のバックスラッシュ \ として解釈され、正規表現の . をエスケープするために適切に機能します。

4. 要点のまとめ

  • 2個のバックスラッシュ は、エスケープシーケンスを適切に解釈させるために必要です。最初のバックスラッシュはエスケープシーケンスとして機能し、もう1つのバックスラッシュがその後の文字をエスケープする役割を果たします。
  • 1個のバックスラッシュ では、意図した通りに解釈されない可能性があるため、2つのバックスラッシュを使って確実にエスケープすることが推奨されます。

このようにして、正確な正規表現がJSONやプログラム内で適切に機能するようにしています。

こうしてプログラミングの勉強ができるのはありがたいですね。

適切に動作させるために言われた通りにやることにしました。これで誤打の予防ができました。便利です。