From 223db10aa19f794abae1a61ae687727ce5b3caf4 Mon Sep 17 00:00:00 2001 From: Nikita Provotorov Date: Fri, 19 Apr 2024 17:35:59 +0200 Subject: 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 b8e9dbf8c9527fd6c412a74f1d920be1bb73b894) --- .../unix/classes/sun/awt/X11/XInputMethod.java | 161 +++++++++++++++++++++ .../unix/classes/sun/awt/X11/XToolkit.java | 28 ++++ .../unix/native/libawt_xawt/awt/awt_InputMethod.c | 75 ++++++++++ 3 files changed, 264 insertions(+) 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 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 @@ -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, -- cgit v1.2.3