Create a gist now

Instantly share code, notes, and snippets.

@shiro-t /init.lua
Last active Jun 18, 2017

What would you like to do?
My init.lua
-- key mappings
defaultKeyMapper = {
{{"ctrl"}, 'a', {'cmd'}, 'left' },
{{"ctrl"}, 'b', {}, 'left' },
{{"ctrl"}, 'e', {'cmd'}, 'right' },
{{"ctrl"}, 'f', {}, 'right' },
{{"ctrl"}, 'h', {}, 'delete' },
{{"ctrl"}, 'n', {}, 'down' },
{{"ctrl"}, 'p', {}, 'up' },
}
appKeyMappers = {
["Microsoft PowerPoint"] = { },
["Microsoft Excel"] = { },
["Microsoft Word"] = { },
["Microsoft OneNote"] = { },
["Microsoft Outlook"] = {
{{"cmd"}, 'return', {}, 'return' },
{{"ctrl"}, 'return', {}, 'return' },
{{"ctrl"}, 'i', {'ctrl'}, 'k' },
{{"ctrl"}, 'o', {'ctrl'}, 'l' },
},
}
-- modal maker
delay = hs.eventtap.keyRepeatDelay()
interval = hs.eventtap.keyRepeatInterval()
function bindKeyMappers(keyMappers,modal)
for i,mapper in ipairs(keyMappers) do
modal:bind(mapper[1], mapper[2], function()
modal.triggered = true
hs.eventtap.keyStroke(mapper[3],mapper[4], delay )
end, nil, function()
hs.eventtap.keyStroke(mapper[3],mapper[4], interval )
end)
end
return modal
end
-- glbals
modal = hs.hotkey.modal.new({}, nil )
appKeyModals = {
[0] = bindKeyMappers( defaultKeyMapper, hs.hotkey.modal.new({}, nil ) )
}
-- 0 is default only keymap
-- main function
function applicationWatcher(appName, eventType, appObject)
if (eventType == hs.application.watcher.activated) then
modal:exit()
-- print(appName)
for app, keyMappers in pairs(appKeyMappers) do
if(appName == app) then
am = appKeyModals[app]
if( am == nil ) then
if( #keyMappers == 0 ) then
am = appKeyModals[0]
-- print("reuse default keymap")
else
am = hs.hotkey.modal.new({}, nil )
bindKeyMappers(defaultKeyMapper,am )
bindKeyMappers(keyMappers, am )
-- print("new keymap")
end
appKeyModals[app] = am
end
-- print("set keymap")
modal = am
modal:enter()
break
end
end
end
end
appWatcher = hs.application.watcher.new(applicationWatcher)
appWatcher:start()
Owner

shiro-t commented Apr 5, 2017 edited

defaultKeyMapper はどのアプリケーションでも共通のキーバインドを定義。ここでは Ctrl-a,b,e,f,p といった Emacs Keybinding での基本的なカーソル移動操作と、ctrl-h による削除を定義している。

appKeyMappers はキーマップを変更するアプリケーションの定義と、アプリケーション独自のキーマップを追加するためのテーブル。
私は Microsoft Office で Emacs Keybinding が使いたいため、各アプリケーションをここで定義。
なお、スペースを含む名前をテーブルに入れる場合、[] でくくった上でダブルクオートでくくる事になるので注意。

Outlook については、アプリケーション独自の追加キーマップとして Cmd-Return と Ctrl-Return をただの Return にリマップと、Ctrl-io を Ctrl-kl にリマップしている。前者は Ctrl-Return でメールが送信処理にマップされているのを無効化するためにある。よく誤操作で送信してしまうためだ。
後者は、ATOK のキーバインドで Ctrl-io を文節の伸ばし縮みに使用しているが、Outlook 側で Ctrl-o あたりが使用されているための改変だ。これにあわせて ATOK 側でも Ctrl-kl を Ctrl-io と同じ用途に追加している。

実際の処理は最後の 88,89行目から始まる。ここで appwatcher という監視オブジェクトを作成、実行している。
監視オブジェクトはアプリケーションの起動終了などのイベントが発生するとコールバック関数 applicationWatcher() を呼ぶ。

関数applicationWatcher() では、まず、アプリケーションのアクティベート(最前面に来てアクティブなアプリケーションとなる)以外のイベントは無視している。

アプリケーションがアクティベートされた場合、まず現在の modal を終了させ、アプリケーション名に対応した modal を検索、なければ先の appKeyMappers から modal を生成し、この modal を有効にする。

modal とは要するにキーマップのモードである。キーのリマップが一段、たとえば Ctrl-A を Command+← に置き換えるだけなら簡単なのだが、Emacs のファイルの保存のように Ctrl-x の次に Ctrl-w を押すような、多段階のキーマップも考えられる。どうやらこういうときのために、Hammperspoon ではあらかじめ複数のキーマップを定義しておき、切り替えて使う、例えば通常キーマップで Ctrl-x を押されたら、Ctrl-x 後にだけ有効なキーマップに遷移する、という流れができるようだ。このキーマップを modal というオブジェクトで格納している。
( ウィンドウシステムでプログラムを書いたことのある人なら、Modal Event Loop とか Modal Panel などの特定の状態遷移を指すモードという言葉はなじみがあるかと。 )

ここではその「複数のキーマップを定義して、切り替えることができる」を利用して、アプリケーションごとに modal を生成、切り替えて使うようにしている。起動時に appKeyMappers に含まれる全てのアプリケーションの modal を作るのも無駄に感じられたので、一度作った modal は変数 appKeyModals に格納しておき、二度目以降は再利用している。

また、デフォルトキーマップ以外に追加していない、言い換えるとアプリケーション名に対応する定義を格納した変数keyMappers の要素数が0の場合、デフォルトだけのキーマップを再利用する。これは48行目の初期化の時点で作られている。これも無駄なマップが作成されるのを避けることでメモリと処理数を減らすためだ。(けちくさいと思う、我ながら。)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment