Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gracefully gain and resign background app focus for dialog

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...
    }
}
like image 841
Alex Nichol Avatar asked Jun 08 '12 14:06

Alex Nichol


2 Answers

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.

like image 165
Ken Thomases Avatar answered Oct 25 '22 16:10

Ken Thomases


Alex's solution doesn't work if there is no selection on the desktop, I'm afraid. Instead, this AppleScript should be more reliable to tell if a window or the desktop had the focus:

tell application "Finder"
    get insertion location
end tell

It'll return the path to the Desktop folder if it has the focus.

Though there's still two problems with that:

  1. If the user has a window of the Desktop open, then the above script returns the same information. I.e. one cannot distinguish between the desktop and a window of the desktop having the focus. Would be nice if there's a solution to that, too.
  2. There's a bug in the Finder since 10.7 (still present in 10.8.2, and known to Apple (see http://openradar.appspot.com/9406282) that causes the Finder to report the wrong information about recently opened windows, making even the above script unreliable.

To re-select the desktop I found the solution now myself, though:

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

There's also a thread on MacScripter.net about this where I explain the options a bit more: http://macscripter.net/viewtopic.php?pid=160529

like image 25
Thomas Tempelmann Avatar answered Oct 25 '22 16:10

Thomas Tempelmann