在Mavericks+中,CGEventTapCreateForPSN (GetCurrentProcess已弃用)是什么?

15

我使用 CGEventTapCreateForPSN 捕获并过滤我的应用程序的按键事件。我不希望拦截其他应用程序的事件。我相信事件监听对于我的目的来说有些过于笨重,但我一直找不到更好的方法,所以还是使用事件监听的方式。

具体而言,这段代码实现了我的需求。

GetCurrentProcess(&psn);
CFMachPortRef eventTap = CGEventTapCreateForPSN(
    &psn,
    kCGHeadInsertEventTap,
    kCGEventTapOptionDefault,
    CGEventMaskBit(kCGEventKeyDown)
        | CGEventMaskBit(kCGEventKeyUp),
    eventCallback,
    userInfo);

我的回调函数被很好地处理了,只拦截当前应用程序的事件。

不幸的是,所有获取当前ProcessSerialNumber的方法在10.9版本后均已弃用。有一种旧的标准方法可以获取ProcessSerialNumber并将其传递给同一进程中的其他例程,初始化方法如下...

ProcessSerialNumber psn = { 0, kCurrentProcess };

但是当调用CGEventTapCreateForPSN时,这种方法不起作用。头文件文档也指出了这一点,下面的代码片段返回NULL作为确认。

ProcessSerialNumber psn = { 0, kCurrentProcess };
CFMachPortRef eventTap = CGEventTapCreateForPSN(
    &psn,
    kCGHeadInsertEventTap,
    kCGEventTapOptionDefault,
    CGEventMaskBit(kCGEventKeyDown)
        | CGEventMaskBit(kCGEventKeyUp),
    eventCallback,
    userInfo);

我可以使用 CGEventTapCreate,但它会捕捉整个主机,因此我需要过滤掉任何不针对我的应用程序的内容,而 CGEventTapProxy 是不透明的,我不知道如何使用它来确定是否是我的应用程序。

我已验证过时的代码仍然有效,但苹果公司可以随时决定将其移除。那么,有没有人有想法应该如何在 Mavericks 及更高版本中调用 CGEventTapCreateForPSN

谢谢!


更新

在 10.11(我认为那是 El Capitan),添加了一个新函数。虽然没有任何文档,但它几乎与 CGEventTapCreateForPSN 具有完全相同的签名。

CFMachPortRef CGEventTapCreateForPSN(
    void *processSerialNumber,
    CGEventTapPlacement place,
    CGEventTapOptions options,
    CGEventMask eventsOfInterest,
    CGEventTapCallBack callback,
    void *userInfo);

CFMachPortRef CGEventTapCreateForPid(
    pid_t pid,
    CGEventTapPlacement place,
    CGEventTapOptions options,
    CGEventMask eventsOfInterest,
    CGEventTapCallBack callback,
    void *userInfo);

因此,由于PID可以用作第一个参数,因此不需要使用已弃用的函数。


1
这并不是对你问题的确切回答,但是关于可能更好的方法,你是否看过 +[NSEvent addLocalMonitorForEventsMatchingMask:handler:] - JWWalker
在这种情况下安装更重的事件tap的动机是,我需要在弹出菜单事件循环期间拦截(并可能更改或防止)键盘事件。不幸的是,[NSEvent addLocalMonitorForEventsMatchingMask:handler:] 不会监视嵌套事件循环中的事件。 - Jody Hagins
2
我用于过滤鼠标事件的另一种方法是在事件分派目标(GetEventDispatcherTarget())上安装Carbon事件处理程序。从10.10 SDK头文件中可以看出,必要的API未被弃用,并且可在64位中使用。 - JWWalker
我今天也遇到了这个问题。好问题。@JWWalker的addLocalMonitor..不适用于非主线程,它需要主线程,这就是为什么我不能使用addLocal的原因:( - Noitidart
2个回答

1
我认为你应该创建一个子类继承NSApplication,并重写- (void)sendEvent:(NSEvent *)theEvent方法来实现这个目的。 来自docs

你很少需要创建一个自定义的NSApplication子类。与一些面向对象的库不同,Cocoa不要求你子类化NSApplication以定制应用程序行为。相反,它提供了许多其他方式来自定义应用程序。

另外:

重要提示

许多AppKit类依赖于NSApplication类,可能在这个类完全初始化之前无法正常工作。因此,你不应该尝试从NSApplication子类的初始化方法中调用其他AppKit类的方法。

因此,你可以拦截通过应用程序传递的所有事件并调用自定义的NSApplicationDelegate继承协议方法。

// in SubApplication.h

@protocol ExtendedApplicationDelegate : NSApplicationDelegate

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

@end

// in SubApplication.m

- (void)sendEvent:(NSEvent *)event
{
    if ([event type] == NSKeyDown && [event keyCode]==_someCode)
    {
      // call application delegate method
    }
    [super sendEvent:event];
}

我不确定这种方法是否解决了问题,但你可以尝试一下。

不,那样行不通。请仔细阅读文档。该方法仅针对在主事件循环中分派的事件调用。它无法处理嵌套的事件循环。有关相同讨论的评论,请参见原帖附加的注释。 - Jody Hagins
据我所知,覆盖-[NSApplication sendEvent:]确实可以处理嵌套事件循环。当文档中说“主事件循环”时,它们指的是“主线程上的事件循环”。如果这不起作用,你能否发布一份代码示例,以一种不被覆盖的sendEvent:捕获的方式运行嵌套事件循环? - s4y
@Astoria 噢,抱歉,我是在回复 @JodyHagins 的评论。您的答案对我来说很有道理! - s4y
@s4y:addLocalMonitorForEventsMatchingMask:handler:的文档中说:“对于被嵌套事件跟踪循环(如控件跟踪、菜单跟踪或窗口拖动)消耗的事件,您的处理程序将不会被调用;只有通过应用程序的sendEvent:方法分派的事件才会传递到您的处理程序。” 这意味着sendEvent:看不到许多事件。 - JWWalker

0

另一种方法是子类化NSApplication并覆盖nextEventMatchingMask:untilDate:inMode:dequeue:。这种方法可以看到鼠标事件,而通过覆盖sendEvent:无法看到,例如跟踪滚动条时。我不确定对于键盘事件是否有影响,因为问题是关于键盘事件的,但对于其他偶然遇到此问题的人可能会感兴趣。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接