この記事は
Hammerspoon というMacの自動化を行えるツールを使って、key binding を設定する方法を紹介するものです。
さて
昨日、Dashのことを褒めたり(少しけなしたりしたのですが)、
Listの移動に矢印キーを使わなければならないのが辛いと書きました。
Dash側にkey Bindingが用意されておらず、またメニューバーにも実装されていないため(メニューバーにあれば、Macのkeyboard設定でショートカットを適用できる)、どうやれるかを調べて見ました。
Macのkey binding tool
かつては Keyremap4macbook というツールでkeyboardのremapができたのですが、
現在は Karabiner という名前に変わっており、しかも macOS Sierra はサポート対象外となっています。
そして、Sierraで動作するKarabiner-Elementsが開発中とのことです。
ただこれは、本当に単純なキーの入れ替えを行うだけのものですので、Vim Binding を実現するには向きませんでした。
選択肢
macOS Sierra で Karabiner が使えない問題にどう対処したか - Qiita
naoyaさんが詳しく書かれていますが、Hammerspoon と keyhac が使えそうです。
しかし、keyhac は iTerm2 との相性が悪いらしく、まずは hammerspoon を試してみることにします。
Hammerspoon
alert、notification、ウィンドウ操作、マウス移動などなどいろんなことができ、それらのトリガーをkey bindingに設定することが可能です。
AppleScriptによく似ていますが、AppleScriptよりも書きやすそうです(ちなみにAppleScriptも実行できます)。
そして、ここで利用する言語はLuaになります。初めて触りましたが、割と書きやすそう。
Wkipediaを見てみると ソニックザヘッジホッグや FF14、Redisなどでも使われているようです。へー。
Getting Started
アプリを立ち上げて、メニューバーから Open Config すると $HOME/.hammerspoon/init.lua ファイルが開かれます。
とりあえずどうして良いかわからないので、公式を見て見ます。
Getting Started
ざっと眺めてみると、色々面白いです。
環境変数取得、ファイル変更監視、アプリケーションイベント監視、wifiイベント監視、iTunes操作など
様々なことができるようです。
debug用として以下のコードが紹介されていたので、とりあえずinit.luaに入れました。
-- 設定ファイル reload hs.hotkey.bind({"cmd", "alt", "ctrl"}, "R", function() hs.reload() end) -- auto reload function reloadConfig(files) doReload = false for _,file in pairs(files) do if file:sub(-4) == ".lua" then doReload = true end end if doReload then hs.reload() end end local myWatcher = hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start() hs.alert.show("Config loaded")
参考
key bindingでは以下が非常に参考になりました。
- Karabiner 使えない対策: Hammerspoon で macOS の修飾キーつきホットキーのキーリマップを実現する - Qiita
- Hammerspoonで macOS Sierra + Xcode に emacs ライクなキーバインドを設定した - Qiita
- home/init.lua at master · ryuta46/home
その他、公式に記載されているリンク集です。色々参考になりそうです。
Sample Configurations · Hammerspoon/hammerspoon Wiki
さて本題
なんとなくわかってきたところで、Vim Binding をやってみます。
全てのアプリで j/k を有効にする
local function remap(key, mods) mods = mods or {} return function() hs.eventtap.keyStroke(mods, key) end end hs.hotkey.new({'ctrl'}, 'J', remap('down')), hs.hotkey.new({'ctrl'}, 'K', remap('up'))
これやって見たのですが、エディタによっては若干もっさりします。
あと、control keyを押しっぱなしで j/k 押しても動作しません。
そもそも私が使っているエディタ自身に key binding 機能があるので、特定アプリのみで動作(または除外)させる方法を模索してみます。
特定アプリでのみ実行
Dashだけで良いような気がしてきたので、特定アプリで動作するようにしてみます。
参考サイトを見てfilterを利用しましたが、hs.application.watcher
というアプリのイベント監視を使ってもよかったかもしれません。filterは1つのアプリに対して操作しますが、watcherだと対象アプリの幅を広げることができそうです。
local function disableAll(keySet) for k, v in pairs(keySet) do v:disable() end end local function enableAll(keySet) for k, v in pairs(keySet) do v:enable() end end local vimBinding = { hs.hotkey.new({'ctrl'}, 'J', remap('down')), hs.hotkey.new({'ctrl'}, 'K', remap('up')) } hs.window.filter.new('Dash') :subscribe(hs.window.filter.windowFocused, function() enableAll(vimBinding) end) :subscribe(hs.window.filter.windowUnfocused, function() disableAll(vimBinding) end)
アプリの切り替えのたびにkey bindingの有効・無効化をしているのが少し解せません。
まぁでも、とりあえずこれで満足。
特定アプリを除外
おまけ。一応、特定アプリを除外する方法でも実装。
local function disableAll(keySet) for k, v in pairs(keySet) do v:disable() end end local function enableAll(keySet) for k, v in pairs(keySet) do v:enable() end end vimBinding = { hs.hotkey.new({'ctrl'}, 'J', remap('down')), hs.hotkey.new({'ctrl'}, 'K', remap('up')) } enableAll(vimBinding) hs.window.filter.new('Atom') :subscribe(hs.window.filter.windowFocused, function() disableAll(vimBinding) end) :subscribe(hs.window.filter.windowUnfocused, function() enableAll(vimBinding) end)
やってみて
Hammerspoonすげー便利。automator, applescript, macのkeyboard shortcut を組み合わせて自動化している人はそこそこいると思いますが、Hammerspoon使うと短く書けて、しかもscript 1個ですみそうです。
これで、dashでの利用での利用もはかどります。
例えば、今現在Evernoteの特定ページをTODOリストにして、shortcutで表示するみたいなことやっているのですが、具体的には以下の段階を踏んで実現しています。
1. shellを作成(open evernote:///view/.... しているだけ) 2. shellをアプリ化 3. アプリをautomatorでサービス化 4. サービスをMacのショートカットキーに登録
Hammerspoon使うと、これが一発で書けそうです。wktkしてきました。