Mac OS X上获取鼠标事件

Mac OS X上获取鼠标事件,第1张

概述原文地址:http://www.keakon.net/2011/11/10/监听MacOSX的全局鼠标事件   Mac OS X下怎么监听全局的鼠标事件呢? 首先不能错过的是Cocoa Event-Handling Guide这篇文档。它详细介绍了Mac OS X下的事件机制,这里只简要说一下事件传播的流程。 考虑一个鼠标点击事件。鼠标硬件先接收到用户点击,然后交给鼠标驱动来处理。这个驱动是在Ma

原文地址:http://www.keakon.net/2011/11/10/监听MacOSX的全局鼠标事件

 

Mac OS X下怎么监听全局的鼠标事件呢?

首先不能错过的是Cocoa Event-Handling Guide这篇文档。它详细介绍了Mac OS X下的事件机制,这里只简要说一下事件传播的流程。
考虑一个鼠标点击事件。鼠标硬件先接收到用户点击,然后交给鼠标驱动来处理。这个驱动是在Mac OS X内核运行的,处理完就通过I/O Kit传递给window server的事件队列。而window server则负责分派这些事件到对应进程的run-loop。

接着是Stack Overflow的这篇Global Mouse Moved Events in Cocoa,列出了监听全局鼠标事件的2个API。

其中之一是NSEvent的+ addGlobalMonitorForEventsMatchingMask:handler:方法。
这个方法有2点需要注意:

它是异步接收的,因此不能修改事件和阻止事件传播。
这个程序本身不能接收到自己的事件,但可以用+ addLocalMonitorForEventsMatchingMask:handler:。

很显然,不能阻止事件传播的话,执行鼠标手势时就会触发默认的右键功能了,因此这个方法并不适合。

另一个方法则是使用CGEventTap,它更为底层,可以修改事件

先来捕捉事件

- (voID)applicationDIDFinishLaunching:(NSNotification *)aNotification{    CGEventMask eventMask = CGEventMaskBit(kCGEventRightmouseDown) | CGEventMaskBit(kCGEventRightmouseUp);    CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap,kCGheadInsertEventTap,kCGEventTapOptionDefault,eventMask,eventCallback,NulL);    CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kcfAllocatorDefault,eventTap,0);    CFRunLoopAddSource(CFRunLoopGetCurrent(),runLoopSource,kcfRunLoopCommonModes);    CGEventTapEnable(eventTap,true);    CFRelease(eventTap);    CFRelease(runLoopSource);}


这段代码有点复杂,于是逐行来解释。
首先是eventMask,它是我要捕捉的事件掩码。这里我只是演示,因此就不捕捉拖动了。
接着是eventTap,它是一个CFMachPort对象,是与Mac OS X内核通信的通道。第一个参数有3种值,注意事件传播是沿着硬件系统 → window server → 用户session → 应用程序这条路径的,每个箭头处都可以捕捉事件,而这个参数就决定了在哪捕捉事件。kCGheadInsertEventTap是指要放在其他event taps之前,避免事件被修改或停止传播。kCGEventTapOptionDefault是指可以修改或停止传播事件,用kCGEventTapOptionListenOnly的话就和上一种方法一样了。eventCallback是事件发生时会被调用的函数,稍后列出。最后一个参数是传给回调函数的值,这里我用不到,所以设为NulL。
然后是拿eventTap创建一个加到RunLoopSource对象,太抽象了也没啥好解释的。
再是把这个runLoopSource加到当前线程(即主线程)的RunLoop。
后面那行代码其实是多余的,用于启用eventTap。事实上添加到RunLoop后就已经启用了,要停用时可以将参数改成false。

再来看看刚才那个回调函数:

static CGEventRef eventCallback(CGEventTapProxy proxy,CGEventType type,CGEventRef event,voID *refcon) {    CGPoint location = CGEventGetLocation(event);    NSInteger windowNumber = [NSWindow windowNumberAtPoint:location belowWindowWithWindowNumber:0];    CGWindowID windowID = (CGWindowID)windowNumber;    CFArrayRef array = CFArrayCreate(NulL,(const voID **)&windowID,1,NulL);    NSArray *windowInfos = (NSArray *)CGWindowListCreateDescriptionFromArray(array);    CFRelease(array);    if (windowInfos.count > 0) {        NSDictionary *windowInfo = [windowInfos objectAtIndex:0];        NSLog(@"Window name:  %@",[windowInfo objectForKey:(Nsstring *)kCGWindowname]);        NSLog(@"Window owner: %@",[windowInfo objectForKey:(Nsstring *)kCGWindowOwnername]);    }    [windowInfos release];    return NulL;}


这一大段代码实际上是获取鼠标坐标对应的窗口信息的。
NSEvent可以直接获取windowNumber,但CGEventRef能在事件传递到窗口之前就捕捉到,也就没法知道这个值了。于是我只能用了NSWindow的类方法来获得窗口,再获取该窗口的名字。
最后返回NulL,这样事件就不会被继续传播了。

更改事件:
接着再把右键改成左键:

static CGEventRef eventCallback(CGEventTapProxy proxy,voID *refcon) {    if (type == kCGEventRightmouseDown) {        CGEventSetType(event,kCGEventleftMouseDown);    } else {        CGEventSetType(event,kCGEventleftMouseUp);    }    return event;}


再试试把它替换成按回车键:

#include <Carbon/Carbon.h>static CGEventRef createEventCallback(CGEventTapProxy proxy,voID *refcon) {    CGEventRef newEvent = CGEventCreateKeyboardEvent(NulL,kVK_Return,type == kCGEventRightmouseDown);    return newEvent;}


注意这里回调函数的名字需要带create或copy,因为我创建了一个对象,却没有释放它。

或者用CGEventTapPostEvent()和CGEventPost()来一次传递2个事件:

static CGEventRef createEventCallback(CGEventTapProxy proxy,voID *refcon) {    if (type == kCGEventRightmouseDown) {        return NulL;    } else {        CGEventRef newEvent = CGEventCreateKeyboardEvent(NulL,true);        CGEventPost(kCGSessionEventTap,newEvent);        // CGEventTapPostEvent(proxy,newEvent);        CFRelease(newEvent);        return CGEventCreateKeyboardEvent(NulL,false);    }}


注意这里最好是发送到kCGSessionEventTap,这样就不会再被kCGHIDEventTap捕捉到,避免重复捕捉。
如果要使用快捷键,不能直接模拟控制键按下的事件,而是要用CGEventSetFlags来设置。例如下面这段代码可以实现Shift + Command + T:

CGEventRef event = CGEventCreateKeyboardEvent(NulL,kVK_ANSI_T,true);CGEventSetFlags(event,kCGEventFlagMaskShift | kCGEventFlagMaskCommand);CGEventPost(kCGSessionEventTap,event);CFRelease(event);event = CGEventCreateKeyboardEvent(NulL,false);CGEventSetFlags(event,event);CFRelease(event);


接着附送些鼠标手势的算法和代码:
Mouse Gesture Recognition
鼠标手势算法
smoothgestures-chromium

最后也送上自己实现的一个4方向的手势检测:

typedef enum {    NONE,RIGHT,UP,left,DOWN,} DIRECTION;static vector<DIRECTION> directions;static CGPoint lastLocation;static voID updateDirections(CGEventRef event) {    CGPoint newLocation = CGEventGetLocation(event);    float deltaX = newLocation.x - lastLocation.x;    float deltaY = newLocation.y - lastLocation.y;    float absX = fabs(deltaX);    float absY = fabs(deltaY);    if (absX + absY < 20) {        return;    }    lastLocation = newLocation;    DIRECTION lastDirection = directions.empty() ? NONE : directions.back();    if (absX > absY) {        if (deltaX > 0) {            if (lastDirection != RIGHT) {                directions.push_back(RIGHT);            }        } else if (lastDirection != left) {            directions.push_back(left);        }    } else {        if (deltaY < 0) {            if (lastDirection != UP) {                directions.push_back(UP);            }        } else if (lastDirection != DOWN) {            directions.push_back(DOWN);        }    }}static CGEventRef eventCallback(CGEventTapProxy proxy,voID *refcon) {    switch (type) {        case kCGEventRightmouseDown:            lastLocation = CGEventGetLocation(event);            break;        case kCGEventRightmouseDragged:            updateDirections(event);            break;        case kCGEventRightmouseUp:            updateDirections(event);            for (vector<DIRECTION>::iterator iter = directions.begin(); iter != directions.end(); ++iter) {                NSLog(@"%d",*iter);            }            NSLog(@"----");            directions.clear();            break;        default:            return event;            break;    }    return NulL;}


如果不喜欢和CGEventRef打交道的话,也可以转换成NSEvent,不过注意Y轴的方向是相反的:

static NSPoint lastLocation;static voID updateDirections(NSEvent* event) {    NSPoint newLocation = event.locationInWindow;    float deltaX = newLocation.x - lastLocation.x;    float deltaY = newLocation.y - lastLocation.y;    float absX = fabs(deltaX);    float absY = fabs(deltaY);    if (absX + absY < 20) {        return;    }    lastLocation = newLocation;    DIRECTION lastDirection = directions.empty() ? NONE : directions.back();    if (absX > absY) {        if (deltaX > 0) {            if (lastDirection != RIGHT) {                directions.push_back(RIGHT);            }        } else if (lastDirection != left) {            directions.push_back(left);        }    } else {        if (deltaY > 0) {            if (lastDirection != UP) {                directions.push_back(UP);            }        } else if (lastDirection != DOWN) {            directions.push_back(DOWN);        }    }}static CGEventRef eventCallback(CGEventTapProxy proxy,voID *refcon) {    NSEvent *mouseEvent = [NSEvent eventWithCGEvent:event];    switch (mouseEvent.type) {        case NSRightmouseDown:            lastLocation = mouseEvent.locationInWindow;            break;        case NSRightmouseDragged:            updateDirections(mouseEvent);            break;        case NSRightmouseUp:            updateDirections(mouseEvent);            for (vector<DIRECTION>::iterator iter = directions.begin(); iter != directions.end(); ++iter) {                NSLog(@"%d",*iter);            }            NSLog(@"----");            directions.clear();            break;        default:            return event;            break;    }    return NulL;}
总结

以上是内存溢出为你收集整理的Mac OS X上获取鼠标事件全部内容,希望文章能够帮你解决Mac OS X上获取鼠标事件所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1054364.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存