Hide Dock icon without closing window - macos

I am creating an app in which I want to give the user the ability to show or hide the dock icon at run time. I have a preferences window with a checkbox, setting a user default value, which fires the following code using KVO:
if (!hideDockIcon) {
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
} else {
TransformProcessType(&psn, kProcessTransformToUIElementApplication);
}
This works, but when hiding, the preferences window is closed directly (which makes sense as it is now a background app). However, I noticed that MS's SkyDrive client manages to hide the icon while keeping the Preferences window open. I have not been able to find out how one would do that, anybody has an idea?
I also tried using [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular] and NSApplicationActivationPolicyAccessory/NSApplicationActivationPolicyProhibited but that doesn't work for me; Accessory doesn't hide the dock icon, Prohibited closes the window as well and seems to make [NSApp activateIgnoringOtherApps:YES] being ignored.

I stumbled upon this thread where the following is suggested to prevent a window from being hidden:
[window setCanHide:NO];
This just covers hiding. If your window gets closed, you might try to use the window delegate?
There's a call that let's you prevent the window from being closed
- (BOOL)windowShouldClose:(id)sender

I solved this problem by not activating the app in the same run loop turn:
dispatch_async(dispatch_get_main_queue(), ^{
[NSApp activateIgnoringOtherApps:YES];
});
Swift:
dispatch_async(dispatch_get_main_queue()) {
NSApp.activateIgnoringOtherApps(true)
}
I'm calling dispatch_async to schedule the block for execution in one of the next run loop turns a few nanoseconds later. This gives the process the chance to finish hiding itself.

Related

Cannot close Window after opening another window modally

I have a problem.
In my mac osx app I have a mainwindow. This window is opening initial. I am working with storyboard.
But there are moments when the user needs to login again into the app. If this is the case (for example when the session ends) I open a new small window modally with this code:
private func openLogin() {
loginController = self.storyboard?.instantiateController(withIdentifier: "LoginController") as? LoginWindowController
guard let window = loginController?.window else {
return false
}
NSApp.runModal(for: window)
}
after I once opened this window and close it again I can never close the main window. If the login window never opened, there is no problem and I can close the MainWindow. But if I opened the loginwindow once, I cannot close the mainwindow. I can click on the close button but it does nothing.
And, I cannot assure this but, I think that I cannot close any window after that.
Do you have any idea?
Thank you for your Help!
Artur
Ok, I have found the issue...
I called the code function openLogin() inside of the windowDidLoad() function of the NSWindowController. The problem is that the applicationDidFinishLaunching of the appDelegate will first be called after the windowDidLoad function finished, which is logically correct.
I stopped the mainThread by running a window modally. Somehow the framework doesn´t like this before the app has completed the launch process.
I solved this by using the NSNotificationCenter and observe the notification name NSApplicationDidFinishLaunching. On that I run the openLogin() code.

Pushing a window into focus

I'm developing a small menubar application and I want to display the settings window when the corresponding NSMenuItem is pressed.
I currently have the following IBAction assigned to the menu item:
#IBAction func settingsButtonPressed(sender: NSMenuItem) {
settingsView.makeKeyAndOrderFront(sender)
}
This displays the window, but doesn't push it into focus, so it's displayed behind the currently active window, which is not the behaviour I'm looking for.
I had a suspicion that this might have been due to the fact that the Application is agent target property is set to YES, but this actually has no effect on the outcome.
Could there be anything to be done with the window in the XIB file?
Probably your app is not the active app. It should work to call [NSApp activateIgnoringOtherApps:YES] in addition to making the window key and ordering it front.

Open Spotify main window from another app when closed

Iam working on an application that tries to extend some Spotify functionality (not another client). I would like to show Spotify's main window when my icon is clicked on the dock - even if the main window in Spotify is closed.
This is my code now:
- (void) applicationDidBecomeActive:(NSNotification *)notification {
// Causes Spotify to hit the front when selecting it!
[[[NSRunningApplication
runningApplicationsWithBundleIdentifier:#"com.spotify.client"] lastObject]
activateWithOptions:NSApplicationActivateAllWindows];
}
It works when the window is open but not in focus (background), but not when I close the Spotify main window (which I people tend to do). Is there any way to re-open this window if it's closed from another application?
The Spotify icon can do this (obviously). Try to hit the close button (the red x) and press the icon (it will reappear). Is that possible from another app?
The Dock sends the reopen Apple Event to the application you clicked on, which then executes code as it sees fit. To simulate a Dock click, you need to send the reopen event to the application manually.
The Apple Documentation on the matter states that the reopen event has the ID of rapp code (kAEReopenApplication) and is part of the kCoreEventClass class.
From there, it's relatively simple to construct the Apple Event in code and send it off to the application. Note that you really should do some error checking from the AESendMessage call, as the event will likely fail if the application isn't launched, etc — I haven't really tested those cases.
Here is my solution. Note that you'll need to keep your original code to bring the application to the front — the Apple Event doesn't change application order.
NSAppleEventDescriptor *target = [[NSAppleEventDescriptor alloc]
initWithDescriptorType:typeApplicationBundleID
data:[#"com.spotify.client" dataUsingEncoding:NSUTF8StringEncoding]];
NSAppleEventDescriptor *event = [[NSAppleEventDescriptor alloc]
initWithEventClass:kCoreEventClass
eventID:kAEReopenApplication
targetDescriptor:target
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
AESendMessage(event.aeDesc, NULL, kAEWaitReply | kAENeverInteract, kAEDefaultTimeout);

Forcing an LSUIElement "Accessory" application to the front

I have an LSUIElement application that needs to keep it's status as an LSUIElement (it cannot have a dock icon) this application is launched and handled by a plugin.
When I change the app so it is a "Regular" app (without LSUIElement), [NSApp activateIgnoringOtherApps:YES] works perfectly. However when I make it an LSUIElement it shows the window, but the window is stuck behind safari (where the plugin is running) but on top of everything else. I am calling -[NSWindow makeKeyAndOrderFront:self]; but that doesn't make any difference.
To be clear the "plugin" side is irrelevant as the plugin and application are two separate processes. The plugin sends an event (over a mach port) to the application which calls some code to open the window and bring it to focus. The problem is the code to bring it to focus only works when it's not an LSUIElement application.
I've exhaustively searched for an existing call to make this work, I'm open to suggestions on how to fake a mouse click, or even run some applescript to make this work, maybe there's an undocumented method of doing this.
The Apple docs for LSUIElement state "The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically" but I cannot find the magic that enables this.
You need to activate your app first:
[NSApp activateIgnoringOtherApps:YES];
and then make your window become key window:
[NSWindow makeKeyAndOrderFront:nil];
This works in my project.
The following approach works for me in an app with "LSUIElement" set to "1", even if the entire application is using Cocoa:
ProcessSerialNumber psn;
if (noErr == GetCurrentProcess(&psn))
{
(OSStatus)SetFrontProcess(&psn);
}
(This is part of the Carbon API.)

Showing a modal NSWindow, without activating the other application windows

I have an NSStatusItem that is properly displaying in the MenuBar. One of the items (when clicked) displays a modal NSWindow from my application, which is meant to perform a one-off task, then disappear. (Eg. the user enters a small bit of text, clicks "Save", and the modal NSWindow goes away.)
The issue occurs when the application is running in the background. The modal window properly appears above whatever application is running in the foreground, but when the user clicks the "Save" button, the rest of the application's windows also are made active. This is undesirable, as the user then has to click back to whatever app they were using. (Destroying the convenience of the NSStatusItem.) I'm displaying the modal window using:
[myWindow setFrame:finalRect display:YES animate:NO];
[myWindow setLevel:NSPopUpMenuWindowLevel];
[NSApp runModalForWindow:myWindow];
Is there any way to prevent clicks/events in my popup window from causing the rest of the application to become active? Or a way to let NSApp know that this particular panel shouldn't automatically activate the rest of the app? Thanks!
Instead of creating an NSWindow, create an NSPanel with the style NSNonactivatingPanelMask. You can then do the usual makeKeyAndOrderFront: and orderOut: to show/hide panel as needed.
NSApp's beginModalSessionForWindow, runModalSession, endModalSession are methods you need.
Have a look here for example how to use it:
Creating a fully customized NSAlert
A solution by Ken Thomases on the cocoa-dev list a couple years ago looks applicable here too:
[[NSApplication sharedApplication] hide:self];
[[NSApplication sharedApplication] performSelector:#selector(unhideWithoutActivation)
withObject:nil
afterDelay:0.05];
Which in theory tells the application to hide itself and unhide at the bottom of the window stack.
You could also intercept the mouse click event and use [NSApp preventWindowOrdering]
You can try something like:
...
if ([NSApp isHidden])
[myWindow makeKeyAndOrderFront:self];
else
[NSApp runModalForWindow:myWindow];
...
and when finish:
...
if ([NSApp isHidden])
[myWindow orderOut:self];
else
[NSApp stopModal];
...

Resources