快捷搜索:

Mac OSX 鼠标键盘事件的监听和模拟

近来完成了 Mac OSX 平台下的远程节制功能,时代找了不少资料,这里做个总结,主要涉及到一下常识点:

OSX 的事故机制

OSX/iOS 相应链者链

鼠标事故的监听及模拟(鼠标单击、双击、拖动、滚动等事故)

键盘事故的监听及模拟(包括组合键的模拟)

Keycode 键盘编码(统一 Windows、OSX、浏览器端键盘按键的编码值)

事故分发机制

在 OSX 系统中鼠标和键盘的活动事故都邑孕育发生底层的系统事故,首先通报到 IOKit 框架处置惩罚后存储到行列步队中,看护 Window Server 办事层处置惩罚。Window Server 存储到 FIFO 优先行列步队中,然后一一转发到当前活动窗口或者能相应这个事故的利用法度榜样去处置惩罚。

在 OSX 或者 iOS 法度榜样中,都邑有一个 Main Run Loop 的线程,RunLoop 轮回中会遍历 event 消息行列步队,一一分发这些事故到利用中相宜的工具去处置惩罚。详细来说便是调用 NSApp 的 sendEvent: 措施发送消息到NSWindow,NSWindow 再分发到 NSView 视图工具,由其鼠标或键盘事故相应措施去处置惩罚。

Alt text

事故相应链

在 OSX 和 iOS 法度榜样中相应者链是 Application Kit 事故处置惩罚架构的中间计心情制,它由一系列链接在一路的相应者工具组成,事故或者动作消息可以沿着这些工具进行通报。假如一个相应者工具不能处置惩罚某个事故或动作,也便是说,它不相应那个消息,或者不熟识那个事故,则将该消息从新发送给链中的下一个相应者。消息沿着相应者链向上、向更高档其余工具通报,直到终极被处置惩罚(假如终极照样没有被处置惩罚,就会被扬弃)。

事故相应者 Responders 类为核心利用法度榜样架构的三个主要模式或机拟订义了一个接口:

它声清楚明了一些处置惩罚事故消息(也便是源自用户事故的消息,比如象鼠标点击或按键按下这样的事故)的措施。

它声清楚明了数十个处置惩罚动作消息的措施,它们和标准的键绑定(比如那些在文本内部移动插入点的绑定)亲昵相关。动作消息会被派发到目标工具;假如目标没有被指定,利用法度榜样会认真检索相宜的相应者。

它定义了一套在利用法度榜样中指派和治理相应者的措施。这些相应者组成了我们所知道的相应者链,即一系列相应者,事故或动作消息在它们之间通报,直到找到能够对它们进行处置惩罚的工具。

当 Application Kit 在利用法度榜样中构造工具时,会为每个窗口建立相应者链。相应者链中的基础工具是NSWindow工具及其视图层次。在视图层次中级别较低的视图将比级别更高的视图优先得随处置惩罚事故或动作消息的时机。NSWindow 中保有一个第一相应者的引用,它平日是当前窗口中处于选择状态的视图,窗口平日把相应消息的时机首先给它。对付事故消息,相应者链平日以发肇事故的窗口对应的 NSWindow 工具作为停止,虽然其它工具也可以作为下一个相应者被加入到 NSWindow 工具的后面。

从层级上看离察看者近来的视图优先相应事故,经由过程 view 的 hitTest 措施检测,满意 hitTest 措施的的子视图优先相应事故。

NSApplication, NSWindow, NSDrawer, NSWindowController, NSView 以及承袭于 NSView 的所有控件工具都直接或间接承袭了 Responders 类,以是这些类都能处置惩罚鼠标和键盘事故。

iOS 法度榜样比拟于 OSX 法度榜样会有点不一样:

OSX 法度榜样可能存在多个窗口,会有多个相应者链,iPhone 的利用法度榜样就一个窗口,以是只会有一个相应者链。

在 iOS 法度榜样中与加速计、陀螺仪和磁力计相关的运动事故不遵照相应者链,Core Motion 会将这些事故直接通报给我们指定的工具。有关更多信息,可以参看 Core Motion Framework。

相关类的解析阐明

NSResponder

NSResponder 在这里是异常紧张的一个类,此中定义了鼠标键盘触控板等多种事故,这里枚举一些鼠标跟键盘的主要措施:

// 鼠标按下事故

- (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)scrollWheel:(NSEvent *)event;

// 鼠标右键拖拽事故

- (void)rightMouseDragged:(NSEvent *)event;

// 鼠标进入监控区域事故

- (void)mouseEntered:(NSEvent *)event;

// 鼠标脱离监控区域事故

- (void)mouseExited:(NSEvent *)event;

// 键盘按下事故

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

// 键盘按下事故

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

// 键盘节制键的按下标记状态发送改变,后面用该措施来获取节制按下事故,参考 NSEventModifierFlags 定义

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

NSResponder 除了定义基础的相应事故外,还定义了很多其他事故措施。详细请参考 NSResponder.h 的头文件定义。

NSEvent

NSEvent 类描述了事故的详细信息,这里枚举跟鼠标和键盘相关的一些字段的先容:

// 事故类型

@property (readonly) NSEventType type;

// 键盘节制键的按下状态的标记

@property (readonly) NSEventModifierFlags modifierFlags;

// 事故的光阴戳

@property (readonly) NSTimeInterval timestamp;

// 鼠标点击的次数(只有鼠标事故,才可应用)

@property (readonly) NSInteger clickCount;

@property (readonly) NSInteger buttonNumber;

@property (readonly) NSInteger eventNumber;

// 压力值

@property (readonly) float pressure;

// 鼠标在窗口的位置

@property (readonly) NSPoint locationInWindow;

// 鼠标滚动时。分手在 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,

NSEventTypeSwipeNS_ENUM_AVAILABLE_MAC(10_5)= 31,

NSEventTypeRotateNS_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 类型描述了一些节制键,是否处于按下状态,定义如下:

/* Device-independent bits found in event modifier flags */

typedef NS_OPTIONS(NSUInteger, NSEventModifierFlags) {

NSEventModifierFlagCapsLock= 1

事故的监听措施

鼠标键盘事故的监听有多种措施,第一种措施是重写事故相应者 Responders 对应的措施来获取对应的事故;第二是经由过程重写 NSWindow 的 sendEvent: 措施; 第三是经由过程的 NSEvent 供给静态措施来监听对应的事故:

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

+ (nullable id)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(NSEvent* __nullable (^)(NSEvent*))block

+ (void)removeMonitor:(id)eventMonitor

NSEvent 供给的静态措施可以用监听全部系统的事故或者当前利用法度榜样内的事故。全局事故是异步历程是以无法修转事故,利用法度榜样内的消息可以在捕获到消息后,修转事故然后继承交由相应者链中下一个相应者处置惩罚。

鼠标事故监听

这里先容鼠标的一下事故处置惩罚措施和留意事变:

左/右键的按下抬起事故

左键的双击(或者多击事故)

左键或者右键的拖拽事故

鼠标移动事故

鼠标的滚动事故

前面先容了三种监听事故的措施,这里应用重写 Responders 的措施来监听鼠标事故:

- (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;

鼠标按键的按下抬起事故,只要判断一下 NSEvent 的 type 属性即可知道。

当前鼠标的位置信息可经由过程 locationInWindow 属性来获取,该坐标是当前 Window 窗口的坐标,此中包孕了 Window 窗口标题栏的高度,以是假如要想获取当前鼠标在当前 NSView 中的位置,必要做一次坐标转换,可以调用 NSView 的 convertPoint: 措施来转换坐标。

鼠标左键的 按下 - 抬起 两个继续的动作被定义为单击事故,clickCount 属于用于描述当前点击的次数。在模拟鼠标双击时,必要用到该字段值,而不能用继续两次点击变糊弄模拟双击。

监听鼠标的移动事故时必要设置一个跟踪区域,只有在跟踪区域内的鼠标移动事故才会触发。可以经由过程 NSView的 - (void)addTrackingArea:(NSTrackingArea *)trackingArea 措施来设置跟踪区域。同时必要重写 - (void)updateTrackingAreas 措施,当跟踪区域发送改变时,必要手动将之前的跟踪区域移除,再添加新的跟踪区域。

鼠标的拖拽事故是指用户按下鼠标左键或右键移动鼠标,当拖拽事故发生时 mouseMoved: 事故将不会触发。

鼠标的滚动可以经由过程 deltaX 和 deltaY 两个属性来获取分手在水平偏向和垂直偏向的滚动偏移。

OSX 的坐标系统以左下角为 (0,0) 右上角为 (x_max, y_max)

键盘事故的监听

键盘事故的监听也应用重写事故相应者 Responders 对应的措施来实现,必要重写的措施如下:

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

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

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

键盘事故重写上述措施外还必要重写以下措施:

- (BOOL)acceptsFirstResponder;

该措施用来阐明是否成为相应者链的第一个相应者,这里必要返回 YES 表示成为第一相应者,否则无法监听键盘消息。

NSEvent 的 characters 描述了当前键盘按键的字符,keyCode 描述了按键的值,每个按键的 keyCode 值定义可以在 Carbon/HIToolbox/Events.h 文件中找到对应的按键的宏定义。

在 keyDown: 和 keyUp: 措施中可以监听到大年夜部分的按键的消息,但一些节制键必要经由过程 flagsChanged: 措施来处置惩罚,当 NSEventModifierFlags 定义的按键状态发送改变时,该措施就会被触发。这里必要留意的是大年夜小写锁定键 NSEventModifierFlagCapsLock 只有昔时夜写锁定或者取消锁准时,该措施才会被调用,并不会由于 CapsLock 按键按下或者抬起时触发。

keyCode 值在 Windows 和浏览器上都有对应的键盘按键的值的定义,当必要与其他平台进行通信时,例如远程节制时,可以将 Mac 下的 keyCode 值转换成浏览器 JS 上的对应值定义,由于浏览器和 Windows 平台的定义是同等的。

CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_CapsLock) 措施可以用来获取按键是否处于按下状态。

鼠标键盘事故的模拟

OSX 下的鼠标和键盘事故模拟必要用到 CoreGraphics 及 Carbon 框架,在 CoreGraphics 框架中定义了一些用于创建底层事故的措施,Carbon 框架定义了一些跟键盘相关的宏和措施。

在模拟鼠标或者键盘事故时,都必要应用 CGEventSourceCreate(CGEventSourceStateID stateID) 措施来创建事故源,事故源类型定义了 3 个类型,如下:

typedef CF_ENUM(int32_t, CGEventSourceStateID) {

kCGEventSourceStatePrivate = -1,

kCGEventSourceStateCombinedSessionState = 0,

kCGEventSourceStateHIDSystemState = 1

};

kCGEventSourceStatePrivate:代表专门的利用,如远程节制法度榜样可以天生和跟踪事故源状态自力于其他进程。

kCGEventSourceStateCombinedSessionState:该状态表反应了所有事故源的组合状态宣布到当前用户的登录会话。假如您的法度榜样宣布的事故在一个登录会话,您应该应用这个源状态当你创建一个事故源。

kCGEventSourceStateHIDSystemState:该状态表反应了组合硬件输入源从 HID 系统硬件层面发送的事故源。天生的事故。 便是外接键盘或者 macbook 本机键盘以及一些系统定义的按键点击事故。

这里自己封装了鼠标事故、鼠标滚动事故及键盘事故的措施,必要引入及头文件

1. 模拟鼠标事故:

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);

拖拽事故: 假如是拖拽事故,例如左键拖拽事故,则必要先发送左键的 kCGEventLeftMouseDown 事故,然后继续发送 kCGEventLeftMouseDragged 事故,再发送 kCGEventLeftMouseUp 事故,代码如下:

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDown, CGPointZero, 1);

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDragged, CGPointZero, 1);

...

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseDragged, CGPointZero, 1);

PostMouseEvent(kCGMouseButtonLeft, kCGEventLeftMouseUp, CGPointZero, 1);

模拟其他鼠标事故,将罗列值改动一下即可。

2. 模拟鼠标滚动事故

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);

}

鼠标滚轮事故只要传入水温和垂直偏向的偏移即可实现。

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 参数,该参数用来模拟组合键的实现,类型定义如下:

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:大年夜小写锁定键是否处于开启状态

kCGEventFlagMaskShift:Shift 键是否按下

kCGEventFlagMaskControl:Control 键是否按下

kCGEventFlagMaskAlternate:Alt 键是否按下,对应 Mac 键盘的 option 键

kCGEventFlagMaskCommand:Command 键是否按下,对应 Windows 的 WIN 键

kCGEventFlagMaskHelp:Help 键

kCGEventFlagMaskSecondaryFn:Fn 键

kCGEventFlagMaskNumericPad:数字键盘

kCGEventFlagMaskNonCoalesced:没有任何键按下

假如有多个节制键同时按下,则应用位运算的或 | 加上对应的键值即可。例如模拟 Command + Control + S:

PostKeyboardEvent(kVK_ANSI_S, true, kCGEventFlagMaskCommand | kCGEventFlagMaskControl)

PostKeyboardEvent(kVK_ANSI_S, false, kCGEventFlagMaskNonCoalesced)

大年夜小写锁定键,无法经由过程 kVK_CapsLock 按键的按下和抬起变糊弄模拟大年夜小键的锁定,同时按键上的 LED 灯也是不会有变更的。

参考资料

鼠标和键盘事故

macOS上模拟发送键盘事故

开拓mouseSync的初衷

监听Mac OS X的全局鼠标事故

本文转自:

http://enkichen.com/2018/09/12/osx-mouse-keyboard-event/

您可能还会对下面的文章感兴趣: