diff --git a/src/java.desktop/aix/native/libawt_xawt/awt/awt_InputMethod.c b/src/java.desktop/aix/native/libawt_xawt/awt/awt_InputMethod.c index 88eb3cbec2d6..1038da7b9524 100644 --- a/src/java.desktop/aix/native/libawt_xawt/awt/awt_InputMethod.c +++ b/src/java.desktop/aix/native/libawt_xawt/awt/awt_InputMethod.c @@ -374,7 +374,7 @@ setXICWindowFocus(XIC ic, Window w) #define INITIAL_LOOKUP_BUF_SIZE 512 Boolean -awt_x11inputmethod_lookupString(XKeyPressedEvent *event, KeySym *keysymp) +awt_x11inputmethod_lookupString(XKeyPressedEvent *event, KeySym *keysymp, Boolean /* not used */) { JNIEnv *env = GetJNIEnv(); X11InputMethodData *pX11IMData = NULL; 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 8d16aeff6cfe..3fc9f51a4b51 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XInputMethod.java @@ -99,11 +99,68 @@ public final class XInputMethod extends X11InputMethod { @Override public void dispatchEvent(AWTEvent e) { if (doesSupportMovingCandidatesNativeWindow) { + if (e.getID() == MouseEvent.MOUSE_PRESSED) { + /* doesSupportMovingCandidatesNativeWindow == true means that natively the IM uses XIMPreeditPosition + * input mode (the term "input style" is used in XOrg docs). + * The main flaw of this mode is that AWT doesn't receive any information about changes in the + * currently composed preedit text. In other words, Java applications don't know whether + * composing is happening now or not and don't have a way to get the current preedit text. Therefore, + * when the caret position changes in response to mouse clicks, Swing doesn't understand that + * there's a need to discard the preedit text (because it thinks there is no any), see + * javax.swing.text.JTextComponent.ComposedTextCaret#positionCaret. + * To prevent the preedit text from following the caret when it's moved in response to mouse clicks, + * let's manually discard the preedit text here. + */ + endComposition(); + } clientComponentCaretPositionTracker.onDispatchEvent(e); } super.dispatchEvent(e); } + @Override + public void endComposition() { + if (!doesSupportMovingCandidatesNativeWindow) { + // Use the old implementation if + // the IM isn't using the new mode introduced in JBR-2460 (XIMPreeditPosition) + super.endComposition(); + return; + } + + if (disposed) { + return; + } + + String preeditText = invokeResetXIC(); + needResetXIC = false; + + awtLock(); + try { + if (composedText != null) { + composedText = null; + // Remove any existing composed text by posting an InputMethodEvent with null composed text + postInputMethodEvent( + InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, + null, + 0, + null, + null, + EventQueue.getMostRecentEventTime() + ); + } + + if (committedText != null) { + preeditText = preeditText == null ? committedText : committedText + preeditText; + committedText = null; + } + + if (preeditText != null && !preeditText.isEmpty()) { + dispatchCommittedText(preeditText); + } + } finally { + awtUnlock(); + } + } // Is called from native private static boolean isJbNewXimClientEnabled() { 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 bd416905deac..52886085b423 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java @@ -955,6 +955,14 @@ public final class XToolkit extends UNIXToolkit implements Runnable { processXkbChanges(ev); } + if (ev.get_type() == XConstants.KeyPress) { + doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC = + mayXResetICReturnThePreeditTextViaNextKeyPressEvent && + isKeyPressSyntetic(ev.get_xkey()); + + mayXResetICReturnThePreeditTextViaNextKeyPressEvent = false; + } + if (XDropTargetEventProcessor.processEvent(ev) || XDragSourceContextPeer.processEvent(ev)) { continue; @@ -1003,6 +1011,7 @@ public final class XToolkit extends UNIXToolkit implements Runnable { XBaseWindow.ungrabInput(); processException(thr); } finally { + doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC = false; // free event data if XGetEventData was called XlibWrapper.XFreeEventData(getDisplay(), ev.pData); awtUnlock(); @@ -1010,6 +1019,62 @@ public final class XToolkit extends UNIXToolkit implements Runnable { } } + + // ================================================================================================================ + // JBR-3112 Linux: Last character issue with Korean. + // XmbResetIC/XwcResetIC are called (by sun.awt.X11InputMethodBase#endComposition) when + // the keyboard focus goes to another Java component. + // By the X11 specification, these functions must return the current preedit text. However, some + // input methods (e.g., iBus and fcitx4) don't return the preedit text, but instead send it later + // (asynchronously) via a combination of a synthetic KeyPress event + XmbLookupString applied to it. + // Not only does this behavior breaks the X11 specification, + // but it also causes the preedit text to wrongly go to the newly focused Java component rather than + // its intended target, the previously focused component for which the preedit text was originally composed. + // Thus, in order to prevent the "outdated" preedit text from going to the newly focused component, let's + // at least discard it at all. + // *How* it's done: the toolkit gets notified whenever XmbResetIC/XwcResetIC gets called and then + // discards the preedit text returned from XmbLookupString/XwcLookupString, applied to the next + // KeyPress event being dispatched. + // ================================================================================================================ + + private volatile boolean mayXResetICReturnThePreeditTextViaNextKeyPressEvent = false; + private boolean doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC = false; + + /** + * Notifies the toolkit that XmbResetIC/XwcResetIC has recently returned null + * (likely meaning that the preedit text will be sent later via a synthetic KeyPress event, + * although the focus may have already moved to another component) + */ + public void xResetICMayReturnThePreeditTextViaNextKeyPressEvent() { + mayXResetICReturnThePreeditTextViaNextKeyPressEvent = true; + } + + /** + * @return true if the composed text returned from XmbLookupString/XwcLookupString + * (see function awt_x11inputmethod_lookupString in awt_InputMethod.c), applied to the currently + * dispatched KeyEvent, must be discarded (instead of being dispatched to the focused component) ; + * false otherwise + * @see XWindow#handleKeyPress(XKeyEvent) + */ + public boolean doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC() { + assert isToolkitThread(); + return doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC; + } + + private static boolean isKeyPressSyntetic(XKeyEvent ev) { + assert (ev.get_type() == XConstants.KeyPress); + + return ( (ev.get_root() == 0) && + (ev.get_subwindow() == 0) && + (ev.get_time() == 0) && + (ev.get_x() == 0) && + (ev.get_y() == 0) && + (ev.get_x_root() == 0) && + (ev.get_y_root() == 0) && + (ev.get_state() == 0) ); + } + + /** * Listener installed to detect display changes. */ diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java b/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java index c14c4ef11275..544507325400 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java @@ -1026,7 +1026,8 @@ class XWindow extends XBaseWindow implements X11ComponentPeer { // REMIND: need to implement looking for disabled events private native boolean x11inputMethodLookupString(long event, - long[] keysymArray); + long[] keysymArray, + boolean keyPressContainsThePreeditTextOfLastXResetIC); private native boolean haveCurrentX11InputMethodInstance(); @@ -1288,7 +1289,12 @@ class XWindow extends XBaseWindow implements X11ComponentPeer { if ( //TODO check if there's an active input method instance // without calling a native method. Is it necessary though? haveCurrentX11InputMethodInstance()) { - if (x11inputMethodLookupString(ev.pData, keysym)) { + + final boolean keyPressContainsThePreeditTextOfLastXResetIC = + Toolkit.getDefaultToolkit() instanceof XToolkit xToolkit && + xToolkit.doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC(); + + if (x11inputMethodLookupString(ev.pData, keysym, keyPressContainsThePreeditTextOfLastXResetIC)) { if (keyEventLog.isLoggable(PlatformLogger.Level.FINE)) { keyEventLog.fine("--XWindow.java XIM did process event; return; dec keysym processed:"+(keysym[0])+ "; hex keysym processed:"+Long.toHexString(keysym[0]) diff --git a/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java b/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java index 7a82384e1187..c038d0947638 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java +++ b/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java @@ -30,6 +30,7 @@ import java.awt.AWTException; import java.awt.Component; import java.awt.Container; import java.awt.EventQueue; +import java.awt.Toolkit; import java.awt.Window; import java.awt.event.InputMethodEvent; import java.awt.font.TextAttribute; @@ -37,10 +38,6 @@ import java.awt.font.TextHitInfo; import java.awt.im.InputMethodHighlight; import java.awt.im.spi.InputMethodContext; import java.awt.peer.ComponentPeer; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.lang.Character.Subset; import java.lang.ref.WeakReference; import java.text.AttributedCharacterIterator; @@ -49,9 +46,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.StringTokenizer; -import java.util.regex.Pattern; +import sun.awt.X11.XToolkit; import sun.awt.im.InputMethodAdapter; import sun.util.logging.PlatformLogger; @@ -255,7 +251,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { */ if (needResetXIC && haveActiveClient() && getClientComponent() != needResetXICClient.get()){ - resetXIC(); + invokeResetXIC(); // needs to reset the last xic focused component. lastXICFocussedComponent = null; @@ -313,7 +309,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { lastXICFocussedComponent = null; isLastXICActive = false; - resetXIC(); + invokeResetXIC(); needResetXICClient.clear(); needResetXIC = false; } @@ -375,7 +371,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { // method could get the input focus. disableInputMethod(); if (needResetXIC) { - resetXIC(); + invokeResetXIC(); needResetXICClient.clear(); needResetXIC = false; } @@ -477,7 +473,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { } } - private void dispatchCommittedText(String str) { + protected final void dispatchCommittedText(String str) { dispatchCommittedText(str, EventQueue.getMostRecentEventTime()); } @@ -625,7 +621,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { return; } - String text = resetXIC(); + String text = invokeResetXIC(); /* needResetXIC is only set to true for active client. So passive client should not reset the flag to false. */ if (active) { @@ -812,6 +808,27 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { } } + protected final String invokeResetXIC() { + if (Toolkit.getDefaultToolkit() instanceof XToolkit xToolkit) { + awtLock(); + try { + final String resetResult = resetXIC(); + if (resetResult == null) { + // If XmbResetIC/XwcResetIC returns null, it means one of the following: + // * There was no preedit text + // * In case of iBus/fcitx4, the preedit text is sent later to the toolkit via + // a synthetic KeyPress event + XmbLookupString/XwcLookupString applied to it + xToolkit.xResetICMayReturnThePreeditTextViaNextKeyPressEvent(); + } + return resetResult; + } finally { + awtUnlock(); + } + } else { + return resetXIC(); + } + } + /* * Native methods */ @@ -826,6 +843,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { protected native void disposeXIC(); + /** Don't use it directly, use {@link #invokeResetXIC} instead */ private native String resetXIC(); protected native boolean setCompositionEnabledNative(boolean enable); 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 a7473da4663e..8af449e848c3 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 @@ -541,7 +541,7 @@ setXICWindowFocus(XIC ic, Window w) #define INITIAL_LOOKUP_BUF_SIZE 512 Boolean -awt_x11inputmethod_lookupString(XKeyPressedEvent *event, KeySym *keysymp) +awt_x11inputmethod_lookupString(XKeyPressedEvent *event, KeySym *keysymp, const Boolean keyPressContainsThePreeditTextOfLastXResetIC) { JNIEnv *env = GetJNIEnv(); X11InputMethodData *pX11IMData = NULL; @@ -627,17 +627,22 @@ awt_x11inputmethod_lookupString(XKeyPressedEvent *event, KeySym *keysymp) /*FALLTHRU*/ case XLookupChars: /* - printf("lookupString: status=XLookupChars, type=%d, state=%x, keycode=%x, keysym=%x\n", - event->type, event->state, event->keycode, keysym); + printf("lookupString: status=XLookupChars, type=%d, state=%x, keycode=%x, keysym=%x, keyPressContainsThePreeditTextOfLastXResetIC=%d\n", + event->type, event->state, event->keycode, keysym, (int)keyPressContainsThePreeditTextOfLastXResetIC); */ - javastr = JNU_NewStringPlatform(env, (const char *)pX11IMData->lookup_buf); - if (javastr != NULL) { - JNU_CallMethodByName(env, NULL, - currentX11InputMethodInstance, - "dispatchCommittedText", - "(Ljava/lang/String;J)V", - javastr, - event->time); + + // JBR-3112 + // See sun.awt.X11.XToolkit#doesCurrentlyDispatchedKeyPressContainThePreeditTextOfLastXResetIC + if (!keyPressContainsThePreeditTextOfLastXResetIC) { + javastr = JNU_NewStringPlatform(env, (const char *)pX11IMData->lookup_buf); + if (javastr != NULL) { + JNU_CallMethodByName(env, NULL, + currentX11InputMethodInstance, + "dispatchCommittedText", + "(Ljava/lang/String;J)V", + javastr, + event->time); + } } break; diff --git a/src/java.desktop/unix/native/libawt_xawt/xawt/XWindow.c b/src/java.desktop/unix/native/libawt_xawt/xawt/XWindow.c index 2b824d15f49e..d97671392a7d 100644 --- a/src/java.desktop/unix/native/libawt_xawt/xawt/XWindow.c +++ b/src/java.desktop/unix/native/libawt_xawt/xawt/XWindow.c @@ -86,7 +86,7 @@ jfieldID targetID; jfieldID graphicsConfigID; extern jobject currentX11InputMethodInstance; -extern Boolean awt_x11inputmethod_lookupString(XKeyPressedEvent *, KeySym *); +extern Boolean awt_x11inputmethod_lookupString(XKeyPressedEvent *, KeySym *, Boolean keyPressContainsThePreeditTextOfLastXResetIC); Boolean awt_UseType4Patch = False; Boolean awt_ServerDetected = False; Boolean awt_XKBDetected = False; @@ -1103,7 +1103,7 @@ JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XWindow_haveCurrentX11InputMethodIns } JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XWindow_x11inputMethodLookupString -(JNIEnv *env, jobject object, jlong event, jlongArray keysymArray) { +(JNIEnv *env, jobject object, jlong event, jlongArray keysymArray, jboolean keyPressContainsThePreeditTextOfLastXResetIC) { KeySym keysym = NoSymbol; Boolean boo; /* keysymArray (and testbuf[]) have dimension 2 because we put there two @@ -1117,7 +1117,11 @@ JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XWindow_x11inputMethodLookupString testbuf[1]=0; - boo = awt_x11inputmethod_lookupString((XKeyPressedEvent*)jlong_to_ptr(event), &keysym); + boo = awt_x11inputmethod_lookupString( + (XKeyPressedEvent*)jlong_to_ptr(event), + &keysym, + (keyPressContainsThePreeditTextOfLastXResetIC == JNI_TRUE) ? True : False + ); testbuf[0] = keysym; (*env)->SetLongArrayRegion(env, keysymArray, 0, 2, (jlong *)(testbuf));