hammerspoon で入力ソースをあやつる

筆者は US 配列のキーボードを使用している。
 US 配列のキーボードには「英数」キーと「かな」キーがないので、スペースキーを挟んで左右にあるコマンドキーに、「英数」「かな」を割り当てていた。
 この割り当ては Karabiner-Elements を使えば簡単に出来るらしいのだが、筆者は趣味的に hammerspoon を使っていた。そのためのコードも公開していた。

🔗 Hammerspoon でコマンドキーに「かな」「英数」を割り当てる | 林檎コンピュータ

上記の記事のコードは、コマンドを押したことで、英数やかなキーを押したことにして、入力ソースを変えていた。
 しかし、筆者は知らなかったのだが、hammerspoon には入力ソースを扱う関数があったのである。上記の記事のコードは書き直したほうがいいのではないか、と思いいたった。読み返すといろいろ間違えてるし。

Hammerspoon

hammerspoon は lua という言語で Mac にホットキーなどを設定できるユーティリティである。

🔗 Hammerspoon

インストールするとホームフォルダに「.hammerspoon」という隠しフォルダが生成される。そのなかにある「init.lua」というファイルを、CotEditor などで開いて、コードを書いていく。

たとえば、

📝 入力ソース表示ショートカット

hs.hotkey.bind({"ctrl", "cmd"}, "down", function()
     local method = hs.keycodes.currentMethod()
     hs.alert.show(method)
end)

上記のように書いてみる。メニューのハンマーアイコンから「Reload Config」を実行すると、[ control ] + [ command ] + [ ↓ ] というキーボード・ショートカットで、現在使用しているインプットメソッド名を表示できるようになるのだ。
 hs.keycodes.currentMethod() という関数でインプットメソッド名を取得して、
 hs.alert.show()という関数で画面に表示している。

🔗 Hammerspoon docs: hs.keycodes

上記リンクのドキュメントによると、setMethod(インプットメソッド名)で入力ソースを変更できるとのこと。

システム環境設定の入力ソース

「システム環境設定」>「キーボード」内の「入力ソース」タブで、使用中の入力ソースを調べられる。今回は右コマンドで日本語入力プログラムの「ひらがな」、左コマンドで日本語入力プログラムの「英字」を設定してみたい。

上記の「📝 入力ソース表示ショートカット」で入力ソース名を調べておく。「ひらがな」の場合、入力ソース名は「Hiragana」、「英字」の場合は「Romaji」だった。

Hiragana

英数、かなをコマンドに割り当てる

📝 init.lua

local simpleCmd = false
local map = hs.keycodes.map
local function eikanaEvent(event)
    local c = event:getKeyCode()
    local f = event:getFlags()
    if event:getType() == hs.eventtap.event.types.keyDown then
        if f['cmd'] then
            simpleCmd = true
        end
    elseif event:getType() == hs.eventtap.event.types.flagsChanged then
        if not f['cmd'] then
            if simpleCmd == false then
                if c == map['cmd'] then
                    hs.keycodes.setMethod('Romaji')
                elseif c == map['rightcmd'] then
                    hs.keycodes.setMethod('Hiragana')
                end
            end
            simpleCmd = false
        end
    end
end

eikana = hs.eventtap.new({hs.eventtap.event.types.keyDown, hs.eventtap.event.types.flagsChanged}, eikanaEvent)
eikana:start()

実際に書いてみるとこのようになった。
 ひとつ注意があって、星条旗アイコンの U.S.を呼び出す場合だ。

U.S.
この入力ソース

上述の 「📝 入力ソース表示ショートカット」でインプットメソッド名を調べても、U.S. の場合は「nil」と返ってくる。U.S. はインプットメソッドではない、ということだろう。
 この場合は、
hs.keycodes.setMethod('Romaji')
 の部分を、
hs.keycodes.setLayout("U.S.")
 と書き直す。
 メソッドではなく、レイアウトらしい。レイアウト名を調べるには、上述の 📝 入力ソース表示ショートカットの、
currentMethod
 を、
currentLayout
 と書き直し、入力ソースを US にしてから、ショートカットキーを押せば出てくる。

2 行目、hs.keycodes.mapはキーマップのテーブルを返してくれる。ここでは「map」という変数に入れており、map['cmd']で左のコマンドのキーコードが取り出せる。右のコマンドキーならmap['rightcmd']だ。
 キーマップについては、下記のドキュメントにある。

🔗 Hammerspoon docs: hs.keycodes

どうであれ、コマンドに英数・かなキーを割り当てるコードはこんな感じだと思う。

モディファイキーを押した時の挙動

ここからは筆者のメモだ。
 今回のことで少しだけ、モディファイキーを押した時の hammerspoon の挙動がわかった。
 以下のようなコードでテストしてみる。

📝 flagtest

local function testEvent(event)
    local c = event:getKeyCode()
    local f = event:getFlags()
    if event:getType() == hs.eventtap.event.types.keyDown then
        if f['cmd'] then
            --hs.alert.show('keydown')
            --hs.alert.show(c)
        end
    elseif event:getType() == hs.eventtap.event.types.keyUp then
        if f['cmd'] then
            --hs.alert.show('keyup')
            --hs.alert.show(c)
        end
    elseif event:getType() == hs.eventtap.event.types.flagsChanged then
        if f['cmd'] then
            --hs.alert.show('flagon')
            --hs.alert.show(c)
        else
            --hs.alert.show('flagoff')
            --hs.alert.show(c)
        end
    end
end

flagtest = hs.eventtap.new({hs.eventtap.event.types.keyDown, hs.eventtap.event.types.keyUp, hs.eventtap.event.types.flagsChanged}, testEvent)
flagtest:start()

コマンドキーを押した時、キーを離した時、フラグが変わった時、三つの挙動を調べる。

まず、キーを押した時はどうなるか。hs.eventtap.event.types.keyDown内のコメントを外して reload config する。
 コマンドキーを押してみるが、無反応である。keyDown の場合はコマンドキーを押しただけでは、フラグにならないし、キーコードも返ってこない。
 [ command ] + [ tab ] というショートカットを試してみると、今度は反応がある。
 設定した「keydown」というメッセージと、タブキーのキーコード「48」が返ってくる。コマンドキーだけでは、なんの手掛かりも得られないが、他のキーと組み合わせた時は、フラグが発生し、フラグを取得できるようになる。
 コマンドキーのキーコードに関しては、keyDown では取得できないようだ。

一方でこれは、[ command ] + [ 他のキー ]が押された状態を検知できる、ということだ。
 コマンドキーを押したら入力ソースを変えてほしいのだが、[ command ] + [ 他のキー ]のコンビネーションの時は、入力ソースを変更して欲しくない。
 上述したコードでは、コマンドがらみのキーコンビネーションを検知した時は、simpleCmd という変数を true にすることにした。simpleCmd が true の時は、入力ソース変更の動作には入らないよう、条件分岐するわけだ。

次にキーを離した時の挙動を調べる。
 hs.eventtap.event.types.keyUp内のコメントを外し、keyDown のほうの「hs.alert」は元のようにコメントアウトしておく。
 試してみると、先ほどの keyDown と同じような反応を示した。
 コマンドキー単体ではまったくの無反応。
 タブキーなど、他のキーと組み合わせるとフラグが発生する。設定した「keyup」というメッセージと、タブキーの「48」が、キーを上げた時に返ってくる。ここでもコマンドキーのキーコードは取得できない。

続いてhs.eventtap.event.types.flagsChanged内にある二箇所のコメントを外して試してみた。
 左コマンドキーを単体で押すと、「flagon」というメッセージと、「55」という左コマンドのキーコードが表示された。押している間はずっと表示され、指を離すと今度は「flagoff」と「55」というメッセージが返ってくる。右コマンドのキーコードは「54」だった。
 flagsChangedは、モディファイキーを押した時、離した時、それぞれを検知できるようだ。しかもキーコードも返してくれる。

今回の入力ソースの切り替えは、コマンドキーが押下された時ではなく、コマンドキーから指が離れた時に実行されたほうがいい。
 条件分岐の書き方は、
 if not f['cmd'] then
 となった。
 フラグがコマンドではない場合、という意味かと思うが、こう書くとコマンドキーがキーアップした瞬間を、キーコードつきで検知できるのだ。

なお、コマンドキーを押している間、「flagon」とキーコードはずっと表示されっぱなしなのだが、 [ command ] + [ tab ] などのキーコンビネーションに入ると表示がすっと消える。タブキーのキーコードは表示されない。
 この辺になにかありそうなのだが、今の段階ではよくわからない。