karabiner.tsがとてもいいぞ
TL;DR
karabiner.tsがとてもいいぞ
はじめに
macOSユーザーの皆様におかれましては、キーボードのカスタマイズツールとして有名なKarabiner-Elementsをご存知かと思います。
Karabiner-Elementsは、macOSのキーボードイベントをフックして、キーの入力をカスタマイズすることができるツールです。
中でも Complex Rules という機能を使うと、かなり自由度の高いカスタマイズが可能です。
例えば、
- CapsLockをCtrlに変更する
- Commandを空打ちで英/かなを切り替える
- アプリを起動するショートカットを追加する
など、様々なカスタマイズが可能です。
自分も初めてMacを手にした時からKarabiner-Elementsを使っていて、結構カスタマイズしています。
しかし、Karabiner-Elementsの設定ファイルはJSON形式で記述するため、設定が複雑になると管理が大変になります。
自分もまあまあな量の設定をしているのですが、JSONなので冗長ですし、繰り返しの設定を書くのが面倒です。
可読性も悪いです。
何かいい方法はないかと探していたところ、karabiner.tsというツールを見つけました。
karabinerの設定をいい感じに書く
karabinerの設定をいい感じに書くための試みはいくつかなされています。
JSON Schemaを使って補完を聞かせる方法や、
Edn形式で設定を書く方法などがあります。
またTypeScriptを使って設定を書く方法もあります。
色々試した結果、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を生成しています。
deno task watch
でファイルを監視して、ファイルが変更されるとkarabiner.jsonが生成されるのはとても体験が良いです。
(deno なので node_mudules
を管理しなくて良いのも👍)
自分の使っている便利設定
ここからは、自分が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
Command + Q
を長押しでアプリケーションを終了する
macOSではCommand + Q でアプリを終了することができますが、これを長押しで終了するようにしています。
間違えて終了してしまうことがなくなります。description
これにはKarabiner-Elementsの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
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
Return
と Shift + Return
を入れ替える
Discord の DiscordのメッセージはReturn
を押すと送信されてしまいます。
この挙動が気に入らないのでShift + Return
とReturn
を入れ替えることで
- 通常のメッセージ送信は
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
h
/j
/k
/l
を矢印キーにする
Trackpadに触れている時だけ 自分はVimを使っているので、矢印キーを使わずにh
/j
/k
/l
を使ってカーソル移動をしています。
これをVim以外でも使いたいわけです。
以前はfn
キーと組み合わせてh
/j
/k
/l
を矢印キーに割り当てていましたが、つい最近 fn
キーの代わりにTrackpadに触れているかどうかをトリガーにすることにしました。
これには MultitouchExtension
というKarabiner-Elementsのプラグインを使っています。
普段は自作キーボードを使っているのでこの設定は不要ですが、いざという時に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
おわりに
karabiner.ts はいいぞ!
ドキュメントにはより高度な使い方(レイヤーの設定等)も書かれているので、興味がある方はぜひ試してみてください。
Discussion