Reputation: 4286
What is the correct way to stop watching keyboard event taps using CGEventTap?
I am building a simple background app that converts the output of specific keys. Thanks to this excellent post on CGEventTap, I've been able to enable the key conversion. Unfortunately, I do not seem to be able to stop it short of killing the app.
The following method is called when the user toggles a checkbox to turn the functionality ON or OFF. Toggle ON happens immediately. Toggle OFF can take a minute or more before it takes affect. I see via log that the "Disabled. Stop converting taps." is detected. But the key conversion just keeps on going. I don't understand why.
- (void)watchEventTap
{
@autoreleasepool
{
CFRunLoopSourceRef runLoopSource = NULL;
CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(NX_SYSDEFINED), myCGEventCallback, NULL);
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
if (!eventTap)
{
NSLog(@"Couldn't create event tap!");
exit(1);
}
if (self.shortcutEnabled) // User default toggled ON
{
NSLog(@"Enabled. Convert taps.");
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
// CFRunLoopRun(); // This blocks rest of app from executing
}
else // User default toggled OFF
{
NSLog(@"Disabled. Stop converting taps.");
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, false);
// Clean up the event tap and source after ourselves.
CFMachPortInvalidate(eventTap);
CFRunLoopSourceInvalidate(runLoopSource);
CFRelease(eventTap);
CFRelease(runLoopSource);
eventTap = NULL;
runLoopSource = NULL;
}
}
// exit(0); // This blocks rest of app from executing
}
Thanks for any suggestions. I'm new building Mac OS X apps, so please forgive me if I'm doing something ignorant.
Upvotes: 1
Views: 1172
Reputation: 4286
Thanks to an experienced Mac developer, I got my issue resolved. I was creating a new runLoopsSource every time the method was called.
Now I've created instance variables for the tapEvent and runLoop. Only one line was needed to stop the eventTap. Modified method below:
- (void)watchEventTap
{
@autoreleasepool
{
if ( [[NSUserDefaults standardUserDefaults] isEnabledNumLockDV] == YES ) // User default toggled ON
{
_runLoopSource = NULL;
_eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(NX_SYSDEFINED), myCGEventCallback, NULL);
_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, _eventTap, 0);
if (!_eventTap)
{
NSLog(@"Couldn't create event tap!");
exit(1);
}
NSLog(@"Enabled. Convert taps.");
CFRunLoopAddSource(CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(_eventTap, true);
}
else if ( [[NSUserDefaults standardUserDefaults] isEnabledNumLockDV] == NO ) // User default toggled OFF
{
NSLog(@"Disabled. Stop converting taps.");
CGEventTapEnable(_eventTap, false);
}
}
}
Upvotes: 3