Macos Application hangs when using NSDistributedNotificationCenter and CFRunLoopAddSource - macos

I have MacOS application built using Qt. There I have created NSDistributedNotificationCenter to be notified when accessibility settings change (observing "com.apple.accessibility.api" ). Also I have CFRunLoopAddSource to monitor key pressed events.
However when I run the program and I change accessibility application hangs and I cannot run it normally.
Could someone help to see why that is happening?
This is the code:
Here I am creating observer:
Creation/deletion is controlled by button click
- (void)createObserver
{
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:#selector(didToggleAccessStatus:)
name:#"com.apple.accessibility.api"
object:nil
suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
}
And this is how I add key event logger:
// Create an event tap to retrieve keypresses.
CGEventMask eventMask = (CGEventMaskBit(kCGEventKeyDown) |
CGEventMaskBit(kCGEventFlagsChanged) |
CGEventMaskBit(kCGEventLeftMouseDown) |
CGEventMaskBit(kCGEventRightMouseDown) |
CGEventMaskBit(kCGEventMouseMoved) |
CGEventMaskBit(kCGEventScrollWheel));
//| CGEventMaskBit(kCGEventLeftMouseDragged)
//| CGEventMaskBit(kCGEventRightMouseDragged)
//| CGEventMaskBit(kCGEventOtherMouseDragged););
CFMachPortRef m_eventTap = nullptr;
CFRunLoopSourceRef m_runLoopSource = nullptr;
m_eventTap = CGEventTapCreate(
kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
eventMask, myCGEventCallback, nullptr);
if (m_eventTap != Q_NULLPTR) {
NSLog(#"CGEventTap created");
// Create a run loop source and add enable the event tap.
m_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent()/*CFRunLoopGetMain()*/, m_runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(m_eventTap, true);
//CFRunLoopRun();
}
else {
m_runLoopSource = Q_NULLPTR;
NSLog(#"Error creating CGEventTap");
}
Any ideas please?
Thanks in advance and regards

Related

Remap `fn` to left mouse button on OSX

I get bad tendinitis from clicking the mouse all day.
In the past I used Karabiner to remap the fn key to simulate a left mouse button. However it doesn't work with Sierra.
I tried to accomplish this in Cocoa, and it correctly performs mouse-down/up when I press and release fn.
However it doesn't handle double-click / triple-click.
Also when dragging, (e.g. dragging a window, or selecting some text) nothing happens visually until I key-up, whereupon it completes.
How can I adapt my code to implement this?
First I create an event tap:
- (BOOL)tapEvents
{
_modifiers = [NSEvent modifierFlags];
if ( ! _eventTap ) {
NSLog( #"Initializing an event tap." );
// kCGHeadInsertEventTap -- new event tap should be inserted before
// any pre-existing event taps at the same location,
_eventTap = CGEventTapCreate( kCGHIDEventTap, // kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit( kCGEventKeyDown )
| CGEventMaskBit( kCGEventFlagsChanged )
| CGEventMaskBit( NSSystemDefined )
,
(CGEventTapCallBack)_tapCallback,
(__bridge void *)(self));
if ( ! _eventTap ) {
NSLog(#"unable to create event tap. must run as root or "
"add privlidges for assistive devices to this app.");
return NO;
}
}
CGEventTapEnable( _eventTap, YES );
return [self isTapActive];
}
Now I implement the callback:
CGEventRef _tapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
Intercept* listener
)
{
//Do not make the NSEvent here.
//NSEvent will throw an exception if we try to make an event from the tap timout type
#autoreleasepool {
if( type == kCGEventTapDisabledByTimeout ) {
NSLog(#"event tap has timed out, re-enabling tap");
[listener tapEvents];
return nil;
}
if( type != kCGEventTapDisabledByUserInput ) {
return [listener processEvent:event];
}
}
return event;
}
Finally I implement a processEvent that will pass through any event apart from fn key up/down, which will get converted to left mouse up/down:
- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
//NSLog( #"- - - - - - -" );
NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
//NSLog(#"%d,%d", event.data1, event.data2);
//NSEventType type = [event type];
NSUInteger m = event.modifierFlags &
( NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSControlKeyMask | NSAlphaShiftKeyMask | NSFunctionKeyMask );
NSUInteger flags_changed = _modifiers ^ m;
_modifiers = m;
switch( event.type ) {
case NSFlagsChanged:
{
assert(flags_changed);
//NSLog(#"NSFlagsChanged: %d, event.modifierFlags: %lx", event.keyCode, event.modifierFlags);
if( flags_changed & NSFunctionKeyMask ) {
bool isDown = _modifiers & NSFunctionKeyMask;
CGEventType evType = isDown ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
CGPoint pt = [NSEvent mouseLocation];
CGPoint mousePoint = CGPointMake(pt.x, [NSScreen mainScreen].frame.size.height - pt.y);
CGEventRef theEvent = CGEventCreateMouseEvent(NULL, evType, mousePoint, kCGMouseButtonLeft);
CGEventSetType(theEvent, evType);
CGEventPost(kCGHIDEventTap, theEvent);
CFRelease(theEvent);
//return theEvent;
}
break;
}
}
_lastEvent = [event CGEvent];
CFRetain(_lastEvent); // must retain the event. will be released by the system
return _lastEvent;
}
EDIT: Performing a double click using CGEventCreateMouseEvent()
EDIT: OSX assign left mouse click to a keyboard key
Karabiner now works on macOS 10.12 and later.

how to disable keys in OS X?

I've an application that needs disable some keys while the application in running (i.e: A, option, command, shift).
how can I do that?
the language or method used does not matter.
You can inspect, modify, and block keyboard events with a CGEventTap.
The user will need to grant your app assistive privileges via the Security pane of System Preferences for events to be disabled before posting.
Something like this:
- (void)setKeyBlocker {
// You should filter this better than kCGEventMaskForAllEvents, depending on your needs.
CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, cgEventCallback, NULL);
CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CGEventTapEnable(eventTap, true);
CFRelease(eventTap);
CFRelease(runLoopSource);
}
CGEventRef callback(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *refcon) {
NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
if (event.type == kCGEventKeyDown) {
if ([event.characters isEqualToString:#"a"]) {
// Kill event
return NULL;
}
}
return cgEvent;
}

Stop Intercepting Keyboard Input While App Running - CGEventTap

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.
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);
}
}
}

How to trap global keydown/keyup events in cocoa

I want to trap, modify and divert all the keydown/keyup events in the system within my cocoa app. I know about CGEventTapCreate but, didn't found any working code from net.
Thanks
Found Solution:
self.machPortRef = CGEventTapCreate(kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown),
(CGEventTapCallBack)eventTapFunction,
self);
if (self.machPortRef == NULL)
{
printf("CGEventTapCreate failed!\n");
} else {
self.eventSrc = CFMachPortCreateRunLoopSource(NULL, self.machPortRef, 0);
if ( self.eventSrc == NULL )
{
printf( "No event run loop src?\n" );
}else {
CFRunLoopRef runLoop = CFRunLoopGetCurrent(); //GetCFRunLoopFromEventLoop(GetMainEventLoop ());
// Get the CFRunLoop primitive for the Carbon Main Event Loop, and add the new event souce
CFRunLoopAddSource(runLoop, self.eventSrc, kCFRunLoopDefaultMode);
}
}
Properties :
CFMachPortRef machPortRef;
CFRunLoopSourceRef eventSrc;
Event Handler:
CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
//printf("eventTap triggered\n");
return event;
}

How to Capture / Post system-wide Keyboard / Mouse events under Mac OS X?

For a scripting utility I need to be able to record a series of keyboard and mouse events that occur when an application has focus. The second part is being able to later send those events to the active window.
I do not need to worry about menus or tracking the identifier of which window receives input.
I know how to do this under Windows but have no idea about Mac OS X.
The first thing i will tell you is that you CAN'T do this without the user enabling support for assitive devices in the accessability control panel. It's some kind of security built into OSX.
Here is a code snippit I am using in one of my applications to do this:
//this method calls a carbon method to attach a global event handler
- (void)attachEventHandlers
{
//create our event type spec for the keyup
EventTypeSpec eventType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventRawKeyUp;
//create a callback for our event to fire in
EventHandlerUPP handlerFunction = NewEventHandlerUPP(globalKeyPress);
//install the event handler
OSStatus err = InstallEventHandler(GetEventMonitorTarget(), handlerFunction, 1, &eventType, self, NULL);
//error checking
if( err )
{
//TODO: need an alert sheet here
NSLog(#"Error registering keyboard handler...%d", err);
}
//create our event type spec for the mouse events
EventTypeSpec eventTypeM;
eventTypeM.eventClass = kEventClassMouse;
eventTypeM.eventKind = kEventMouseUp;
//create a callback for our event to fire in
EventHandlerUPP handlerFunctionM = NewEventHandlerUPP(globalMousePress);
//install the event handler
OSStatus errM = InstallEventHandler(GetEventMonitorTarget(), handlerFunctionM, 1, &eventTypeM, self, NULL);
//error checking
if( errM )
{
//TODO: need an alert sheet here
NSLog(#"Error registering mouse handler...%d", err);
}
}
Here is an example of the callback method i am using:
OSStatus globalKeyPress(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData)
{
NSEvent *anEvent = [NSEvent eventWithEventRef:theEvent];
NSEventType type = [anEvent type];
WarStrokerApplication *application = (WarStrokerApplication*)userData;
//is it a key up event?
if( type == NSKeyUp)
{
//which key is it?
switch( [anEvent keyCode] )
{
case NUMERIC_KEYPAD_PLUS:
//this is the character we are using for our toggle
//call the handler function
[application toggleKeyPressed];
break;
//Comment this line back in to figure out the keykode for a particular character
default:
NSLog(#"Keypressed: %d, **%#**", [anEvent keyCode], [anEvent characters]);
break;
}
}
return CallNextEventHandler(nextHandler, theEvent);
}
For the latter part, posting events, use the CGEvent methods provided in ApplicationServices/ApplicationServices.h
Here's an example function to move the mouse to a specified absolute location:
#include <ApplicationServices/ApplicationServices.h>
int to(int x, int y)
{
CGPoint newloc;
CGEventRef eventRef;
newloc.x = x;
newloc.y = y;
eventRef = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved, newloc,
kCGMouseButtonCenter);
//Apparently, a bug in xcode requires this next line
CGEventSetType(eventRef, kCGEventMouseMoved);
CGEventPost(kCGSessionEventTap, eventRef);
CFRelease(eventRef);
return 0;
}
For tapping mouse events, see Link
I haven't checked this under 10.5 Leopard but on 10.4 it works.

Resources