Mac event tap just delays discarded events
Asked Answered
P

3

18

I'm trying to write some code that discards all keyboard and mouse events when enabled on Mac OSX 10.6. My code runs as the root user. The approach I'm taking is to create an event tap that discards all events passed to it (while enabled). The event tap callback function looks like this:

CGEventRef MyTapCallback(CGEventTapProxy proxy,
                         CGEventType type,
                         CGEventRef event,
                         void *refcon)
{
    return CKeyLocker::isEnabled() ? NULL : event;
}

And the code I'm using to enable and disable the event tap looks like this:

void CKeyLocker::enable(bool bEnable)
{
    if (bEnable == m_bEnabled)
        return;

    if (bEnable)
    {
        // which events are we interested in?
        CGEventMask evMask = kCGEventMaskForAllEvents;
        CFMachPortRef mp = CGEventTapCreate(kCGHIDEventTap,
                                            kCGHeadInsertEventTap,
                                            kCGEventTapOptionDefault,
                                            evMask,
                                            MyTapCallback,
                                            NULL);

        if (mp)
        {
            qDebug() << "Tap created and active. mp =" << mp;
            m_enabledTap = mp;
            m_bEnabled = true;
        }
    }
    else
    {
        CGEventTapEnable(m_enabledTap, false);
        CFRelease(m_enabledTap);
        m_enabledTap =0;
        m_bEnabled = false;
        qDebug() << "Tap destroyed and inactive";
    }
}

This approach works very well while the event tap is active - I can hammer on the keyboard and mouse as much as I want and no events make it through the system. However, when the tap is disabled all the keys I pushed while the tap was active appear in the current window. It's like the event tap is just delaying the events, rather than destroying them, which is odd, since the Mac documentation clearly states:

If the event tap is an active filter, your callback function should return one of the following:

The (possibly modified) event that is passed in. This event is passed back to the event system.

A newly-constructed event. After the new event has been passed back to the event system, the new event will be released along with the original event.

NULL if the event passed in is to be deleted.

I'm returning NULL, but the event doesn't seem to be deleted. Any ideas?

Patrilineage answered 12/5, 2010 at 9:57 Comment(3)
Did you solve this problem? I've just encountered the exact same behavior.Tabulator
See also #4519059Cheviot
looks like the above linked question has the answerFeudatory
K
1

The linked comment does not have an answer from what I see, so I'll dump some info from what I've seen when poking around with this stuff.

First, I have much better luck with CGEventTapCreateForPSN. It's as if the system gives you some leeway for restricting your tap. However, from this example it looks like this is not sufficient.

Next up - and this /may/ be all you need... In your call back, you probably want (and may need) to check for the following events:

switch (type)
{
    case kCGEventTapDisabledByTimeout:
    case kCGEventTapDisabledByUserInput:
    {
        CFMachPortRef *pTap = (CFMachPortRef*)refcon;
        CGEventTapEnable( *pTap, true );
        return NULL;
    }
    default:
        break;
}

Regardless of what the various documentation does or doesn't say, it's been my observation that the OS feels like it's 'probing' for bad callbacks; basically disabling event tap callbacks that are universally eating events. If you re-register in these cases the OS seems to be ok with it, as if saying: OK, you seem to know what you're doing, but I'll probably poke you again in a bit to make sure.

Kempis answered 26/3, 2012 at 18:7 Comment(0)
I
1

It's really strange, we use event taps for the same purpose (input blocking in a given scenario) and works perfectly 10.4 - 10.8.2. excpet one thing, it should not block or receive events from a password dialog (which is not a big surprise)

What I can see now is different compared to you sample is:

  • we use kCGTailAppendEventTap instead of kCGHeadInsertEventTap (this should not matter)
  • we do some event logging in the installed callback
  • we have some user event data in some self injected events, that filtered out, but apart from this we simply return NULL to drop an unwanted event (like you do), I can confirm, not all events are ignorable!
  • we turn on/off the event tap this way:
bool SetInputFilter(bool bOn)
{
    bool result = false;
    CFRunLoopRef runLoopRef = CFRunLoopGetMain();

    if (bOn) {
        // Create an event tap.
        CGEventMask eventMask = kCGEventMaskForAllEvents;
        if ((m_eventTapInput = CGEventTapCreate(kCGHIDEventTap, 
                                              kCGTailAppendEventTap, 
                                              kCGEventTapOptionDefault,
                                              eventMask, CGInputEventCallback, this)) == NULL) {
            Log(L"Failed to create event tap");
            return result;
        }

        // Create a run loop source.
        m_runLoopEventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapInput, 0);
        CFRelease(m_eventTapInput);   // CFMachPortCreateRunLoopSource retains m_eventTapInput
        if (m_runLoopEventTapSource == NULL) {
            Log(L"Failed to create run loop source for event tap");
            return result;
        }

        // Add to the current run loop.
        CFRunLoopAddSource(runLoopRef, m_runLoopEventTapSource, kCFRunLoopCommonModes);//kCFRunLoopDefaultMode);
        CFRelease(m_runLoopEventTapSource);   // CFRunLoopAddSource retains m_runLoopEventTapSource
        result = true;
    }
    else {
        // Disable the event tap.
        if (m_eventTapInput)
            CGEventTapEnable(m_eventTapInput, false);

        // Remove our run loop source from the current run loop.
        if (runLoopRef && m_runLoopEventTapSource) {
            CFRunLoopRemoveSource(runLoopRef, m_runLoopEventTapSource, kCFRunLoopCommonModes);//kCFRunLoopDefaultMode);
            m_runLoopEventTapSource = NULL;   // removing m_runLoopEventTapSource releases last reference of m_runLoopEventTapSource too
            m_eventTapInput = NULL;           // removing m_runLoopEventTapSource releases last reference of m_eventTapInput too
        }
    }
    return result;
}
Immense answered 25/10, 2012 at 21:31 Comment(0)
C
0

I can verify that returning NULL does effectively delete some events, but i have also seen times when it does not, exactly how it decides what deletions to permit is unclear but it looks like mass deletions seem to be prevented e.g.: when you delete more than 100 events or so in a row.

Carlyncarlynn answered 28/2, 2012 at 21:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.