PDA

View Full Version : Assertion failure in NSEvent when runModalForWindow




Saphrosit
Apr 30, 2011, 03:12 PM
Hi everybody,

I'm experiencing a strange problem: in my application I wait for keyDown events, using this code


machPort = CGEventTapCreate(kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown),
(CGEventTapCallBack) eventTapFunction,
self );

CFRunLoopSourceRef mKeyboardEventSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, machPort, 0);
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(runLoop, mKeyboardEventSrc, kCFRunLoopDefaultMode);

I need to show a window after the pressure of some keys. I need also the program to stop while the user makes a choice in that window, so I call


[[NSApplication sharedApplication] runModalForWindow:myWindow];

To stop runModal I linked a button in the window to a method that runs:

[myWindow orderOut:self];
[[NSApplication sharedApplication] stopModal];

The problem is that I receive a

Assertion failure in -[NSEvent _initWithCGSEvent:eventRef:], /SourceCache/AppKit/AppKit-1038.35/AppKit.subproj/NSEvent.m:1260
Invalid parameter not satisfying: cgsEvent.type > 0 && cgsEvent.type <= kCGSLastEventType

error when trying to `stopModal`. Can someone explain me why and how to solve it?
Thanks in advance



jiminaus
Apr 30, 2011, 08:26 PM
Don't use the CoreFoundation run loop functions with NSApplication. Use NSRunLoop instead.


@interface MYAppDelegate : NSObject <NSApplicationDelegate> {
@private
CFMachPortRef machPort;
NSMachPort *machPortWrapper;
}
@end

@implementation MYAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
machPort = CGEventTapCreate(kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown),
eventTapFunction,
self);
if (!machPort) {
NSException *e =
[NSException exceptionWithName:@"FailedToCreateKeyDownEventTap"
reason:@"Failed to create an event tap for key down events"
userInfo:[NSDictionary dictionary]];
@throw e;
}

/*
Wrap the Mach port behind machPort into an NSMachPort object.
Note the NSMachPortDeallocateNone option is critical. We don't
want NSMachPort to invalidate or destroy the mach port. That
responsibility lies with machPort.
*/
machPortWrapper =
[[NSMachPort alloc]
initWithMachPort:CFMachPortGetPort(machPort)
options:NSMachPortDeallocateNone];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:machPortWrapper forMode:NSDefaultRunLoopMode];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
if (machPortWrapper) {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop removePort:machPortWrapper forMode:NSDefaultRunLoopMode];
[machPortWrapper release];
machPortWrapper = NULL;
}
if (machPort) {
CFRelease(machPort);
machPort = NULL;
}
}

@end

Saphrosit
May 1, 2011, 05:25 AM
Nothing changed...

I think the problem is due to an illegal CGEventType passed to eventTapFunction:

CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {

NSEvent* sysEvent = [NSEvent eventWithCGEvent:event];
Controller *app = (Controller*) refcon;
#if DEBUG_EVENT
NSLog(@"event received: %@, type = %lu",sysEvent,[sysEvent type]);
#endif
if (type == NX_KEYDOWN && [sysEvent type] == NSKeyDown) {
[app myFunction];
}
return event;
}


I found that, when app crashes, type is -2. I tried to add

if (type != NX_KEYDOWN)
return event;


so I do not receive the assertion failure but the application stops to handle events. Not sure this is the right way to solve the problem...

jiminaus
May 1, 2011, 05:52 AM
I don't know why you're getting that. I don't get that in my test project which shows a modal dialog whenever the user hits Ctrl+Cmd+A.

Note that you should have only been getting key down events in your eventTapFunction so testing for it shouldn't have been necessary.

Just returning the event is fine. The event will continue to be processed as though your tap wasn't in place at all. But are you saying that when you do this, your app stops receiving further events?

Me being me, I'd want to know what this spurious event is, in case it's a symptom of another problem.

EDIT: Actually an event type CGEventType is defined in CGEventType.h as unsigned 32-bit integer. So your -2 is actually 0xFFFFFFFE. In CGEventType.h that's defined as kCGEventTapDisabledByTimeout. There's a comment just about this that says:

/* Out of band event types. These are delivered to the event tap callback
to notify it of unusual conditions that disable the event tap. */

So it looks like your event tap is timing out. That's why your not getting anymore events after this.


EDIT 2: Just to confirm, you using an event tap because you need this functionality to trigger even if your application is not in the foreground right? Otherwise, I think you'd better off overriding sendEvent: in NSApplication.

Saphrosit
May 1, 2011, 06:46 PM
Actually an event type CGEventType is defined in CGEventType.h as unsigned 32-bit integer. So your -2 is actually 0xFFFFFFFE. In CGEventType.h that's defined as kCGEventTapDisabledByTimeout. There's a comment just about this that says:

So it looks like your event tap is timing out. That's why your not getting anymore events after this.

Wonder why I get this event... I thought of a workaround, may this be a solution?

if (type == -2) {
[app reHandleEvent];
return event;
}


-(void)reHandleEvent {

machPort = CGEventTapCreate(kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown),
(CGEventTapCallBack) eventTapFunction,
self );

NSMachPort *machPortWrapper = [[NSMachPort alloc]
initWithMachPort:CFMachPortGetPort(machPort)
options:NSMachPortDeallocateNone];

[[NSRunLoop currentRunLoop] addPort:machPortWrapper forMode:NSDefaultRunLoopMode];

[machPortWrapper release];

}

Not sure if, when event tap get disabled, it gets also dismissed correctly or if it leaks somewhere...

EDIT 2: Just to confirm, you using an event tap because you need this functionality to trigger even if your application is not in the foreground right? Otherwise, I think you'd better off overriding sendEvent: in NSApplication.
Application runs in background :)