続き: Karabiner-Elements と Swift で background のアプリに特定のキーストロークを送る
ターミナルやエディタでコーディングしつつ、参考書を表示しているKindleのページ送りをしたかったのが動機です。 表題で言っている background のアプリとは最前面(foremost)ではないウィンドウで動作しているアプリを指しています。
上の記事で試したのは、指定したアプリ向けにキーストロークを送る AppleScript を Karabiner-Elements で特定のキーにより実行する、というものでした。しかし AppleScript の限界なのか background のアプリにキーストロークを送ることはできず、実際には一瞬ターゲットのアプリを foremost に持ってきてキーを送り foremost を元のアプリに戻すという処理しか書けませんでした。
Swift を使えばターゲットのアプリを activate することなく特定のアプリにキーストロークを送れるようだったので、使用感向上のため実装してみました。
Swift スクリプトの用意
アプリにキーストロークを送る send-key-to-app.swift
を用意します。
AppleScript よりこちらの方が好きです。
import Foundation import AppKit let args = CommandLine.arguments if args.count != 3 { print("\(args[0]) [APP_NAME] [KEY_CODE]") print("APP_NAME: should be an executable file name(not a path)") print("KEY_CODE: a number such as 123(LEFT), 124(RIGHT), ...") exit(1) } let appName = args[1] let keyCode = CGKeyCode(args[2]) ?? 0 let src = CGEventSource(stateID: CGEventSourceStateID.hidSystemState) func sendKeyStroke(pid: pid_t, keyCode: CGKeyCode) { let keyUp = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: true) keyUp?.postToPid(pid) let keyDown = CGEvent(keyboardEventSource: src, virtualKey: keyCode, keyDown: false) keyDown?.postToPid(pid) } func getPidByName(executableFileName: String) -> Optional<pid_t> { let apps = NSWorkspace.shared.runningApplications for a in apps { print(a.bundleIdentifier) if a.executableURL?.lastPathComponent == executableFileName { return a.processIdentifier } } return nil } if let pid = getPidByName(executableFileName: appName) { sendKeyStroke(pid: pid, keyCode: keyCode) } else { print("executable '\(appName)' not found in user processes") }
始めて Swift を書きましたが、Optional
型と if let
のコードが気持ちいいですね。
値を wrap した Optional の扱い方はちょっと Rust っぽいです。
Karabiner-Elements に設定する前に以下のように動作確認をしておきます。
swift send-key-to-app.swift Kindle 124
これで background で動いている Kindle のページが進めば成功です。 AppleScript のものと比べるとウィンドウ切り替えのチカチカ感が減ってとても快適です。
Karabiner-Elements の設定
冒頭で紹介した以前の記事と shell_command
以外は同じです。
適当に Command + 1
でページ送り、Command + 2
でページバックするようにしています。
{ "profiles": [ { "complex_modifications": { "rules": [ { "description": "Kindleのページ移動", "manipulators": [ { "from": { "key_code": "1", "modifiers": { "mandatory": ["command"], "optional": ["any"] } }, "to": [{"shell_command": "swift /path/to/send-key-to-app.swift Kindle 123"}], "type": "basic" }, { "from": { "key_code": "2", "modifiers": { "mandatory": ["command"], "optional": ["any"] } }, "to": [{"shell_command": "swift /path/to/send-key-to-app.swift Kindle 124"}], "type": "basic" } ] },
更新したら「Restart Karabiner-Elements」をすれば早速反映されます。
この使い方でもいいのですが、現状ページ送りごとに毎回 swift インタプリターが実行されているので、気になる場合はビルドすると良さそうですね。
余裕があったので Swift をビルドする
swift package build --type executable
でコマンド用のプロジェクトが簡単に作成できたので作っておきました。
https://github.com/asa-taka/send-key-to-app
心なしかページ遷移を繰り返してもiMacのファンが鳴らなくなった気がします。