Mac OSX 鼠標(biāo)鍵盤事件的監(jiān)聽和模擬

本文轉(zhuǎn)自(原文太雜亂,這里調(diào)整了格式及內(nèi)容):
http://enkichen.com/2018/09/12/osx-mouse-keyboard-event/

最近完成了 Mac OSX 平臺下的遠(yuǎn)程控制功能,期間找了不少資料,這里做個(gè)總結(jié),主要涉及到一下知識點(diǎn):

  1. OSX 的事件機(jī)制
  2. OSX/iOS 響應(yīng)鏈者鏈
  3. 鼠標(biāo)事件的監(jiān)聽及模擬(鼠標(biāo)單擊、雙擊、拖動、滾動等事件)
  4. 鍵盤事件的監(jiān)聽及模擬(包括組合鍵的模擬)
  5. Keycode 鍵盤編碼(統(tǒng)一 Windows、OSX、瀏覽器端鍵盤按鍵的編碼值)

事件分發(fā)機(jī)制

在 OSX 系統(tǒng)中鼠標(biāo)和鍵盤的活動事件都會產(chǎn)生底層的系統(tǒng)事件,首先傳遞到 IOKit 框架處理后存儲到隊(duì)列中,通知 Window Server 服務(wù)層處理。Window Server 存儲到 FIFO 優(yōu)先隊(duì)列中,然后逐一轉(zhuǎn)發(fā)到當(dāng)前活動窗口或者能響應(yīng)這個(gè)事件的應(yīng)用程序去處理。

在 OSX 或者 iOS 程序中,都會有一個(gè) Main Run Loop 的線程,RunLoop 循環(huán)中會遍歷 event 消息隊(duì)列,逐一分發(fā)這些事件到應(yīng)用中合適的對象去處理。具體來說就是調(diào)用 NSAppsendEvent: 方法發(fā)送消息到NSWindow,NSWindow 再分發(fā)到 NSView 視圖對象,由其鼠標(biāo)或鍵盤事件響應(yīng)方法去處理。

事件響應(yīng)鏈

在 OSX 和 iOS 程序中響應(yīng)者鏈?zhǔn)?Application Kit 事件處理架構(gòu)的中心機(jī)制,它由一系列鏈接在一起的響應(yīng)者對象組成,事件或者動作消息可以沿著這些對象進(jìn)行傳遞。如果一個(gè)響應(yīng)者對象不能處理某個(gè)事件或動作,也就是說,它不響應(yīng)那個(gè)消息,或者不認(rèn)識那個(gè)事件,則將該消息重新發(fā)送給鏈中的下一個(gè)響應(yīng)者。消息沿著響應(yīng)者鏈向上、向更高級別的對象傳遞,直到最終被處理(如果最終還是沒有被處理,就會被拋棄)。

事件響應(yīng)者 Responders 類為核心應(yīng)用程序架構(gòu)的三個(gè)主要模式或機(jī)制定義了一個(gè)接口:

  • 它聲明了一些處理事件消息(也就是源自用戶事件的消息,比如象鼠標(biāo)點(diǎn)擊或按鍵按下這樣的事件)的方法。
  • 它聲明了數(shù)十個(gè)處理動作消息的方法,它們和標(biāo)準(zhǔn)的鍵綁定(比如那些在文本內(nèi)部移動插入點(diǎn)的綁定)密切相關(guān)。動作消息會被派發(fā)到目標(biāo)對象;如果目標(biāo)沒有被指定,應(yīng)用程序會負(fù)責(zé)檢索合適的響應(yīng)者。
  • 它定義了一套在應(yīng)用程序中指派和管理響應(yīng)者的方法。這些響應(yīng)者組成了我們所知道的響應(yīng)者鏈,即一系列響應(yīng)者,事件或動作消息在它們之間傳遞,直到找到能夠?qū)λ鼈冞M(jìn)行處理的對象。

當(dāng) Application Kit 在應(yīng)用程序中構(gòu)造對象時(shí),會為每個(gè)窗口建立響應(yīng)者鏈。響應(yīng)者鏈中的基本對象是NSWindow對象及其視圖層次。在視圖層次中級別較低的視圖將比級別更高的視圖優(yōu)先獲得處理事件或動作消息的機(jī)會。NSWindow 中保有一個(gè)第一響應(yīng)者的引用,它通常是當(dāng)前窗口中處于選擇狀態(tài)的視圖,窗口通常把響應(yīng)消息的機(jī)會首先給它。對于事件消息,響應(yīng)者鏈通常以發(fā)生事件的窗口對應(yīng)的 NSWindow 對象作為結(jié)束,雖然其它對象也可以作為下一個(gè)響應(yīng)者被加入到 NSWindow 對象的后面。

從層級上看離觀察者最近的視圖優(yōu)先響應(yīng)事件,通過 viewhitTest 方法檢測,滿足 hitTest 方法的的子視圖優(yōu)先響應(yīng)事件。

NSApplication, NSWindow, NSDrawer, NSWindowController, NSView 以及繼承于 NSView 的所有控件對象都直接或間接繼承了 Responders 類,所以這些類都能處理鼠標(biāo)和鍵盤事件。

iOS 程序相比于 OSX 程序會有點(diǎn)不一樣:

  1. OSX 程序可能存在多個(gè)窗口,會有多個(gè)響應(yīng)者鏈,iPhone 的應(yīng)用程序就一個(gè)窗口,所以只會有一個(gè)響應(yīng)者鏈。
  2. 在 iOS 程序中與加速計(jì)、陀螺儀和磁力計(jì)相關(guān)的運(yùn)動事件不遵循響應(yīng)者鏈,Core Motion 會將這些事件直接傳遞給我們指定的對象。有關(guān)更多信息,可以參看 Core Motion Framework。

相關(guān)類的解析說明

NSResponder

NSResponder 在這里是非常重要的一個(gè)類,其中定義了鼠標(biāo)鍵盤觸控板等多種事件,這里列舉一些鼠標(biāo)跟鍵盤的主要方法:

// 鼠標(biāo)按下事件
- (void)mouseDown:(NSEvent *)event;
// 鼠標(biāo)右鍵按下事件
- (void)rightMouseDown:(NSEvent *)event;
// 鼠標(biāo)抬起事件
- (void)mouseUp:(NSEvent *)event;
// 鼠標(biāo)右鍵抬起事件
- (void)rightMouseUp:(NSEvent *)event;
// 鼠標(biāo)移動事件
- (void)mouseMoved:(NSEvent *)event;
// 鼠標(biāo)拖拽事件
- (void)mouseDragged:(NSEvent *)event;
// 鼠標(biāo)滾動事件
- (void)scrollWheel:(NSEvent *)event;
// 鼠標(biāo)右鍵拖拽事件
- (void)rightMouseDragged:(NSEvent *)event;
// 鼠標(biāo)進(jìn)入監(jiān)控區(qū)域事件
- (void)mouseEntered:(NSEvent *)event;
// 鼠標(biāo)離開監(jiān)控區(qū)域事件
- (void)mouseExited:(NSEvent *)event;
// 鍵盤按下事件
- (void)keyDown:(NSEvent *)event;
// 鍵盤按下事件
- (void)keyUp:(NSEvent *)event;
// 鍵盤控制鍵的按下標(biāo)記狀態(tài)發(fā)送改變,后面用該方法來獲取控制按下事件,參考 NSEventModifierFlags 定義
- (void)flagsChanged:(NSEvent *)event;

NSResponder 除了定義基本的響應(yīng)事件外,還定義了很多其他事件方法。具體請參考 NSResponder.h 的頭文件定義。

NSEvent

NSEvent 類描述了事件的具體信息,這里列舉跟鼠標(biāo)和鍵盤相關(guān)的一些字段的介紹:

// 事件類型
@property (readonly) NSEventType type;
// 鍵盤控制鍵的按下狀態(tài)的標(biāo)記
@property (readonly) NSEventModifierFlags modifierFlags;
// 事件的時(shí)間戳
@property (readonly) NSTimeInterval timestamp;
// 鼠標(biāo)點(diǎn)擊的次數(shù)(只有鼠標(biāo)事件,才可使用)
@property (readonly) NSInteger clickCount;
@property (readonly) NSInteger buttonNumber; 
@property (readonly) NSInteger eventNumber;
// 壓力值
@property (readonly) float pressure;
// 鼠標(biāo)在窗口的位置
@property (readonly) NSPoint locationInWindow;
// 鼠標(biāo)滾動時(shí)。分別在 X 和 Y 軸上的偏移 
@property (readonly) CGFloat scrollingDeltaX NS_AVAILABLE_MAC(10_7);
@property (readonly) CGFloat scrollingDeltaY NS_AVAILABLE_MAC(10_7);
// 鍵盤事件的字符編碼和 key code 值
@property (nullable, readonly, copy) NSString *characters;
@property (readonly) unsigned short keyCode;

NSEventType

NSEventType 類型定義了事件的具體類型,如下:

typedef NS_ENUM(NSUInteger, NSEventType) {        /* various types of events */
    NSEventTypeLeftMouseDown             = 1,
    NSEventTypeLeftMouseUp               = 2,
    NSEventTypeRightMouseDown            = 3,
    NSEventTypeRightMouseUp              = 4,
    NSEventTypeMouseMoved                = 5,
    NSEventTypeLeftMouseDragged          = 6,
    NSEventTypeRightMouseDragged         = 7,
    NSEventTypeMouseEntered              = 8,
    NSEventTypeMouseExited               = 9,
    NSEventTypeKeyDown                   = 10,
    NSEventTypeKeyUp                     = 11,
    NSEventTypeFlagsChanged              = 12,
    NSEventTypeAppKitDefined             = 13,
    NSEventTypeSystemDefined             = 14,
    NSEventTypeApplicationDefined        = 15,
    NSEventTypePeriodic                  = 16,
    NSEventTypeCursorUpdate              = 17,
    NSEventTypeScrollWheel               = 22,
    NSEventTypeTabletPoint               = 23,
    NSEventTypeTabletProximity           = 24,
    NSEventTypeOtherMouseDown            = 25,
    NSEventTypeOtherMouseUp              = 26,
    NSEventTypeOtherMouseDragged         = 27,
    /* The following event types are available on some hardware on 10.5.2 and later */
    NSEventTypeGesture NS_ENUM_AVAILABLE_MAC(10_5)       = 29,
    NSEventTypeMagnify NS_ENUM_AVAILABLE_MAC(10_5)       = 30,
    NSEventTypeSwipe   NS_ENUM_AVAILABLE_MAC(10_5)       = 31,
    NSEventTypeRotate  NS_ENUM_AVAILABLE_MAC(10_5)       = 18,
    NSEventTypeBeginGesture NS_ENUM_AVAILABLE_MAC(10_5)  = 19,
    NSEventTypeEndGesture NS_ENUM_AVAILABLE_MAC(10_5)    = 20,
    
#if __LP64__
    NSEventTypeSmartMagnify NS_ENUM_AVAILABLE_MAC(10_8) = 32,
#endif
    NSEventTypeQuickLook NS_ENUM_AVAILABLE_MAC(10_8) = 33,
    
#if __LP64__
    NSEventTypePressure NS_ENUM_AVAILABLE_MAC(10_10_3) = 34,
    NSEventTypeDirectTouch NS_ENUM_AVAILABLE_MAC(10_10) = 37,
#endif
};

NSEventModifierFlags

NSEventModifierFlags 類型描述了一些控制鍵,是否處于按下狀態(tài),定義如下:

/* Device-independent bits found in event modifier flags */
typedef NS_OPTIONS(NSUInteger, NSEventModifierFlags) {
    NSEventModifierFlagCapsLock           = 1 << 16, // Set if Caps Lock key is pressed.
    NSEventModifierFlagShift              = 1 << 17, // Set if Shift key is pressed.
    NSEventModifierFlagControl            = 1 << 18, // Set if Control key is pressed.
    NSEventModifierFlagOption             = 1 << 19, // Set if Option or Alternate key is pressed.
    NSEventModifierFlagCommand            = 1 << 20, // Set if Command key is pressed.
    NSEventModifierFlagNumericPad         = 1 << 21, // Set if any key in the numeric keypad is pressed.
    NSEventModifierFlagHelp               = 1 << 22, // Set if the Help key is pressed.
    NSEventModifierFlagFunction           = 1 << 23, // Set if any function key is pressed.
    
    // Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
    NSEventModifierFlagDeviceIndependentFlagsMask    = 0xffff0000UL
};

事件的監(jiān)聽方法

鼠標(biāo)鍵盤事件的監(jiān)聽有多種方法,第一種方法是重寫事件響應(yīng)者 Responders 對應(yīng)的方法來獲取對應(yīng)的事件;第二是通過重寫 NSWindowsendEvent: 方法; 第三是通過的 NSEvent 提供靜態(tài)方法來監(jiān)聽對應(yīng)的事件:

+ (nullable id)addGlobalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(void (^)(NSEvent*))block`
+ (nullable id)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(NSEvent* __nullable (^)(NSEvent*))block
+ (void)removeMonitor:(id)eventMonitor

NSEvent 提供的靜態(tài)方法可以用監(jiān)聽整個(gè)系統(tǒng)的事件或者當(dāng)前應(yīng)用程序內(nèi)的事件。全局事件是異步過程因此無法修改事件,應(yīng)用程序內(nèi)的消息可以在捕獲到消息后,修改事件然后繼續(xù)交由響應(yīng)者鏈中下一個(gè)響應(yīng)者處理。

鼠標(biāo)事件監(jiān)聽

這里介紹鼠標(biāo)的一下事件處理方法和注意事項(xiàng):

  1. 左/右鍵的按下抬起事件
  2. 左鍵的雙擊(或者多擊事件)
  3. 左鍵或者右鍵的拖拽事件
  4. 鼠標(biāo)移動事件
  5. 鼠標(biāo)的滾動事件

前面介紹了三種監(jiān)聽事件的方法,這里使用重寫 Responders 的方法來監(jiān)聽鼠標(biāo)事件:

- (void)mouseDown:(NSEvent *)event;
- (void)rightMouseDown:(NSEvent *)event;
- (void)mouseUp:(NSEvent *)event;
- (void)rightMouseUp:(NSEvent *)event;
- (void)mouseMoved:(NSEvent *)event;
- (void)mouseDragged:(NSEvent *)event;
- (void)rightMouseDragged:(NSEvent *)event;
- (void)scrollWheel:(NSEvent *)event;

鼠標(biāo)按鍵的按下抬起事件,只要判斷一下 NSEventtype 屬性即可知道。

當(dāng)前鼠標(biāo)的位置信息可通過 locationInWindow 屬性來獲取,該坐標(biāo)是當(dāng)前 Window 窗口的坐標(biāo),其中包含了 Window 窗口標(biāo)題欄的高度,所以如果要想獲取當(dāng)前鼠標(biāo)在當(dāng)前 NSView 中的位置,需要做一次坐標(biāo)轉(zhuǎn)換,可以調(diào)用 NSViewconvertPoint: 方法來轉(zhuǎn)換坐標(biāo)。

鼠標(biāo)左鍵的 按下 - 抬起 兩個(gè)連續(xù)的動作被定義為單擊事件,clickCount 屬于用于描述當(dāng)前點(diǎn)擊的次數(shù)。在模擬鼠標(biāo)雙擊時(shí),需要用到該字段值,而不能用連續(xù)兩次點(diǎn)擊事件來模擬雙擊。

監(jiān)聽鼠標(biāo)的移動事件時(shí)需要設(shè)置一個(gè)跟蹤區(qū)域,只有在跟蹤區(qū)域內(nèi)的鼠標(biāo)移動事件才會觸發(fā)。可以通過 NSView- (void)addTrackingArea:(NSTrackingArea *)trackingArea 方法來設(shè)置跟蹤區(qū)域。同時(shí)需要重寫 - (void)updateTrackingAreas 方法,當(dāng)跟蹤區(qū)域發(fā)送改變時(shí),需要手動將之前的跟蹤區(qū)域移除,再添加新的跟蹤區(qū)域。

鼠標(biāo)的拖拽事件是指用戶按下鼠標(biāo)左鍵或右鍵移動鼠標(biāo),當(dāng)拖拽事件發(fā)生時(shí) mouseMoved: 事件將不會觸發(fā)。

鼠標(biāo)的滾動可以通過 deltaXdeltaY 兩個(gè)屬性來獲取分別在水平方向和垂直方向的滾動偏移。

OSX 的坐標(biāo)系統(tǒng)以左下角為 (0,0) 右上角為 (x_max, y_max)

鍵盤事件的監(jiān)聽

鍵盤事件的監(jiān)聽也使用重寫事件響應(yīng)者 Responders 對應(yīng)的方法來實(shí)現(xiàn),需要重寫的方法如下:

- (void)keyDown:(NSEvent *)event;
- (void)keyUp:(NSEvent *)event;
- (void)flagsChanged:(NSEvent *)event;

鍵盤事件重寫上述方法外還需要重寫以下方法:

- (BOOL)acceptsFirstResponder;

該方法用來說明是否成為響應(yīng)者鏈的第一個(gè)響應(yīng)者,這里需要返回 YES 表示成為第一響應(yīng)者,否則無法監(jiān)聽鍵盤消息。

NSEventcharacters 描述了當(dāng)前鍵盤按鍵的字符,keyCode 描述了按鍵的值,每個(gè)按鍵的 keyCode 值定義可以在 Carbon/HIToolbox/Events.h 文件中找到對應(yīng)的按鍵的宏定義。

keyDown:keyUp: 方法中可以監(jiān)聽到大部分的按鍵的消息,但一些控制鍵需要通過 flagsChanged: 方法來處理,當(dāng) NSEventModifierFlags 定義的按鍵狀態(tài)發(fā)送改變時(shí),該方法就會被觸發(fā)。這里需要注意的是大小寫鎖定鍵 NSEventModifierFlagCapsLock 只有當(dāng)大寫鎖定或者取消鎖定時(shí),該方法才會被調(diào)用,并不會因?yàn)?CapsLock 按鍵按下或者抬起時(shí)觸發(fā)。

keyCode 值在 Windows 和瀏覽器上都有對應(yīng)的鍵盤按鍵的值的定義,當(dāng)需要與其他平臺進(jìn)行通信時(shí),例如遠(yuǎn)程控制時(shí),可以將 Mac 下的 keyCode 值轉(zhuǎn)換成瀏覽器 JS 上的對應(yīng)值定義,因?yàn)闉g覽器和 Windows 平臺的定義是一致的。

CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_CapsLock) 方法可以用來獲取按鍵是否處于按下狀態(tài)。

鼠標(biāo)鍵盤事件的模擬

OSX 下的鼠標(biāo)和鍵盤事件模擬需要用到 CoreGraphicsCarbon 框架,在 CoreGraphics 框架中定義了一些用于創(chuàng)建底層事件的方法,Carbon 框架定義了一些跟鍵盤相關(guān)的宏和方法。

在模擬鼠標(biāo)或者鍵盤事件時(shí),都需要使用 CGEventSourceCreate(CGEventSourceStateID stateID) 方法來創(chuàng)建事件源,事件源類型定義了 3 個(gè)類型,如下:

typedef CF_ENUM(int32_t, CGEventSourceStateID) {
  kCGEventSourceStatePrivate = -1,
  kCGEventSourceStateCombinedSessionState = 0,
  kCGEventSourceStateHIDSystemState = 1
};
  • kCGEventSourceStatePrivate:代表專門的應(yīng)用,如遠(yuǎn)程控制程序可以生成和跟蹤事件源狀態(tài)獨(dú)立于其他進(jìn)程。
  • kCGEventSourceStateCombinedSessionState:該狀態(tài)表反映了所有事件源的組合狀態(tài)發(fā)布到當(dāng)前用戶的登錄會話。如果您的程序發(fā)布的事件在一個(gè)登錄會話,您應(yīng)該使用這個(gè)源狀態(tài)當(dāng)你創(chuàng)建一個(gè)事件源。
  • kCGEventSourceStateHIDSystemState:該狀態(tài)表反映了組合硬件輸入源從 HID 系統(tǒng)硬件層面發(fā)送的事件源。生成的事件。 就是外接鍵盤或者 macbook 本機(jī)鍵盤以及一些系統(tǒng)定義的按鍵點(diǎn)擊事件。

這里自己封裝了鼠標(biāo)事件、鼠標(biāo)滾動事件及鍵盤事件的方法,需要引入 <Carbon/Carbon.h><AppKit/AppKit.h> 頭文件

1. 模擬鼠標(biāo)事件:

void PostMouseEvent(CGMouseButton button, CGEventType type, const CGPoint &point, int64_t clickCount)
{
    CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
    CGEventRef theEvent = CGEventCreateMouseEvent(source, type, point, button);
    CGEventSetIntegerValueField(theEvent, kCGMouseEventClickState, clickCount);
    CGEventSetType(theEvent, type);
    CGEventPost(kCGHIDEventTap, theEvent);
    CFRelease(theEvent);
    CFRelease(source);
}

左鍵單擊模擬:

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, CGPointZero, 1);
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseUp, CGPointZero, 1);

左鍵雙擊模擬:

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, CGPointZero, 1);
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseUp, CGPointZero, 1);
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, CGPointZero, 2);
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseUp, CGPointZero, 2);

拖拽事件: 如果是拖拽事件,例如左鍵拖拽事件,則需要先發(fā)送左鍵的 kCGEventLeftMouseDown 事件,然后連續(xù)發(fā)送 kCGEventLeftMouseDragged 事件,再發(fā)送 kCGEventLeftMouseUp 事件,代碼如下:

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, CGPointZero, 1);
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDragged, CGPointZero, 1);
...
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDragged, CGPointZero, 1);
PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseUp, CGPointZero, 1);

模擬其他鼠標(biāo)事件,將枚舉值修改一下即可。

2. 模擬鼠標(biāo)滾動事件

void PostScrollWheelEvent(int32_t scrollingDeltaX, int32_t scrollingDeltaY)
{
    CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
    CGEventRef theEvent = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitPixel, 2, scrollingDeltaY, scrollingDeltaX);
    CGEventPost(kCGHIDEventTap, theEvent);
    CFRelease(theEvent);
    CFRelease(source);
}

鼠標(biāo)滾輪事件只要傳入水平和垂直方向的偏移即可實(shí)現(xiàn)。

3. 模擬鍵盤事件

void PostKeyboardEvent(CGKeyCode virtualKey, bool keyDown, CGEventFlags flags)
{
    CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
    CGEventRef push = CGEventCreateKeyboardEvent(source, virtualKey, keyDown);
    CGEventSetFlags(push, flags);
    CGEventPost(kCGHIDEventTap, push);
    CFRelease(push);
    CFRelease(source);
}

鍵盤事件的模擬需要注意的就是 CGEventFlags flags 參數(shù),該參數(shù)用來模擬組合鍵的實(shí)現(xiàn),類型定義如下:

typedef CF_OPTIONS(uint64_t, CGEventFlags) { /* Flags for events */
  /* Device-independent modifier key bits. */
  kCGEventFlagMaskAlphaShift =          NX_ALPHASHIFTMASK,
  kCGEventFlagMaskShift =               NX_SHIFTMASK,
  kCGEventFlagMaskControl =             NX_CONTROLMASK,
  kCGEventFlagMaskAlternate =           NX_ALTERNATEMASK,
  kCGEventFlagMaskCommand =             NX_COMMANDMASK,
  /* Special key identifiers. */
  kCGEventFlagMaskHelp =                NX_HELPMASK,
  kCGEventFlagMaskSecondaryFn =         NX_SECONDARYFNMASK,
  /* Identifies key events from numeric keypad area on extended keyboards. */
  kCGEventFlagMaskNumericPad =          NX_NUMERICPADMASK,
  /* Indicates if mouse/pen movement events are not being coalesced */
  kCGEventFlagMaskNonCoalesced =        NX_NONCOALSESCEDMASK
};

解析如下:

  • kCGEventFlagMaskAlphaShift:大小寫鎖定鍵是否處于開啟狀態(tài)
  • kCGEventFlagMaskShift:Shift 鍵是否按下
  • kCGEventFlagMaskControl:Control 鍵是否按下
  • kCGEventFlagMaskAlternate:Alt 鍵是否按下,對應(yīng) Mac 鍵盤的 option 鍵
  • kCGEventFlagMaskCommand:Command 鍵是否按下,對應(yīng) Windows 的 WIN 鍵
  • kCGEventFlagMaskHelp:Help 鍵
  • kCGEventFlagMaskSecondaryFn:Fn 鍵
  • kCGEventFlagMaskNumericPad:數(shù)字鍵盤
  • kCGEventFlagMaskNonCoalesced:沒有任何鍵按下

如果有多個(gè)控制鍵同時(shí)按下,則使用位運(yùn)算的或 | 加上對應(yīng)的鍵值即可。例如模擬 Command + Control + S:

PostKeyboardEvent(kVK_ANSI_S, true, kCGEventFlagMaskCommand | kCGEventFlagMaskControl)
PostKeyboardEvent(kVK_ANSI_S, false, kCGEventFlagMaskNonCoalesced)

大小寫鎖定鍵,無法通過 kVK_CapsLock 按鍵的按下和抬起事件來模擬大小鍵的鎖定,同時(shí)按鍵上的 LED 燈也是不會有變化的。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容