mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-2496 Prevent JVM stealing focus from other applications on Linux (JBR-2497, JBR-2499, JBR-2503, JBR-2652)
(cherry picked from commits87525d1d2a,66381f0dec,8a789e04e9,665ebc5d47,98a9219c23)
This commit is contained in:
committed by
alexey.ushakov@jetbrains.com
parent
97bba86061
commit
f07119d5e7
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
132
test/jdk/jb/java/awt/Focus/TitleBarClickTest.java
Normal file
132
test/jdk/jb/java/awt/Focus/TitleBarClickTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user