Macアプリでグローバルホットキーを使用する

Macアプリケーションを作成する際、通常のアプリメニューに登録できるホットキー以外のホットキーを使用したい場合があります。
また、常駐アプリ等メニューを持たないアプリケーションに関してはホットキーを使用するためにはを全てアプリケーション側で管理する必要があります。

今までこのような時は自身で実装するか[PTHotKey](https://github.com/keith/PTHotKeyTest)を使用するのが一般的だったかと思います。
しかし、この `PTHotKey` は長らくメンテナンスされておらず、各アプリがソースコードをコピーし独自改造が加えられて使用されていたりと新規で使用する際かなりの問題点がありました。

ここ最近Macアプリを作成する人が増えてきた中、このような基本となるライブラリがないため作成を諦めてしまうのはもったいないですし、iosのようにmacOS周辺ライブラリを増やすため、swiftを使用したホットキーを使用するためのライブラリを作成しました。

本ライブラリは[Clipy](https://clipy-app.com)を作成する上で使用した物をライブラリとして切り出したものです。
Clipyの開発については少し古いですがこちらの[Qiitaの記事](http://qiita.com/econa77/items/1848bf3fdfb7127ca9b8)を参照ください。

# Magnet.framework
リポジトリ： [Clipy/Magnet](https://github.com/Clipy/Magnet)

基本的な使用方法は`PTHotKey`に準拠しましたが、わかりずらい`Carbon`や`Cocoa`フレームワークの違いを明確にし、Alfredのショートカットのような修飾キーダブルタップのホットキーにも対応しています。

下記のような簡単な登録を行うことでアプリにホットキーを導入することができます。
また、本ライブラリはSanbox環境にも対応していますので、AppStoreで配布するアプリにも使用することができます。

### 使用方法
`⌘` + `Control` + `B` のホットキーを登録する場合は以下のような形となります。
キーコードに関しては[stackoverflow](http://stackoverflow.com/questions/3202629/where-can-i-find-a-list-of-mac-virtual-key-codes)等を参考にしてください。

```
if let keyCombo = KeyCombo(keyCode: 11, carbonModifiers: 4352) {
   let hotKey = HotKey(identifier: "CommandControlB", keyCombo: keyCombo, target: self, action: #selector())
   hotKey.register() // or HotKeyCenter.shared.register(with: hotKey)
}
```

`Carbon`の修飾キーだけでなく、`Cocoa`の `NSEventModifierFlags` にも対応しています。
上記の`KeyCombo`を書き換えるとこのように書くことができます。

```
let keyCombo = KeyCombo(keyCode: 11, cocoaModifiers: [.command, .control])
```

### ダブルタップ
`⌘`ダブルタップのホットキーを登録する場合は以下のような形となります。

```
if let keyCombo = KeyCombo(doubledCarbonModifiers: cmdKey) {
   let hotKey = HotKey(identifier: "CommandDobuleTap", keyCombo: keyCombo, target: self, action: #selector())
   hotKey.register()
}
```

同様に`Cocoa`ではこのように書き換えることができます。

```
keyCombo = KeyCombo(doubledCocoaModifiers: .command)
```

### ホットキーの登録
内部的にはホットキーの管理を`identifier`を使用しておこなっています。
複数のホットキーを使用する場合はこの`identifier`が重複しないように注意してください。

`keyCode`に関して現状数値のみでしか入力できませんが、将来的にはenumや文字で入力できるようにする予定です。

# KeyHolder.framework
`PTHotKey`とよく同時に使用されていたものに[shortcutrecorder](https://github.com/shortcutrecorder/shortcutrecorder)や[ShortcutRecorder 2](https://github.com/Kentzo/ShortcutRecorder)があります。
こちらはユーザが任意のホットキーを登録する際に使用されるライブラリで内部的に`PTHotKey`がカスタマイズされて使用されています。

![example.png](https://qiita-image-store.s3.amazonaws.com/0/1500/c4442726-23a7-b514-bbf6-2f4332955166.png)

上記のライブラリは比較的メンテナンスされていますが日本語IMEでのバグがでていたりしていたためClipyではforkプロジェクトを元々使用していました。
今回`Magnet`の作成に合わせてこちらも新しいライブラリとして作成しました。

リポジトリ： [Clipy/KeyHolder](https://github.com/Clipy/KeyHolder)

`KeyHolder`では`ShortcutRecorder`の機能に加え`Magnet`で追加された修飾キーダブルタップのホットキー入力に対応しています

<img width="242" alt="double_tap_shortcut.png" src="https://qiita-image-store.s3.amazonaws.com/0/1500/e84e52fb-2d52-c7c9-9c3b-a69fbcf20b63.png">　<img width="240" alt="normal_shortcut.png" src="https://qiita-image-store.s3.amazonaws.com/0/1500/e0a236f2-4a50-015c-c987-da741354c9a1.png">

入力UIはAlfredをホットキー入力部を模倣し作成しています。
簡単ではありますが`@IBDesignable`に対応していますのでStoryboard等でグラフィカルに表示することもできます。

### 使用方法
入力パーツを画面に追加し文字色や角丸等を変更することができます。
`keyCombo`を設定することでデフォルトで表示されるホットキーも指定できます。

```
let recordView = RecordView(frame: CGRect.zero)
recordView.delegate = self
recordView.tintColor = NSColor(red: 0.164, green: 0.517, blue: 0.823, alpha: 1)
let keyCombo = KeyCombo(doubledModifiers: .command)
recordView.keyCombo = keyCombo
```

下記のdelegateを使用し入力のキャンセルや変更の取得、バリデーションを行うことができます。

```
func recordViewShouldBeginRecording(_ recordView: RecordView) -> Bool
func recordView(_ recordView: RecordView, canRecordShortcut keyCombo: KeyCombo) -> Bool
func recordViewDidClearShortcut(_ recordView: RecordView)
func recordViewDidEndRecording(_ recordView: RecordView)
```

# まとめ
簡単にですが２個のライブラリを紹介させていただきました。
上記のライブラリ郡を使用すればmacアプリ上で比較的簡単にホットキーの登録、ユーザー入力を実装することができますので、ぜひ使用してみてください。

[Clipy](https://github.com/Clipy)のリポジトリにはClipy内で使用される機能が他にもライブラリとして書き出されています。
多くの人が使用しそうな機能はライブラリとして書き出すことで誰かを助けることがあるかもしれませんので、みなさんもぜひライブラリとして機能を分離し公開してみてださい。

要望やバグ等ありました、Issueか[@econa77](http://twitter.com/econa77)までご連絡ください。