1

What I want

I want a helper app user agent (LSUIElement in Info.plist is True) to add itself to the login items on terminate.

The Problem

I can't get any code to run before the helper app process terminates. My "add to login items" code runs fine.

Background

  • I've got a helper app user agent process that runs all the time
  • On first launch, it adds itself to login items
  • If the app was moved, on next login, the helper app process can't be found and so isn't launched

What I've Tried

I wonder if I should subclass NSApplication and override terminate: or stop: and put my code in there, but that seems overkill. Surely there's a better way?

I've tried all these different things in the NSApp delegate:

-(void)applicationWillTerminate:(NSApplication *)sender {
     [self addHelperAppToLoginItems]
}

-(void)applicationDidTerminate:(NSApplication *)sender {
     [self addHelperAppToLoginItems]
}

-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
     [self addHelperAppToLoginItems]
}

-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
     [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self 
                                                            selector:@selector(addHelperAppToLoginItems) 
                                                                name:NSWorkspaceDidTerminateApplicationNotification
                                                              object:nil];  

}

-(void)addHelperAppToLoginItems {
      // This code never gets called!
}

Why do the NSApplication delegate methods not work with a user agent process?

And how can I get the process to check the login items on terminate?

I'd be very grateful for any help. Thanks!

UPDATE 1 6/2/11

After some further digging, the problem is more that processes never really get quit, it's more common for them to get killed by the OS.

This means when you choose to "Quit" a process in Activity Monitor or you shut down the computer, the terminate delegate methods don't get called.

When the following Applescript is run, the delegate methods do get called:

tell application "System Events"
    tell application "LapsusHelperApp"
         quit
    end tell
end tell
John Gallagher
  • 6,208
  • 6
  • 40
  • 75

1 Answers1

4

After some further digging, the problem is more that processes never really get quit, it's more common for them to get killed by the OS.

This is because you have sudden termination enabled for your application. It's opt-in, so simply remove that key-value pair from your Info.plist and you will then start getting applicationWillTerminate: messages.

Also, the Terminate button in Xcode (at least in 3.x) always works the same way sudden termination does, so you will never get a applicationWillTerminate: message when terminating your app from Xcode.

By the way:

  • applicationWillTerminate: is a notification message, so its argument is an NSNotification object, not an NSApplication object.
  • There is no applicationDidTerminate:. A moment's reflection will reveal why. ☺
Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Thanks, Peter, but after adding a boolean key of `NSSupportsSuddenTermination` with value `NO` for my helper app Info.plist, cleaning and rebuilding, it still doesn't seem to get `applicationWillTerminate:` notifications. I've even tried calling `[[NSProcessInfo processInfo] disableSuddenTermination]` on startup and still no joy. Any ideas why this could be? – John Gallagher Feb 06 '11 at 21:34
  • Incidentally, what you've suggested works fine for a Cocoa application where `LSUIElement` key is NO or not present in the info.plist. But my helper app process should be a user agent so I'm using an `LSUIElement` of `YES` and with this setting, I can't get even a simple Xcode project to run `applicationWillTerminate:` – John Gallagher Feb 06 '11 at 21:56
  • There's no need to explicitly disable sudden termination; as I said, it's opt-in, meaning it's disabled by default. You would have to explicitly enable it. If you use Xcode's Terminate command, that will kill your app regardless of sudden termination. Activity Monitor will do that only if you select “Force Quit”. How are you normally quitting your app, besides logout? Apple Event from another process (e.g., System Preferences)? Sending NSApp `terminate:`? Some other means? – Peter Hosey Feb 06 '11 at 22:01
  • Looks like I'm wrong about Activity Monitor. It sends `SIGTERM` (just like Sudden Termination) for normal “Quit”, `SIGKILL` for “Force Quit”. Sounds Radar-worthy to me. – Peter Hosey Feb 06 '11 at 22:19
  • I can confirm that both quitting the app from within the app (i.e., `-[NSApplication terminate:]`) and quitting as a consequence of logout (when not running under Xcode) trigger `applicationWillTerminate:`. – Peter Hosey Feb 06 '11 at 22:24
  • I did read your opt-in comment, but for some reason I get a bit paranoid about these things and thought it safer to manually disable it to make sure. I tested quit within activity monitor, but the most important thing is for this method to get called on shutdown. Ideally it'd happen on a SIGTERM too, but looks like that's not possible. I assume the best way would be to register for the `NSWorkspaceWillPowerOffNotification` instead. – John Gallagher Feb 06 '11 at 22:27
  • 1
    Don't forget logout. If you need this to happen at quit pretty much no matter what (see my comment on your question), trap `SIGTERM` using the `signal` or `sigaction` functions, which are documented in manpages. Be aware that this means your app will not exit immediately—and may crash if `addHelperAppToLoginItems` is broken—when you terminate the app from Xcode. – Peter Hosey Feb 06 '11 at 22:38
  • 1
    Thanks for all this detailed information Peter. I'm going to stick with putting my code in `applicationWillTerminate:` and an `NSWorkspaceWillPowerOffNotification`. Incidentally, from the Cocoa docs on this notification: "Posted when the user has requested a logout or that the machine be powered off." It's now working well on logout. Thanks again! – John Gallagher Feb 07 '11 at 13:20