Karabiner-Elementsを使うには設定ファイルを自分で書く必要がありますが、設定の数が少ないうちはまだしも、設定が増えるに従い、だんだんと設定ファイルを書く作業が面倒になります。また、同じような設定を複数書く必要がある場合、コピペを繰り返して一部だけ修正するというような作業を強いられたりします。
こうした問題を解消するツールとして、pqrs-org/KE-complex_modificationsというツールがあります。このツールを簡単に紹介しますと、karabiner-elementsの設定を'erb'ファイルに書いて'json'ファイルに変換するとともに、以下のような設定をImportできるページまで作成するというツールです。'erb'ファイルに設定をかけることから、Rubyのeach
などのメソッドが利用できるうえ、ツール自体にもいくつかの便利なメソッドが用意されており、これを使うとKarabiner-Elementsの設定が楽になります。
なお、Karabiner-Elementsの設定自体は、こちらのページを参考にしてください。
Karabiner-Elementsの設定項目をまとめました - Qiita
また、karabiner-elementsの設定例はこちらを参考にしてください。
[Karabiner-Elementの設定例について - Qiita] (https://qiita.com/s-show/items/40ad22c4ee4a0465fad5)
[Karabiner-Elementsを使ってJISキーボードの"全角/半角"でIMEを切り替える方法 - Qiita] (https://qiita.com/s-show/items/08a7c1b558e4d7e6f1b0)
[Karabiner-Elementsの設定項目が増えてEmacsライクな設定が楽になった - Qiita] (https://qiita.com/s-show/items/e83215f4ee10422abd7c)
Karabiner-Elementsでenthumble(Windows App)のような操作を実現する方法 – 考えるために書くブログ
導入方法
GitHubで公開されていますので、以下のコマンドで導入できます。
git clone https://github.com/pqrs-org/KE-complex_modifications
また、上記のリポジトリは本家ですが、その本家からフォークして、'erb'ファイルに追加した説明文をHTMLに出力する際のオプションを指定できたり、メソッドも本家より豊富で使いやすいリポジトリがありますので、こちらを使うのも良いかと思います。
git clone https://github.com/rcmdnk/KE-complex_modifications
なお、単にツールを使うだけならclone
でローカルにコピーすれば良いのですが、自分の設定や設定をインポートする画面も公開したい場合は、上記のリポジトリをフォークして使用する方が良いです。
導入後のカスタマイズ
ファイルの整理
'src/json'に、色々な人が作成した設定ファイルが多数ありますので、不要なものを削除し、あわせて自分用のファイルを作成します(既存のファイルをリネームするのがオススメです)。また、'docs/json'にあるファイルは、'src/json'にある'erb'ファイルからmake
コマンドで作成されるファイルですので、全て削除しても大丈夫です。また、'src/example.html.erb'ファイルも削除します。
Makefileの編集
Makefile
を以下のとおり編集します。('example.html.erb'ファイルを操作することはないため)
all:
scripts/update-json.sh
scripts/erb2html.rb < src/index.html.erb > docs/index.html
- scripts/erb2html.rb < src/example.html.erb > docs/example.html
scripts/apply-lint.sh
rebuild:
touch src/json/*
$(MAKE) all
script/updata-json.shの編集
make
コマンドを実施すると、'.erb'ファイルとそれに対応する'.json'ファイルのタイムスタンプを確認し、'erb'ファイルの更新日時が'json'ファイルより古い場合、'json'ファイルへの変換は行わない設定になっています。
不要な変換を行わないこの設定は確かに合理的ですが、これにより、新しいメソッドを追加するために'erb2json.rb'だけ編集した場合、make
コマンドを実行しても'json'への変換が行われません。make
コマンドの所要時間は短いので、いつでも全ての'erb'ファイルを'json'ファイルに変換するように設定するように'script/update-json.sh'を編集しました。この編集を行うかどうかは自由です。なお、この編集を行わない場合で、全ての'erb'ファイルを強制的に変換したい場合はmake rebuild
コマンドでOKです。
#!/bin/sh
for srcfile in src/json/*.erb; do
dstfile="docs/json/`basename $srcfile .erb`"
- if [ "$srcfile" -nt "$dstfile" ]; then
+ # if [ "$srcfile" -nt "$dstfile" ]; then
if scripts/erb2json.rb < "$srcfile" > "$dstfile"; then
echo "$dstfile"
else
echo "Failed to convert $srcfile to $dstfile"
rm -f "$dstfile"
exit 1
- fi
+ # fi
done
src/index.htmlの編集
'src/index.html.erb'ファイルのうち、<div class="container">
タグの中にある<% ~ %>
の箇所を編集します。
変更前
<div class="container" style="margin-bottom: 100px">
<div class="text-right" style="margin-bottom: 30px">
<a href="https://github.com/pqrs-org/KE-complex_modifications">GitHub</a>
</div>
<%
$toc = []
$groups = ""
add_group("Modifier Keys","modifier_keys",[
"docs/json/caps_and_return_to_ctrl.json",
"docs/json/caps_lock.json",
"docs/json/control.json",
"docs/json/shift.json",
"docs/json/escape.json",
"docs/json/change_grave_accent_to_escape.json",
"docs/json/modifiers_to_f-keys.json",
"docs/json/delete.json",
"docs/json/change_command_l.json",
"docs/json/change_command_r.json",
"docs/json/ctrl_command_fn_letter_specials.json"
])
//中略
変更後
<div class="container" style="margin-bottom: 100px">
<div class="text-right" style="margin-bottom: 30px">
<a href="https://github.com/pqrs-org/KE-complex_modifications">GitHub</a>
</div>
<%
$toc = []
$groups = ""
add_group("Personal Settings","personal_settings",[
"personal_s-show.json"
])
%>
//中略
add_group
の引数は、一番目が「画面に表示する文字列(上のスクショならPersonal Settings)」、二番目が設定毎に割り当てたID値、三番目が公開するJSONファイルのパスです。そのため、上の変更後の設定は、「personal_s-show.jsonというファイルの設定を、Personal Settingsという名前で公開する」という設定になります。
ここまで行えば、ひとまずの導入作業は完了です。自分の設定を上のスクリーンショットのような形で公開したい場合、GitHub Pagesの設定も必要になりますが、本筋から外れそうなので省略します。
メソッド(当初から用意されているもの)
KE-complex_modificationsの'scripts'ディレクトリにある'erb2json.rb'というファイルに、'erb'ファイルで設定を書くときに便利なメソッドが用意されています。用意されているメソッドのうち、使用頻度が高そうなものを紹介していきます。なお、自分でメソッドを作る際は、このファイルに書くことになります。私が作ったメソッドも紹介します。
from
from
に設定するキーの組み合わせを生成するメソッドです。一番目の引数が設定したいキー、二番目の引数がmodifiers
のmandatory
に設定するキー(Shift, Controlなど)、三番目の引数がmodifiers
のoptional
に設定するキーです。
使用例
from
に、"CapsLock"のオンオフに関わらず、"ctrl-shift-PageUp"の組み合わせに反応するよう指定する設定です。
"from": <%= from("page_up", ["control", "shift"], ["caps_lock"]) %>
from
に、"全角/半角"キーを指定する設定です。
"from": <%= from("grave_accent_and_tilde") %>
変換結果
"from": {
"key_code": "page_up",
"modifiers": {
"mandatory": [ "control", "shift" ],
"optional": [ "caps_lock" ]
}
}
"from": { "key_code": "grave_accent_and_tilde" }
なお、変換結果については、ページを少しでも短くするため、改行された出力結果の一部を1行にまとめています。(以下同じ)
to
to
に設定するキーの組み合わせを生成するメソッドです。メソッドの返り値は配列になりますので、以下の使用例のように、返り値を[]
で囲む必要はありません。
使用例
to
に「何かのキーの組み合わせを"command-k"に変換し、続けて、続けて"command-左矢印"に変換」するという動作を指定する設定です。なお、引数は配列で渡す必要があるのですが、配列が入れ子になっていて頭が混乱しやすいので、以下の使用例ではわざと改行しています。
"to":
<%=
to([
["k", ["command"]],
["left_arrow", ["command", "shift"]]
])
%>
何かのキーの組み合わせを"tab"に変換する設定です。
"to": <%= to(["tab"]) %>
変換結果
"to": [
{
"key_code": "k",
"modifiers": [ "command" ]
},
{
"key_code": "left_arrow",
"modifiers": [ "command", "shift" ]
}
]
"to": [
{ "key_code": "tab" }
]
frontmost_application_if, frontmost_application_unless
変換ルールを適用(または除外)するアプリを指定するため、conditions
のfrontmost_application_if(or unless)
を設定するときに使用します。
使用例
"conditions": [ <%= frontmost_application_if("finder") %> ]
変換結果
"conditions": [
{
"type": "frontmost_application_if",
"bundle_identifiers": [ "^com\\.apple\\.finder$" ]
}
]
アプリの指定方法
対象となるアプリは、引数に所定のアプリ名で指定します。この所定のアプリ名は、'erb2json.rb'のfrontmost_application
メソッドの中で指定されていますので、それと同じものを使う必要があります。所定のアプリ名は、以下のコードの形で行われていますので、自分でアプリ名を追加したい場合は、以下のコードを参考にして自分で'erb2json.rb'ファイルを編集する必要があります。
def frontmost_application(type, app_aliases)
# 中略
finder_bundle_identifiers = [
'^com\.apple\.finder$',
]
# 中略
bundle_identifiers = []
to_array(app_aliases).each do |app_alias|
case app_alias
when 'finder'
bundle_identifiers.concat(finder_bundle_identifiers)
# 以下省略
each_key
複数の同じような設定がある場合に、それらの設定をまとめて書くために使います。文章で説明するよりも実例を見た方が分かりやすいので、以下の使用例と変換結果をみてください。
なお、each_key
メソッドは、manipulators
の直下で使うことが想定されているメソッドで、返り値は配列です。そのため、以下の「上手くいかない例」のように、manipulators
の直下にeach_key
を使わずに設定を書いた後、each_key
を使って設定を書くという使い方はできません。
使用例
"control-command-h, j, k, l"を"カーソルキー"に変換する処理です。
"description": "Change cmd-ctrl-h/j/k/l to arrow keys",
"manipulators":
<%=
each_key(
source_keys_list: ["h", "j", "k", "l"],
dest_keys_list: ["left_arrow", "down_arrow", "up_arrow", "right_arrow"],
from_mandatory_modifiers: ["control", "command"],
as_json: true
)
%>
}
上手くいかない例
{
"description": "(Enthumble) IJKL Mode / normal",
"manipulators": [
{
"type": "basic",
"from": <%= from("international5", [], ["any"]) %>,
"to": [ <%= set_variable(["enthumble_mode"], [1]) %> ]
},
<%=
each_key(
source_keys_list: ["j", "k", "i", "l"],
dest_keys_list: ["left_arrow", "down_arrow", "up_arrow", "right_arrow"],
from_mandatory_modifiers: ["control", "command"],
as_json: true
)
%>
]
}
変換結果
{
"description": "Change cmd-ctrl-h/j/k/l to arrow keys",
"manipulators": [
{
"type": "basic",
"from": {
"key_code": "h",
"modifiers": { "mandatory": [ "control" ] }
},
"to": [ { "key_code": "left_arrow" } ]
},
{
"type": "basic",
"from": {
"key_code": "j",
"modifiers": { "mandatory": [ "control" ] }
},
"to": [ { "key_code": "down_arrow" } ]
},
{
"type": "basic",
"from": {
"key_code": "k",
"modifiers": { "mandatory": [ "control" ] }
},
"to": [ { "key_code": "up_arrow" } ]
},
{
"type": "basic",
"from": {
"key_code": "l",
"modifiers": { "mandatory": [ "control" ] }
},
"to": [ { "key_code": "right_arrow" } ]
}
]
}
メソッド(自分で作ったもの)
ここからは、'erb2json.rb'ファイルに私が追加したメソッドを紹介していきます。
make_data
他のメソッドから呼び出されて、JSON形式の文字列を生成するメソッドです。erb
ファイルから直接使うことはないですが、以下の自作メソッドから呼び出されることが多いメソッドです。
コード
def make_data(data, as_json=true)
if as_json
JSON.generate(data)
else
data
end
end
set_variable
to
にset_variable
を設定する場合に使うメソッドです。一番目の引数に変数名を、二番目の引数に変数の値を指定します。
使用例
enthumble_mode
という変数に1
を代入する処理です。
"to": [ <%= set_variable(["enthumble_mode"], [1]) %> ]
変換結果
"to": [
{
"set_variable": { "name": "enthumble_mode", "value": 1 }
}
]
コード
def set_variable(names, values, as_json=true)
data =[]
unless names.empty?
names.each_with_index do |name, index|
data = {
"set_variable" => {
"name" => name,
"value" => values[index]
}
}
end
else
$stderr << "name empty.\n"
end
make_data(data, as_json)
end
variable_if, variable_unless
conditions
に"type": "variable_if"
または"type": "variable_unless"
を設定する場合に使うメソッドです。一番目の引数に変数名を、二番目の引数に変数の値を指定します。
使用例
「enthumble_mode
変数の値が0
なら」という設定です。
"conditions": [ <%= variable_if(["enthumble_mode"], [0]) %> ]
変換結果
"conditions": [
{ "type": "variable_if", "name": "enthumble_mode", "value": 0 }
]
コード
def variable(type, names, values, as_json=true)
data =[]
unless names.empty?
names.each_with_index do |name, index|
data = {
"type" => type,
"name" => name,
"value" => values[index]
}
end
else
$stderr << "name empty.\n"
end
make_data(data, as_json)
end
def variable_if(names, values, as_json=true)
variable('variable_if', names, values, as_json)
end
def variable_unless(names, values, as_json=true)
variable('variable_unless', names, values, as_json)
end
input_source_if, input_source_unless
IMEの状態に応じて処理を振り分けるため、conditions
に"type": "input_source_if"
または"type": "input_sources_unless"
を設定する場合に使うメソッドです。引数にIMEの状態を示す文字列を渡します。IMEオンなら"ja"
、IMEオフなら"en"
を指定します。
使用例
"conditions": [ <%= input_source_if("ja") %> ]
変換結果
"conditions": [
{
"type": "input_source_if",
"input_sources": [ { "language": "ja" } ]
}
]
コード
def input_source(type, input_source_aliases, as_json=true)
input_sources = []
to_array(input_source_aliases).each do |input_source_alias|
if input_source_alias.is_a? Hash
input_sources << input_source_alias
end
if input_source_alias.include?("keylayout")
input_sources << { "input_source_id": input_source_alias}
elsif input_source_alias.include?("inputmethod")
input_sources << { "input_mode_id": input_source_alias}
else
input_sources << { "language": input_source_alias}
end
end
unless input_sources.empty?
data = {
"type" => type,
"input_sources" => input_sources
}
make_data(data, as_json)
end
end
def input_source_if(input_source_aliases, as_json=true)
input_source('input_source_if', input_source_aliases, as_json)
end
def input_source_unless(input_source_aliases, as_json=true)
input_source('input_source_unless', input_source_aliases, as_json)
end
set_shell_command
to
に変換後のキーの組み合わせではなく、シェルコマンドの実行を割り当てたいときに使用します。引数にはシェルコマンドダブルクオテーションで囲んで渡します。
使用例
何らかのキーの組み合わせに対して、Finderを開くという処理を設定するものです。ターミナルで入力するときはopen -a 'finder'
と入力しますので、このコマンドをダブルクオテーションで囲んで引数にしています。
"to": <%= set_shell_command(["open -a 'finder'"]) %>
変換結果
"to": [ { "shell_command": "open -a 'finder'" } ]
コード
def set_shell_command(commands, as_json=true)
data =[]
unless commands.empty?
commands.each do |command|
data << { "shell_command" => command }
end
else
$stderr << "name empty.\n"
end
make_data(data, as_json)
end
Rubyのメソッドでよく使うもの
Rubyのメソッドでは、each
やzip
を使っています。以下の使用例のように、複数のキーにまとめて類似の設定を割り当てる際に、これらのメソッドが重宝します。
each, zip
使用例
enthumble_mode
変数の値が1
なら、"j, k, i, l"をカーソルキーに変換するという処理です。文字列型のtypes
変数にtypes
以下の設定を追加していく方法で4つのキーの設定をひとまとめに設定しているのですが、末尾の","を削除する必要がありますので、types.chop!
で末尾の1文字を削除しています。
<%=
from_keys = ["j", "k", "i", "l"]
allow_keys = ["left_arrow", "down_arrow", "up_arrow", "right_arrow"]
types = ""
from_keys.zip(allow_keys) do |from_key, allow_key|
types += "{
\"type\": \"basic\",
\"from\": #{from(from_key, [], ["any"])},
\"to\": #{to([[allow_key]])},
\"conditions\": [
#{variable_if(["enthumble_mode"], [1])}
]
},"
end
types.chop!
types
%>
変換結果
{
"type": "basic",
"from": {
"key_code": "j",
"modifiers": { "optional": [ "any" ] }
},
"to": [ { "key_code": "left_arrow" } ],
"conditions": [
{ "type": "variable_if", "name": "enthumble_mode", "value": 1 }
]
},
{
"type": "basic",
"from": {
"key_code": "k",
"modifiers": { "optional": [ "any" ] }
},
"to": [ { "key_code": "down_arrow" } ],
"conditions": [
{ "type": "variable_if", "name": "enthumble_mode", "value": 1 }
]
},
{
"type": "basic",
"from": {
"key_code": "i",
"modifiers": { "optional": [ "any" ] }
},
"to": [ { "key_code": "up_arrow" } ],
"conditions": [
{ "type": "variable_if", "name": "enthumble_mode", "value": 1 }
]
},
{
"type": "basic",
"from": {
"key_code": "l",
"modifiers": { "optional": [ "any" ] } },
"to": [ { "key_code": "right_arrow" } ],
"conditions": [
{ "type": "variable_if", "name": "enthumble_mode", "value": 1 }
]
}
参考したページ
KE-complex_modificationsを使ってKarabiner-Elementsの定義を色々作って公開する
Karabiner-Elements用のKE-complex_modificationsのextra_descriptionsを自動で生成する
私の設定