JBR-6456 Sudden keyboard death on Linux using iBus.

Add a workaround for the iBus's bug which leads to the issue.

(cherry picked from commit b8e9dbf8c9)
(cherry picked from commit 7355948065)
This commit is contained in:
Nikita Provotorov
2024-04-19 17:35:59 +02:00
committed by jbrbot
parent 099c53541c
commit 9cb30a006d
4 changed files with 265 additions and 0 deletions

View File

@@ -332,6 +332,7 @@ ifeq ($(call isTargetOs, windows macosx)+$(ENABLE_HEADLESS_ONLY), false+false)
DISABLED_WARNINGS_gcc_XRBackendNative.c := maybe-uninitialized, \
DISABLED_WARNINGS_gcc_XToolkit.c := unused-result, \
DISABLED_WARNINGS_gcc_XWindow.c := unused-function, \
DISABLED_WARNINGS_gcc_awt_InputMethod.c := unused-label, \
DISABLED_WARNINGS_clang_awt_Taskbar.c := parentheses, \
DISABLED_WARNINGS_clang_gtk3_interface.c := unused-function parentheses, \
DISABLED_WARNINGS_clang_GLXSurfaceData.c := unused-function, \
@@ -339,6 +340,7 @@ ifeq ($(call isTargetOs, windows macosx)+$(ENABLE_HEADLESS_ONLY), false+false)
DISABLED_WARNINGS_clang_OGLPaints.c := format-nonliteral, \
DISABLED_WARNINGS_clang_screencast_pipewire.c := format-nonliteral, \
DISABLED_WARNINGS_clang_sun_awt_X11_GtkFileDialogPeer.c := parentheses, \
DISABLED_WARNINGS_clang_awt_InputMethod.c := unused-label, \
DISABLED_WARNINGS_clang_XWindow.c := unused-function, \
DISABLED_WARNINGS_clang_aix := deprecated-non-prototype, \
DISABLED_WARNINGS_clang_aix_awt_Taskbar.c := parentheses, \

View File

@@ -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;
@@ -303,6 +306,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:
@@ -616,6 +768,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);

View File

@@ -988,11 +988,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;
@@ -1004,6 +1015,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);
@@ -1075,6 +1094,14 @@ 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.
*/

View File

@@ -1638,6 +1638,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,