JBR-3112 Linux: Last character issue with Korean.

- Ignores the IM text returned from XmbLookupString/XwcLookupString if the KeyPress event which XmbResetIC was called with was synthetic and the first after a call of XmbResetIC/XwcResetIC.
- Only for the new mode introduced in JBR-2460 (-Djb.awt.newXimClient.preferBelowTheSpot=true): cancel text composing on each mouse press, so that preedit text stops following the caret if it's moving in response to mouse clicks.

(cherry picked from commit 43a9a3a17a)
(cherry picked from commit 156e5d9b65)
(cherry picked from commit 59f0ca804f)
This commit is contained in:
Nikita Provotorov
2024-03-12 04:39:33 +01:00
committed by jbrbot
parent 56e349f350
commit bf80402fae
7 changed files with 183 additions and 28 deletions

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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.
*/

View File

@@ -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])

View File

@@ -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);

View File

@@ -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;

View File

@@ -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));