Gracefully gain and resign background app focus for dialog

cocoa, nswindow, objective-c

Solution

Have you considered simply invoking `-[NSApp hide:nil]`? That is, let the system worry about how to reactivate the previously active app and just make sure your app is no longer active.

By the way, the behavior you've observed, where the Finder activates a window instead of the desktop when it receives active status is the same as what happens when you Command-Tab away and then back. Or Command-Tab away and then hide the application you switched to. So, it may be considered the correct behavior. On the other hand, in my testing, hiding the foreground app when the Finder had been focused on the desktop but has a window on a different space doesn't switch to the other space. It does what you want: activates the Finder with the Desktop focused.

Finally, `-[NSRunningApplication activateWithOptions:]` is the modern replacement for `SetFrontProcessWithOptions()`.

If all you really want is a way to run the equivalent of

tell application "Finder"
    activate
    select the desktop's window
end tell

from Objective-C, I see two options. First, with the Scripting Bridge API:

[self.finder activate];
[(FinderWindow*)self.finder.desktop.containerWindow select];

(I assume you've already got the basics of using the Scripting Bridge with the Finder covered. If not, Apple has a ScriptingBridgeFinder sample. For some reason, it's in the legacy documentation section.)

Second, you could use `NSAppleScript`:

NSAppleScript* s = [[NSAppleScript alloc] initWithSource:@"tell application \"Finder\"\nactivate\nselect the desktop's window\nend tell"];
NSDictionary* err = nil;
if (![s executeAndReturnError:&err])
    /* handle error */;

In my testing on Snow Leopard, though, neither approach worked to switch to the Finder with the desktop focused. But then, neither did your AppleScript code run from the AppleScript Editor.

Problem

I am working on a small Mac application which will typically run invisibly in the background. However, the app's primary functionality comes into play when the user renames a file on their desktop or elsewhere in Finder. When this occurs, I would like to present a dialog similar to that which appears when a user changes a file-extension through Finder. Since this would require my application getting frontmost focus (rather than Finder), I would like to return Finder as the frontmost application when the user hits "OK" in my dialog. I am currently using Apple's Process Manager function `SetFrontProcessWithOptions()`, but I run into trouble in the following scenario: - A user opens a Finder window somewhere in their workspace - The user then clicks on their desktop, unfocusing the window. - The user renames a file on their desktop - My application causes itself to focus using `SetFrontProcessWithOptions()` - The user hits `OK` in the dialog, my app focuses Finder using `SetFrontProcessWithOptions()` - When Finder refocuses, it focuses the window that the user had opened before, despite the fact that it was not focused when Finder was previously frontmost. This gets really annoying if you have a Finder window open in another space before you rename a file on your desktop: in this scenario, hitting "OK" in the dialog causes Finder to automatically switch spaces and go back to the window. This is only because of the nature of the `SetFrontProcessWithOptions()` function, which can only focus a window of a given application. Since the desktop apparently does not count as a window, the function instead finds another window to focus, despite the fact that the user had not previously had that window focused. It would be great if anyone has any better ideas of how to do a sort of dialog-based thing such as this, maybe even without the need for focusing and unfocusinng Finder at all. EDIT: I found a somewhat ugly way to fix this behavior for the most part, but it involves the Scripting Bridge, and it does not re-focus the desktop in the event that an item from it was renamed. Here is my code for doing this: ``` FinderApplication * app = [SBApplication applicationWithBundleIdentifier:@"com.apple.finder"]; if (!app || ![app isRunning]) { SetFrontProcessWithOptions(&processSerial, kSetFrontProcessFrontWindowOnly); return; } SBElementArray * selArray = app.selection.get; if ([selArray count] == 0) { SetFrontProcessWithOptions(&processSerial, kSetFrontProcessFrontWindowOnly); return; } else { FinderWindow * window = [[[selArray objectAtIndex:0] container].get containerWindow].get; if ([window isKindOfClass:NSClassFromString(@"FinderFinderWindow")]) { SetFrontProcessWithOptions(&processSerial, kSetFrontProcessFrontWindowOnly); } else { // TODO: this is where I'd insert code to select the item // on the desktop... } } ```

Original source