Global Hotkey eventNotHandledErr Pass to Event Handler - objective-c

I've pored through most of the posts regarding the creation of Global Hotkeys using Carbon. Is it possible in the hot key handler function to return eventNotHandledErr and have the event passed on to the next handler? Here's some pseudocode:
OSStatus myHotKeyHandler(EventHandlerCallRef nextHandler, EventRef anEvent, void *userData)
{
OSStatus result;
if ( appX is running || appY is running ) {
[(MyAppController *) userData doSomething];
result = noErr;
} else {
result = eventNotHandledErr;
}
return result;
}
In the event that I'm not in application X or Y, I want to be able to pass the event on. Is this possible?
I know I can set up a notification for application switched events, but that requires Enable access for assistive devices to be turned on. If there's a way to pass the event to the next handler, that would be great.

Related

How to detect if the active window has changed on Mac OS X

I'm trying to create a program that tracks the focused window of an application. I've come across a few partial answers, but I don't think it's working.
This is an Objective C++ part of a Qt application, so it might have to do with the RunLoop, but I'm not certain.
void focusObserverCallback( AXObserverRef observer, AXUIElementRef element,
CFStringRef notificationName, void * contextData )
{
// Never executes.
qInfo("Focus changed.");
}
QString updateActiveWindow (void)
{
NSRunningApplication* app = [[NSWorkspace sharedWorkspace]
frontmostApplication];
pid_t pid = [app processIdentifier];
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
if (!appElem) {
qInfo() << "!appElem";
return nullptr;
}
// Get the accessibility element corresponding to the frontmost window
// of the frontmost application.
CFStringRef appName=nullptr;
AXUIElementRef window = nullptr;
if (AXUIElementCopyAttributeValue (appElem, kAXTitleAttribute, ((CFTypeRef*)&appName)) !=kAXErrorSuccess){
if(appElem)
CFRelease(appElem);
}
focusedAppName=toQString(appName);
if (AXUIElementCopyAttributeValue (appElem, kAXFocusedWindowAttribute, (CFTypeRef*)&window) != kAXErrorSuccess) {
if(appElem)
CFRelease(appElem);
}
AXObserverRef observer = nullptr;
if(AXObserverCreate(pid, focusObserverCallback, &observer) !=kAXErrorSuccess){
qInfo("Failed to register observer");
}
AXObserverAddNotification(observer, window, kAXApplicationActivatedNotification, nullptr);
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop],
AXObserverGetRunLoopSource(observer),
kCFRunLoopDefaultMode );
// Finally, get the title of the frontmost window.
CFStringRef title = nullptr;
if(AXUIElementCopyAttributeValue(window, kAXTitleAttribute, (CFTypeRef*)&title)!=kAXErrorSuccess){
qInfo("Problem Copying title");
}
focusedAppTitle= toQString(title);
return toQString(title);
}
What this code does, is it runs once to grab the name and the title of the frontmost application's frontmost window. That part works like a charm.
Problem is, it doesn't register the callback, and it doesn't fire when the window loses focus. I'm completely new to Objective C, so there might be other issues (e.g. Garbage Collection). If you can suggest some changes to those, I'd be doubly obliged.
An application sends kAXApplicationActivatedNotification when the application is activated and becomes the front most application. Observe the application's kAXFocusedWindowChangedNotification to observe focused window changes of the application.

Working with events for processing global hotkeys on Mac OS X

What I want:
I have a program running. When the program is in the tray and out of focus, I want to have a couple of global shortcuts set up to send messages to the program. What do I mean by "send messages"? Well, inside my program, all I want is to have an access to some flag, which would indicate the state the specified key-pair (fired or not). I would poll the flag in the loop and take a decision from there.
What I found:
System-wide hotkey for an application
system wide shortcut for Mac OS X
What I do not understand:
From the links above it looks like I have to pass a handler when registering a hotkey. On a hotkey press, OS calls the handler. It that right? What I do not understand is how in the world the system would call a handler inside my program if my program is running.
I think your main problem is that you do not understand how Mac programming was done in the days before objective C and Cocoa became the norm. Before that, most programming was done in C (or C++) using Carbon. This name was used for a library that was supposed to be a "carbon" copy of a more modern set of APIs during the transition between Mac OS (Classic) and Mac OS X.
Another thing you have to understand is that the registration of hotkeys as given in the examples you give above must be paired by a registration of a Carbon Event handler that will be invoked when you hit that hotkey combination.
That said, I think you should read this legacy document about the Carbon Event Manager:
https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/Carbon_Event_Manager/CarbonEvents.pdf
And pay particular attention to how Carbon Events are supposed to be registered. I particularly use:
OSStatus InstallEventHandler(EventTargetRef target,
EventHandlerUPP handlerProc,
UInt32 numTypes,
const EventTypeSpec* typeList,
void* userData,
EventHandlerRef* handlerRef);
The way I use it is that I made an objective C wrapper in which I basically do the following:
This is a part of a class, let's call it MyOwnEventHandler:
- (EventHandlerRef)handlerRef {
if ( handlerRef == nil ) {
NSAssert( InstallEventHandler(GetApplicationEventTarget(),
&EventHandler,
0,
nil,
self,
&handlerRef ) == noErr, #"handlerRef" );
}
return handlerRef;
}
// this is a Carbon callback that the OS invokes when your app gets
// a hotkey event that must be handled by you
OSStatus EventHandler( EventHandlerCallRef inHandler,
EventRef inEvent,
void* inUserData )
{
EventHotKeyID hotKeyID;
GetEventParameter( inEvent,
kEventParamDirectObject,
typeEventHotKeyID,
nil,
sizeof(EventHotKeyID),
nil,
&hotKeyID );
// use this to get your MyOwnEventHandler object back if need be
// the reason why we get this is because we passed self in InstallEventHandler
// in Carbon event callbacks you cannot access self directly
// because this is a C callback, not an objective C method
MyOwnEventHandler* handler = (MyOwnEventHandler *)inUserData;
// handle the hotkey here - I usually store the id of the EventHotKeyID struct
// in a objective C hotkey object to look up events in an array of registered hotkeys
return eventNotHandledErr; // return this error for other handlers to handle this event as well
}
// call this objective C wrapper method to register your Carbon Event handler
- (void)registerForGettingHotKeyEvents {
const EventTypeSpec kHotKeysEvent[] = {{ kEventClassKeyboard, kEventHotKeyPressed }};
AddEventTypesToHandler( [self handlerRef], GetEventTypeCount(kHotKeysEvent), kHotKeysEvent );
}
// call this objective C wrapper method to unregister your Carbon Event handler
- (void)unregisterFromGettingHotKeyEvents {
const EventTypeSpec kHotKeysEvent[] = {{ kEventClassKeyboard, kEventHotKeyPressed }};
RemoveEventTypesFromHandler( [self handlerRef], GetEventTypeCount(kHotKeysEvent), kHotKeysEvent );
}
I hope this helps. If you are stuck somewhere let me know and I will try to help you.

Application randomly stops receiving key presses (CGEventTaps)

So, I've wasted a bunch of time creating this really cool keyboard macro application. It works great, the only problem is that after a couple minutes, it just stops working. It ceases to get called when I press a key.
I haven't been able to lock it down, but it always takes at least 30 seconds to happen. Usually it won't happen for several minutes. I'll have intercepted and sent out many events by then. The application is still running when it happens.
Here's an example of what I'm doing to listen
-(void)listen {
CFMachPortRef downEventTap = CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionDefault,CGEventMaskBit(kCGEventKeyDown),&onKeyDown,self);
downSourceRef = CFMachPortCreateRunLoopSource(NULL, downEventTap, 0);
CFRelease(downEventTap);
CFRunLoopAddSource(CFRunLoopGetCurrent(), downSourceRef, kCFRunLoopDefaultMode);
CFRelease(downSourceRef)
}
And the handler -
CGEventRef onKeyDown(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
NSLog(#"DOWN (%i)", CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
// When it matches, I return CGEventCreate(NULL) to stop the event
return event;
}
Also note that when I intercept an event (and return that CGEventCreate(NULL)), I usually issue one or more key presses of my own, using the following code. Note that KeyCmd, etc, are just shortcuts to the normal constants.
- (void)sendKey:(KeyCode)code cmd:(BOOL)cmd alt:(BOOL)alt ctl:(BOOL)ctl shift:(BOOL)shift {
CGEventFlags flags = 0;
if (cmd) flags = flags | KeyCmd;
if (alt) flags = flags | KeyAlt;
if (ctl) flags = flags | KeyCtl;
if (shift) flags = flags | KeyShift;
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef keyDownPress = CGEventCreateKeyboardEvent(source, (CGKeyCode)code, YES);
CGEventSetFlags(keyDownPress, flags);
CGEventPost(kCGAnnotatedSessionEventTap, keyDownPress);
CFRelease(keyDownPress);
CFRelease(source);
}
Thanks!
There is a bug in Snow Leopard, I think, that stops your listener if something times out.
In your keyDown handler, check for the following type, and just re-enable the listener.
if (type == kCGEventTapDisabledByTimeout) {
NSLog(#"Event Taps Disabled! Re-enabling");
CGEventTapEnable(eventTap, true);
return event;
}

Mouse tracking daemon

I need to write something using Cocoa to surface raw mouse movement data. Optimally, the app would just be a little daemon that would run, passing the data to a socket server which another application could tap into to gain access to the events.
Can anyone point me in the right direction with regard to approach and tools? I am not even sure where to begin with this right now.
The other simple way to do this is to add a global event monitor (10.6 only, however):
id eventHandler = [NSEvent addGlobalMonitorForEventsMatchingMask:NSMouseMovedMask handler:^(NSEvent * mouseEvent) {
NSLog(#"Mouse moved: %#", NSStringFromPoint([mouseEvent locationInWindow]));
}];
Then when you're done tracking, you do:
[NSEvent removeMonitor:eventHandler];
Write an EventTap. Documentation can be found here.
In MacOS X every event (e.g. every keyboard key pressed, every mouse key pressed or mouse movement) creates an event that travels the following path:
Driver (Kernel) -> Window Server (privileged) -> User (Login) Session -> Active Application
Everywhere where I wrote an arrow (->) an EventTap can be placed to either only look at the event (a listen only EventTap) or to either modify or drop the event (an event filtering EventTap). Please note that to catch an event between Driver and WindowServer, your daemon must run with root privileges.
Here is some sample code:
// Compile with:
// gcc -framework ApplicationServices -o MouseWatcher MouseWatcher.c
//
// Start with:
// ./MouseWatcher
//
// Terminate by hitting CTRL+C
#include <ApplicationServices/ApplicationServices.h>
static CGEventRef myEventTapCallback (
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void * refcon
) {
CGPoint mouseLocation;
// If we would get different kind of events, we can distinguish them
// by the variable "type", but we know we only get mouse moved events
mouseLocation = CGEventGetLocation(event);
printf(
"Mouse is at x/y: %ld/%ld\n",
(long)mouseLocation.x,
(long)mouseLocation.y
);
// Pass on the event, we must not modify it anyway, we are a listener
return event;
}
int main (
int argc,
char ** argv
) {
CGEventMask emask;
CFMachPortRef myEventTap;
CFRunLoopSourceRef eventTapRLSrc;
// We only want one kind of event at the moment: The mouse has moved
emask = CGEventMaskBit(kCGEventMouseMoved);
// Create the Tap
myEventTap = CGEventTapCreate (
kCGSessionEventTap, // Catch all events for current user session
kCGTailAppendEventTap, // Append to end of EventTap list
kCGEventTapOptionListenOnly, // We only listen, we don't modify
emask,
&myEventTapCallback,
NULL // We need no extra data in the callback
);
// Create a RunLoop Source for it
eventTapRLSrc = CFMachPortCreateRunLoopSource(
kCFAllocatorDefault,
myEventTap,
0
);
// Add the source to the current RunLoop
CFRunLoopAddSource(
CFRunLoopGetCurrent(),
eventTapRLSrc,
kCFRunLoopDefaultMode
);
// Keep the RunLoop running forever
CFRunLoopRun();
// Not reached (RunLoop above never stops running)
return 0;
}
The answer from Dave is the nicer Cocoa way of doing pretty much the same thing; basically Cocoa does the same as I do in my sample above behind the scenes, just wrapped into a static method. The code of Dave works only on 10.6, though, the above works in 10.4, 10.5 and 10.6.

How can my app detect a change to another app's window?

In Cocoa on the Mac, I'd like to detect when a window belonging to another app is moved, resized, or repainted. How can I do this?
You would need to use the Accessibility APIs, which are plain-C, located inside the ApplicationServices framework. For instance:
First you create an application object:
AXUIElementRef app = AXUIElementCreateApplication( targetApplicationProcessID );
Then you get the window from this. You can request the window list and enumerate, or you can get the frontmost window (look in AXAttributeConstants.h for all the attribute names you'd use).
AXUIElementRef frontWindow = NULL;
AXError err = AXUIElementCopyAttributeValue( app, kAXMainWindowAttribute, &frontWindow );
if ( err != kAXErrorSuccess )
// it failed -- maybe no main window (yet)
Now you can request notification via a C callback function when a property of this window changes. This is a four-step process:
First you need a callback function to receive the notifications:
void MyAXObserverCallback( AXObserverRef observer, AXUIElementRef element,
CFStringRef notificationName, void * contextData )
{
// handle the notification appropriately
// when using ObjC, your contextData might be an object, therefore you can do:
SomeObject * obj = (SomeObject *) contextData;
// now do something with obj
}
Next you need an AXObserverRef, which manages the callback routine. This requires the same process ID you used to create the 'app' element above:
AXObserverRef observer = NULL;
AXError err = AXObserverCreate( applicationProcessID, MyObserverCallback, &observer );
if ( err != kAXErrorSuccess )
// handle the error
Having got your observer, the next step is to request notification of certain things. See AXNotificationConstants.h for the full list, but for window changes you'll probably only need these two:
AXObserverAddNotification( observer, frontWindow, kAXMovedNotification, self );
AXObserverAddNotification( observer, frontWindow, kAXResizedNotification, self );
Note that the last parameter there is passing an assumed 'self' object as the contextData. This is not retained, so it's important to call AXObserverRemoveNotification when this object goes away.
Having got your observer and added notification requests, you now want to attach the observer to your runloop so you can be sent these notifications in an asynchronous manner (or indeed at all):
CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop],
AXObserverGetRunLoopSource(observer),
kCFRunLoopDefaultMode );
AXUIElementRefs are CoreFoundation-style objects, so you need to use CFRelease() to dispose of them cleanly. For cleanliness here, for example, you would use CFRelease(app) once you've obtained the frontWindow element, since you'll no longer need the app.
A note about Garbage-Collection: To keep an AXUIElementRef as a member variable, declare it like so:
__strong AXUIElementRef frontWindow;
This instructs the garbage collector to keep track of this reference to it. When assigning it, for compatibility with GC and non-GC, use this:
frontWindow = (AXUIElementRef) CFMakeCollectable( CFRetain(theElement) );
Further research turned up "Quartz Display Services"
The interesting function for my needs is CGRegisterScreenRefreshCallback.