aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Provotorov <nikita.provotorov@jetbrains.com>2024-04-19 17:35:59 +0200
committerVitaly Provodin <vitaly.provodin@jetbrains.com>2024-05-16 01:43:25 +0400
commit223db10aa19f794abae1a61ae687727ce5b3caf4 (patch)
tree7b24878ad30eaef2892bc155b5cecee830c5dd64
parent1131076ecba8a9f6d09587c92c3535c17964b07c (diff)
downloadJetBrainsRuntime-jetbrains-master-mirror.tar.gz
JBR-6456 Sudden keyboard death on Linux using iBus.jbr-release-17.0.11b1207.24jb17.0.11-b1207.24jetbrains-master-mirror
Add a workaround for the iBus's bug which leads to the issue. (cherry picked from commit b8e9dbf8c9527fd6c412a74f1d920be1bb73b894)
-rw-r--r--src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java161
-rw-r--r--src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java28
-rw-r--r--src/java.desktop/unix/native/libawt_xawt/awt/awt_InputMethod.c75
3 files changed, 264 insertions, 0 deletions
diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java b/src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java
index a7968bc163e..3c8f8eeb542 100644
--- a/src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java
+++ b/src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java
@@ -33,12 +33,15 @@ import java.awt.im.spi.InputMethodContext;
import java.awt.peer.ComponentPeer;
import java.lang.ref.WeakReference;
import java.util.Arrays;
+import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Objects;
+import java.util.Queue;
import java.util.function.Supplier;
import java.util.stream.Stream;
import sun.awt.AWTAccessor;
+import sun.awt.SunToolkit;
import sun.awt.X11GraphicsDevice;
import sun.awt.X11GraphicsEnvironment;
import sun.awt.X11InputMethod;
@@ -246,6 +249,155 @@ public class XInputMethod extends X11InputMethod {
}
+ // JBR-6456: Sudden keyboard death on Linux using iBus.
+ // xicDestroyMustBeDelayed, XIC_DELAYED_TO_BE_DESTROYED_CAPACITY, xicDelayedToBeDestroyed can only be accessed
+ // under the AWT lock
+ // See the #disposeXIC method for the purpose of these fields
+ private static boolean xicDestroyMustBeDelayed = false;
+ private static final int XIC_DELAYED_TO_BE_DESTROYED_CAPACITY = 16;
+ private static final Queue<Long> xicDelayedToBeDestroyed = new ArrayDeque<>(XIC_DELAYED_TO_BE_DESTROYED_CAPACITY);
+
+ static void delayAllXICDestroyUntilAFurtherNotice() {
+ if (log.isLoggable(PlatformLogger.Level.FINE)) {
+ log.fine("delayAllXICDestroyUntilAFurtherNotice(): is being called", new Throwable("Stacktrace"));
+ }
+
+ XToolkit.awtLock();
+ try {
+ if (log.isLoggable(PlatformLogger.Level.FINE)) {
+ log.fine("delayAllXICDestroyUntilAFurtherNotice(): xicDestroyMustBeDelayed=={0}", xicDestroyMustBeDelayed);
+ }
+
+ xicDestroyMustBeDelayed = true;
+ } finally {
+ XToolkit.awtUnlock();
+ }
+ }
+
+ static void delayedXICDestroyShouldBeDone() {
+ XToolkit.awtLock();
+ try {
+ xicDestroyMustBeDelayed = false;
+ doDelayedXICDestroy(false, -1);
+ } finally {
+ XToolkit.awtUnlock();
+ }
+ }
+
+ private static void doDelayedXICDestroy(boolean forced, int maxCountToDestroy) {
+ final boolean isFineLoggable = log.isLoggable(PlatformLogger.Level.FINE);
+
+ if (isFineLoggable) {
+ log.fine(
+ "doDelayedXICDestroy(forced==" + forced + ", maxCountToDestroy==" + maxCountToDestroy + "): is being called",
+ new Throwable("Stacktrace")
+ );
+ }
+
+ assert(SunToolkit.isAWTLockHeldByCurrentThread());
+ assert(forced || !xicDestroyMustBeDelayed);
+
+ while ( (maxCountToDestroy != 0) && !xicDelayedToBeDestroyed.isEmpty() ) {
+ final long pX11IMData = xicDelayedToBeDestroyed.remove();
+ --maxCountToDestroy;
+
+ if (isFineLoggable) {
+ log.fine("doDelayedXICDestroy(): destroying pX11IMData={0}", pX11IMData);
+ }
+
+ assert(pX11IMData != 0);
+ delayedDisposeXIC_disposeXICNative(pX11IMData);
+ }
+ }
+
+ @Override
+ protected void disposeXIC() {
+ awtLock();
+ try {
+ if (log.isLoggable(PlatformLogger.Level.FINE)) {
+ log.fine("disposeXIC(): xicDestroyMustBeDelayed=={0}", xicDestroyMustBeDelayed);
+ }
+
+ if (!xicDestroyMustBeDelayed) {
+ // JBR-6456: Sudden keyboard death on Linux using iBus.
+ // iBus's X11 frontend being run in the async mode (IBUS_ENABLE_SYNC_MODE=0) has a bug leading to a
+ // violation of the communication protocol between iBus and Xlib (so-called "XIM protocol"),
+ // later causing Xlib to behave unexpectedly from iBus's point of view, breaking iBus's
+ // internal state. After all, iBus starts to "steal" all the keyboard events
+ // (so that each call of XFilterEvent(...) with an instance of XKeyEvent returns True).
+ // The initial iBus's bug only appears when XDestroyIC(...) gets called right after a call of
+ // XFilterEvent(...) with an instance of XKeyEvent returned True,
+ // meaning that iBus has started, but hasn't finished yet processing of the key event.
+ // In case of AWT/Swing apps, XDestroyIC gets called whenever a focused HW window gets closed
+ // (because it leads to disposing of the associated input context,
+ // see java.awt.Window#doDispose and sun.awt.im.InputContext#dispose)
+ // So, to work around iBus's bug, we have to avoid calling XDestroyIC until iBus finishes processing of
+ // all the keyboard events it has already started processing of, i.e. until a call of
+ // XFilterEvent(...) returns False.
+ // To achieve that, the implemented fix delays destroying of input contexts whenever a call of
+ // XFilterEvent(...) with an instance of XKeyEvent returns True until one of the next calls of
+ // XFilterEvent(...) with the same instance of XKeyEvent returns False.
+ // The delaying is implemented via storing the native pointers to the input contexts to
+ // xicDelayedToBeDestroyed instead of applying XDestroyIC(...) immediately.
+ // The xicDelayedToBeDestroyed's size is explicitly limited to
+ // XIC_DELAYED_TO_BE_DESTROYED_CAPACITY. If the limit gets reached, a few input contexts gets
+ // pulled from there and destroyed regardless of the current value of xicDestroyMustBeDelayed.
+ // The xicDestroyMustBeDelayed field is responsible for indication whether it's required to delay
+ // the destroying or not. It gets set in #delayAllXICDestroyUntilAFurtherNotice
+ // and unset in delayedXICDestroyShouldBeDone; both are called by sun.awt.X11.XToolkit depending on
+ // the value returned by the calls of sun.awt.X11.XlibWrapper#XFilterEvent.
+
+ super.disposeXIC();
+ return;
+ }
+
+ final long pX11IMData = pData;
+
+ // To make sure that the delayed to be destroyed input context won't get used by AWT/Swing or Xlib
+ // by a mistake, the following things are done:
+ // 1. The input method focus gets detached from the input context (via a call of XUnsetICFocus)
+ // 2. All the native pointers to this instance of XInputMethod
+ // (now it's just the variable currentX11InputMethodInstance in awt_InputMethod.c) get unset
+ // 3. All the java pointers to the native context (now it's just sun.awt.X11InputMethodBase#pData)
+ // get unset as well
+ delayedDisposeXIC_preparation_unsetFocusAndDetachCurrentXICNative();
+
+ // 4. The state of the native context gets reset (effectively via a call of XmbResetIC)
+ delayedDisposeXIC_preparation_resetSpecifiedCtxNative(pX11IMData);
+
+ if (pX11IMData == 0) {
+ if (log.isLoggable(PlatformLogger.Level.FINE)) {
+ log.fine("disposeXIC(): pX11IMData==NULL, skipped");
+ }
+ return;
+ }
+
+ // If the storage is full, a few input context are pulled from there and destroyed regardless of
+ // the value of xicDestroyMustBeDelayed
+ if (xicDelayedToBeDestroyed.size() >= XIC_DELAYED_TO_BE_DESTROYED_CAPACITY) {
+ if (log.isLoggable(PlatformLogger.Level.FINE)) {
+ log.fine(
+ "disposeXIC(): xicDelayedToBeDestroyed.size()=={0} >= XIC_DELAYED_TO_BE_DESTROYED_CAPACITY",
+ xicDelayedToBeDestroyed.size()
+ );
+ }
+
+ doDelayedXICDestroy(true, xicDelayedToBeDestroyed.size() - XIC_DELAYED_TO_BE_DESTROYED_CAPACITY + 1);
+ }
+
+ if (log.isLoggable(PlatformLogger.Level.FINE)) {
+ log.fine(
+ "disposeXIC(): adding pX11IMData=={0} to xicDelayedToBeDestroyed (which already contains {1} elements)",
+ pX11IMData, xicDelayedToBeDestroyed.size()
+ );
+ }
+ xicDelayedToBeDestroyed.add(pX11IMData);
+ } finally {
+ awtUnlock();
+ }
+ }
+
+
static void onXKeyEventFiltering(final boolean isXKeyEventFiltered) {
// Fix of JBR-1573, JBR-2444, JBR-4394 (a.k.a. IDEA-246833).
// Input method is considered broken if and only if all the following statements are true:
@@ -559,6 +711,15 @@ public class XInputMethod extends X11InputMethod {
private native void setXICFocusNative(long window, boolean value, boolean active);
private native void adjustStatusWindow(long window);
+ // 1. Applies XUnsetICFocus to the current input context
+ // 2. Unsets currentX11InputMethodInstance if it's set to this instance of XInputMethod
+ // 3. Unsets sun.awt.X11InputMethodBase#pData
+ private native void delayedDisposeXIC_preparation_unsetFocusAndDetachCurrentXICNative();
+ // Applies XmbResetIC to the passed input context
+ private static native void delayedDisposeXIC_preparation_resetSpecifiedCtxNative(long pX11IMData);
+ // Applies XDestroyIC to the passed input context
+ private static native void delayedDisposeXIC_disposeXICNative(long pX11IMData);
+
private native boolean doesFocusedXICSupportMovingCandidatesNativeWindow();
private native void adjustCandidatesNativeWindowPosition(int x, int y);
diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java
index e2594b3e8ac..2d57707f465 100644
--- a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java
+++ b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java
@@ -1016,11 +1016,22 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
final boolean isKeyEvent = ( (ev.get_type() == XConstants.KeyPress) ||
(ev.get_type() == XConstants.KeyRelease) );
+ final long keyEventSerial = isKeyEvent ? ev.get_xkey().get_serial() : -1;
+
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE) && isKeyEvent) {
keyEventLog.fine("before XFilterEvent:" + ev);
}
if (XlibWrapper.XFilterEvent(ev.getPData(), w)) {
if (isKeyEvent) {
+ if (keyEventLog.isLoggable(PlatformLogger.Level.FINE)) {
+ keyEventLog.fine(
+ "Setting lastFilteredKeyEventSerial=={0} to {1}",
+ lastFilteredKeyEventSerial, keyEventSerial
+ );
+ }
+ lastFilteredKeyEventSerial = keyEventSerial;
+
+ XInputMethod.delayAllXICDestroyUntilAFurtherNotice();
XInputMethod.onXKeyEventFiltering(true);
}
continue;
@@ -1032,6 +1043,14 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
if (isKeyEvent) {
XInputMethod.onXKeyEventFiltering(false);
+ if (keyEventSerial == lastFilteredKeyEventSerial) {
+ // JBR-6456: Sudden keyboard death on Linux using iBus.
+ // If more than 1 key events are being processed by iBus
+ // (i.e. more than one in a row calls of XFilterEvent(...) with instances of XKeyEvent have
+ // returned true),
+ // we have to postpone destroying until the very last one is completely processed)
+ XInputMethod.delayedXICDestroyShouldBeDone();
+ }
}
dispatchEvent(ev);
@@ -1052,6 +1071,15 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
}
}
+
+ // JBR-6456: Sudden keyboard death on Linux using iBus.
+ // The field holds the value of sun.awt.X11.XKeyEvent#get_serial of the last key event, which
+ // XFilterEvent(...) returned True for.
+ // See the usages of the variable for more info.
+ // See sun.awt.X11.XInputMethod#disposeXIC for the detailed explanation of the whole fix.
+ private long lastFilteredKeyEventSerial = -1;
+
+
/**
* Listener installed to detect display changes.
*/
diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/awt_InputMethod.c b/src/java.desktop/unix/native/libawt_xawt/awt/awt_InputMethod.c
index a769721ee58..93f54ace269 100644
--- a/src/java.desktop/unix/native/libawt_xawt/awt/awt_InputMethod.c
+++ b/src/java.desktop/unix/native/libawt_xawt/awt/awt_InputMethod.c
@@ -1634,6 +1634,81 @@ Java_sun_awt_X11_XInputMethod_releaseXICNative(JNIEnv *env,
JNIEXPORT void JNICALL
+Java_sun_awt_X11_XInputMethod_delayedDisposeXIC_1preparation_1unsetFocusAndDetachCurrentXICNative
+ (JNIEnv *env, jobject this)
+{
+ DASSERT(env != NULL);
+ X11InputMethodData *pX11IMData = NULL;
+
+ AWT_LOCK();
+
+ pX11IMData = getX11InputMethodData(env, this);
+ if (pX11IMData == NULL) {
+ AWT_UNLOCK();
+ return;
+ }
+
+ if (pX11IMData->ic_active.xic != (XIC)0) {
+ setXICFocus(pX11IMData->ic_active.xic, False);
+ }
+ if ( (pX11IMData->ic_passive.xic != (XIC)0) && (pX11IMData->ic_passive.xic != pX11IMData->ic_active.xic) ) {
+ setXICFocus(pX11IMData->ic_passive.xic, False);
+ }
+ pX11IMData->current_ic = (XIC)0;
+
+ setX11InputMethodData(env, this, NULL);
+ if ( (*env)->IsSameObject(env, pX11IMData->x11inputmethod, currentX11InputMethodInstance) == JNI_TRUE ) {
+ // currentX11InputMethodInstance never holds a "unique" java ref - it only holds a "weak" copy of
+ // _X11InputMethodData::x11inputmethod, so we mustn't DeleteGlobalRef here
+ currentX11InputMethodInstance = NULL;
+ currentFocusWindow = 0;
+ }
+
+ AWT_UNLOCK();
+}
+
+JNIEXPORT void JNICALL
+Java_sun_awt_X11_XInputMethod_delayedDisposeXIC_1preparation_1resetSpecifiedCtxNative
+ (JNIEnv *env, jclass clazz, const jlong pData)
+{
+ X11InputMethodData * const pX11IMData = (X11InputMethodData *)pData;
+ char* preeditText = NULL;
+
+ if (pX11IMData == NULL) {
+ return;
+ }
+
+ AWT_LOCK();
+
+ if (pX11IMData->ic_active.xic != (XIC)0) {
+ if ( (preeditText = XmbResetIC(pX11IMData->ic_active.xic)) != NULL ) {
+ (void)XFree(preeditText); preeditText = NULL;
+ }
+ }
+ if ( (pX11IMData->ic_passive.xic != (XIC)0) && (pX11IMData->ic_passive.xic != pX11IMData->ic_active.xic) ) {
+ if ( (preeditText = XmbResetIC(pX11IMData->ic_passive.xic)) != NULL ) {
+ (void)XFree(preeditText); preeditText = NULL;
+ }
+ }
+
+ AWT_UNLOCK();
+}
+
+JNIEXPORT void JNICALL
+Java_sun_awt_X11_XInputMethod_delayedDisposeXIC_1disposeXICNative(JNIEnv *env, jclass clazz, jlong pData)
+{
+ X11InputMethodData *pX11IMData = (X11InputMethodData *)pData;
+ if (pX11IMData == NULL) {
+ return;
+ }
+
+ AWT_LOCK();
+ destroyX11InputMethodData(env, pX11IMData); pX11IMData = NULL; pData = 0;
+ AWT_UNLOCK();
+}
+
+
+JNIEXPORT void JNICALL
Java_sun_awt_X11_XInputMethod_setXICFocusNative(JNIEnv *env,
jobject this,
jlong w,