How to programmatically terminate NSApp without encoding window state?

564 Views Asked by At

All OS X application that support NSWindowRestoration can be closed by selecting the menu entry "Quit and Close All Windows" (Option-Command Q). This disables the state restoration and the next time you open the app all windows will be in their default position.

The menu entry triggers the terminate: method on NSApplication. But so does the regular "Close App" menu as well (Command Q).

How can I do the "Quit and Close All Windows" programmatically? Do I really have to close all windows by myself and then call terminate:?

How does Apple magically decide what to do, when both actions are connected to the same terminate: method?

2

There are 2 best solutions below

4
On BEST ANSWER

There doesn't seem to be a great way to do this. You might want to file a bug with Apple requesting (along with an explanation for why you need it).

How does Apple magically decide what to do, when both actions are connected to the same terminate: method?

Well, looking at the disassembly of AppKit, it appears that -[NSApplication terminate:] checks if the sender is an instance of NSMenuItem. If it is, it checks if its userInterfaceItemIdentifier is equal to @"NSAlternateQuitMenuItem".

You could, I suppose, create a dummy menu item with that identifier and pass that as the sender to -terminate:, although since this is relying on an implementation detail it could break at any time.

The other controlling factor is the setting of System Preferences > General > "Close windows when quitting an app". That corresponds to the user defaults key NSAlternateQuitMenuItem, although, again, that's an implementation detail. It appears that you could set that before calling -terminate: and then, in the -applicationWillTerminate: delegate method, remove that setting. (Your changes will be associated with your application. They won't affect other applications or the setting in System Preferences.) Of course, you'll have to make sure that sudden termination is disabled to get that delegate method call.

0
On

NSApplication can be subclassed.

Set the Principal class name in the build target's info, and then override -terminate:

@interface MyApplication : NSApplication
@end

@implementation MyApplication

- (void) terminate:(id)sender
{
    if ([sender isKindOfClass:NSMenuItem.class])
    {
        BOOL alwaysKeeps = [NSUserDefaults.standardUserDefaults boolForKey:@"NSQuitAlwaysKeepsWindows"];
        
        NSMenuItem* item = sender;
        item.identifier = alwaysKeeps ? @"NSAlternateQuitMenuItem" : @"";
    }
    [super terminate:sender];
}

@end

You can inspect the sender (NSMenuItem) in the debugger and see that member _uiid is set to @"NSAlternateQuitMenuItem" when you hold down the Option key upon quitting.

Depending on the system user preference, you need to either add or remove the identifier.

I would use this technique only for the opposite effect; to always encode restorable state of open windows, ignoring system user preference. Otherwise, I would just put a -terminate action somewhere in the responder chain to close all windows before NSApplication gets the message:

- (void) terminate:(id)sender
{
    for (NSWindow* window in NSApp.windows)
    {
        [window close];
    }
    [NSApp terminate:sender];
}