@ryoppippi

karabiner.tsがとてもいいぞ

23 May 2024 ・ 9 min read


TL;DR

karabiner.tsが​とても​いいぞ

0.png

https://evan-liu.github.io/karabiner.ts/

はじめに

macOSユーザーの​皆様に​おかれましては、​キーボードの​カスタマイズツールと​して​有名な​Karabiner-Elementsを​ご存知かと​思います。

https://karabiner-elements.pqrs.org/

Karabiner-Elementsは、​macOSの​キーボードイベントを​フックして、​キーの​入力を​カスタマイズする​ことができる​ツールです。

中でも​ Complex Rules と​いう​機能を​使うと、​かなり​自由度の​高い​カスタマイズが​可能です。 例えば、

  • CapsLockを​Ctrlに​変更する
  • Commandを​空打ちで​英/かなを​切り​替える
  • アプリを​起動する​ショートカットを​追加する

など、​様々な​カスタマイズが​可能です。

https://karabiner-elements.pqrs.org/docs/manual/configuration/configure-complex-modifications/

自分も​初めて​Macを​手に​した​時から​Karabiner-Elementsを​使っていて、​結構​カスタマイズしています。

しかし、​Karabiner-Elementsの​設定ファイルは​JSON形式で​記述する​ため、​設定が​複雑に​なると​管理が​大変に​なります。 自分も​まあまあな​量の​設定を​しているのですが、​JSONなので​冗長ですし、​繰り返しの​設定を​書くのが​面倒です。 可読性も​悪いです。

何か​いい方​法は​ないかと​探していた​ところ、​karabiner.tsと​いう​ツールを​見つけました。

karabinerの​設定を​いい​感じに​書く

karabinerの​設定を​いい​感じに​書く​ための​試みは​いく​つかなされています。

JSON Schemaを​使って​補完を​聞かせる​方​法や、 https://github.com/pqrs-org/Karabiner-Elements/issues/1918

Edn形式で​設定を​書く​方​法などが​あります。 https://github.com/yqrashawn/GokuRakuJoudo

また​TypeScriptを​使って​設定を​書く​方法も​あります。 https://github.com/mxstbr/karabiner https://github.com/evan-liu/karabiner.ts

色々試した​結果、​karabiner.tsが​一番​自分に​合っていると​感じました。

karabiner.ts

karabiner.tsは、​TypeScriptを​使って​karabinerの​設定を​書く​ための​ツールです。 karabiner.tsを​使うと、​TypeScriptの​型システムを​使って​karabinerの​設定を​書く​ことができます。 その​ため、​キーの​名前に​補完が​効いたり、​型エラーが​出たりするので、​設定を​書く​際に​とても​便利です。 もちろんTypeScriptの​構文が​使えるので、​mapや​filterなどの​関数を​使って​設定を​書く​ことも​できます。 APIが​だいぶ関数型を​意識して​設計されているので、​とても​書きやすかったです。

例えば、​CapsLockを​Ctrlに​変更する​設定は​以下のように​書く​ことができます。

import * as k from 'karabiner_ts';

k.rule('Change CapsLock to Ctrl')
	.manipulators([
		k.map({ key_code: 'caps_lock' })
			.to({ key_code: 'left_control' })
			.toIfAlone({ key_code: 'caps_lock' }),
	]);

これが​コンパイルされると、​以下のような​JSONが​生成されます。

{
	"description": "Change CapsLock to Ctrl",
	"manipulators": [
		{
			"type": "basic",
			"from": {
				"key_code": "caps_lock"
			},
			"to": [
				{
					"key_code": "left_control"
				}
			],
			"to_if_alone": [
				{
					"key_code": "caps_lock"
				}
			]
		}
	]
}

自分は​karabiner.tsを​使って​設定を​書き、​denoで​コンパイルして​karabiner.jsonを​生成しています。

https://github.com/ryoppippi/dotfiles/tree/65dc955a4187c9c375793a99271b8af4c2014d3e/karabiner

deno task watch で​ファイルを​監視して、​ファイルが​変更されると​karabiner.jsonが​生成されるのは​とても​体験が​良いです。

(deno なので​ node_modulesを​管理しなくて​良いのも​👍)

自分の​使っている​便利設定

ここからは、​自分が​karabiner.tsを​使って​設定している​便利な​設定を​紹介します。

Commandを​空打ちで​英/かなを​切り​替える

USキーボードを​使っている​人には​定番の​設定ですね。

  k.rule("Tap CMD to toggle Kana/Eisuu", ifNotSelfMadeKeyboard).manipulators([
    k.withMapper(
      {
        "left_command": "japanese_eisuu",
        "right_command": "japanese_kana",
      } as const,
    )((cmd, lang) =>
      k.map({ key_code: cmd, modifiers: { optional: ["any"] } })
        .to({ key_code: cmd, lazy: true })
        .toIfAlone({ key_code: lang })
        .description(`Tap ${cmd} alone to switch to ${lang}`)
        .parameters({ "basic.to_if_held_down_threshold_milliseconds": 100 })
    ),
  ]),

withMapperと​いう​関数を​使うとarray.mapのように​複数の​設定を​一気に​書く​ことができます。 descriptionも​literal stringで​書けて​とても​良いですね。

JSON

https://github.com/ryoppippi/dotfiles/blob/295e8c0a3ab6e4e9642163eae53d12582578be5b/karabiner/karabiner.json#L90-L168

Command + Q を​長押しで​アプリケーションを​終了する

macOSでは​Command + Q で​アプリを​終了する​ことができますが、​これを​長押しで​終了するように​しています。 間違えて​終了してしまうことがなくなります。​description

これには​Karabiner-Elementsのto_if_held_downを​使います。 https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/to-if-held-down/

  k.rule("Quit application by holding command-q").manipulators([
    k.map({
      key_code: "q",
      modifiers: { mandatory: ["command"], optional: ["caps_lock"] },
    })
      .toIfHeldDown({
        key_code: "q",
        modifiers: ["left_command"],
        repeat: false,
      }),
  ]),
JSON

https://github.com/ryoppippi/dotfiles/blob/295e8c0a3ab6e4e9642163eae53d12582578be5b/karabiner/karabiner.json#L62-L89

Ctrl + , で​ Wezterm を​起動する

自分の​使っている​ターミナルエミュレータである​Weztermを​起動する​Hotkeyを​設定しています。

function toHideApp(name: string) {
  return k.to$(
    `osascript -e 'tell application "System Events" to set visible of process "${name}" to false'`,
  );
}

  k.rule("Toggle WezTerm by ctrl+,")
    .manipulators([
      k.withMapper(
        [
          toHideApp("WezTerm"),
          k.toApp("WezTerm"),
        ] as const,
      )((event, i) =>
        k.withCondition(
          ...[k.ifApp("wezterm")].map((c) => i === 0 ? c : c.unless()),
        )([
          k.map({ key_code: "comma", modifiers: { mandatory: ["control"] } })
            .to(event),
        ])
      ),
    ]),
JSON

https://github.com/ryoppippi/dotfiles/blob/295e8c0a3ab6e4e9642163eae53d12582578be5b/karabiner/karabiner.json#L169-L221

Discord の​ Return と​ Shift + Return を​入れ替える

Discordの​メッセージはReturnを​押すと​送信されてしまいます。 この​挙動が​気に入らないのでShift + ReturnReturnを​入れ替える​ことで

  • 通常の​メッセージ送信はShift + Return
  • 改行はReturn

と​いう​挙動に​しています。

  k.rule(
    "Swap Enter & Shift+Enter in Discord",
    k.ifApp({ bundle_identifiers: ["com.hnc.Discord"] }),
  )
    .manipulators([
      k.map({
        key_code: "return_or_enter",
        modifiers: { mandatory: ["shift"] },
      })
        .to({ key_code: "return_or_enter" }),

      k.map({ key_code: "return_or_enter" })
        .to({ key_code: "return_or_enter", modifiers: ["shift"] }),
    ]),
JSON

https://github.com/ryoppippi/dotfiles/blob/295e8c0a3ab6e4e9642163eae53d12582578be5b/karabiner/karabiner.json#L222-L271

Trackpadに​触れている​時だけ h/j/k/l を​矢印キーに​する

自分は​Vimを​使っているので、​矢印キーを​使わずにh/j/k/lを​使って​カーソル移動を​しています。 これを​Vim以外でも​使いたいわけです。

以前はfnキーと​組み合わせてh/j/k/lを​矢印キーに​割り当てていましたが、​つい​最近​ fn キーの​代わりに​Trackpadに​触れているか​どうかを​トリガーに​する​ことにしました。 これには​ MultitouchExtension と​いう​Karabiner-Elementsの​プラグインを​使っています。

https://karabiner-elements.pqrs.org/docs/json/extra/multitouch-extension/

普段は​自作キーボードを​使っているので​この​設定は​不要ですが、​いざと​いう​時に​MacBookの​キーボードを​使う​ときに​この​設定を​有効に​するように​しています。

こういった​「似たような​設定だけど​繰り返し書くのが​面倒」と​いう​場合に、​条件を​変数に​まとめて​使い回すことができるのも​karabiner.tsの​良い​ところです。

/** not apple keyboard */
const ifNotSelfMadeKeyboard = k.ifDevice([
  { product_id: 1, vendor_id: 22854 }, // Claw44
]).unless();

/**
* trackpad touched
* if not touched, multi touch finger count is 0
*/
const ifTrackpadTouched = k.ifVar("multitouch_extension_finger_count_total", 0)
  .unless();

  k.rule(
    "toggle h/j/k/l to arrow keys",
    ifTrackpadTouched,
    ifNotSelfMadeKeyboard,
  ).manipulators([
    k.withMapper(
      {
        "h": "left_arrow",
        "j": "down_arrow",
        "k": "up_arrow",
        "l": "right_arrow",
      } as const,
    )((key, arrow) =>
      k.map({ key_code: key })
        .to({ key_code: arrow })
        .description(`Tap ${key} to ${arrow}`)
    ),
  ]),
JSON

https://github.com/ryoppippi/dotfiles/blob/295e8c0a3ab6e4e9642163eae53d12582578be5b/karabiner/karabiner.json#L273-L389

おわりに

karabiner.ts は​いいぞ!

ドキュメントには​より​高度な​使い方​(レイヤーの​設定等)も​書かれているので、​興味が​ある方は​ぜひ試してみてください。

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi