Application randomly stops receiving key presses (CGEventTaps) - objective-c

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

Related

(Mac) creating keyboard events causes memory leaks

My app's memory usage goes up permanently, each time I create a keyboard event using Quartz Event Services.
The following is the problematic code inside of an infinite loop:
int keyCode = 0;
BOOL keyDownBool = FALSE;
while (TRUE) {
/* creating a keyboard event */
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
CGEventRef keyboardEvent =
CGEventCreateKeyboardEvent(source, (CGKeyCode)keyCode, keyDownBool);
CFRelease(source);
CFRelease(keyboardEvent);
}
Instruments.app says that there are no memory leaks...
What is the problem here?
Thank you for your help!
Okay so the solution is pretty simple. You only need to create your CGEventSourceRef once, and then you can reuse it each time you want to post an event. Creating your CGEventSourceRefover and over again causes the "leaks" to happen.
The proper code looks like this:
int keyCode = 0;
BOOL keyDownBool = FALSE;
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
while (TRUE) {
/* creating a keyboard event */
CGEventRef keyEvent =
CGEventCreateKeyboardEvent(source, (CGKeyCode)keyCode, keyDownBool);
CFRelease(keyEvent);
}
Thanks to #Willeke for the suggestion.

Remove pause after first keypress with raw input

I'm using RAWINPUT to handle the input in a C++ application. However, after the a key on the keyboard is pressed and the corresponding WM_ message has been sent, keyboard messages seems to pause for a half second before resuming without any pause between subsequent messages.
How can I change the repeat delay?
Here is the code is am using:
I forward the input from my message proc:
case WM_INPUT:
if (!m_inputHandler.HandleInput(msg, wParam, lParam))
return 0;
char buffer[sizeof(RAWINPUT)] = {};
UINT size = sizeof(RAWINPUT);
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, buffer, &size, sizeof(RAWINPUTHEADER));
RAWINPUT *raw = (RAWINPUT*)buffer;
if (raw->header.dwType == RIM_TYPEMOUSE)
{
//
}
else if (raw->header.dwType == RIM_TYPEKEYBOARD)
{
RAWKEYBOARD const &rawKB = raw->data.keyboard;
UINT virtualKey = rawKB.VKey;
UINT scanCode = rawKB.MakeCode;
UINT flags = rawKB.Flags;
// handle keyboard input and dispatch to listeners
}
The issue is not with the handling of the keyboard data but lies in the fact that the WM_INPUT message gets sent once, then pauses, and then gets sent regularly after the pause.

Monitor keyboard events by adding observer in RunLoop

My Run Loop Observer is written as follows:
void observerCallback(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void* info)
{
println("%u", activity);
}
//-----------------------------
void InstallObserver()
{
CFRunLoopObserverRef myObserver = NULL;
int myActivities = kCFRunLoopEntry;
myObserver = CFRunLoopObserverCreate(NULL, myActivities, YES,
/* repeat */ 0, &observerCallback, NULL);
if (myObserver)
{
CFRunLoopAddObserver(CFRunLoopGetCurrent(), myObserver,
kCFRunLoopCommonModes);
}
}
Every time I press any key in my Application the observerCallback is called 4 times.
The question is:
How can I obtain key code inside observerCallback?
Thanks.
Based on the comments on your question, you want a local event monitor, AKA:
+[NSEvent addLocalMonitorForEventsMatchingMask:handler:]
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/Reference/Reference.html#//apple_ref/occ/clm/NSEvent/addLocalMonitorForEventsMatchingMask:handler:

Global Hotkey eventNotHandledErr Pass to Event Handler

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.

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.