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