Skip to content

Commit

Permalink
JBR-6456 Sudden keyboard death on Linux using iBus.
Browse files Browse the repository at this point in the history
Add a workaround for the iBus's bug which leads to the issue.

(cherry picked from commit b8e9dbf)
  • Loading branch information
NikitkoCent authored and vprovodin committed May 15, 2024
1 parent 1131076 commit 223db10
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 0 deletions.
161 changes: 161 additions & 0 deletions src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@
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;
Expand Down Expand Up @@ -246,6 +249,155 @@ long getCurrentParentWindow() {
}


// 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:
Expand Down Expand Up @@ -559,6 +711,15 @@ private static int obtainDistanceBetween(final Rectangle rectangle, final Point
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);
Expand Down
28 changes: 28 additions & 0 deletions src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java
Original file line number Diff line number Diff line change
Expand Up @@ -1016,11 +1016,22 @@ public void run(boolean loop)
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;
Expand All @@ -1032,6 +1043,14 @@ public void run(boolean loop)

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);
Expand All @@ -1052,6 +1071,15 @@ public void run(boolean loop)
}
}


// 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.
*/
Expand Down
75 changes: 75 additions & 0 deletions src/java.desktop/unix/native/libawt_xawt/awt/awt_InputMethod.c
Original file line number Diff line number Diff line change
Expand Up @@ -1633,6 +1633,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,
Expand Down

0 comments on commit 223db10

Please sign in to comment.