Hammerspoonを使ってエスケープキーで英数モードへ・コロンとセミコロンを入れ替える

Hammerspoonは、macOSでキー入力やウィンドウ操作をLuaスクリプティングでカスタマイズできる便利なアプリです。 特にキーマッピングを変更するhs.hotkey.bindは便利なAPIですが、デフォルトの処理も行われなくなるのでたまに困る場面があります。

例えば、escapeキーを押した時に、普通のエスケープ処理に加えてIMEを英数モードにするみたいなことをしたいときにはhs.hotkey.bindは使えません。 正確には、一時的にbindを解除しておいてデフォルト処理のためのイベントを発火して、タイマーで再度bindを有効化するみたいな処理を書けば使えなくはないのですが、これはあまり良い設定方法ではありません。 こういう場面ではhs.eventtap.newを使ってキーが押されたときに処理を差し込むことで綺麗に設定できます。

switchToEisuOnEscape = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(e)
  if hs.keycodes.map[e:getKeyCode()] == 'escape' then
    hs.eventtap.keyStroke({}, 'eisu', 0)
  end
end):start()

switchToEisuOnEscapeグローバル変数で参照されていませんが、こうしておかないとGCが走ってeventtapが動かなくなるので、こういうものをグローバル変数に入れておくのはHammerspoonの設定あるあるです (参考)。

ここからが本題ですが、皆さんはコロンとセミコロンを入れ替えていますか? この記事に辿り着いた皆さんはVimを使う英字キーボードユーザーだと思うのですが、当然なんらかの方法でコロンとセミコロンは入れ替えていますよね? そういう場面でもhs.hotkey.bindよりもhs.eventtap.newを使う方が良いでしょう。

swapColonAndSemicolon = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(e)
  if hs.keycodes.map[e:getKeyCode()] == ';' then
    remappingColonAndSemicolon = not remappingColonAndSemicolon
    if remappingColonAndSemicolon then
      hs.eventtap.event.newKeyEvent(hs.keycodes.map.shift, not e:getFlags().shift):post()
      hs.eventtap.event.newKeyEvent(';', true):post()
      return true
    end
  end
end):start()

コロンまたはセミコロンが押されたときにシフトキーの状態をトグルして、再度セミコロンキーをkey-downするイベントを発火します (hs.eventtap.event.newKeyEventの第二引数がkey-downかkey-upかを表すbooleanです)。 ここで発火したイベントに対してもeventtapが反応してしまうので、booleanのグローバル変数で処理が無限再帰してしまわないようにしています。 trueを返してイベントの伝搬を止めているのも重要なポイントです。 この設定ならキーリピート時にもきちんと動作してとても便利です。

私は最近までhs.hotkey.bindを使ってコロンとセミコロンを入れ替えていたのですが、いつの間にかリマップの処理に時間がかかるようになってしまい (:qと押したつもりがq:になるなど)、またキーリピート時にも思うように動作しなくなってしまいました。 コロンを打ったあとは気持ち少し待つみたいにしてしばらくは頑張っていたのですが、設定をちゃんと見直したところhs.eventtap.newを使う方が良いということがわかりました。 Hammerspoonを使っていて困っている方にお役に立てると幸いです。