15

I am using CGEventTapCreateForPSN to trap and filter keys for my application. I'm not interested in intercepting events for other applications. I'm pretty sure an event tap is too heavy handed for my purpose, but I've been unable to find a better way, and using the event tap works.

Specifically, this code does what I want.

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

And my callback is handled nicely, with the events being intercepted from the current application only.

Unfortunately, all the methods to get the current ProcessSerialNumber have been deprecated as of 10.9. There is an old standard way of getting the ProcessSerialNumber to pass to other routines in the same process, with this initialization...

ProcessSerialNumber psn = { 0, kCurrentProcess };

but that does not work when calling CGEventTapCreateForPSN. The header file docs indicates as much, and the following code snippet returns NULL as confirmation.

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

I can use CGEventTapCreate but it taps the entire host, and I would then need to filter anything not directed to my application, and the CGEventTapProxy is opaque, and I don't know how to use it to determine if its my app or not.

I have verified that the deprecated code still works, but Apple can decide to remove it at any time. So, does anyone have an idea how I should proceed for calling CGEventTapCreateForPSN in Mavericks and beyond?

Thanks!


UPDATE

In 10.11 (I think that was El Capitan), a new function was added. While it has zero documentation, it has almost the exact same signature as 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);

Thus, the deprecated function is not needed since the PID can be used as the first parameter.

Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
  • 1
    This isn't exactly an answer to your question, but regarding a possible better way, have you looked at `+[NSEvent addLocalMonitorForEventsMatchingMask:handler:]`? – JWWalker Apr 17 '15 at 21:04
  • The impetus for installing the heavier weight event tap in this particular case is that I need to intercept (and possibly alter or prevent) keyboard events during a popup menu event loop. Unfortunately, `[NSEvent addLocalMonitorForEventsMatchingMask:handler:]` does not monitor events in a nested event loop. – Jody Hagins Apr 18 '15 at 14:02
  • 2
    Another method I've used for filtering mouse events is installing a Carbon event handler on the event dispatcher target (`GetEventDispatcherTarget()`). As far as I can tell from the 10.10 SDK headers, the necessary APIs are not deprecated and are available in 64 bits. – JWWalker Apr 18 '15 at 15:14
  • I ran into this today s well. Great question. @JWWalker `addLocalMonitor..` doesnt work for off mainthread, it requires mainthread thats why I cant use addLocal :( – Noitidart Oct 19 '15 at 05:49

2 Answers2

1

I think you should subclass NSApplication and override - (void)sendEvent:(NSEvent *)theEvent method for this purpose. From docs:

You rarely should find a real need to create a custom NSApplication subclass. Unlike some object-oriented libraries, Cocoa does not require you to subclass NSApplication to customize app behavior. Instead it gives you many other ways to customize an app.

Also:

IMPORTANT

Many AppKit classes rely on the NSApplication class and may not work properly until this class is fully initialized. As a result, you should not, for example, attempt to invoke methods of other AppKit classes from an initialization method of an NSApplication subclass.

Thus, you can intercept all the events passed though your application and call custom NSApplicationDelegate-inherited protocol methods.

// 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];
}

I'm not sure if this approach solves the problem but you still make a try.

Community
  • 1
  • 1
Daniyar
  • 2,975
  • 2
  • 26
  • 39
  • No, that does not work. Read the documentation more carefully. That method is called only for events dispatched in the main event loop. It does not handle nested event loops. See the comments attached to the original post for the same discussion. – Jody Hagins May 21 '15 at 21:24
  • As far as I can tell, overriding `-[NSApplication sendEvent:]` *does* handle nested event loops. When the docs say "main event loop", they mean "the event loop on the main thread". If this doesn't work, could you post a code sample which runs a nested event loop in a way that isn't caught by an overridden `sendEvent:`? – s4y Mar 18 '18 at 12:45
  • @Astoria Oh, sorry, I was replying to @JodyHagins’ comment. Your answer makes sense to me! – s4y Mar 20 '18 at 11:36
  • @s4y: the docs on `addLocalMonitorForEventsMatchingMask:handler:` say "Your handler will not be called for events that are consumed by nested event-tracking loops such as control tracking, menu tracking, or window dragging; only events that are dispatched through the applications `sendEvent:` method will be passed to your handler." That implies that there are lots of events that `sendEvent:` won't see. – JWWalker Aug 28 '18 at 21:58
0

Another way is to subclass NSApplication and override nextEventMatchingMask:untilDate:inMode:dequeue:. This sees mouse events that are not seen by overriding sendEvent:, e.g., when tracking a scroll bar. I'm not sure if it matters for keyboard events, which the question was about, but it might be of interest to others who stumble on this question.

JWWalker
  • 22,385
  • 6
  • 55
  • 76