Losing key-up event on OS X - objective-c

I am writing a game-like app for OS X and need to know if the left and right arrows are pressed during the game loop. I also check if the space bar is down. For this I implement
-(void)keyDown:(NSEvent *)event;
-(void)keyUp:(NSEvent *)event
in my view and store the state of each key in some flags. I also check if the command button is pressed using:
-(void)flagsChanged:(NSEvent *)event;
If the window isn't main (another window gets focus) or resigns key (spotlight opens) I reset the flags. I do this by implementing:
-(void)windowDidBecomeKey:(NSNotification *)notification;
-(void)windowDidResignKey:(NSNotification *)notification;
-(void)windowDidResignMain:(NSNotification *)notification;
-(void)windowDidBecomeMain:(NSNotification *)notification;
This works almost all the time. If I press command and then space, spotlight opens and my app resigns key. However, if I hold for example the left arrow key, and first presses and holds space, then command, spotlight won't show up, but I sometimes lose the key-up event for the arrow key when I release them. It doesn't happen every time but (could depend on the release order) but it is easily reproduced in a few tries. So the key gets stuck down until the next press.
Is there another state my app enters that I'm not aware of? If not, this approach seems a bit fragile.
Is there a more robust way of checking that a key is down (that doesn't require installation of a logger tool or enabling accessibility for the app in preferences)?

For this purpose I use NSEvent's addLocalMonitorForEventsMatchingMask:handler:-static method. It works while the app is active. Use addGlobalMonitor... for handling global events but be aware your app may be rejected from the AppStore. Here some code sample.
id monitor=[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:(NSEvent *)^(NSEvent *theEvent){
if (theEvent.keyCode==/*your key code*/) // you should check the key modifiers too
{
// your code here
}
return theEvent; // you may return the event to pass the key to the receiver or nil if no need
}];
// remove monitor
[NSEvent removeMonitor:monitor];

Related

How to detect and implement touch events for single tap on home button?

Not sure if it is possible but is there any way to detect a single touch on the home button. To start with, I would simply like to add an NSLog if the user touches down once on the home button (without actually pressing), but I don't know where I would add this functionality. Does Apple allow you to interact with the home button?
I looked at the app delegate methods, but I can't see how any would work in a single tap (touch) context. Would really appreciate your help.
Does Apple allow you to interact with the home button?
No, not yet. There are no APIs available to explicitly detect home button interactions.
You can rely on the traditional app delegate lifecycle function invocations to perform any logic you wanted to.
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

Detect user activity in Cocoa app (taps, clicks, ...)

For a Mac application, I want to detect user activity in the app, so I can periodically let a web service know that the user is still active on the endpoint.
In Cocoa Touch, I would override sendEvent of UIApplication, but the sendEvent in NSApplication equivalent in Cocoa, doesn't do the same.
Which APIs should I use instead for a Mac application, to detect user activity? Can I perhaps somehow have a global responder hookup from where I can send the pings to my service?
Preferably, I want to listen for actions the user can be expected to perform every 15-30 second, ie. clicks, tabs, typing, switching windows or applications.
You most likely want to create a global event monitor using +[NSEvent addGlobalMonitorForEventsMatchingMask:handler:]. This calls your handler whenever an event whose type matches the passed mask (you should use NSAnyEventMask) is sent to another application. You can observe, but not change, the event here, which suits your usage perfectly. There is one thing to watch out for: the documentation says that you won't receive key events unless your app is trusted for Accessibility.
You can do similarly for events that are routed to your own application with +[NSEvent addLocalMonitorForEventsMatchingMask:handler:].
It's not a notification, but you can query the time since user activity using CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType).
This worked for me:
-(void) addMyApplicationEventsMonitor {
self.localEventsMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskAny handler:^NSEvent * (NSEvent * event) {
// do your stuff here
return event;
}];
}
-(void)dealloc{
//remember add this to remove your monitor
[NSEvent removeMonitor:self.localEventsMonitor];
}
reference here

Capture the space key in a window containing several NSTextFields

I have a large window with a number of NSTextField fields on it, and I would like to intercept the space bar even while any of the text fields have focus, so that pressing space at any time will cause a separate action.
Subclassing NSTextField and overriding -keyUp: allows me to detect the space key, but a blank is still inserted in the text field. -keyDown: does not fire at all.
For other keys like Return and the arrow keys, I could use the control:textView:doCommandBySelector: delegate method, but it does not fire with the space bar.
There's a lot of advice out there for NSTextView, but I have found none for NSTextField.
Have you experimented with adding an event monitor? You create them via the NSEvent class method addLocalMonitorForEventsMatchingMask(_:handler:)
and they give you first dibs on the events you specify. In your case you'd add a monitor for keyDown events, then in the associated block you decide what to do with them. At the end of the block you can return the event just as it arrived, in which case it will behave normally, you can swallow the event (return nil), or you can modify the event by creating a new event object and returning that instead. Here's an example:
// You can add monitors anywhere - I've chosen the AppDelegate for
// this simple example.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^NSEvent * (NSEvent * theEvent) {
if (theEvent.keyCode == 49) {
NSLog(#"swallowing spacebar");
theEvent = nil;
}
return theEvent;
}];
}
Since the behavior you're trying to implement is at the level of the window (or perhaps a view that's an ancestor of all of the text fields), you should consider implementing -performKeyEquivalent: at that level instead of trying to capture the space key in each individual text field.
In your implementation, check the event's modifierFlags to make sure none of Control, Option, or Command are pressed (assuming you only want to handle unmodified space key presses) and if its charactersIgnoringModifiers equals #" ". If so, do what you want and then return YES to indicate you've handled the key event. Otherwise, return NO to allow it to be handled normally.
If you know how to do it for NSTextView, then you know how to do it for NSTextField, because NSTextField uses an NSTextView called the "field editor" for editing. You can get the window's default field editor like so:
NSTextView* fieldEd = [myWindow fieldEditor: YES forObject: nil];
You can also use a window delegate to provide a custom field editor.
So you might want to look into ReactiveCocoa
Go to the video on this page and look at 5:14 to see something similar to what you might like.
ReactiveCocoa can be hooked into all your text fields and then any time a space is hit a signal handler could pick it up. (Probably, however, more than you want to get into).

Alternatives to applicationDidEnterBackground and applicationWillResignActive?

I've got an app that changes the screen brightness with [UIScreen mainScreen].brightness = newBrightness, and I want to restore the brightness to it's previous state when the user finishes using it.
I've tried these two delegate methods:
- (void)applicationDidEnterBackground:(UIApplication *)application
- (void)applicationWillResignActive:(UIApplication *)application
But without much success. I suspect my app must be in the foreground to change the brightness? When I change the brightness in didEnterBackgroundMethod, it has no effect at all. When I use willResignActive it does restore the brightness if I switch to another app, but it has no effect when I press the home button.
Are there any notifications or delegate methods that are executed before the app leaves the foreground?
It seems this happens to others as well: see this S.O. post.
Only way around it seems to be forgetting about setBrightness and simulating it by overlaying a black-semi-transparent on your view...
OLD ANSWER:
willResignActive should also be called when you press the home button before the application enters the background state.
This method is called to let your application know that it is about to move from the active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. An application in the inactive state continues to run but does not dispatch incoming events to responders.
This is also the behavior I witness. So, my guess (but it's just a guess) is that your app is not set to support background, so that when pressing the home button it is terminated. In this case applicationDidEnterBackground is not called.
I would suggest to check the info.plist file in your project for the UIApplicationExitsOnSuspend or "Select Application does not run in background" key.
Furthermore, you could try and put some breakpoints (or NSLog traces) in those functions and check whether they are effectively called as expected.
According to Apple´s DevForum it seems to be a bug that Apple don´t want to fix soon.

NSMenuItem and bare esc key equiv

I'm trying to bind a bare esc key press to an NSMenuItem that toggles full screen (currently just a stub function). Manually selecting the menu item sends the desired IBAction. When I set the NSMenuItem's Key Equiv. in Interface Builder to an arbitrary bare key (eg. w) that key command sends the desired IBAction. When I set the Key Equiv. to command + esc, that key command sends the desired IBAction. But a bare esc key press is ignored.
I'm assuming the esc key is special-cased. Other applications (eg. Bannister's various emulators) are able to achieve this, any idea how?
I'm no Objective-C veteran, so apologies if I'm misunderstanding the question. But have you tried moving up the responder chain and grabbing the keyDown event in NSWindow? Something like:
- (void)keyDown: (NSEvent *) event {
if ([event keyCode] == 53) {
NSLog(#"Esc. pressed");
}
}
Of course, this solution will require that you subclass NSWindow.
The Escape key is tightly bound to the cancelOperation class of NSResponder.
Try to subclass your NSWindow and give it this method:
- (void)cancelOperation:(id)sender {
if (![SomeController doSomeAction]) {
[super cancelOperation:sender];
}
}
Your window will then react to the Escape key and evoke doSomeAction. That would be the method your NSMenuItem would have liked to call, but it refused to do so :)
In the doSomeAction method you should return a Boolean that indicates whether the action did actually do something. If it did, good. If it did not (and returned NO), your NSWindow will pass on the Escape key event to the next responder in the chain.
I like this solution because it gives the user audio feedback about whether his key press actually did something. Because if your method didn't do anything, and no other repsonder did do anything, there will be some "beep" sound.
Note that in an NSView without an attached window, you might have to use [self nextResponder] instead of super.
Please have a look at http://developer.apple.com/mac/library/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGUserInput/XHIGUserInput.html
“… The Esc (Escape) key basically means “let me out of here.” It has specific meanings in certain contexts. The user can press Esc in the following situations: …”
Greetings