KarabinerからHammerspoonへ

そろそろ新しいMacBook欲しい… けど El Capitan から Sierra になるとKarabinerが使えなくなるって話だし… とか色々不安があり躊躇していた。 代替、というかちゃんと設定すれば代替できそうなHammerspoonが良さげだったので それを使ってKarabinerからの脱却を試みてみた。

Hammerspoon

www.hammerspoon.org

Mac OSの様々な操作を自動化するためのツールで、ユーザがLuaのスクリプトで設定を書くことでOSの様々な機能を使えるようにブリッジしてくれる。

Key Remap

基本的に 以下の記事のコピペで大丈夫そう。

qiita.com

自分は以下のように書いた。

local function keyStroke(mods, key)
  return function() hs.eventtap.keyStroke(mods, key, 0) end
end

local function remap(mods, key, fn)
  return hs.hotkey.bind(mods, key, fn, nil, fn)
end

-- global
remap({'ctrl'}, 'h', keyStroke({}, 'delete'))
remap({'ctrl'}, '[', keyStroke({}, 'escape'))
remap({'ctrl'}, 'j', keyStroke({}, 'return'))
remap({'ctrl', 'cmd'}, 'j', keyStroke({'cmd'}, 'return'))

-- disable when a specific application is active
local remapKeys = {
  remap({'ctrl'}, 'b', keyStroke({}, 'left')),
  remap({'ctrl'}, 'f', keyStroke({}, 'right')),
  remap({'ctrl'}, 'n', keyStroke({}, 'down')),
  remap({'ctrl'}, 'p', keyStroke({}, 'up')),
  remap({'ctrl'}, 'y', keyStroke({'cmd'}, 'v'))
}

local function handleGlobalAppEvent(name, event, app)
  if event == hs.application.watcher.activated then
    if name == 'iTerm2' or name == 'Code' then
      for i, key in ipairs(remapKeys) do
        key:disable()
      end
    else
      for i, key in ipairs(remapKeys) do
        key:enable()
      end
    end
  end
end

appsWatcher = hs.application.watcher.new(handleGlobalAppEvent)
appsWatcher:start()

hs.hotkey.bindで指定したキー操作時の振る舞いを指定できるので、hs.eventtap.keyStrokeで置き換えたいキー操作を指定してそれをemitさせるfunctionをセットする。pressedfn, releasedfn, repeatfnと3種類の振る舞いを定義できるので注意。リマップは基本的にpressedfn, repeatfnを同じkeyStrokeを起こすfunctionにしておけば良さそう。

あと上述記事にもある通りiTermやVSCodeなどのターミナルやエディタではそれぞれ独自にキーバインド設定できたりするし リマップによる変換があると逆にそれが災いして期待した動作にならなかったりするので、applicationの切り替え時にイベントを取得してdisable()/enable()するように。

別にすべてのリマップを無効にする必要は無かったりもするので、常に有効にしておきたいものとは別に切り替え対象のリマップのkeybindだけを保持しておいて それらだけを有効化/無効化の対象にした。

Sticky Shift

Karabinerでもう一つお世話になっていたのが、所謂sticky-shift的なものをセミコロン;で行う、というもの。

セミコロン;を押した後にキーを押す、という操作でShiftキー押しながらの操作を代替する。(別に使うのはセミコロンじゃなくてもいいのだけど 最も一般的なのがこれ、なのかな)

openlab.ring.gr.jp

AquaSKKを使っていると変換位置の指定の際にShiftキーを多用することになりつらいので、ずっとこの方法にお世話になっていたし、すっかりそれに慣れてしまっていた。

Karabinerでは AquaSKK で ; (セミコロン) を Sticky Shift に使う - Qiita のように設定して使っていたのだけど、これもHammerspoonでどうにか。

local stickyShift = false
local targets = {}
for i = 96, 122 do
  targets[hs.keycodes.map[string.char(i)]] = true
end

hs.hotkey.bind({}, ';', function()
  if hs.keycodes.currentMethod():find('AquaSKK') then
    stickyShift = true
  else
    hs.eventtap.keyStrokes(';')
  end
end)

keyTap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(event)
  if stickyShift and targets[event:getKeyCode()] then
    event:setFlags({shift = true})
  end
  stickyShift = false
end):start()

stickyShiftという変数を持っておいて、入力ソースがAquaSKKのときに;が押されたらフラグを立てる。それ以外のときは普通にそのまま;のkeyStrokeをemitするだけ。

keyDownイベントを監視し、stickyShiftフラグが立っていて かつtargetのkeyCode(母音と主な子音だけで良いのだけど とりあえず全アルファベットを対象にしている)だったら、setFlagsを使ってshiftが押されている状態にイベントを書き換える。stickyShiftフラグは継続させる必要ないので強制的にfalseに戻す。

これで今まで通りに;を使ったsticky-shiftでの日本語入力ができるようになった。

ひらがなモードのときだけフラグを変えて ASCIIモードなら;はそのまま通す、とかも出来れば良いのだけど、入力ソースの判別までは出来ても 現在の入力モードが何か、までは取る方法が分からなかった(無い?)ので;を普通に打ちたい場合は普通に英字入力に切り替えるしかない。 まぁこれはKarabinerのときもそうだったし仕方ない。

あと どういうわけか、SierraだとVisual Studio Code上でAquaSKKがまったく効かなくて、ひらがなモードに入ることすらできないようだ。キー設定とかそういう問題じゃなさそうで どうにもならないんだけど、、他の人は普通に使えているのだろうか…?

まぁVSCodeはコード書くものだし日本語の文章書きたいときは他のアプリケーションを使えばいいか…ということで割り切る(この記事の下書きはAtomで書いてる)。

Launcher

あとよく使っていたのがhotkeyでのアプリ切り替え。cmd+ctrl+左手で打てるキー の組み合わせでターミナル/エディタ/ブラウザ/チャット/ツイッター などをサクサク切り替えたくて 以前はSlateやPhoenix、もしくは有料アプリのShortcutsなんかを使っていたりもしたけど、これもHammerspoonで解決できるようになったのでLauncherアプリは不要になった。

local function launcher(mods, key, appname)
  hs.hotkey.bind(mods, key, function()
    hs.application.launchOrFocus('/Applications/' .. appname .. '.app')
  end)
end

launcher({'cmd', 'ctrl'}, 'q', 'iTerm')
launcher({'cmd', 'ctrl'}, 'w', 'Visual Studio Code')
launcher({'cmd', 'ctrl'}, 'e', 'Google Chrome')
launcher({'cmd', 'ctrl'}, 't', 'Twitter')
launcher({'cmd', 'ctrl'}, 's', 'Slack')

Key Repeat

あとそういえばkarabinerで便利だったのがキーリピート間隔の設定だったのだけど、 システム環境設定での設定可能範囲を超える場合はdefaultsコマンドで設定すれば良いだけ、というのをググって知ったので 問題なかった。

$ defaults write -globalDomain KeyRepeat -int 1
$ defaults write -globalDomain InitialKeyRepeat -int 10

とかやって再起動すれば反映される(システム環境設定での変更ではKeyRepeatは2、InitialKeyRepeatは15が最小値っぽい)

雑感

とりあえずKarabinerが無くても困らない程度の設定は出来たっぽい。他にもカスタマイズしたければinit.luaを頑張って書けば色々できそうではある。

しかしまぁKarabinerがSierraでも動いてくれるのが何よりだし このHammerspoonだって いつまたMac OSの変化で使えなくなってしまうか分からないし

こうしてMacの変更に振り回されて色々と苦労しないといかんのはどうにからならないもんかなぁ、と思ってしまう