Reputation: 11808
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?
Upvotes: 18
Views: 1789
Reputation: 966
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:
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; }
Upvotes: 1
Reputation: 2976
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.
Upvotes: 1
Reputation: 4503
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.
Upvotes: 0