ターミナルや iTerm2 で AquaSKK を使う場合

2019年12月28日

最近、iTerm2 を使いはじめた。
 これ格好よくて、ctrl キーを二回連打すると、しゅばってターミナルのスクリーンが降りてくるのである。

設定は iTerm2 プリファレンスの「Keys」>「Hotkey」タブ>「Create a Dedicated Hotkey Window…」で行なえる。
 なお「システム環境設定」>「セキュリティとプライバシー」>「プライバシー」タブ>「アクセシビリティ」でアプリケーションに許可を与えてやる必要がある。

ctrl + j

本稿の目的はそういうことではなく、ターミナルや iTerm で AquaSKK を使う時の話だ。
 AquaSKK というのは日本語入力インプットメソッドである。日本語入力モードにする時、ctrl + j というキーコンビネーションを使う。
 しかし、ターミナルや iTerm で ctrl + j を押すと、改行されてしまうのである。

この ctrl + j を抑制するなら、karabiner-elements を使うのがいいみたいだ。
 下記のブログさまが json を公開されている。

🔗 Karabinar-ElementsでiTerm2 + AquaSKK環境下でのCtrl-J問題を解決する – nil.nu

下のほうにインストール用のリンクまであって非常に便利。

Hammerspoon で対応する

Hammerspoon でもできるはずなので、コードを書いてみた。

📝 ターミナルで ctrl + j

-- ターミナルでctrl + j
local function terminalEvent(name, event, app)
  if event == hs.application.watcher.activated then
    if name == 'ターミナル' or name == 'iTerm2' then
        hs.hotkey.bind({"ctrl"}, "j", function()
            hs.eventtap.keyStroke({}, 104, 0)
        end)
    else
        hs.hotkey.disableAll("ctrl", "j")
    end
  end
end

terminalWatch = hs.application.watcher.new(terminalEvent)
terminalWatch:start()

hs.application.watcher は、アプリケーションが起動したり終了したり、アクティブになったり、非アクティブになったりするのを監視する。
 アプリを選択したり隠したりするごとに、三つの引数を引き受けてめちゃめちゃ監視している。その三つというのはアプリの名前、イベント(起動した、アクティブになった、など)、アプリのオブジェクト、である。

if event == hs.application.watcher.activatedというのはすなわち、なにかのアプリケーションがアクティブになったら、という意味だ。その下でアプリの名前で識別している。
 if name == 'ターミナル' or name == 'iTerm2'
 アクティブになったアプリの名前が「ターミナル」や「iTerm2」なら、という意味である。
 ターミナルや iTerm ならキーバインドを 104 にする。
 104 というのは「かなキー」のキーコードである。

AquaSKK における iTerm 上の L キーの問題

問題は L キーだ。
 AquaSKK 統合では、日本語入力モードの時 L キーを押すと英数入力になる。
 ターミナルは何事もなくその通りに動作する。
 しかし iTerm だと「 l 」が入力されてしまうのだ。l が入力されて英数入力になる。

これを抑制したいのだが難しい。
 まず現在の入力ソースを調べる。日本語入力モードなら、L キーの入力を監視する。
 L キーが押されたら入力を抑制する、という流れが考えられる。

現在の入力ソースを調べるにはどうすればいいか。ターミナルで、
 defaults read com.apple.HIToolbox
 を実行すると、AppleSelectedInputSources という項目に現在の入力ソースが表示される。

AppleSelectedInputSources =     (
                {
            "Bundle ID" = "jp.sourceforge.inputmethod.aquaskk";
            "Input Mode" = "com.apple.inputmethod.Japanese";
            InputSourceKind = "Input Mode";
        }
    );

Hammerspoon ではコマンドを実行できるので(hs.execute(“コマンド”))、なんとかなりそうに思える。
 しかし AquaSKK 統合では、日本語入力モードだろうが英数入力だろうが、この部分に変化は出ないのである。
 つまり、どうやって AquaSKK の入力モードを調べればいいのかわからない。
 初手から挫折なわけだ。

次に考えたのが、かなキーが押された時に true になる変数を設置する方法だ。
 かなキーが押されたなら、間違いなく日本語入力モードに入ってると考える。
 その変数が true の時だけは、L キーを押しても l が入力されず、かわりに「英数キー」を押したことにする。
 実際に書いたのが以下のようなコード。
 変数の名前は aquaHira とした。

aquaHira = false
local function lkeyWatch(ev)
    local c = ev:getKeyCode()
    if c == 104 then
        aquaHira = true
    end
    if c == 37 and aquaHira then
        ev:setKeyCode(-1)
        hs.eventtap.keyStroke({}, 102, 0)
        aquaHira = false
    end
end

aquaL = hs.eventtap.new({hs.eventtap.event.types.keyDown}, lkeyWatch)

local function terminalEvent(name, event, app)
  if event == hs.application.watcher.activated then
    if name == 'ターミナル' or name == 'iTerm2' then
        hs.hotkey.bind({"ctrl"}, "j", function()
            hs.eventtap.keyStroke({}, 104, 0)
        end)
    else
        hs.hotkey.disableAll("ctrl", "j")
    end
    if name == 'iTerm2' then
        hs.eventtap.keyStroke({}, 102, 0)
        aquaHira = false
        aquaL:start()
    else
        aquaL:stop()
    end
  end
end

terminalWatch = hs.application.watcher.new(terminalEvent)
terminalWatch:start()

上記コードを init.lua に記述してリロードすると、ちゃんと l の入力が抑制されるようになる。
 ちなみにキーコード 102 は「英数キー」、キーコード 37 は「L キー」だ。

このコードの問題点は、かなキーを押さない限り aquaHira が true にならないところだ。
 つまり AquaSKK が日本語入力モードの状態で iTerm を前面に出すと aquaHira は false のまま。L キーの文字入力は抑制されない。
 この問題をふせぐため、もう iTerm がアクティブになったらすぐさま「英数キー」を送信することにした。iTerm が前面に出たら、それまでの入力モードがなんだろうと、むりやり英数入力にしちゃうのである。だいぶ力づくの解決法だ。

が、iTerm をたちあげて、いきなり日本語を入力するシチュエーションっていうのも、あまりないんじゃねーかな、と思う。
 けっしてスマートなやり方じゃないけど、まぁしょうがないので、しばらくはこれでいくつもりだ。

追記 19.12.28

AquaSKK はユーザの config である程度、キーマップを変更できる。
 これを利用しない手はないことに気づいた。
 Keymap.conf は ユーザの「ライブラリ/Application support/AquaSKK/」に入っている。文法は以下に書いてある。

🔗 keymap.confの文法 – AquaSKK Wiki – AquaSKK – OSDN

ここで、押しやすさとかは考慮せず、他のキーボード・ショートカットとかぶらないキーの組み合わせを考えて、SwitchToAscii に割り当てるわけだ。
 筆者は ctrl + shift + k にした。

# ======================================
# attribute section(for SKK_CHAR)
# ======================================
ToggleKana        q
ToggleJisx0201Kana    ctrl::q
SwitchToAscii        l||ctrl::shift::k # ←ここを変更した

これで、[ L ] キーか、あるいは [ctrl] + [shift] + [ k ] で AquaSKK の英数モードに出来るようになった(パイプ二本、「 || 」で区切ることで複数のキーを設定できる)。

あとは上述した Hammerspoon のスクリプトで [英数]キーを送っていた部分を [ctrl] + [shift] + [ k ] に書き換えればいい。具体的には以下のようになる。

aquaHira = false
local function lkeyWatch(ev)
    local c = ev:getKeyCode()
    if c == 104 then
        aquaHira = true
    end
    if c == 37 and aquaHira then
        ev:setKeyCode(-1)
        hs.eventtap.keyStroke({"ctrl","shift"}, "k", 0)
        aquaHira = false
    end
end

aquaL = hs.eventtap.new({hs.eventtap.event.types.keyDown}, lkeyWatch)

local function terminalEvent(name, event, app)
  if event == hs.application.watcher.activated then
    if name == 'ターミナル' or name == 'iTerm2' then
        hs.hotkey.bind({"ctrl"}, "j", function()
            hs.eventtap.keyStroke({}, 104, 0)
        end)
    else
        hs.hotkey.disableAll("ctrl", "j")
    end
    if name == 'iTerm2' then
        hs.eventtap.keyStroke({}, 104, 0)
        hs.eventtap.keyStroke({"ctrl","shift"}, "k", 0)
        aquaHira = false
        aquaL:start()
    else
        aquaL:stop()
    end
  end
end

terminalWatch = hs.application.watcher.new(terminalEvent)
terminalWatch:start()

こっちのほうが素直な感じだな。