xpra icon
Bug tracker and wiki

Opened 6 weeks ago

Closed 4 weeks ago

#1980 closed defect (fixed)

Java Menu Stays on top of all other windows

Reported by: mjharkin Owned by: mjharkin
Priority: critical Milestone: 2.4
Component: client Version: trunk
Keywords: Cc:

Description

Server on Centos7 r20582
Client Python on Windows r20582
Can't reproduce outside Xpra.

Another Java issue:
When running the attached example: java -jar menuOnTop.jar
If you open the menu and click outside of any other xpra window (set_focus(0)) then the menu remains on top of all other windows. See attached screenshot.

Not sure if it's this problem, but AWT menus seem to open with DIALOG window type instead of POPUP_MENU:

process_new_common: [11, 1370, 267, 131, 100, {'xid': '0xc00040', 'title': 'win0', 'client-machine': '2f8ecabfb918', 'pid': 8589, 'group-leader-xid': 12582919, 'window-type': ('DIALOG',), 'above': True, 'skip-taskbar': True, 'transient-for': 6, 'class-instance': ('sun-awt-X11-XWindowPeer', 'org-eclipse-jdt-internal-jarinjarloader-JarRsrcLoader'), 'override-redirect': True}], metadata={'xid': '0xc00040', 'title': 'win0', 'client-machine': '2f8ecabfb918', 'pid': 8589, 'group-leader-xid': 12582919, 'window-type': ('DIALOG',), 'above': True, 'skip-taskbar': True, 'transient-for': 6, 'class-instance': ('sun-awt-X11-XWindowPeer', 'org-eclipse-jdt-internal-jarinjarloader-JarRsrcLoader'), 'override-redirect': True}, OR=True
2018-10-05 09:16:50,050 make_new_window(..) client_window_classes=[<class 'xpra.client.gtk2.client_window.ClientWindow'>], group_leader_window=<gtk.gdk.Window object at 0x46ee960 (GdkWindow at 0x3797a20)>

At present the only place where this behaves correctly is clicking into another window, on the frame or outside doesn't work.

A really dirty fix I've done is to destroy the menu window in window_manager.py:

    def send_focus(self, wid):
        focuslog("send_focus(%s)", wid)
        if wid==0:
            for w in self._id_to_window.values():
                if w._metadata.get("skip-taskbar"):
                        #look for java AWT
                        wm_class = w._metadata.get("class-instance")
                        if wm_class and len(wm_class)==2 and wm_class[0].startswith("sun-awt-X11"):
                            w.destroy()

Is there a better approach?

Attachments (1)

MenuOnTop.zip (36.5 KB) - added by mjharkin 6 weeks ago.
jar, source and screenshot

Download all attachments as: .zip

Change History (10)

Changed 6 weeks ago by mjharkin

Attachment: MenuOnTop.zip added

jar, source and screenshot

comment:1 Changed 6 weeks ago by Antoine Martin

Owner: changed from Antoine Martin to mjharkin

I believe this is due to grab handling (see #139, ticket:705#comment:7).
Java must be grabbing the pointer, expecting to receive the click outside the drop down menu window.
Since we're forwarding the window to a different display server, we may never get the click event, only a loss of focus. Most toolkits will handle that just fine, but Java is different...

Your patch looks a little bit dangerous to me, I remember testing something similar and causing crashes.
At least, I believe that the check should be modified to only affect OR windows since the drop down menu uses that. And it should be possible to disable this behaviour using an environment variable if this is to be enabled by default.

Ideally, this would be fixed server side by causing the grab to be broken and making Java notice it. I've tried using xdotool key XF86Ungrab but this didn't help.
Looking at XUngrabPointer: The X server performs an UngrabPointer request automatically if the event window or confine_to window for an active pointer grab becomes not viewable or if window reconfiguration causes the confine_to window to lie completely outside the boundaries of the root window - we can't "randomly" make windows hidden and hope that's going to help either. (we don't even know when they expect to hold a grab..)

The Java source around grabs reads: We should always grab both keyboard and pointer to control event flow on popups. This also simplifies synthetic grab implementation. The active grab overrides activated automatic grab.

I patched your source code right at the start of initialize() to add debug logging to AWT X11:

		LogManager logManager = LogManager.getLogManager();
		ConsoleHandler consoleHandler = new ConsoleHandler();
		consoleHandler.setLevel(Level.FINEST);
		consoleHandler.setFormatter(new SimpleFormatter());
		logManager.getLogger("").setLevel(Level.FINER);
		logManager.getLogger("").addHandler(consoleHandler);

And this is what I got (edited slightly) when I clicked on another window (not an xpra window):

FINER: XEvent = type = PropertyNotify, xany = XAnyEvent = type = PropertyNotify, serial = 539, send_event = false, display = 140140154739328, window = 44, 
Oct 06, 2018 2:02:36 AM sun.awt.X11.XToolkit callTimeoutTasks
FINER: XToolkit.callTimeoutTasks(): current time=1538766156238;  tasks=null
Oct 06, 2018 2:02:36 AM sun.awt.X11.XToolkit run
FINER: XEvent = type = FocusOut, xany = XAnyEvent = type = FocusOut, serial = 539, send_event = false, display = 140140154739328, window = sun.awt.X11.XFocusProxyWindow@1e907ad3(c0001f), 
Oct 06, 2018 2:02:36 AM sun.awt.X11.XWindowPeer handleFocusEvent
FINE: XFocusChangeEvent = type = FocusOut, serial = 539, send_event = false, display = 140140154739328, window = sun.awt.X11.XFocusProxyWindow@1e907ad3(c0001f), mode = 0, detail = 3, 
Oct 06, 2018 2:02:36 AM sun.awt.X11.XKeyboardFocusManagerPeer setCurrentFocusedWindow
FINER: Setting current focused window null
Oct 06, 2018 2:02:36 AM sun.awt.X11.XDecoratedPeer handleFocusEvent
FINER: Received focus event on shell: XFocusChangeEvent = type = FocusOut, serial = 539, send_event = false, display = 140140154739328, window = sun.awt.X11.XFocusProxyWindow@1e907ad3(c0001f), mode = 0, detail = 3, 
Oct 06, 2018 2:02:36 AM sun.awt.X11.XToolkit callTimeoutTasks
FINER: XToolkit.callTimeoutTasks(): current time=1538766156243;  tasks=null
Oct 06, 2018 2:02:36 AM java.awt.KeyboardFocusManager retargetFocusEvent
FINER: >>> java.awt.event.WindowEvent[WINDOW_LOST_FOCUS,opposite=null,oldState=0,newState=0] on frame0
Oct 06, 2018 2:02:36 AM java.awt.DefaultKeyboardFocusManager dispatchEvent
FINE: java.awt.event.WindowEvent[WINDOW_LOST_FOCUS,opposite=null,oldState=0,newState=0] on frame0
Oct 06, 2018 2:02:36 AM sun.awt.X11.XToolkit callTimeoutTasks
FINER: XToolkit.callTimeoutTasks(): current time=1538766156244;  tasks=null
Oct 06, 2018 2:02:36 AM java.awt.DefaultKeyboardFocusManager dispatchEvent
FINE: Active javax.swing.JFrame[frame0,100,63,300x80,invalid,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true], Current focused javax.swing.JFrame[frame0,100,63,300x80,invalid,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true], losing focus javax.swing.JFrame[frame0,100,63,300x80,invalid,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true] opposite null
Oct 06, 2018 2:02:36 AM java.awt.KeyboardFocusManager retargetFocusEvent
FINER: >>> java.awt.FocusEvent[FOCUS_LOST,temporary,opposite=null,cause=ACTIVATION] on javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=]
Oct 06, 2018 2:02:36 AM java.awt.DefaultKeyboardFocusManager dispatchEvent
FINE: java.awt.FocusEvent[FOCUS_LOST,temporary,opposite=null,cause=ACTIVATION] on javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=]
Oct 06, 2018 2:02:36 AM sun.awt.X11.XComponentPeer handleJavaFocusEvent
FINER: java.awt.FocusEvent[FOCUS_LOST,temporary,opposite=null,cause=ACTIVATION] on javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=]
Oct 06, 2018 2:02:36 AM sun.awt.X11.XComponentPeer focusLost
FINE: java.awt.FocusEvent[FOCUS_LOST,temporary,opposite=null,cause=ACTIVATION] on javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=]
Oct 06, 2018 2:02:36 AM java.awt.KeyboardFocusManager retargetFocusEvent
FINER: >>> java.awt.event.WindowEvent[WINDOW_DEACTIVATED,opposite=null,oldState=0,newState=0] on frame0
Oct 06, 2018 2:02:36 AM java.awt.DefaultKeyboardFocusManager dispatchEvent
FINE: java.awt.event.WindowEvent[WINDOW_DEACTIVATED,opposite=null,oldState=0,newState=0] on frame0
Oct 06, 2018 2:02:36 AM java.awt.KeyboardFocusManager setGlobalActiveWindow
FINER: Setting global active window to null, old active javax.swing.JFrame[frame0,100,63,300x80,invalid,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,37,300x43,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

So Java is seeing the FocusOut event, handling it but not closing the drop down menu window..

Last edited 6 weeks ago by Antoine Martin (previous) (diff)

comment:2 Changed 6 weeks ago by mjharkin

Great analysis, thanks.

I'll look at trying something on the server side over the weekend.
For now, the best I've got is this which is quite a bit safer and works well apart from 2 clicks are need to enter the menu again.
It's also very close to what's actually happening at least from the perspective of the xpra windows.

    def send_focus(self, wid):
        focuslog("send_focus(%s)", wid)
        if wid==0:
            mouselog("send_focus() Sending mouse event to wid 0 to ungrap java awt menus")
            self.send_button(0, 1, True, [0,0], [], [])

comment:3 Changed 6 weeks ago by mjharkin

My bad, needed the unpress and now works as expected.
Think the place for this would be send_lost_focus().
Can you accept this patch?

    def send_lost_focus(self):
        self.lost_focus_timer = None
        #check that a new window has not gained focus since:
        if self._focused is None:
            mouselog("send_focus() sending button click to wid=0")
            self.send_button(0, 1, True, [0,0], [], [])
            self.send_button(0, 1, False, [0,0], [], [])
            self.send_focus(0)

comment:4 Changed 6 weeks ago by Antoine Martin

Can you accept this patch?

I don't think so: we can't just send click events at 0,0 whenever we get lost focus events as that would wreak havoc with many well behaved applications that do have windows mapped in the top left corner. (ie: OR fullscreen windows)

The w.destroy() solution is actually safer, as it is more targeted.

A better solution might be to automatically grab the pointer (and keyboard?) whenever we see an override-redirect AWT window. The AWT window would then receive the button events outside the window area.

comment:5 Changed 6 weeks ago by mjharkin

Am I understanding this wrong?
I thought a mouse button event sent to wid=0 would have no effect on other windows apart from lost of grab or focus.

comment:6 Changed 6 weeks ago by Antoine Martin

I believe r20608 solves this problem for win32 clients much more cleanly: we force a pointer grab when we find an AWT override redirect window.
And maybe we should apply this workaround to more than just Java's windows.. (the env var XPRA_OR_FORCE_GRAB makes it easier to test at runtime)

For X11 clients, this is a bit more complicated: we were breaking the grab when we got focus change events.
r20609 looks correct, but this is the sort of change that makes me nervous: this "bug" had been there since r12645 (more than 2 years ago) and could cause regressions.

comment:7 Changed 6 weeks ago by mjharkin

Resolution: fixed
Status: newclosed

Perfect, tested on r20608 and this solves the problem from my side at least and a lot cleaner.
I'm still trying to get a handle on all this window manager stuff.

Much appreciated.

comment:8 Changed 4 weeks ago by Antoine Martin

Priority: majorcritical
Resolution: fixed
Status: closedreopened

This causes a serious regression with intellij: all the context menus are broken.

comment:9 Changed 4 weeks ago by Antoine Martin

Resolution: fixed
Status: reopenedclosed

Intellij's popup windows look like this:

process_new_common: [6, 1721, 791, 403, 494, {'xid': '0xe00214', 'title': 'win1', \
    'client-machine': 'desktop', 'pid': 31424, 'group-leader-xid': 14680134, \
    'window-type': ('POPUP_MENU',), 'skip-taskbar': True, 'transient-for': 3, \
    'class-instance': ('sun-awt-X11-XWindowPeer', 'jetbrains-idea-ce'), \
    'override-redirect': True}], metadata=.., OR=True

So r20710 fixes the regression by only applying the OR force grab workaround to awt windows of type DIALOG.
Backported to v2.4 in r20712.

r20711 makes the code more generic and changes the environment variable format to accept wildcards for both the window-type and wmclass:

XPRA_OR_FORCE_GRAB=DIALOG:sun-awt-X11

So we can do things like:

XPRA_OR_FORCE_GRAB=DIALOG:sun-awt-X11,*:anotherwmclass,MENU:someotherwmclass,TOOLBAR:*
Note: See TracTickets for help on using tickets.