MacOS上键盘/鼠标控制应用的Swift语言开发笔记
MacOS上键盘/鼠标控制应用的Swift语言开发笔记
背景
继续在做小gadget,先学习基本操作。
这次需要实现的功能是程序控制键盘和鼠标,也就是人不需要碰键盘鼠标而键盘自动输入,鼠标自动移动点击的功能。
网上搜索了一下,Objective-C的实现例子倒是不少,可是基本找不到太多讲swift上面实现的例子,无奈自行摸索,在此总结一下。
准备
本功能应该属于Accessibility(辅助功能)的范畴,测试需要给予Xcode相应的操作权限。
打开【系统编好设置】,【安全性与隐私】,【隐私】里面勾选Xcode前面的方框。如果看不到Xcode的话手动添加。
库和官方文件
需要用到Core Graphics里面的
- Quartz Event Services | Apple Developer Documentation
- Quartz Display Services | Apple Developer Documentation
按键事件控制键盘
首先定义所需要的按键事件,然后通过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
};
参考和其他注意事项:
- 触发用户授权注册系统辅助权限:
MacOS获取辅助功能权限控制鼠标点击事件 - 为程序员服务
let opts = NSDictionary(object: kCFBooleanTrue,
forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
) as CFDictionary
guard AXIsProcessTrustedWithOptions(opts) == true else { return }
鼠标控制的一个范例:
让iMac与MacBook高效协同工作——mouseSync开发心得 · Zhihao’s Studio
mouseSync后续功能完善心得 · Zhihao’s Studio
GitHub - zhihaozhang/mouseSync: 两台Mac共用一个触控板Trackpad/鼠标mouseOS TrackPad from other mobile devices:
GitHub - zhijie/trackpad: use your iphone or android as a trackpad for your mac/pc. make your smartphone smarterCGKeyCode:
macos - Where can I find a list of Mac virtual key codes? - Stack Overflowmacos - Simulating mouse input programmatically in OS X - Stack Overflow
swift - Swift2: CGEventSetFlags with multi CGEventFlags - Stack Overflow
macos - Simulate keypress for system wide hotkeys - Stack Overflow
վ 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语言开发笔记/