経緯
macOS Sierra になってからKarabinerが使えなくなって以来(自分は2017年4月頃〜)、Hammerspoon を使ってています。
現在ではSierra以上でも動くKarabiner-Elementsの安定版が出ているので使ってみたのですが、以下の2点が自力で克服できておらず、まだちょっとしんどいのでまたHammerspoonに戻ってきました(誰かがなんとかしてそうだけど…)。
- Office(Outlook) で何故かバインド(Ctrl+A, Ctrl+E)が効かなくなる
- Ctrl+A/E の移動は、単なる行頭行末移動ではなくMove to beginning of text (インデントを考慮して行頭) になってほしい
というわけでほぼほぼ1年使ってある程度手に馴染んできたのと、Karabiner-Elementsの安定版が出た現在でも Hammerspoon で設定を書きたい、書きたいがうまくいっていないという人もいるんではないかと思い、一旦軽くまとめてみることにしました。
HammerSpoon
Hammerspoon はキーリマップツールというよりかは自動化ツールと謳っていて、リマップ以外にウィンドウサイズの変更や移動、WiFiやUSBデバイスの監視なんかもできるみたいみたいです。
設定ファイルはLuaという言語で書きます。ある程度馴染みのあるPythonで設定が書けるkeyhacというソフトもあるので、最初はLua…うぐぐ…となったのですが簡単な範囲なら思ったより全然困ったことが起こらず直感的に書くことができました。
設定ファイル
とりあえず設定ファイルを晒します。
-- -- Hammerspoon用 KeyRemap 設定 -- local function keyCode(key, modifiers) modifiers = modifiers or {} return function() hs.eventtap.event.newKeyEvent(modifiers, string.lower(key), true):post() hs.timer.usleep(1000) hs.eventtap.event.newKeyEvent(modifiers, string.lower(key), false):post() end end local function remapKey(modifiers, key, keyCode) hs.hotkey.bind(modifiers, key, keyCode, nil, keyCode) end local function disableAllHotkeys() for k, v in pairs(hs.hotkey.getHotkeys()) do v['_hk']:disable() end end local function enableAllHotkeys() for k, v in pairs(hs.hotkey.getHotkeys()) do v['_hk']:enable() end end local function handleGlobalAppEvent(name, event, app) if event == hs.application.watcher.activated then -- hs.alert.show(name) if name ~= "iTerm2" then enableAllHotkeys() else disableAllHotkeys() end end end appsWatcher = hs.application.watcher.new(handleGlobalAppEvent) appsWatcher:start() -- -- ここから KeyRemap 設定 -- -- カーソル移動 -- 現状 hs.hotkey.bind の挙動が怪しいので getFlags+getKeyCode を使うといい hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(e) -- Ctrl + Shift + FBNP(ctrl単体のものよりより先に書く必要がある) if e:getFlags().ctrl and e:getFlags().shift then if e:getKeyCode() == 35 then hs.eventtap.event.newKeyEvent({"shift"}, "up", true):post(); return true; elseif e:getKeyCode() == 11 then hs.eventtap.event.newKeyEvent({"shift"}, "left", true):post(); return true; elseif e:getKeyCode() == 45 then hs.eventtap.event.newKeyEvent({"shift"}, "down", true):post(); return true; elseif e:getKeyCode() == 3 then hs.eventtap.event.newKeyEvent({"shift"}, "right", true):post(); return true; elseif e:getKeyCode() == 6 then hs.eventtap.event.newKeyEvent({'shift','cmd'}, 'z', true):post(); return true; end end -- Ctrl + FBNP if e:getFlags().ctrl then -- log の吐き方 -- local log = hs.logger.new('mymodule','debug') -- log.i(e:getKeyCode()) if e:getKeyCode() == 35 then hs.eventtap.event.newKeyEvent({}, 'up', true):post(); return true; elseif e:getKeyCode() == 11 then hs.eventtap.event.newKeyEvent({}, 'left', true):post(); return true; elseif e:getKeyCode() == 45 then hs.eventtap.event.newKeyEvent({}, 'down', true):post(); return true; elseif e:getKeyCode() == 3 then hs.eventtap.event.newKeyEvent({}, 'right', true):post(); return true; -- PCライクなバインディング、たとえば -- ctrl + W を cmd + W にするのも hs.hotkey.bind だと何故か出来ないので -- こっちの方法を使っている -- elseif e:getKeyCode() == 6 then -- hs.eventtap.event.newKeyEvent({'cmd'}, 'z', true):post(); return true; -- elseif e:getKeyCode() == 7 then -- hs.eventtap.event.newKeyEvent({'cmd'}, 'x', true):post(); return true; -- elseif e:getKeyCode() == 8 then -- hs.eventtap.event.newKeyEvent({'cmd'}, 'c', true):post(); return true; -- elseif e:getKeyCode() == 9 then -- hs.eventtap.event.newKeyEvent({'cmd'}, 'v', true):post(); return true; -- elseif e:getKeyCode() == 13 then -- hs.eventtap.event.newKeyEvent({'cmd'}, 'w', true):post(); return true; -- elseif e:getKeyCode() == 46 then -- hs.eventtap.event.newKeyEvent({}, 'return', true):post(); return true; -- hs.eventtap.keyStroke({}, 'return'); end end return false end):start() -- 行頭行末移動 -- home/end は 行頭ではなく、Move to beginning of text (インデントを考慮して行頭) になってほしい remapKey({"ctrl"}, "a", keyCode("left", {"cmd"})) remapKey({"ctrl"}, "e", keyCode("right", {"cmd"})) remapKey({"alt"}, "b", keyCode("left", {"alt"})) remapKey({"alt"}, "f", keyCode("right", {"alt"})) remapKey({"alt"}, "n", keyCode("down", {"alt"})) remapKey({"alt"}, "p", keyCode("up", {"alt"})) remapKey({"alt"}, "h", keyCode('delete', {"alt"})) local function deleteWordForward() keyCode('right', {'shift', 'alt'})() keyCode('delete')() end remapKey({'alt'}, 'd', deleteWordForward) -- Return remapKey({'ctrl'}, 'm', keyCode('return')) -- Delete -- Ctrl+H を文字編集以外(ブラウザバック)でも使いたいため remapKey({'ctrl'}, 'h', keyCode('delete')) -- Ctrl+K は OS 標準のものを使用 -- Office だと効かない 悔しい -- ページスクロール remapKey({'ctrl'}, 'v', keyCode('pagedown')) remapKey({'alt'}, 'v', keyCode('pageup')) -- Esc remapKey({'ctrl'}, 'g', keyCode('escape')) -- -- 参考 -- -- 【テンプレ】 -- Karabiner 使えない対策: Hammerspoon で macOS の修飾キーつきホットキーのキーリマップを実現する - Qiita -- http://qiita.com/naoya@github/items/81027083aeb70b309c14 -- 【行頭、行末移動】 -- Sierra+Hammerspoonでキーバインドを設定する - たまめも(tech) -- http://tamamemo.hatenablog.com/entry/2017/01/30/183650 -- 【hotkey.bind の代替】 -- 5ch -- https://potato.5ch.net/test/read.cgi/mac/1485327943/#217 -- 【困った時】 -- Hammerspoon docs -- http://www.hammerspoon.org/docs/index.html -- 【Ctrl+K (使わなかった)】 -- http://qiita.com/swdyh/items/04f7da8c1209a067add5 -- local function killLine() -- keyCode('e', {'shift', 'ctrl'})() -- keyCode('x', {'cmd'})() -- end -- remapKey({'ctrl'}, 'k', killLine)
やりたいこと
Ctrl + X 始動のキーバインド等は使っていないので基本的な部分をEmacsっぽくしています。以前Windows用にKeyhacの設定ファイルを書いてた時にはもっとゴチャゴチャ色々書いていたのですが、割りと削ぎ落とされてこのくらいになりました。
Ctrl + FBNP: →←↓↑
Emacs風移動。あんまり合理的とも思わないキーマッピングですが、身体に馴染んでしまっているので墓場まで持っていくしかない。テキストエリアだけでなくFinderやChromeなどでも使いたいので完全にリマップしてしまいたいキーです。
Shiftキーを押すと範囲を選択したいし、Altキーを押すと単語ジャンプにしたいです。単語ジャンプはプログラミングのときなど、ASCII文字ばっかり出てくる時は大抵活躍しますし、JetBrainsのエディタならAlt+↑↓はかなり素早く範囲指定できるので協力です。
2017年4月時点では hs.hotkey.bind の挙動が怪しいらしく、カーソル長押し移動が効かなくなるので下記を参考に getFlags+getKeyCode で設定するようにしました。がもしかするとAltキーのほうが今元気に動いているのでこんなに回りくどい方法を取る必要ないかも‥
【Karabiner】キーリマップ・カスタマイズ総合 for Mac【英かな】 [無断転載禁止]©2ch.net
Ctrl + A/E: cmd + ←→
Ctrl + A/E は 行頭行末ではなく、Move to beginning of text (インデントを考慮して行頭) になってほしい(未設定の状態だとXcodeでカーソルが完全に左に行ってしまいつらい気持ちになる)のでcmd + ←→にリマップしました。
JetBrainsのエディタとかだとならないのでXcode使わない人はわざわざマッピングする必要無いかもです。
Ctrl + H: BackSpace, Ctrl + D: Delete
他のCtrl + Hは他のショートカットキーに負けて発動しなくなることがあるし、ブラウザの戻るキーとしても使いたい(ChromeのGo Back With Backspaceというエクステンションを使っています)ので delete にリマップしたいです。
Ctrl+D に関してはOS標準のでほしい挙動になるのでリマップしていないです。他のキーはたいてい大丈夫なのですが、JetBrainsのエディタだとこのマッピングが大体コンフリクトするのでエディタ側のリマップ設定を削除してます。
Ctrl + M: Enter
Enterは遠い(JIS配列だとことさら遠い)のでリマップしておきたいです。
Ctrl +G: Esc
Esc は遠いのでリマップしておきたいです。
Ctrl + V / Alt + V: PageUp / PageDown
このリマップは設定してはいるもののちょっと使いづらくって、表示領域の移動はしてもカーソル移動をしてくれないときがあり、そういうときはトラックパッドに手を伸ばしたほうが速いという感じになります。
- カーソル移動をやってくれる: Android Studio / VSCode
- カーソル移動をやってくれない: Xcode / Google Chrome(はてなブログのエディタも)
Ctrl + Z/X/C: cmd+ Z/X/C
これは最近使ってないです。LinuxやPCのときの癖でCtrl+Z/X/C/Vを押してしまいがちだったので設定していましたが、すっかりMacに慣れて最近は素直にcmdキーを押すようになりました。
困ってるとこ
起動してしばらく経つと Finder などで Ctrl + FBNP が効かなくなるときがある
Reloadするとなおります。たまに起こる。
Chrome の検索バーで Ctrl+N すると検索してほしいが、Chromeに Ctrl+N のショートカットが設定されていて変なURLに飛ぶ
Firefoxだと起きないです。説明が難しいのですが、Chrome はCtrl+Nがホスト名を補完してくようとするキーバインドみたいで、これに負けてしまいます(Karabinerだと起きない)。たとえば hogehoge → Ctrl + N するとhogehoge.com に遷移してしまい、しょんぼりします。
Ctrl+K が Outlookで効かない
local function killLine() keyCode('e', {'shift', 'ctrl'})() keyCode('x', {'cmd'})() end remapKey({'ctrl'}, 'k', killLine)
Ctrl+Kが効かなくなる場面があって、たとえばお仕事でよく使うOutlookだと効かなくなります。
killLine(Ctrl-K一発でその行を消して内容をクリップボードに入れる)のは上記のような方法で実現できるのですが、自分の欲しい挙動であるmac OSの標準の挙動とちょっと違います。
以下の挙動になるキーマップが思いつけば設定できそうです。
- カーソル位置から右側を削除
- カーソル位置が右端ならその位置の改行を削除
- クリップボードは汚してほしくない
ただ行まるまる1行コピー、削除、貼り付けのショートカットは
まとめ
Hammerspoonの設定ファイルの紹介と、なんでこういう設定にしているのかという理由などをまとめてみました。
個人的には国産 Karabiner-Elements にとても期待してます(少額ですが寄付させていだきました)。Office で Ctrl+A/E が効かなくなる問題はぜひご存知な方いらっしゃったら対策を教えてください><
若い頃(学生の頃)は「設定ファイルをゴチャゴチャいじるのは時間の無駄、デフォルトが一番じゃよ」という達観おじさん達を少し軽蔑していたのですが、自分も段々気持ちがわかってくるようになり、こういう設定ファイルをいつまで書いているかはわかりませんが、まだ多少、老いに抗っていきたいと感じてます。