MacOS上键盘/鼠标控制应用的Swift语言开发笔记

Author Avatar
$H_P? Feb 21, 2018
  • Read this article on other devices

MacOS上键盘/鼠标控制应用的Swift语言开发笔记

背景

继续在做小gadget,先学习基本操作。
这次需要实现的功能是程序控制键盘和鼠标,也就是人不需要碰键盘鼠标而键盘自动输入,鼠标自动移动点击的功能。

网上搜索了一下,Objective-C的实现例子倒是不少,可是基本找不到太多讲swift上面实现的例子,无奈自行摸索,在此总结一下。

准备

本功能应该属于Accessibility(辅助功能)的范畴,测试需要给予Xcode相应的操作权限。
打开【系统编好设置】,【安全性与隐私】,【隐私】里面勾选Xcode前面的方框。如果看不到Xcode的话手动添加。

库和官方文件

需要用到Core Graphics里面的

按键事件控制键盘

首先定义所需要的按键事件,然后通过post方法让系统执行事件。
每一次按键需要先按下再离开,通过函数分别定义这两个操作。

属性方面:

  • 这里的CGEventSourceStateID看了一下官方文件,分privateState,combinedSessionState和hidSystemState三种,这里选择最后一个,一般source定义为nil也可以。
  • virtualKey后面是需要的按键的代码,mac的英语键盘的话参考文末的一览表。
  • keyDown当然true是按下,false是松开了
  • tap后面是按键时候的鼠标位置
//按下按键
func keyboardKeyDown(key: CGKeyCode) {

        let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
        let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: true) 
        event?.post(tap: CGEventTapLocation.cghidEventTap)
        print("key \(key) is down")
    }

//松开按键    
func keyboardKeyUp(key: CGKeyCode) {
        let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)
        let event = CGEvent(keyboardEventSource: source, virtualKey: key, keyDown: false)
        event?.post(tap: CGEventTapLocation.cghidEventTap)
        print("key \(key) is released")
    }

按键例子

比如说需要按下F5的话,调用上面两个函数就可以

keyboardKeyDown(key: 0x60) //0x60是F5功能键代码
keyboardKeyUp(key: 0x60)

Command+C怎么办?

这时候加入CGEventFlags - Core Graphics | Apple Developer Documentation

let cmd_c_D = CGEventCreateKeyboardEvent(nil, 0x08, true); //0x08是C键代码 cmd-c down
CGEventSetFlags(cmd_c_D, CGEventFlags.MaskCommand);
CGEventPost(CGEventTapLocation.CGHIDEventTap, cmd-c-D);

let cmd_c_U = CGEventCreateKeyboardEvent(nil, 0x08, false); // cmd-c up
CGEventSetFlags(cmd_c_U, CGEventFlags.MaskCommand);
CGEventPost(CGEventTapLocation.CGHIDEventTap, cmd_c_U);

shift,control等都有相应的CGEventSetFlags

鼠标控制

鼠标控制注意

  • 移动鼠标和显示移动后的鼠标是不同的事件操作。
  • 还有注意显示器的坐标轴原点不是左上角,而是左下角。留意别搞错y轴的方向了。

移动点击鼠标事件

// 鼠标左键按下
guard let mouseDown = CGEvent(mouseEventSource: nil,
                        mouseType: .leftMouseDown,
                        mouseCursorPosition: CGPoint(x: 200, y: 300),
                        mouseButton: .left
                        ) else {return}
mouseDown?.post(tap: .cghidEventTap)

// 鼠标左键抬起
guard let mouseUp = CGEvent(mouseEventSource: nil,
                      mouseType: .leftMouseUp,
                      mouseCursorPosition: CGPoint(x: 200, y: 300),
                      mouseButton: .left
                      ) else {return}
mouseUp?.post(tap: .cghidEventTap)

属性一览表:CGEventType - Core Graphics | Apple Developer Documentation

只移动鼠标

guard let moveEvent = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, 
                      mouseCursorPosition: point, mouseButton: .left 
                      ) else {return}
moveEvent?.post(tap: .cghidEventTap)

移动屏幕上面的鼠标图标

利用上面的事件移动鼠标之后,屏幕上的鼠标图标是不动的。需要再用下面操作才能看到鼠标的图标在屏幕上面移动了。

func CGDisplayMoveCursorToPoint(_ display: CGDirectDisplayID, 
                              _ point: CGPoint) -> CGError

完整的鼠标移动函数

func moveMouse(_ dx:CGFloat , _ dy:CGFloat){
  //先监控移动前鼠标位置
  var mouseLoc = NSEvent.mouseLocation  
  mouseLoc.y = NSHeight(NSScreen.screens[0].frame) - mouseLoc.y;
  //计算鼠标新位置
  let newLoc = CGPoint(x: mouseLoc.x-CGFloat(dx), y: mouseLoc.y+CGFloat(dy)) 
  print("moving \(dx) \(dy)")
  CGDisplayMoveCursorToPoint(0, newLoc)
    }

其他先留坑

附录

MouseType类型

/* Constants that specify the different types of input events. */
public enum CGEventType : UInt32 {


    /* The null event. */
    case null


    /* Mouse events. */
    case leftMouseDown

    case leftMouseUp

    case rightMouseDown

    case rightMouseUp

    case mouseMoved

    case leftMouseDragged

    case rightMouseDragged


    /* Keyboard events. */
    case keyDown

    case keyUp

    case flagsChanged


    /* Specialized control devices. */
    case scrollWheel

    case tabletPointer

    case tabletProximity

    case otherMouseDown

    case otherMouseUp

    case otherMouseDragged


    /* Out of band event types. These are delivered to the event tap callback
       to notify it of unusual conditions that disable the event tap. */
    case tapDisabledByTimeout

    case tapDisabledByUserInput
}

Mac的键盘代码一览

/*
 *  Summary:
 *    Virtual keycodes
 *  
 *  Discussion:
 *    These constants are the virtual keycodes defined originally in
 *    Inside Mac Volume V, pg. V-191. They identify physical keys on a
 *    keyboard. Those constants with "ANSI" in the name are labeled
 *    according to the key position on an ANSI-standard US keyboard.
 *    For example, kVK_ANSI_A indicates the virtual keycode for the key
 *    with the letter 'A' in the US keyboard layout. Other keyboard
 *    layouts may have the 'A' key label on a different physical key;
 *    in this case, pressing 'A' will generate a different virtual
 *    keycode.
 */
enum {
  kVK_ANSI_A                    = 0x00,
  kVK_ANSI_S                    = 0x01,
  kVK_ANSI_D                    = 0x02,
  kVK_ANSI_F                    = 0x03,
  kVK_ANSI_H                    = 0x04,
  kVK_ANSI_G                    = 0x05,
  kVK_ANSI_Z                    = 0x06,
  kVK_ANSI_X                    = 0x07,
  kVK_ANSI_C                    = 0x08,
  kVK_ANSI_V                    = 0x09,
  kVK_ANSI_B                    = 0x0B,
  kVK_ANSI_Q                    = 0x0C,
  kVK_ANSI_W                    = 0x0D,
  kVK_ANSI_E                    = 0x0E,
  kVK_ANSI_R                    = 0x0F,
  kVK_ANSI_Y                    = 0x10,
  kVK_ANSI_T                    = 0x11,
  kVK_ANSI_1                    = 0x12,
  kVK_ANSI_2                    = 0x13,
  kVK_ANSI_3                    = 0x14,
  kVK_ANSI_4                    = 0x15,
  kVK_ANSI_6                    = 0x16,
  kVK_ANSI_5                    = 0x17,
  kVK_ANSI_Equal                = 0x18,
  kVK_ANSI_9                    = 0x19,
  kVK_ANSI_7                    = 0x1A,
  kVK_ANSI_Minus                = 0x1B,
  kVK_ANSI_8                    = 0x1C,
  kVK_ANSI_0                    = 0x1D,
  kVK_ANSI_RightBracket         = 0x1E,
  kVK_ANSI_O                    = 0x1F,
  kVK_ANSI_U                    = 0x20,
  kVK_ANSI_LeftBracket          = 0x21,
  kVK_ANSI_I                    = 0x22,
  kVK_ANSI_P                    = 0x23,
  kVK_ANSI_L                    = 0x25,
  kVK_ANSI_J                    = 0x26,
  kVK_ANSI_Quote                = 0x27,
  kVK_ANSI_K                    = 0x28,
  kVK_ANSI_Semicolon            = 0x29,
  kVK_ANSI_Backslash            = 0x2A,
  kVK_ANSI_Comma                = 0x2B,
  kVK_ANSI_Slash                = 0x2C,
  kVK_ANSI_N                    = 0x2D,
  kVK_ANSI_M                    = 0x2E,
  kVK_ANSI_Period               = 0x2F,
  kVK_ANSI_Grave                = 0x32,
  kVK_ANSI_KeypadDecimal        = 0x41,
  kVK_ANSI_KeypadMultiply       = 0x43,
  kVK_ANSI_KeypadPlus           = 0x45,
  kVK_ANSI_KeypadClear          = 0x47,
  kVK_ANSI_KeypadDivide         = 0x4B,
  kVK_ANSI_KeypadEnter          = 0x4C,
  kVK_ANSI_KeypadMinus          = 0x4E,
  kVK_ANSI_KeypadEquals         = 0x51,
  kVK_ANSI_Keypad0              = 0x52,
  kVK_ANSI_Keypad1              = 0x53,
  kVK_ANSI_Keypad2              = 0x54,
  kVK_ANSI_Keypad3              = 0x55,
  kVK_ANSI_Keypad4              = 0x56,
  kVK_ANSI_Keypad5              = 0x57,
  kVK_ANSI_Keypad6              = 0x58,
  kVK_ANSI_Keypad7              = 0x59,
  kVK_ANSI_Keypad8              = 0x5B,
  kVK_ANSI_Keypad9              = 0x5C
};

/* keycodes for keys that are independent of keyboard layout*/
enum {
  kVK_Return                    = 0x24,
  kVK_Tab                       = 0x30,
  kVK_Space                     = 0x31,
  kVK_Delete                    = 0x33,
  kVK_Escape                    = 0x35,
  kVK_Command                   = 0x37,
  kVK_Shift                     = 0x38,
  kVK_CapsLock                  = 0x39,
  kVK_Option                    = 0x3A,
  kVK_Control                   = 0x3B,
  kVK_RightShift                = 0x3C,
  kVK_RightOption               = 0x3D,
  kVK_RightControl              = 0x3E,
  kVK_Function                  = 0x3F,
  kVK_F17                       = 0x40,
  kVK_VolumeUp                  = 0x48,
  kVK_VolumeDown                = 0x49,
  kVK_Mute                      = 0x4A,
  kVK_F18                       = 0x4F,
  kVK_F19                       = 0x50,
  kVK_F20                       = 0x5A,
  kVK_F5                        = 0x60,
  kVK_F6                        = 0x61,
  kVK_F7                        = 0x62,
  kVK_F3                        = 0x63,
  kVK_F8                        = 0x64,
  kVK_F9                        = 0x65,
  kVK_F11                       = 0x67,
  kVK_F13                       = 0x69,
  kVK_F16                       = 0x6A,
  kVK_F14                       = 0x6B,
  kVK_F10                       = 0x6D,
  kVK_F12                       = 0x6F,
  kVK_F15                       = 0x71,
  kVK_Help                      = 0x72,
  kVK_Home                      = 0x73,
  kVK_PageUp                    = 0x74,
  kVK_ForwardDelete             = 0x75,
  kVK_F4                        = 0x76,
  kVK_End                       = 0x77,
  kVK_F2                        = 0x78,
  kVK_PageDown                  = 0x79,
  kVK_F1                        = 0x7A,
  kVK_LeftArrow                 = 0x7B,
  kVK_RightArrow                = 0x7C,
  kVK_DownArrow                 = 0x7D,
  kVK_UpArrow                   = 0x7E
};

参考和其他注意事项:

let opts = NSDictionary(object: kCFBooleanTrue,
                        forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
                        ) as CFDictionary

guard AXIsProcessTrustedWithOptions(opts) == true else { return }

վ HᴗP ի

This blog is under a CC BY-NC-SA 3.0 Unported License
Link to this article: https://hanspond.github.io/2018/02/21/MacOS上键盘鼠标控制应用的Swift语言开发笔记/