Karabiner 使えない対策: Hammerspoon で macOS の修飾キーつきホットキーのキーリマップを実現する


# 背景

http://qiita.com/naoya@github/items/56a34be85710f4ed5531 でも書いたとおり Karabiner が使えない現状、修飾キーつきのホットキーのカスタマイズをどうするかという問題がある。記事を書いた時点では macOS の DefaultKeybidings.dict でショートカットを定義していたが、[Hammerspoon](http://www.hammerspoon.org/) を使うことでやりたいことが実現できたので、こちらの方法に移行した。

# 課題に感じてたこと

- http://qiita.com/naoya@github/items/56a34be85710f4ed5531#comment-1554604656a7ec49f63d

こちらのコメントに書いたとおり、DefaultKeyBiding.dict では特定のキーイベントを別のキーイベントに変換するという指定はできない。おかげで Karabiner のときにカスタマイズしていた挙動を再現できなかった。

# それ Hammerspoon でできるよ

Hammerspoon は macOS の挙動を自動化するためのツール。自動化の処理を Lua で書けるフレームワークみたいなもの。キーリマップだけが目的のソフトウエアではないが、それにも使える代物。

Hammerspoon で、やっと Karabiner と同じ挙動を再現できた。keyhac のときのように iTerm2 のキーリマップ機能とコンフリクトを起こしたりといった不具合も今のところ起きていない。

## init.lua

試行錯誤の末、`~/.hammerspoon/init.lua` を以下のように書いた。

```lua
local function keyCode(key, modifiers)
   modifiers = modifiers or {}
   return function()
      hs.eventtap.event.newKeyEvent(modifiers, string.lower(key), true):post()
      hs.timer.usleep(1000)
      hs.eventtap.event.newKeyEvent(modifiers, string.lower(key), false):post()      
   end
end

local function remapKey(modifiers, key, keyCode)
   hs.hotkey.bind(modifiers, key, keyCode, nil, keyCode)
end

local function disableAllHotkeys()
   for k, v in pairs(hs.hotkey.getHotkeys()) do
      v['_hk']:disable()
   end
end

local function enableAllHotkeys()
   for k, v in pairs(hs.hotkey.getHotkeys()) do
      v['_hk']:enable()
   end
end

local function handleGlobalAppEvent(name, event, app)
   if event == hs.application.watcher.activated then
      -- hs.alert.show(name)
      if name ~= "iTerm2" then
         enableAllHotkeys()
      else
         disableAllHotkeys()
      end
   end
end

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

-- カーソル移動
remapKey({'ctrl'}, 'f', keyCode('right'))
remapKey({'ctrl'}, 'b', keyCode('left'))
remapKey({'ctrl'}, 'n', keyCode('down'))
remapKey({'ctrl'}, 'p', keyCode('up'))

-- テキスト編集
remapKey({'ctrl'}, 'w', keyCode('x', {'cmd'}))
remapKey({'ctrl'}, 'y', keyCode('v', {'cmd'}))

-- コマンド
remapKey({'ctrl'}, 's', keyCode('f', {'cmd'}))
remapKey({'ctrl'}, '/', keyCode('z', {'cmd'}))
remapKey({'ctrl'}, 'g', keyCode('escape'))

-- ページスクロール
remapKey({'ctrl'}, 'v', keyCode('pagedown'))
remapKey({'alt'}, 'v', keyCode('pageup'))
remapKey({'cmd', 'shift'}, ',', keyCode('home'))
remapKey({'cmd', 'shift'}, '.', keyCode('end'))
```

- キーリマップ
- アプリの切り替えを監視して、対象が iTerm2 だったらキーリマップを無効にする

という二点をやっている。後者を入れないと iTerm2 上でもキーリマップが作用してシェルや Emacs を使ってるときに不都合がありまくったので、こうした。

- https://github.com/naoya/hammerspoon-init/blob/master/init.lua

# 参考情報

- http://qiita.com/deg84/items/792bf6b2adf467df9bdc ･･･ 導入方法。(このエントリの init.lua だとキーリピートが効かないので注意)
- https://github.com/Hammerspoon/hammerspoon/wiki/Sample-Configurations ･･･ Hammerspoon のスクリプトの例がたくさん載ってる
- https://github.com/kkamdooong/hammerspoon-control-hjkl-to-arrow/blob/master/init.lua ･･･ キーリマップのシンプルな例 (iTerm2 の考慮とかなし)
- http://www.hammerspoon.org/docs/index.html ･･･ Hammerspoon の API ドキュメント
- http://www.hammerspoon.org/docs/hs.application.watcher.html ･･･ アプリケーションイベントをウォッチする API
- http://www.hammerspoon.org/docs/hs.hotkey.html ･･･ hotkey 周りの API

## Hammerspoon のバージョン

Version 0.9.50

![スクリーンショット 2016-11-23 12.09.36.png](https://qiita-image-store.s3.amazonaws.com/0/1834/a33ba5c4-72d1-b460-31ef-55bd02657152.png "スクリーンショット 2016-11-23 12.09.36.png")