IntelliJとかJVMのIDE使いでもKarabinerなしでバックスラッシュを片手入力したい

  • 28
    Like
  • 7
    Comment

結論

日本語入力の設定で円記号をバックスラッシュに割り当てるのはやめて、Hammerspoon で以下のスクリプトを使います。

https://gist.github.com/tanakahisateru/ea015d3e5ee5e088898411c2643f1123

--- Perfect backslash(\) for Mac JIS keybord users

-- The problem:
--   On Mac, Japanese IME setting to replace Yen-backslash is ignored by
--   IntelliJ, jEdit or such as because JVM uses another keymap traditionally.

-- Solution:
--   Use Hammerspoon (http://www.hammerspoon.org/) instead of Japanese IME
--   setting. Paste below to your ~/.hammerspoon/init.lua file.

-- @author Hisateru Tanaka (tanakahisateru@gmail.com)

local VK_1 = 0x12
local VK_ESC = 0x35

-- Secret key codes not included in hs.keycodes.map
local VK_JIS_YEN = 0x5d
local VK_JIS_UNDERSCORE = 0x5e

--local log = hs.logger.new('keyhook','debug')

function flagsMatches(flags, modifiers)
    local set = {}
    for _, k in ipairs(modifiers) do set[string.lower(k)] = true end
    for _, k in ipairs({'fn', 'cmd', 'ctrl', 'alt', 'shift'}) do
        if set[k] ~= flags[k] then return false end
    end
    return true
end

-- NEVER define as local variable!
jisKeyboardFilter = hs.eventtap.new({
    hs.eventtap.event.types.keyDown,
    hs.eventtap.event.types.keyUp
}, function(event)
    local c = event:getKeyCode()
    local f = event:getFlags()
    -- log.d(...)
    if c == VK_JIS_YEN then
        -- To input \ even if JVM, toggle Option key status when Yen key.
        if flagsMatches(f, {'alt'}) then
            event:setFlags({})
        elseif flagsMatches(f, {}) then
            event:setFlags({alt=true})
        end
        -- Hint: Never replace key code to backslash itself because JIS
        -- keyboard does'nt have phisical backslash and assignes it to close
        -- bracket (]) key.
    elseif c == VK_JIS_UNDERSCORE then
        -- Also map single undetscore (_) key to backslash (\).
        if flagsMatches(f, {}) then
            event:setKeyCode(VK_JIS_YEN)
            event:setFlags({alt=true})
        end
    elseif c == VK_1 then
        -- Customization example: Option+1 => ESC
        if flagsMatches(f, {'alt'}) then
            event:setKeyCode(VK_ESC)
            event:setFlags({})
        end
    end
end)
jisKeyboardFilter:start()

elseif のところはオマケなので、物理 ESC あるよって人はさっくり消してください。

解説

JVM でできた GUI アプリケーションは伝統的に、Macでバックスラッシュを標準で 入力するように しても、円記号でバックスラッシュを入力するとした設定を、なぜか無視します。ATOK使いもMacでシュッとバックスラッシュを打ちたい という動機で ~/Library/KeyBindings/DefaultKeyBinding.dict を書いても、やはり無視されます。そのため、JetbrainsのIDEでバックスラッシュがデフォルト入力にならない ことになり、JVM ベースのエディタを使う人はこれまでだいたい Karabiner を使うのが通例でした。

ところが、いま Karabiner の Sierra 対応が、Karabiner-Elements を済ませてからということで、遅れています。そして、Karabiner-Elements のナイトリービルドでも、ある理由からうまく円記号をバックスラッシュに割り当てることができません。(2017.01.27 現在)

keyhack は Python なんだけどどうも Mac がもともと持っているホットキーとコンフリクトしそうだし... というわけで使うのは Hammerspoon が現在最有力です。ここではやらないけど、こいつだけで、Mac 標準では設定できないショートカットを作れたり、アプリケーションフォーカスの監視ができたり...

さらに深い解説

JIS キーボード版の Mac には、物理的にバックスラッシュキーが付いていません。円記号の刻印のあるキーは、 international3 キーだそうです。なんと! ちなみにアンダースコアに見えるのは international1 だそうな。

Karabiner-Element で international3backslash に書き換えればいいか、というと、話はそう簡単ではありません。物理的に存在しないバックスラッシュキーの信号が来たとき、JIS キーボードの Mac OS はそれを ] キー信号とみなします。

低レベルなIOの世界では、修飾キーなしでバックスラッシュを入力できる信号パターンは存在せず、また JVM は IME の変換設定をことごとく無視します。つまり、JIS キーボードを使っている以上、Option + ¥ と操作するしかないわけです。

では、Hammerspoon でさっくり簡単に行くかというと、まだもうひとつ罠があります。hs.keycodes.map (http://www.hammerspoon.org/docs/hs.keycodes.html#map) には international3 にあたるキーの名前が登録されていません。文字で指定できないため、ハックしたいキーのコード 0x5d を調べなければなりません (Karabiner-Element に付いてくるキーコードイベントの監視ツールで調べました)。

これでようやく、「Option キーなしで 0x5d が来てもムリヤリ Option キーを押していたことにする(キーダウン/アップとも)」 というアイデアにたどり着けます。

ちなみに、Karabiner 使えない対策: Hammerspoon で macOS の修飾キーつきホットキーのキーリマップを実現する ではホットキーを使っていますが、そもそもホットキーは「Siriを呼び出す」みたいな単機能の発動に向いています。発動の頻度が非常に高かったり、キーリピートを活かしたいような場合は、hs.eventtap.new(...):start() で、ホットキーよりもっと手前でキーボード信号イベントを横取りするのが良さそうです。(最初はとても参考にさせてもらいましたが)