OS X: Launching app from dock is a no-op if app is already running headless (via NSApplicationActivationPolicyProhibited) - macos

My app can run with a UI and run headless, chosen via a command-line argument. (I don't want to build two separate apps.) When running headless, I'm calling:
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
during app initialization, and don't show any windows.
This all works fine, and I can run the app headed (via the dock) and headless (via another app passing the command-line argument) at the same time. The little dot shows up next to the dock icon only for the headed app. Good. The problem happens if the headless app is launched first. In that state (no dot in the dock), any clicking on the app icon (or launching via Spotlight or the Finder) does nothing, or sometimes brings up "You can’t open the application “Foo” because it is not responding." Presumably the OS thinks the app is already running headed, and is trying to activate it. Is there a way to convince it to ignore that background app and launch a new instance?
Quitting the background app allows the app to launch normally again from the dock.
I don't want to set LSBackgroundOnly in the plist, because that would require two separate apps unless I changed the setActivationPolicy to NSApplicationActivationPolicyRegular in the case of the headed app. But I've read that doing that causes various other bugs, like the menu bar not always showing up.
Any ideas?
I'm on OS 10.10.5 (the oldest OS I need to support). Thanks for any help!
Update: I just noticed that when clicking on the dock and it appears to do nothing, if there are no app windows open I can see that it actually unhides my app's main window (which was created hidden) but doesn't bring it to the front (presumably it notices that NSApplicationActivationPolicyProhibited is set, though that wasn't enough to prevent it from showing the window!).
Update 2: At this point, I'd settle for a way to notify the user that the reason the app isn't launching is that they need to quit the other app that has sublaunched the headless process. Of course this code would need to be in the headless app, since the headed doesn't even launch.

It looks like you can call setActivationPolicy from applicationShouldHandleReopen and set the policy back to NSApplicationActivationPolicyRegular at that time. I made a test app with the following code and it seems to behave as you describe you would like it to above (although I've only run it on 10.11.5):
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification) {
if NSUserDefaults.standardUserDefaults().stringForKey("background") == "true" {
NSApp.setActivationPolicy(.Prohibited)
print("launched in background")
}
}
func applicationShouldHandleReopen(sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
NSApp.setActivationPolicy(.Regular)
return true
}
}

Related

PrefPane in 10.15 will not reopen external windows

My Pref Pane opens a window for each display similar to how the Displays PrefPane works. When I first launch, the OS calls my mainViewDidLoad where I open external windows using initWithWindowNibName. This works fine.
Then in willUnselect, I call [window orderOut:self]; for each external window and they correctly hide. This will happen for example if the user switches from my PrefPane to the Sound or Network PrefPane.
When they come back to my PrefPane, I get willSelect and call:
[window orderWindow:NSWindowAbove relativeTo:0];
This call no longer works (it works in all versions of 10.14.6 and earlier and may work in early versions of 10.15, but it definitely broken in 10.15.6).
I have tried using other methods to hide/show the windows including [window setIsVisible:] but nothing works to restore the window.
I think it might be related to something this blog discusses:
https://www.noodlesoft.com/blog/2019/08/28/preference-panes-and-catalina/
Has anyone seen this or know a fix for it?
After working with Apple DTS, this is expected behavior in that PrefPanes now run in a separate process and that process does not expect panes to open additional windows.
It is still possible to open/close windows within didSelect, but that is more of a side-effect and may not work in the long run. We have decided to pull our software out of System Preferences and into a regular application to ensure long term compatibility.

NSURLSession background configuration benefits on macOS

On iOS it’s critical to init a NSURLSession with a background configuration to get the benefit of uploading and downloading while the app is background:
Example in Swift 2:
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("SomeSessionName");
NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil);
On OSX 10.10+, is there any benefit to using a background session configuration with the intention to continually upload or download even when the app is not currently in focus? In my experience, a default session configuration is far less vulnerable to bugs:
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration();
NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil);
On OS X, background download tasks continue to run even after the user quits the app (as long as the user doesn't force-quit the app, IIRC). Unlike iOS, it doesn't relaunch your app in the background when the download finishes; your app will instead find out that the download finished after the user manually relaunches the app and your app reassociates itself with the existing named background sessions.
So the most common reason for using them on OS X would be for things like games that download large data sets. You could kick off the download in the background and let the user continue playing the game (without the expanded levels or whatever), and the download would continue even if the user quits the game, but the next time the user runs the game, the download would be available for installation.
That said, it is significantly less critical on OS X, because you have the option of forking a child process that keeps running and downloading.

Set MacOS X menubar app to launch at startup

I have a sandboxed menubar application (no dock icon) that in it's preferences window allows the user to check a checkbox to have the app launch at login. I used to use the LSSharedFileList api, but as this is not allowed anymore for sandboxed apps, I've migrated to using SMLoginItemSetEnabled. What I've found is that although the app will launch at login, as expected, if I go back into the Preferences and uncheck and re-check the launch at login checkbox, I get a second instance of my menubar app launched.
Here's my helper app code (in its app delegate):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSString * path = [[[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent];
[[NSWorkspace sharedWorkspace] launchApplication:path];
[NSApp terminate:nil];
}
Here is the code in my preferences window (main app):
- (IBAction)toggleLoginStatus:(NSButton*)sender{
if(!SMLoginItemSetEnabled((__bridge CFStringRef)#"myAppBundleIdentifier", (BOOL)[sender state])){
NSLog(#"Dagnabit!");
}
}
After the second instance launches, unchecking/re-checking the checkbox does not launch anymore instances. Does anyone have any idea what's going on?
Thanks
I've found the answer. None of the tutorials I looked at mentioned this, but in the docs for SMLoginItemEnabled says this:
The Boolean enabled state of the helper application. This value is
effective only for the currently logged in user. If true, the helper
application will be started immediately (and upon subsequent logins)
and kept running. If false, the helper application will no longer be
kept running.
So I'll have to check if the app is running already before allowing the helper to launch it.
I spent almost two days figuring out this. Finally dropped the idea to support launch at login.
Although it is just a case of Yes or no in the Front end, developer on the other hand has to spent huge amount of time(Adding a helper application and doing all those project settings in both the apps). The process in early days (before sandboxing)was pretty much simpler than it is now.
Also when the application is added to the 'launch at login' list, there is no way to verify because the application is not listed in system preferences -> users -> login items, as it was before sand boxing.
I am also facing a strange problem where I added helper app to the launch at login and then uninstalled the helper, but the activity monitor still shows helper app in the list, after each reboot.
Lets hope apple adds something like LaunchAtLoginController in coming future.

Close other applications using swift

Is there a way to close running applications in swift? For instance, if the application I create needs to close safari.
Here's a Swift 5 version for closing running applications without using AppleScript (AppleScript is a perfect way but it isn't the only way), Safari is used as the example in this case:
let runningApplications = NSWorkspace.shared.runningApplications
if let safari = runningApplications.first(where: { (application) in
return application.bundleIdentifier == "com.apple.Safari" && application.bundleURL == URL(fileURLWithPath: NSWorkspace.shared.fullPath(forApplication: "Safari")!)
}) {
// option 1
safari.terminate()
// option 2
kill(safari.processIdentifier, SIGTERM)
}
SIGTERM instead of SIGKILL, referencing from here
Of course, make sure you notify the user of this activity since this may cause negative impact on the user-experience (for example, user-generated contents in the targeted application are not saved before terminating)
It is certainly possible via an applescript directly IF:
your app is not running sandboxed (note that if you plan to distribute it via the App Store, your app will be sandboxed)
OR
your app has the necessary entitlements to use applescript: com.apple.security.scripting-targets (apple needs to approve that AND you need to know which apps to target. this isn't a blanket permission)
then
https://apple.stackexchange.com/questions/60401/how-do-i-create-an-applescript-that-will-quit-an-application-at-a-specific-time
Can you execute an Applescript script from a Swift Application
if you aren't going for App Store complicity anyways, you might also use NSTask directly
scripts / code snippets:
How to force kill another application in cocoa Mac OS X 10.5
Can you execute an Applescript script from a Swift Application
short & sweet: technically yes, 'politically' maybe :D

Unloading my plugin from another process

I'm toying with adding an NSDockTilePlugIn to my application, but I've come across some strange behavior -- understandable behavior, but I'd like to see if there is a way around it.
When I run my app from the dmg or keep the (dmg version of the) app in the Dock, the Dock loads my .docktileplugin bundle.
When the application quits, the plugin receives a setDockTile: message with a nil NSDockTile * (as per the documentation). However, the plugin keeps running, and I cannot eject (unmount, detach) the dmg unless I kill the Dock (I suppose logging out would work too).
I've tried sending a Notification through the NSDistributedNotificaitonCenter (from a non-dmg version of the app) to tell it to unload, but the Console tells me that now I have two of the same docktileplugin loaded, and it is undefined which one will execute.
And even though [[NSBundle bundleWithPath:...] unload] returns YES, I still cannot eject the dmg.

Resources