JBR-2496 Prevent JVM stealing focus from other applications on Linux (JBR-2497, JBR-2499, JBR-2503, JBR-2652)

(cherry picked from commits 87525d1d2a, 66381f0dec, 8a789e04e9, 665ebc5d47, 98a9219c23)
This commit is contained in:
Dmitry Batrak
2020-06-16 13:13:04 +03:00
committed by alexey.ushakov@jetbrains.com
parent 97bba86061
commit f07119d5e7
8 changed files with 206 additions and 52 deletions

View File

@@ -77,6 +77,9 @@ public class XBaseWindow {
private static XAtom wm_client_leader;
private long userTime;
private static long globalUserTime;
static enum InitialiseState {
INITIALISING,
INITIALISED,
@@ -625,9 +628,10 @@ public class XBaseWindow {
XToolkit.awtLock();
try {
if (focusLog.isLoggable(PlatformLogger.Level.FINER)) {
focusLog.finer("XSetInputFocus on " + Long.toHexString(getWindow()));
focusLog.finer("XSetInputFocus on " + Long.toHexString(getWindow())
+ " with global time " + globalUserTime);
}
XlibWrapper.XSetInputFocus(XToolkit.getDisplay(), getWindow());
XlibWrapper.XSetInputFocus2(XToolkit.getDisplay(), getWindow(), globalUserTime);
} finally {
XToolkit.awtUnlock();
}
@@ -650,6 +654,7 @@ public class XBaseWindow {
try {
this.visible = visible;
if (visible) {
setUserTimeFromGlobal();
XlibWrapper.XMapWindow(XToolkit.getDisplay(), getWindow());
}
else {
@@ -1009,6 +1014,7 @@ public class XBaseWindow {
public void handleVisibilityEvent(XEvent xev) {
}
public void handleKeyPress(XEvent xev) {
setUserTime(xev.get_xkey().get_time());
}
public void handleKeyRelease(XEvent xev) {
}
@@ -1039,6 +1045,7 @@ public class XBaseWindow {
if (!isWheel) {
switch (xev.get_type()) {
case XConstants.ButtonPress:
setUserTime(xbe.get_time());
if (buttonState == 0) {
XWindowPeer parent = getToplevelXWindow();
// See 6385277, 6981400.
@@ -1256,4 +1263,43 @@ public class XBaseWindow {
return x >= getAbsoluteX() && y >= getAbsoluteY() && x < (getAbsoluteX()+getWidth()) && y < (getAbsoluteY()+getHeight());
}
void setUserTimeFromGlobal() {
setUserTime(globalUserTime);
}
protected void setUserTime(long time) {
if (time == userTime) return;
userTime = time;
if ((int)time - (int)globalUserTime > 0 /* accounting for wrap-around */) {
globalUserTime = time;
}
XNETProtocol netProtocol = XWM.getWM().getNETProtocol();
if (netProtocol != null && netProtocol.active()) {
netProtocol.XA_NET_WM_USER_TIME.setCard32Property(this, time);
}
}
static {
String desktopStartupId = XToolkit.getDesktopStartupId();
if (desktopStartupId != null) {
int timePos = desktopStartupId.indexOf("_TIME");
if (timePos < 0) {
log.fine("Couldn't find startup time in DESKTOP_STARTUP_ID: " + desktopStartupId);
} else {
try {
globalUserTime = Long.parseLong(desktopStartupId.substring(timePos + 5));
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine("Extracted startup time from DESKTOP_STARTUP_ID: " + globalUserTime);
}
}
catch (NumberFormatException e) {
log.fine("Couldn't parse startup time in DESKTOP_STARTUP_ID: " + desktopStartupId);
}
}
}
else {
log.fine("No DESKTOP_STARTUP_ID");
}
}
}

View File

@@ -1071,20 +1071,31 @@ abstract class XDecoratedPeer extends XWindowPeer {
focusLog.fine("WM_TAKE_FOCUS on {0}", this);
}
long requestTimeStamp = cl.get_data(1);
if (requestTimeStamp == 0) {
// KDE window manager always sends 0 ('CurrentTime') as timestamp,
// even though it seems to violate ICCCM specification
// (https://bugs.kde.org/show_bug.cgi?id=347153)
requestTimeStamp = XToolkit.getCurrentServerTime();
}
// we should treat WM_TAKE_FOCUS message as user interaction, as it can originate e.g. from user clicking
// on window title bar (there will be no ButtonPress/ButtonRelease events in this case)
setUserTime(requestTimeStamp);
if (XWM.getWMID() == XWM.UNITY_COMPIZ_WM) {
// JDK-8159460
Window focusedWindow = XKeyboardFocusManagerPeer.getInstance()
.getCurrentFocusedWindow();
Window activeWindow = XWindowPeer.getDecoratedOwner(focusedWindow);
if (activeWindow != target) {
requestWindowFocus(cl.get_data(1), true);
requestWindowFocus(requestTimeStamp, true);
} else {
WindowEvent we = new WindowEvent(focusedWindow,
WindowEvent.WINDOW_GAINED_FOCUS);
sendEvent(we);
}
} else {
requestWindowFocus(cl.get_data(1), true);
requestWindowFocus(requestTimeStamp, true);
}
}

View File

@@ -515,23 +515,4 @@ public class XMenuBarPeer extends XBaseMenuWindow implements MenuBarPeer {
selectItem(getFirstSelectableItem(), true);
}
/*
* In previous version keys were handled in handleKeyPress.
* Now we override this function do disable F10 explicit
* processing. All processing is done using KeyEvent.
*/
public void handleKeyPress(XEvent xev) {
XKeyEvent xkey = xev.get_xkey();
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine(xkey.toString());
}
if (isEventDisabled(xev)) {
return;
}
final Component currentSource = getEventSource();
//This is the only difference from XWindow.handleKeyPress
//Ancestor's function can invoke handleF10KeyPress here
handleKeyPress(xkey);
}
} //class XMenuBarPeer

View File

@@ -286,6 +286,8 @@ final class XNETProtocol extends XProtocol implements XStateProtocol, XLayerProt
XAtom XA_NET_WM_WINDOW_OPACITY = XAtom.get("_NET_WM_WINDOW_OPACITY");
XAtom XA_NET_WM_USER_TIME = XAtom.get("_NET_WM_USER_TIME");
/* For _NET_WM_STATE ClientMessage requests */
static final int _NET_WM_STATE_REMOVE =0; /* remove/unset property */
static final int _NET_WM_STATE_ADD =1; /* add/set property */

View File

@@ -311,28 +311,4 @@ public class XPopupMenuPeer extends XMenuWindow implements PopupMenuPeer {
hide();
}
/************************************************
*
* Overriden XWindow keyboard processing
*
************************************************/
/*
* In previous version keys were handled in handleKeyPress.
* Now we override this function do disable F10 explicit
* processing. All processing is done using KeyEvent.
*/
@Override
public void handleKeyPress(XEvent xev) {
XKeyEvent xkey = xev.get_xkey();
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine(xkey.toString());
}
if (isEventDisabled(xev)) {
return;
}
final Component currentSource = getEventSource();
handleKeyPress(xkey);
}
}

View File

@@ -203,6 +203,8 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
static int awt_multiclick_time;
static boolean securityWarningEnabled;
private static String desktopStartupId;
/**
* Dimensions of default virtual screen in pixels. These values are used to
* limit the maximum size of the window.
@@ -398,6 +400,9 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
static native String getEnv(String key);
static String getDesktopStartupId() {
return desktopStartupId;
}
static String getAWTAppClassName() {
return awtAppClassName;
@@ -422,6 +427,10 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
}
awtAppClassName = getCorrectXIDString(mainClassName);
// this should be done before 'load_gtk', as the latter clears the environment variable
desktopStartupId = AccessController.doPrivileged((PrivilegedAction<String>) () ->
getEnv("DESKTOP_STARTUP_ID"));
init();
XWM.init();

View File

@@ -1338,11 +1338,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
return;
}
final String desktopStartupId = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return XToolkit.getEnv("DESKTOP_STARTUP_ID");
}
});
final String desktopStartupId = XToolkit.getDesktopStartupId();
if (desktopStartupId == null) {
return;
}
@@ -2024,6 +2020,7 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
this.visible = visible;
if (visible) {
applyWindowType();
setUserTimeFromGlobal();
XlibWrapper.XMapRaised(XToolkit.getDisplay(), getWindow());
} else {
XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), getWindow());

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2000-2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* @test
* @summary Regression test for JBR-2652 Window is not focused in some cases on Linux
* @key headful
*/
public class TitleBarClickTest {
private static final CompletableFuture<Boolean> f1Opened = new CompletableFuture<>();
private static final CompletableFuture<Boolean> f2Opened = new CompletableFuture<>();
private static final CompletableFuture<Boolean> t2Clicked = new CompletableFuture<>();
private static CompletableFuture<Boolean> t1Focused;
private static CompletableFuture<Boolean> t2Focused;
private static JFrame f1;
private static JFrame f2;
private static JTextField t1;
private static JTextField t2;
private static Robot robot;
public static void main(String[] args) throws Exception {
robot = new Robot();
try {
SwingUtilities.invokeAndWait(TitleBarClickTest::initFrame1);
f1Opened.get(10, TimeUnit.SECONDS);
SwingUtilities.invokeAndWait(TitleBarClickTest::initFrame2);
f2Opened.get(10, TimeUnit.SECONDS);
clickAt(t2);
t2Clicked.get(10, TimeUnit.SECONDS);
SwingUtilities.invokeAndWait(() -> t1Focused = new CompletableFuture<>());
clickAtTitle(f1);
t1Focused.get(10, TimeUnit.SECONDS);
SwingUtilities.invokeAndWait(() -> t2Focused = new CompletableFuture<>());
robot.delay(1000); // give WM a bit more time
f2.toFront();
t2Focused.get(10, TimeUnit.SECONDS);
} finally {
SwingUtilities.invokeAndWait(TitleBarClickTest::disposeUI);
}
}
private static void initFrame1() {
f1 = new JFrame("first");
f1.add(t1 = new JTextField());
t1.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (t1Focused != null) t1Focused.complete(Boolean.TRUE);
}
});
f1.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
f1Opened.complete(Boolean.TRUE);
}
});
f1.setBounds(100, 100, 300, 100);
f1.setVisible(true);
}
private static void initFrame2() {
f2 = new JFrame("second");
f2.add(t2 = new JTextField());
t2.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
if (t2Focused != null) t2Focused.complete(Boolean.TRUE);
}
});
t2.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
t2Clicked.complete(Boolean.TRUE);
}
});
f2.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
f2Opened.complete(Boolean.TRUE);
}
});
f2.setBounds(450, 100, 300, 100);
f2.setVisible(true);
}
private static void disposeUI() {
if (f1 != null) f1.dispose();
if (f2 != null) f2.dispose();
}
private static void clickAt(int x, int y) {
robot.delay(1000); // needed for GNOME, to give it some time to update internal state after window showing
robot.mouseMove(x, y);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
}
private static void clickAt(Component component) {
Point location = component.getLocationOnScreen();
clickAt(location.x + component.getWidth() / 2, location.y + component.getHeight() / 2);
}
private static void clickAtTitle(Window window) {
int topInset = window.getInsets().top;
if (topInset <= 0) throw new RuntimeException("Window doesn't have title bar");
Point location = window.getLocationOnScreen();
clickAt(location.x + window.getWidth() / 2, location.y + topInset / 2);
}
}