2005-09-24

requestFocus, requestFocusInWindow, toFront, Mac OS

You may be more on the ball than me, but it's taken me until very recently to understand the difference between Java's requestFocus and requestFocusInWindow methods. They draw a distinction between giving the focus to a component even if that requires changing the currently focused window (requestFocus) and giving the focus to a component only if its window has the focus (requestFocusInWindow).

So if you've got a Java application that's being annoying and stealing the focus from other applications, it's likely calling requestFocus when it should be calling requestFocusInWindow.

(This problem can be more difficult to spot than you might realize. If you have code that does some work and then requests focus, you can miss this problem if you're the sort of person who always waits for it to complete anyway, or if it usually completes so fast you can't get bored and start doing something else, or – as I'll mention later – if you're on Mac OS.)

Requesting focus is made slightly more confusing and complicated than described above by platform differences.

When I'm using Linux, a new window will automatically get the focus. When I'm using Solaris, with what seems like the same window manager (but presumably with some different configuration option somewhere), a new window won't get the focus. Should my application gain a call to requestFocus? On the one hand, it would make the application far more usable (it's crazy for the user to ask for a dialog to appear but not have the focus go to the new dialog), but on the other hand it seems to be deliberately going against some aspect of the user's configuration. On the other other hand, that user may, like me, not have explicitly chosen such a configuration, and may, like me, not have a clue of how to fix it. (I'm not actually certain it's quite as simple as a matter of configuration, because sometimes it works as I'd expect.)

When I'm using Mac OS, there's yet another notion of focus: application focus. So requestFocus behaves slightly differently again. It will transfer the focus to a window only if the application is currently focused. (Thanks to the screen menu bar, an application can be focused even when it has no windows open. Despite having used Mac OS since 2001-12, I still find it odd that I can close every Safari window, say, and have iTunes as the only window on the display, yet Safari still has the "focus". It's especially disconcerting if you leave the computer and come back to it in such a state.) You might think toFront would be the answer, but it isn't.

If you ask on Apple's java-dev mailing list, as I did, how to get around this restriction, you get two kinds of response. There's the "you're evil, Mac applications shouldn't do that" response and the "you can't" response. (Strictly, there was a third, suggesting I use open(1), but I wanted something that worked for arbitrary Java programs, even ones not packaged as a .app/ directory.) Neither of these two claims are true. If you have two applications working in concert, as in my case, it's idiotic for them not to be able to transfer focus between themselves. And it turns out you can give the focus to an arbitrary program, if you're prepared to write a bit of C++.

Here's the version of the code in salma-hayek at the time of writing:

#include <Carbon/Carbon.h>
#include <iostream>

/*
* On Mac OS, there's a strong notion of application. One consequence is that
* toFront doesn't work on an application that isn't the one the user's
* interacting with. Normally this is fine, if not preferable, but it's awkward
* for a Java program that wants to be brought to the front via indirect user
* request. For example, without this, it's impossible for the "edit" script
* to bring Edit to the front on Mac OS.
*
* (This file is Objective C++ simply so that the make rules add
* "-framework Cocoa", which will drag in Carbon for us.)
*/

static void bringProcessToFront(pid_t pid) {
ProcessSerialNumber psn;
OSStatus status = GetProcessForPID(pid, &psn);
if (status == noErr) {
status = SetFrontProcess(&psn);
}
if (status != noErr) {
std::cout << "pid " << pid << " = " << status << std::endl;
}
}

int main(int, char*[]) {
bringProcessToFront(getppid());
return 0;
}

Spawn this from the Java program that wants to grab the application focus, and hey presto! You're the focused application. My editor uses this to come to the front when the user causes it to open a file from a terminal emulator.