Skip to main content

Grabbing keyboard focus from native application

27 replies [Last post]
cowwoc
Offline
Joined: 2003-08-24

Hi,

I need to grab keyboard focus from a native application. For example, say I am browsing some webpage in Firefox I want my JPopupMenu to suddenly become visible and grab focus.

What I am actually trying to do is implement a system-tray API and I have noticed that when a user clicks on the icon which is meant to trigger a pop-up menu, the JPopupMenu doesn't get keyboard focus because under the hood what happens is that the notification area gets keyboard focus (you clicked on it) then the popup comes up but since Java does not have focus, the popup doesn't get keyboard focus either. Obviously this is not the intended behavior so I am forced to *grab* the focus forcibly. Any ideas?

Thanks,
Gili

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
zander
Offline
Joined: 2003-06-13

Maybe the obvious question (well, obvious to me anyway) is how a native, non-java application does get this done correctly.

Is there a reason why a java window (dialog or frame) can not have a native window as a parent?
Since if the popup could get the taskbar as its parent window then the rest is simply fixing bugs since you follow the native idea and stop fighting the native window manager behavior.

cowwoc
Offline
Joined: 2003-08-24

Denis,

A follow-up to my post. I think I found a bug either in the focus subsystem or JPopupMenu. I have noticed that if I insert menuRoot.requestFocusInWindow() before menuRoot.toFront() then when the popup menu shows up I can use the arrow-keys to move around the selection but hitting enter fails! If I remove menuRoot.requestFocusInWindow() then enter works again.

Should I file a bug report against JPopupMenu?

Gili

Scott Violet

On Sun, Nov 21, 2004 at 02:49:14PM -0500, swing-feedback@javadesktop.org wrote:
> Denis,
>
> A follow-up to my post. I think I found a bug either in the focus
> subsystem or JPopupMenu. I have noticed that if I insert
> menuRoot.requestFocusInWindow() before menuRoot.toFront() then when
> the popup menu shows up I can use the arrow-keys to move around the
> selection but hitting enter fails! If I remove
> menuRoot.requestFocusInWindow() then enter works again.
>
> Should I file a bug report against JPopupMenu?

Gili,

This is popup menu specific. Swing attempts to put focus on a
specific component so that it can register the necessary bindings on
that component. Your requestFocusInWindow is most likely putting
focus on an unexpected component so that the bindings don't work.
This isn't a bug.

-Scott

cowwoc
Offline
Joined: 2003-08-24

> This is popup menu specific. Swing attempts to put
> focus on a
> specific component so that it can register the
> necessary bindings on
> that component. Your requestFocusInWindow is most
> likely putting
> focus on an unexpected component so that the bindings
> don't work.
> This isn't a bug.
>
> -Scott

Please bear with me, I didn't get much sleep last night so it might take me longer than usual to grasp things :)

I'm sorry but that doesn't make much sense to me. Under win32, toFront() grabs the focus as well as requestFocusInWindow(). I can invoke toFront() just fine, but not requestFocusInWindow(). Why wouldn't toFront() break the bindings?

Gili

Scott Violet

On Tue, Nov 23, 2004 at 10:28:02AM -0500, swing-feedback@javadesktop.org wrote:
> I'm sorry but that doesn't make much sense to me. Under win32,
> toFront() grabs the focus as well as requestFocusInWindow(). I can
> invoke toFront() just fine, but not requestFocusInWindow(). Why
> wouldn't toFront() break the bindings?

One must be placing focus at a different place than the other. Try
printing out who gets focus after these calls (try it from an
invokeLater). Is it different?

-Scott

cowwoc
Offline
Joined: 2003-08-24

Sorry I took so long to reply. I've been preoccupied with higher-priority items.

First of all, a lot of time has past since I last reported this issue. Since then it seemed to have magically gone away. I've done some minor reorganization of the code like moving to a different package name (but the underlying implementation remains untouched). I have no idea why the problem disappeared. Anyway, here is what I discovered nonetheless.

If I invoke menuRoot.requestFocusInWindow() followed by menuRoot.toFront() it has the same effect of just invoking menuRoot.toFront() *only* if I do this immediately after activeMenu.show(menuRoot, 0, 0).

If, on the other hand, I invoke this code in EventQueue.invokeLater() then I get different results.

Calling just menuRoot.toFront() results in this focus owner:

javax.swing.JRootPane[,0,0,171x149,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=]

Calling menuRoot.requestFocusInWindow() before menuRoot.toFront() results in this focus owner:

javax.swing.JFrame[frame0,1055,795,171x149,invalid,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,171x149,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

I don't understand why requestFocusInWindow() is having this effect and if it is a bug or not. Please comment on it.

Thank you,
Gili

denismo
Offline
Joined: 2003-06-28

Without the test case, it is hard to say exactly, but I suspect the following happens:

When you call requestFocusInWindow, menuRoot is being remembered as the most-recent focus owner. You also override Swing's request to JRootPane. When you make menuRoot active and focused via toFront call, most-recent focus owner is used as the initial component and it gets focus.

When you just call toFront, Swing's request to JRootPane is processed when menuRoot becomes active and focused, and focus remains on it.

cowwoc
Offline
Joined: 2003-08-24

Ok, so for now I am closing this issue (in my head) as "not reproducible" (i.e. I can't reproduce keyboard focus problems anymore). I assume you meant to say that the requestFocusInWindow() followed by toFront() behavior is acceptable and is not a bug.

Thanks,
Gili

cowwoc
Offline
Joined: 2003-08-24

Ok, here is my testcase. It differs from my application's behavior in that requestFocus() within invokeLater() is returning false in this testcase, but in my application it returns true. In either case, focus is not being shifted and I don't know why.

[code]
import com.tog.tray.win32.JDesktopPopupMenu;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

public class Testcase
{
public static void main(String[] args)
{
try
{
System.setProperty("javax.swing.adjustPopupLocationToFit", "false");
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
final JFrame invoker = new JFrame();
invoker.setUndecorated(true);

final JPopupMenu popupMenu = new JDesktopPopupMenu();
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
System.out.println("clicked on " + ((JMenuItem) e.getSource()).getText());
}
};
JMenuItem menuItem = new JMenuItem("choice 1");
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1,
InputEvent.ALT_DOWN_MASK));
popupMenu.add(menuItem);
menuItem = new JMenuItem("choice 2");
popupMenu.add(menuItem);
menuItem = new JMenuItem("choice 3");
popupMenu.add(menuItem);

popupMenu.addPopupMenuListener(new PopupMenuListener()
{
public void popupMenuCanceled(PopupMenuEvent e)
{
// If the menu is dismissed, hide the invoker
invoker.setVisible(false);
}

public void popupMenuWillBecomeInvisible(PopupMenuEvent e)
{
// If the menu is dismissed, hide the invoker
invoker.setVisible(false);
}

public void popupMenuWillBecomeVisible(PopupMenuEvent e)
{}
});
// Make sure popup overlaps with taskbar at bottom of screen
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension screenSize = toolkit.getScreenSize();
invoker.setLocation(0, screenSize.height - popupMenu.getPreferredSize().height);

invoker.setSize(popupMenu.getPreferredSize());
invoker.setVisible(true);

// As soon as the invoker becomes visible, we should shift the keyboard
// focus away from Java by clicking on the desktop
Thread.sleep(5000);

// This returns true, but the focus does not shift
System.out.println("requestFocus() returned " + invoker.getRootPane().requestFocus(false));

// There seems to be a bug in JPopupMenu.show(); unless invokeLater() is
// used, the invoker ends up displaying on top of the JPopupMenu.
EventQueue.invokeLater(new Runnable()
{
public void run()
{
popupMenu.show(invoker, 0, 0);

// These all return true
System.out.println("displayable=" + invoker.getRootPane().isDisplayable());
System.out.println("visible=" + invoker.getRootPane().isVisible());
System.out.println("focusable=" + invoker.getRootPane().isFocusable());

// This returns false, the reason is unknown
System.out.println("requestFocus() returned " + invoker.getRootPane().requestFocus(false));
}
});
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
[/code]

Message was edited by: shan-man (Fixed code formatting)

Scott Violet

Mouse grab would work for you, but I see what you are after.

When you click on the tray isn't it Java that's requesting focus? Or
is it the OS that is requesting focus? In the tray there must be some
Java component or heavyweight, couldn't you request focus on that?

-Scott

cowwoc
Offline
Joined: 2003-08-24

Right, what is happening is this:

1) User right-clicks on tray icon (the tray is native, the icon inside is also native).
2) I have a hook on the native icon. When clicked upon, it calls showPopupMenu() in my Java code.
3) I create a JFrame (called menuRoot) to act as a container for the lightweight JPopupMenu. I set its size equal to the dimensions of the JPopupMenu, sets its location exactly where I want the JPopupMenu to appear. I then invoke menuRoot.setVisible(true);
4) I invokeLater(JPopupMenu.show(menuRoot, 0, 0))
5) I try grabbing focus by invoke menuRoot.requestFocus(false) or the same on the JPopupMenu but although both return success, nothing happens.

That is to say, I *am* requesting focus on the native peer, but it doesn't seem to help.

Scott Violet

I would try requesting focus on the menuRoot.getRootPane() at 5, or
even 3.1.

-Scott

cowwoc
Offline
Joined: 2003-08-24

When you say "request focus" do you mean I should invoke menuRoot.getRootPane().requestFocus(false)?

I tried doing that at steps 3.1 and 5 and in both cases it returned true but the focus was not shifted.

Is this normal?!

Scott Violet

On Fri, Nov 19, 2004 at 06:10:41PM -0500, swing-feedback@javadesktop.org wrote:
> When you say "request focus" do you mean I should invoke
> menuRoot.getRootPane().requestFocus(false)?

Yep.

> I tried doing that at steps 3.1 and 5 and in both cases it returned
> true but the focus was not shifted.

Well, it was worth a shot. Last resort is an invokeLater with a
request focus on the menuRoot.getRootPane. If that doesn't do it,
than we'll need a test case.

-Scott

cowwoc
Offline
Joined: 2003-08-24

Ok, that doesn't work either. If I have a testcase ready for you within 30 mins, can you look at it today?

Scott Violet

Probably not.

You've indicated you're on Windows and I don't have that environment
readily set up. Additionally I think you'll need AWT help on this...

-Scott

cowwoc
Offline
Joined: 2003-08-24

> Probably not.
>
> You've indicated you're on Windows and I don't have
> that environment
> readily set up. Additionally I think you'll need AWT
> help on this...
>
> -Scott

Ok, I'll wait until Monday then... I'm really itching to get this issue resolved already.

PS: How do you get the forum to format source-code using indents and colors?

Thanks anyway,
Gili

denismo
Offline
Joined: 2003-06-28

The problem is that in AWT/Swing, regular Window can't have focus being shown alone - it needs [b]active[/b] Frame as a parent.

Therefore, I don't see a solution that will work everywhere, consistently.

On Windows, using native code, you could probably force window that holds popup to get the focus. Or you could have a Frame floating somewhere, being invisible to the eye, but being visible in Java sense, and holding focus.

I am not sure how tray is organized on Gnome/KDE, but I suppose you can use XEmbed and you'll get key events even when you don't have "real" native focus. Or you can use Frame again.

PS: Try using keyword "code" in square brackets [] to get code formatting, instead of

cowwoc
Offline
Joined: 2003-08-24

Denis,

I'm not sure I understand. My JPopupMenu has a JFrame that is visible and the JPopupMenu is drawn inside it as a lightweight component. You specify that Java needs the frame to be "active". Are you saying that once the active frame under win32 is not Java, it is impossible to bring it back? I thought that according to the documentation that is not true (i.e. cross-window focus changes are allowed under win32 but not under Solaris).

Furthermore, I tried forcing a focus change using native code which calls ShowWindow(hwnd, SW_SHOW) but it did not help. Replace "hwnd" with the hwnd of the invoker, invoker.getRootPane(), and SwingUtilities.getWindowAncestor(popupMenu). None of them worked.

I am in no way an expert in win32 programming so it is possible I am not doing something right, but I thought invoking ShowWindow() would be enough to force a focus change. Any ideas?

Gili

denismo
Offline
Joined: 2003-06-28

Hi Gili,

I suspect requestFocus doesn't work because the application is not active. This seems like a bug on our side - we shouldn't return true in this case. To make Frame actually focused, try using toFront() instead.

In the test case, your second requestFocus fails because JRootPane is already focused.

To force the native focus change, you need to use ::SetFocus().

cowwoc
Offline
Joined: 2003-08-24

Denis,

toFront() works, thanks!

I've got a problem with the focus subsystem. I'm going to rant for a bit and please comment on why my rant makes sense or not.

Currently the focus subsystem documentation states that there is no way to guarantee cross-window focus change so requestFocusInWindow() is implemented in a manner that is guaranteed to always fail across all platforms if a cross-window focus change is requested. Ok, so here is my comment:

The documentation does not make it clear on how to make a window "active" on any given platform. There is confusion when to use requestFocus() versus toFront(). Instead of describing each method independantly, why not document them both together and say: for platform X, to make a window active, you must call Y followed by Z and only if both return true is there a chance you will get focus. Right now it is not clear why I had to use toFront() to get keyboard focus under win32 when requestFocus()'s documentation seems to indicate I should call it instead. If "getting focus" under win32 also implies that the window is active (under unix where the two can be independant) then calling requestFocus() should implicitly call toFront(), and vice-versa. The current implementation is very confusing.

Gili

denismo
Offline
Joined: 2003-06-28

This unpredictability is the property of any platform, so this is the reason why we can't and shouldn't write anything like this. The only thing that we can guarantee, as described in "boolean Component.requestFocus(boolean)", is the failur of the request.

Any solution that you may think is a guaranteed way to make something focused on a given platform, may not work on another version of this platform, or just another configuration of it.

On Windows, Win32 API may only return you "true" when it succeeded, but no guarantee when it will succeed. On Unix, we don't even get the result. Therefore, the best we can do Unix is to always return false, and on Windows - to return that result "AND"-ed with the result of our own post-processing of focus events.

Think about it from a different perspective - many HUID guidelines suggest that application shouldn't generally interrupt user from interacting with other windows on the desktop, meaning that cross-application focus requests are not suggested. Windows enforces this restriction (see, for example, documentation for SetForegroundWindow), it is a good programming practice to respect platform rules to let your application look "natural" among other platform application, and Java tries to follow these rules.

So, may be you should rethink the design of your application, and try to accomodate this rule as a part of it rather than fight with it?

cowwoc
Offline
Joined: 2003-08-24

Denis,

Normally I would agree with you, but this is a special case. When the user right-clicks on a tray icon, he's *asking* for a popup menu and as such "stealing focus" away from the desktop and moving it to the popup menu is the desired/expected behavior. I am fairly confident the behavior of my code is the correct one.

I understand the inability to guarantee a focus change, but under win32 calling menuRoot.requestFocusInWindow() should not prevent menuRoot.toFront() from working. The fact that it does sounds like a pretty straight-forward bug to me. Please let me know if you agree/disagree and if you disagree, why (i.e. what is going on when I call menuRoot.requestFocusInWindow() that would -- rightfully so -- prevent menuRoot.toFront() to fail). I'd like to file a bug against this behavior if possible.

Thanks,
Gili

denismo
Offline
Joined: 2003-06-28

To implement your idea correctly, you need to use native popups or AWT should provide a way for Swing to create such popups. They will perform the grab of input in a platform-defined way. It is 4311449.

> I'd like to file a bug against this behavior if possible.

Please file a bug, with a test case and instructions, we'll see if this behavior is incorrect.

Scott Violet

Unfortunately we do not offer any mouse grabbing API:(

-Scott

cowwoc
Offline
Joined: 2003-08-24

You misunderstand. I am not looking for a *mouse* grabbing API, rather a *keyboard* grabbing API.

The JPopupMenu is visible but because the user clicked on the taskbar to get the popup menu in the first place, Java lost its focus. I need to give Java back its focus before making the popup visible, or else when the popup becomes visible with JPopupMenu.show() it does not gain keyboard focus.

I hope this explains the problem in detail. If there is anything else unclear, feel free to ask.

cowwoc
Offline
Joined: 2003-08-24

A follow-up, I've tried calling
[code]
EventQueue.invokeLater(new Runnable()
{
public void run()
{
JPopupMenu.requestFocus(false));
}
};
[/code]
after making the JPopupMenu visible. The call returns true but I still don't get keyboard focus. I am using WinXP where (to my recollection) it denies applications from stealing focus by default. Is it possible that is the reason for this failure?

I also read the one cannot transfer keyboard focus across windows under Solaris. What then do you do for system-tray APIs? Maybe I can apply the same technique for windows. Do you guys have any tips for what I can try?

Thanks,
Gili

Message was edited by: shan-man (Added code formatting)