9

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...
    }
}
Alex Nichol
  • 7,512
  • 4
  • 32
  • 30

3 Answers3

2

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.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Ken, I am well aware of the behaviors you describe. Others on the net have commented on this odd behavior, too. Why would [NSApp hide] help? It still does the same - any open window will be activated. I have no problem switch processes, I (as well as Alex) have a problem with the behavior of the Desktop not getting the focus back. Because, for my application, this is critical that the Finder has the same focus before and after my app got activated. What you describe with the Cmd-Tab behavior is no surprise either, yet it doesn't help me. – Thomas Tempelmann Feb 25 '13 at 20:43
  • Two things: First, `-[NSApp hide:]` does seem to help for the case where the Finder has no windows on the current space but does have windows open on another case. Second, my point was to stop trying to micromanage things. If hiding the current app causes the Finder to activate one of its windows, then that's possibly the correct behavior for your situation. And it's simpler. – Ken Thomases Feb 26 '13 at 01:14
  • If the question we ask is: how do I get the focus back on the Desktop if the Desktop had it before the Process Switch, then how can you argue: Well, maybe you don't need this because OSX doesn't like to do it the way you want. You're arguing for a solution that we're not asking for. Or do I misread your answer and there's a hidden answer to our question in there? I don't see it. – Thomas Tempelmann Feb 27 '13 at 09:30
  • I updated my answer with the specific thing you asked about (the Objective-C code for the AppleScript). – Ken Thomases Feb 28 '13 at 01:06
  • Ken are you saying that the "select the desktop's window" script doesn't work in 10.6? That's odd. But 10.6 isn't having the original problem anyway, i.e. switching away from the Finder and then back to it, e.g. with Cmd-Tab, doesn't change the focus as it does in 10.7 and later. So this entire disucssion is relevant to 10.7 and later only, anyway. Agreed? And for 10.7, we hopefully all agree, we still have no complete solution, as we cannot reliably test if the desktop has the focus, thanks to radr:9406282. – Thomas Tempelmann Mar 01 '13 at 22:54
  • I'm running 10.6. If the Finder has a window open but the focus is on the desktop, switching away and back with Command-Tab will switch focus to the window. Running the script you posted works differently depending on whether the Finder has any windows open on the current space. If the Finder has no windows open or one of its windows is on the current space, the script will focus the desktop. If the only open windows are on another space, the script will switch to that space and focus the window, not the desktop. – Ken Thomases Mar 02 '13 at 13:00
  • Thanks for the clarification, Ken. Just so that there's no misunderstanding: The original question still has no fully working solution. I.e.: A program that get temporarily activated, e.g. to show a dialog, has no reliable way preserve the Finder's focus on the Desktop if there's also a non-active window open. I was hoping someone else would figure out a work-around for the Finder bug. – Thomas Tempelmann Mar 03 '13 at 14:44
  • To be fair, though, I'm going to award the bounty to you, because I wrote in the bounty that I'm just looking for a solution to reselect the Finder. I had not realized at that point that the Finder bug is the actual issue that leaves this question unsolved. But no one's going to solve that part, so what the heck. – Thomas Tempelmann Mar 03 '13 at 14:49
  • Thanks. Yes, I agree, that we haven't found a solution. Have you tried a non-activating panel? In theory, that might sidestep the issue by allowing the use of your GUI without ever taking activation away from the Finder. Unfortunately, I think the user has to click on it. I'm not sure how you programmatically give such a panel keyboard focus without activating your app. – Ken Thomases Mar 03 '13 at 20:43
  • I've solved my own issue now totally differently: All I needed was to show a window to the user and let him choose options from it using the keyboard. So I'm now showing a floating window that doesn't require my bg app to come to the front, and use the Carbon Hotkey API to fetch the key presses. That way, the Finder remains active and none of the described issues arise. – Thomas Tempelmann Mar 11 '13 at 21:56
1

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

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
  • 1
    This might have been better as a separate question, with a bounty placed on that, linking back to this question. However, there's enough here expanding on a possible solution that I'll leave this answer alone (it was getting flagged). – Brad Larson Feb 24 '13 at 22:26
0

Why not call the actual Apple Script:

NSDictionary *errorInfoDictionary = nil;
NSAppleScript *script = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FocusOnFinder" ofType:@"scpt"]] error:&errorInfoDictionary];

// OR

// NSAppleScript *script = [[NSAppleScript alloc] initWithSource:@"tell application \"Finder\"\n activate\n select the desktop's window\n end tell"];

if (!errorInfoDictionary)
{
    if (![script executeAndReturnError:&errorInfoDictionary])
    {
        NSLog(@"%@", [errorInfoDictionary description]);
    }
}
else
    NSLog(@"%@", [errorInfoDictionary description]);

Content of FocusOnFinder.scpt

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

If the Finder is not running or is minimized then there is no effect.

SAPLogix
  • 1,744
  • 1
  • 11
  • 9