Make OSX application respond to first mouse click when not focused - cocoa

Normal OSX applications eat the first mouse click when not focused to first focus the application. Then future clicks are processed by the application. iTunes play/pause button and Finder behave differently, the first click is acted on even when not focused. I am looking for a way to force an existing application (Remote Desktop Connection.app) to act on the first click and not just focus.

Check NSView's acceptsFirstMouse, it may be what you're looking for.
acceptsFirstMouse:
Overridden by subclasses to return YES if the receiver should be sent a mouseDown: message for an initial mouse-down event, NO if not.
(BOOL)acceptsFirstMouse:(NSEvent *)theEvent
Parameters
theEvent
The initial mouse-down event, which must be over the receiver in its window.
Discussion
The receiver can either return a value unconditionally or use the location of theEvent to determine whether or not it wants the event. The default implementation ignores theEvent and returns NO.
Override this method in a subclass to allow instances to respond to click-through. This allows the user to click on a view in an inactive window, activating the view with one click, instead of clicking first to make the window active and then clicking the view. Most view objects refuse a click-through attempt, so the event simply activates the window. Many control objects, however, such as instances of NSButton and NSSlider, do accept them, so the user can immediately manipulate the control without having to release the mouse button.

Responding to the first mouse click when not focused is called 'click through'. And its worth is debated heatedly, for instance here and here.

// Assuming you have 1 view controller that's always hanging around. Over ride the loadview. N.B. this won't work pre-yosemite.
- (void)loadView {
NSLog(#"loadView");
self.view = [[NSView alloc] initWithFrame:
[[app.window contentView] frame]];
[self.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways);
trackingArea0 = [[NSTrackingArea alloc] initWithRect:self.view.bounds
options:opts
owner:self
userInfo:nil];
[self.view addTrackingArea:trackingArea0];
}
- (void)mouseEntered:(NSEvent *)theEvent {
NSLog(#"entered");
if ([[NSApplication sharedApplication] respondsToSelector:#selector(activateIgnoringOtherApps:)]) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
}

Related

Why is the NSStatusItem displaying multiple times?

A NSStatusItem has a NSMenu attached, and one of the buttons of the NSMenu opens a NSWindow. Whenever one of these buttons is clicked, the window opens as expected and works properly, but another display of the NSStatusItem is opened.
The NSStatusItem is a clock, so I can see that it is updating correctly. However, the cloned NSStatusItem doesn't have its own menu. If I push the button that makes the window more times, more cloned versions of the NSStatusItem pop up.
Everything works fine except for this.
That's not a whole lot of information to go off of, but there's nothing else I can think of that could potentially help you. I would be happy to provide more information or try something.
EDIT: Every time the button is clicked, awakeFromNib is somehow called, which is why another half-working NSStatusItem happens.
EDIT: Temporary workaround is to put the awakeFromNib method in a dispatch_once.
EDIT: Added method that is triggered when button is clicked, as suggested by #zpasternack
- (IBAction)preferences:(id)sender {
self.windowController = [[NSWindowController alloc] initWithWindowNibName:#"PreferencesWindow"];
[[self windowController] showWindow:self];
}
Is the NSStatusItem contained in the PreferencesWindow nib? That might explain it, since you're loading the nib each time the button is clicked.
Also, is there a reason you need to recreate that window each time the button is clicked? Maybe you could only do it the first time?
- (IBAction)preferences:(id)sender {
if( self.windowController == nil ) {
self.windowController = [[NSWindowController alloc] initWithWindowNibName:#"PreferencesWindow"];
}
[[self windowController] showWindow:self];
}

Is it possible to force control to an NSWindowController Subclass?

I am creating a cocoa application and I am loading a preference window with a NSWindowController.
When the user selected the prefs button and the preference window opens
I want the application to force the user to finish what they are doing with the newly opened window controller before going back to the application in the background.
Is there anyway to block out whats happening in the background and force the user to complete there interactions with the foreground?
Thakns
Perhaps this works? Create a subclass of the HUD window and implement
- (BOOL)resignFirstResponder {
if (userMayLeave) return YES;
return NO;
}
You can use the following method to achieve this and display your window as a modal:
[[NSApplication sharedApplication] runModalForWindow:aWindow];
Then you need to use one of the following methods in your modal window in order to dismiss it:
[[NSApplication sharedApplication] stopModal];
[[NSApplication sharedApplication] abortModal];
[[NSApplication sharedApplication] stopModalWithCode:anInteger];

NSEvent - NSLeftMouseDown

I'm trying to trigger basic functions using NSEvent and mouse clicks. For example close the window when pressing left mouse button. What else do I need in this method?
Thanks.
- (void)mouseDown:(NSEvent *)theEvent {
if ([theEvent type] == NSLeftMouseDown){
[window orderOut:nil];
}
}
Assuming this is in a custom view and the window outlet is connected (or you fill in that variable with [self window] when the view is added to a superview), that should be all you need. I would suggest handling mouseUp: instead of mouseDown:, though, to give the user the opportunity to back out by moving the mouse outside of your view.
You might also consider using an NSButton instead of (or inside of) a custom view. You could hook it up directly to the window's performClose: or orderOut: action.

How to use NSTrackingArea

I'm new to Mac programming and I want to fire events when the cursor enters or exits the main window. I read something about NSTrackingArea but I don't understand exactly what to do.
Apple provides documentation and examples for NSTrackingAreas.
The easiest way to track when a mouse enters or exits a window is by setting a tracking area in the window's contentView. This will however not track the window's toolbar
Just as a quick example, in the custom content view's code:
- (void) viewWillMoveToWindow:(NSWindow *)newWindow {
// Setup a new tracking area when the view is added to the window.
NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options: (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
}
- (void) mouseEntered:(NSEvent*)theEvent {
// Mouse entered tracking area.
}
- (void) mouseExited:(NSEvent*)theEvent {
// Mouse exited tracking area.
}
You should also implement NSView's updateTrackingAreas method and test the event's tracking area to make sure it is the right one.
Answer by Matt Bierner really helped me out; needing to implement -viewWillMoveToWindow: method.
I would also add that you will also need to implement this if you want to handle tracking areas when the view is resized:
- (void)updateTrackingAreas
{
// remove out-of-date tracking areas and add recomputed ones..
}
in the custom sub-class, to handle the view's changing geometry; this'll be invoked for you automatically.

LSUIElement behaves inconsistently with activateIgnoringOtherApps

Specifically, it behaves inconsistently regarding text field focus.
I have an LSUIElement popping up a status menu. Within that menu there is a view containing a text field. The text field needs to be selectable -- not necessarily selected by default, but whichever.
When the status item is clicked, it triggers
[NSApp activateIgnoringOtherApps:YES];
And it works, about half the time.* The other half the status menu seems to consider itself "in the background" and won't let me put focus on the text field even by clicking on it. (I know the status item click-trigger is firing b/c there's an NSLog on it.)
Is this a bug in the way Apple handles these status items, or am I mishandling activateIgnoringOtherApps?
*In fact, it seems to fail only the first time after another app is activated. After that it works fine.
The complete snippet:
-(void)statusItemClicked:(id)sender {
//show the popup menu associated with the status item.
[statusItem popUpStatusItemMenu:statusMenu];
//activate *after* showing the popup menu to obtain focus for the text field.
[NSApp activateIgnoringOtherApps:YES];
}
Finally came up with a workaround for this.
Instead of popping the menu in your click handler, activate the app then schedule an NSTimer with no delay that pops the menu:
-(void)pop:(NSTimer *)timer {
[statusItem popUpStatusItemMenu:theMenu];
}
-(void)statusItemClicked:sender {
[NSApp activateIgnoringOtherApps:YES];
[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:#selector(pop:) userInfo:nil repeats:NO];
}
pop: is called on the next frame so the delay is imperceptible but long enough for activateIgnoringOtherApps: to do whatever was preventing it from working as expected when popping the menu in the same frame.
I know from experience that you have to call activateIgnoringOtherApps: after you've popped up the menu that contains your text field. So you would need to do it in this order:
- (void)statusItemClicked:sender {
[statusItem popUpStatusItemMenu:theMenu];
[NSApp activateIgnoringOtherApps:YES]; // FYI, NSApp is shorthand for [NSApplication sharedApplication]
}
Based on what you've said, it sounds like your application is activating too late, so that it's not getting activated the first time you click on the item, but it is already activated on subsequent clicks.

Resources