Swift：キー入力・マウスクリック・ドラッグ・座標移動・ホイール入力を強制的に発動させる

#はじめに

~~`プロジェクト名.entitlements`の`App SandBox`を`Yes`から`No`に変更してください．~~
macOS Mojaveからなのか確認できていませんが，`App SandBox`の有無に関わらず，環境設定のアクセシビリティの制御許可を与えないと動かないものがあるっぽいです．

本記事の手法はあくまでmacOS向けのものです．

#キー入力

##単キー入力
```swift:
func keyDown(key: CGKeyCode) {
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: true)
    event?.post(tap: CGEventTapLocation.cghidEventTap)
}
    
func keyUp(key: CGKeyCode) {
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: false)
    event?.post(tap: CGEventTapLocation.cghidEventTap)
}

// 使用例
keyDown(key: 0x7E) //　0x7Eは↑キー
keyUp(key: 0x7E)
```

###CGKeyCode(US配列)
| key name                | code | key name                | code |
|:------------------------|:----:|:------------------------|:----:|
| kVK_ANSI_A              | 0x00 | kVK_ANSI_I              | 0x22 |
| kVK_ANSI_S              | 0x01 | kVK_ANSI_P              | 0x23 |
| kVK_ANSI_D              | 0x02 | kVK_ANSI_L              | 0x25 |
| kVK_ANSI_F              | 0x03 | kVK_ANSI_J              | 0x26 |
| kVK_ANSI_H              | 0x04 | kVK_ANSI_Quote          | 0x27 |
| kVK_ANSI_G              | 0x05 | kVK_ANSI_K              | 0x28 |
| kVK_ANSI_Z              | 0x06 | kVK_ANSI_Semicolon      | 0x29 |
| kVK_ANSI_X              | 0x07 | kVK_ANSI_Backslash      | 0x2A |
| kVK_ANSI_C              | 0x08 | kVK_ANSI_Comma          | 0x2B |
| kVK_ANSI_V              | 0x09 | kVK_ANSI_Slash          | 0x2C |
| kVK_ANSI_B              | 0x0B | kVK_ANSI_N              | 0x2D |
| kVK_ANSI_Q              | 0x0C | kVK_ANSI_M              | 0x2E |
| kVK_ANSI_W              | 0x0D | kVK_ANSI_Period         | 0x2F |
| kVK_ANSI_E              | 0x0E | kVK_ANSI_Grave          | 0x32 |
| kVK_ANSI_R              | 0x0F | kVK_ANSI_KeypadDecimal  | 0x41 |
| kVK_ANSI_Y              | 0x10 | kVK_ANSI_KeypadMultiply | 0x43 |
| kVK_ANSI_T              | 0x11 | kVK_ANSI_KeypadPlus     | 0x45 |
| kVK_ANSI_1              | 0x12 | kVK_ANSI_KeypadClear    | 0x47 |
| kVK_ANSI_2              | 0x13 | kVK_ANSI_KeypadDivide   | 0x4B |
| kVK_ANSI_3              | 0x14 | kVK_ANSI_KeypadEnter    | 0x4C |
| kVK_ANSI_4              | 0x15 | kVK_ANSI_KeypadMinus    | 0x4E |
| kVK_ANSI_6              | 0x16 | kVK_ANSI_KeypadEquals   | 0x51 |
| kVK_ANSI_5              | 0x17 | kVK_ANSI_Keypad0        | 0x52 |
| kVK_ANSI_Equal          | 0x18 | kVK_ANSI_Keypad1        | 0x53 |
| kVK_ANSI_9              | 0x19 | kVK_ANSI_Keypad2        | 0x54 |
| kVK_ANSI_7              | 0x1A | kVK_ANSI_Keypad3        | 0x55 |
| kVK_ANSI_Minus          | 0x1B | kVK_ANSI_Keypad4        | 0x56 |
| kVK_ANSI_8              | 0x1C | kVK_ANSI_Keypad5        | 0x57 |
| kVK_ANSI_0              | 0x1D | kVK_ANSI_Keypad6        | 0x58 |
| kVK_ANSI_RightBracket   | 0x1E | kVK_ANSI_Keypad7        | 0x59 |
| kVK_ANSI_O              | 0x1F | kVK_ANSI_Keypad8        | 0x5B |
| kVK_ANSI_U              | 0x20 | kVK_ANSI_Keypad9        | 0x5C |
| kVK_ANSI_LeftBracket    | 0x21 |                         |      | 

##修飾キーを付加したキー入力
```swift:
func keyDown(key: CGKeyCode, with: CGEventFlags) {
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: true)
    event?.flags = with
    event?.post(tap: CGEventTapLocation.cghidEventTap)
}
    
func keyUp(key: CGKeyCode, with: CGEventFlags) {
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: false)
    event?.flags = with
    event?.post(tap: CGEventTapLocation.cghidEventTap)
}

// 使用例
// Control + A
keyDown(key: 0x00, with: CGEventFlags.maskControl)
keyUp(key: 0x00, with: CGEventFlags.maskControl)

// Shift + Control + A
keyDown(key: 0x00, with: CGEventFlags.maskShift.union(CGEventFlags.maskControl))
keyUp(key: 0x00, with: CGEventFlags.maskShift.union(CGEventFlags.maskControl))
```
よく使う修飾キー
Optionキー：`CGEventFlags.maskAlternate`
Shiftキー：`CGEventFlags.maskShift`
Commandキー：`CGEventFlags.maskCommand`
Controlキー：`CGEventFlags.maskControl`

#マウスクリック&ドラッグ
```swift:
func leftClickDown(position: CGPoint) {
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(mouseEventSource: source, mouseType: CGEventType.leftMouseDown, 
                        mouseCursorPosition: position, mouseButton: CGMouseButton.left)
    event?.post(tap: CGEventTapLocation.cghidEventTap)
}
    
func leftClickUp(position: CGPoint) {
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(mouseEventSource: source, mouseType: CGEventType.leftMouseUp, 
                        mouseCursorPosition: position, mouseButton: CGMouseButton.left)
    event?.post(tap: CGEventTapLocation.cghidEventTap)
}
    
func leftDragged(to: CGPoint, from: CGPoint) {
    leftClickDown(position: from)
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(mouseEventSource: source, mouseType: CGEventType.leftMouseDragged, 
                        mouseCursorPosition: to, mouseButton: CGMouseButton.left)
    event?.post(tap: CGEventTapLocation.cghidEventTap)
    leftClickUp(position: to)
}

// 使用例
// 現在のマウス座標にてクリックを行う
var location = NSEvent.mouseLocation
for screen in NSScreen.screens {
    if screen.frame.contains(location) {
        location = CGPoint(x: location.x, y: NSHeight(screen.frame) - location.y)
        break
    }
}
leftClickDown(position: location)
leftClickUp(position: location)

// 現在のマウス座標からx座標+200px, y座標＋２００pxの地点に向けてドラッグを行う
leftDragged(to: CGPoint(x: location.x + 200, y: location.y + 200), from: location)
```

#マウス座標移動
```swift:
func moveMouse(dx: CGFloat, dy: CGFloat) {
    var location = NSEvent.mouseLocation
    for screen in NSScreen.screens {
        if screen.frame.contains(location) {
            location = CGPoint(x: location.x, y: NSHeight(screen.frame) - location.y)
            location = CGPoint(x: location.x - dx, y: location.y + dy)
            CGDisplayMoveCursorToPoint(0, location)
            break
        }
    }
}
```

#ホイール入力
```swift:
func wheel(dx: Int32, dy: Int32) {
    appDelegate.toggleIgnore(flag: false)
    let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
    let event = CGEvent(scrollWheelEvent2Source: source, units: CGScrollEventUnit.pixel,
                        wheelCount: 25, wheel1: dy, wheel2: dx, wheel3: 0)
    event?.post(tap: CGEventTapLocation.cghidEventTap)
    appDelegate.toggleIgnore(flag: true)
}
```
wheelCountがどういうものなのかはよくわかっていません．

#参考
[Swiftでマウスとキーボードのイベント制御](https://qiita.com/hanspond/items/7e23d4853ab22968500c)
[Where can I find a list of Mac virtual key codes?
](https://stackoverflow.com/questions/3202629/where-can-i-find-a-list-of-mac-virtual-key-codes/16125341)