JBR-4394, IDEA-246833: refactoring and blocking the fix of JBR-1573 to recreate IMs and ICs during preediting.

(cherry picked from commits 7a36587db8d3d5a63c5691243cd5bf56733c06e6, 321a4a3f83)
This commit is contained in:
Nikita Provotorov
2022-06-03 19:31:44 +07:00
committed by jbrbot
parent 6a422ac7f7
commit ff35faf77c
5 changed files with 270 additions and 75 deletions

View File

@@ -156,6 +156,95 @@ public class XInputMethod extends X11InputMethod {
return peer.getContentWindow();
}
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:
// * XFilterEvent have filtered more than filteredEventsThreshold last events of types KeyPress, KeyRelease;
// * Input method hasn't been changed (e.g. recreated);
// * The input context is not in preedit state (XNPreeditStartCallback has been called but then XNPreeditDoneCallback - hasn't)
// The functionality is disabled
if (BrokenImDetectionContext.EATEN_EVENTS_THRESHOLD < 1) {
return;
}
// Must be called within AWT_LOCK
if (!XToolkit.isAWTLockHeldByCurrentThread()) {
return;
}
if (isXKeyEventFiltered) {
final long nativeDataPtr = BrokenImDetectionContext.obtainCurrentXimNativeDataPtr();
if (nativeDataPtr == 0) {
++BrokenImDetectionContext.eatenKeyEventsCount;
} else {
final int isDuringPreediting = BrokenImDetectionContext.isDuringPreediting(nativeDataPtr);
if (isDuringPreediting > 0) {
BrokenImDetectionContext.eatenKeyEventsCount = 0;
} else if (isDuringPreediting == 0) {
++BrokenImDetectionContext.eatenKeyEventsCount;
} else if (BrokenImDetectionContext.isCurrentXicPassive(nativeDataPtr)) {
// Unfortunately for passive XIC (XIMPreeditNothing | XIMStatusNothing) we have no way to get know
// whether the XIC is in preediting state or not, so we won't handle this case.
BrokenImDetectionContext.eatenKeyEventsCount = 0;
} else {
++BrokenImDetectionContext.eatenKeyEventsCount;
}
}
} else {
BrokenImDetectionContext.eatenKeyEventsCount = 0;
}
if (BrokenImDetectionContext.eatenKeyEventsCount > BrokenImDetectionContext.EATEN_EVENTS_THRESHOLD) {
BrokenImDetectionContext.eatenKeyEventsCount = 0;
recreateAllXIC();
}
}
private static class BrokenImDetectionContext {
static final int EATEN_EVENTS_THRESHOLD;
static int eatenKeyEventsCount = 0;
/**
* @return pointer to X11InputMethodData
*/
static native long obtainCurrentXimNativeDataPtr();
/**
* <0 - unknown
* >0 - true
* 0 - false
*/
static native int isDuringPreediting(long ximNativeDataPtr);
static native boolean isCurrentXicPassive(long ximNativeDataPtr);
static {
int eatenEventsThresholdInitializer = 7;
final String eventsThresholdMode = System.getProperty("recreate.x11.input.method", "true");
if ("false".equals(eventsThresholdMode)) {
eatenEventsThresholdInitializer = 0;
} else if (!"true".equals(eventsThresholdMode)) {
try {
eatenEventsThresholdInitializer = Integer.parseInt(eventsThresholdMode);
} catch (NumberFormatException err) {
log.warning(
"Invalid value of \"recreate.x11.input.method\" system property \"" +
eventsThresholdMode +
"\". Only \"true\", \"false\" and integer values are supported",
err
);
}
}
EATEN_EVENTS_THRESHOLD = eatenEventsThresholdInitializer;
}
}
/*
* Native methods
*/

View File

@@ -1021,21 +1021,28 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
}
}
}
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE) && (
ev.get_type() == XConstants.KeyPress
|| ev.get_type() == XConstants.KeyRelease)) {
final boolean isKeyEvent = ( (ev.get_type() == XConstants.KeyPress) ||
(ev.get_type() == XConstants.KeyRelease) );
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE) && isKeyEvent) {
keyEventLog.fine("before XFilterEvent:" + ev);
}
if (XlibWrapper.XFilterEvent(ev.getPData(), w)) {
if (isKeyEvent) {
XInputMethod.onXKeyEventFiltering(true);
}
continue;
}
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE) && (
ev.get_type() == XConstants.KeyPress
|| ev.get_type() == XConstants.KeyRelease)) {
if (keyEventLog.isLoggable(PlatformLogger.Level.FINE) && isKeyEvent) {
keyEventLog.fine(
"after XFilterEvent:" + ev); // IS THIS CORRECT?
}
if (isKeyEvent) {
XInputMethod.onXKeyEventFiltering(false);
}
dispatchEvent(ev);
} catch (Throwable thr) {
XBaseWindow.ungrabInput();

View File

@@ -364,8 +364,8 @@ public abstract class X11InputMethod extends X11InputMethodBase {
}
}
static void recreateAllXIC() {
// NOTE: called from native within AWT_LOCK
protected static void recreateAllXIC() {
// NOTE: called within AWT_LOCK
Map<X11InputMethod, Integer> im2ctxid = new HashMap<>(activeInputMethods.size());
for (X11InputMethod im : activeInputMethods) {
im2ctxid.put(im, im.releaseXIC());

View File

@@ -32,6 +32,7 @@
#include <sun_awt_X11InputMethodBase.h>
#include <sun_awt_X11_XInputMethod.h>
#include <sun_awt_X11_XInputMethod_BrokenImDetectionContext.h>
#include <stdio.h>
#include <stdlib.h>
@@ -129,6 +130,10 @@ typedef struct _X11InputMethodData {
#endif
char *lookup_buf; /* buffer used for XmbLookupString */
int lookup_buf_len; /* lookup buffer size in bytes */
struct {
Boolean isBetweenPreeditStartAndPreeditDone;
} brokenImDetectionContext;
} X11InputMethodData;
/*
@@ -404,6 +409,8 @@ freeX11InputMethodData(JNIEnv *env, X11InputMethodData *pX11IMData)
free((void *)pX11IMData->lookup_buf);
}
pX11IMData->brokenImDetectionContext.isBetweenPreeditStartAndPreeditDone = False;
free((void *)pX11IMData);
}
@@ -1041,6 +1048,8 @@ createXIC(JNIEnv * env, X11InputMethodData *pX11IMData, Window w)
XNResetState, XIMInitialState,
NULL);
pX11IMData->brokenImDetectionContext.isBetweenPreeditStartAndPreeditDone = False;
/* Add the global reference object to X11InputMethod to the list. */
addToX11InputMethodGRefList(pX11IMData->x11inputmethod);
@@ -1061,16 +1070,53 @@ createXIC(JNIEnv * env, X11InputMethodData *pX11IMData, Window w)
static int
PreeditStartCallback(XIC ic, XPointer client_data, XPointer call_data)
{
/*ARGSUSED*/
/* printf("Native: PreeditStartCallback\n"); */
/* printf("Native: PreeditStartCallback(%p, %p, %p)\n", ic, client_data, call_data); */
JNIEnv * const env = GetJNIEnv();
AWT_LOCK();
jobject javaInputMethodGRef = (jobject)client_data;
if (!isX11InputMethodGRefInList(javaInputMethodGRef)) {
goto finally;
}
X11InputMethodData * const pX11IMData = getX11InputMethodData(env, javaInputMethodGRef);
if (pX11IMData == NULL) {
goto finally;
}
pX11IMData->brokenImDetectionContext.isBetweenPreeditStartAndPreeditDone = True;
finally:
AWT_UNLOCK();
return -1;
}
static void
PreeditDoneCallback(XIC ic, XPointer client_data, XPointer call_data)
{
/*ARGSUSED*/
/* printf("Native: PreeditDoneCallback\n"); */
/* printf("Native: PreeditDoneCallback(%p, %p, %p)\n", ic, client_data, call_data); */
JNIEnv * const env = GetJNIEnv();
AWT_LOCK();
jobject javaInputMethodGRef = (jobject)client_data;
if (!isX11InputMethodGRefInList(javaInputMethodGRef)) {
goto finally;
}
X11InputMethodData * const pX11IMData = getX11InputMethodData(env, javaInputMethodGRef);
if (pX11IMData == NULL) {
goto finally;
}
pX11IMData->brokenImDetectionContext.isBetweenPreeditStartAndPreeditDone = False;
finally:
AWT_UNLOCK();
return;
}
/*
@@ -1083,6 +1129,8 @@ static void
PreeditDrawCallback(XIC ic, XPointer client_data,
XIMPreeditDrawCallbackStruct *pre_draw)
{
/* printf("Native: PreeditDrawCallback(%p, %p, %p)\n", ic, client_data, pre_draw); */
JNIEnv *env = GetJNIEnv();
X11InputMethodData *pX11IMData = NULL;
jmethodID x11imMethodID;
@@ -1175,7 +1223,7 @@ PreeditCaretCallback(XIC ic, XPointer client_data,
XIMPreeditCaretCallbackStruct *pre_caret)
{
/*ARGSUSED*/
/* printf("Native: PreeditCaretCallback\n"); */
/* printf("Native: PreeditCaretCallback(%p, %p, %p)\n", ic, client_data, pre_caret); */
}
#if defined(__linux__)
@@ -1183,14 +1231,14 @@ static void
StatusStartCallback(XIC ic, XPointer client_data, XPointer call_data)
{
/*ARGSUSED*/
/*printf("StatusStartCallback:\n"); */
/*printf("Native: StatusStartCallback(%p, %p, %p)\n", ic, client_data, call_data); */
}
static void
StatusDoneCallback(XIC ic, XPointer client_data, XPointer call_data)
{
/*ARGSUSED*/
/*printf("StatusDoneCallback:\n"); */
/*printf("Native: StatusDoneCallback(%p, %p, %p)\n", ic, client_data, call_data); */
JNIEnv *env = GetJNIEnv();
X11InputMethodData *pX11IMData = NULL;
StatusWindow *statusWindow;
@@ -1221,7 +1269,7 @@ StatusDrawCallback(XIC ic, XPointer client_data,
XIMStatusDrawCallbackStruct *status_draw)
{
/*ARGSUSED*/
/*printf("StatusDrawCallback:\n"); */
/*printf("Native: StatusDrawCallback(%p, %p, %p)\n", ic, client_data, status_draw); */
JNIEnv *env = GetJNIEnv();
X11InputMethodData *pX11IMData = NULL;
StatusWindow *statusWindow;
@@ -1274,6 +1322,8 @@ StatusDrawCallback(XIC ic, XPointer client_data,
#endif /* __linux__ */
static void CommitStringCallback(XIC ic, XPointer client_data, XPointer call_data) {
/* printf("Native: CommitStringCallback(%p, %p, %p)\n", ic, client_data, call_data); */
JNIEnv *env = GetJNIEnv();
XIMText * text = (XIMText *)call_data;
X11InputMethodData *pX11IMData = NULL;
@@ -1514,6 +1564,110 @@ Java_sun_awt_X11_XInputMethod_setXICFocusNative(JNIEnv *env,
AWT_UNLOCK();
}
/*
* Class: sun_awt_X11_XInputMethod_BrokenImDetectionContext
* Method: obtainCurrentXimNativeDataPtr
* Signature: ()J
*
* NOTE: MUST BE CALLED WITHIN AWT_LOCK
*/
JNIEXPORT jlong JNICALL Java_sun_awt_X11_XInputMethod_00024BrokenImDetectionContext_obtainCurrentXimNativeDataPtr
(JNIEnv *env, jclass cls)
{
jlong result = 0;
if (isX11InputMethodGRefInList(currentX11InputMethodInstance)) {
X11InputMethodData * const pX11IMData = getX11InputMethodData(env, currentX11InputMethodInstance);
result = ptr_to_jlong(pX11IMData);
}
return result;
}
/*
* Class: sun_awt_X11_XInputMethod_BrokenImDetectionContext
* Method: isCurrentXicPassive
* Signature: (J)Z
*
* NOTE: MUST BE CALLED WITHIN AWT_LOCK
*/
JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XInputMethod_00024BrokenImDetectionContext_isCurrentXicPassive
(JNIEnv *env, jclass cls, jlong ximNativeDataPtr)
{
X11InputMethodData * const pX11ImData = (X11InputMethodData *)jlong_to_ptr(ximNativeDataPtr);
if (pX11ImData == NULL) {
return JNI_FALSE;
}
const jboolean result = (pX11ImData->current_ic == NULL) ? JNI_FALSE
: (pX11ImData->current_ic == pX11ImData->ic_passive) ? JNI_TRUE
: JNI_FALSE;
return result;
}
static XIMPreeditState getPreeditStateOf(XIC xic) {
#if defined(__linux__) && defined(_LP64) && !defined(_LITTLE_ENDIAN)
// XIMPreeditState value which is used for XGetICValues must be 32bit on BigEndian XOrg's xlib
unsigned int state = XIMPreeditUnKnown;
#else
XIMPreeditState state = XIMPreeditUnKnown;
#endif
XVaNestedList preeditStateAttr = XVaCreateNestedList(0, XNPreeditState, &state, NULL);
if (preeditStateAttr == NULL) {
return XIMPreeditUnKnown;
}
const char * const unsupportedAttrs = XGetICValues(xic, XNPreeditAttributes, preeditStateAttr, NULL);
XFree((void *)preeditStateAttr);
if (unsupportedAttrs != NULL) {
return XIMPreeditUnKnown;
}
return (state == XIMPreeditEnable) ? XIMPreeditEnable
: (state == XIMPreeditDisable) ? XIMPreeditDisable
: XIMPreeditUnKnown;
}
/*
* Class: sun_awt_X11_XInputMethod_BrokenImDetectionContext
* Method: isDuringPreediting
* Signature: ()I
*
* Returns the following values:
* * >0 in case the IM is in preediting state;
* * 0 in case the IM is not in preediting state;
* * <0 in case it's unknown whether the IM is in preediting state or not.
*
* NOTE: MUST BE CALLED WITHIN AWT_LOCK
*/
JNIEXPORT jint JNICALL Java_sun_awt_X11_XInputMethod_00024BrokenImDetectionContext_isDuringPreediting
(JNIEnv *env, jclass cls, jlong ximNativeDataPtr)
{
X11InputMethodData * const pX11ImData = (X11InputMethodData *)jlong_to_ptr(ximNativeDataPtr);
if (pX11ImData == NULL) {
return -1;
}
jint result = -1;
if (pX11ImData->brokenImDetectionContext.isBetweenPreeditStartAndPreeditDone) {
result = 1;
} else if (pX11ImData->current_ic != NULL) {
const XIMPreeditState preeditState = getPreeditStateOf(pX11ImData->current_ic);
if (preeditState == XIMPreeditEnable) {
result = 1;
} else if (preeditState == XIMPreeditDisable) {
result = 0;
}
}
return result;
}
/*
* Class: sun_awt_X11InputMethodBase
* Method: initIDs

View File

@@ -653,63 +653,6 @@ JNIEXPORT void JNICALL Java_sun_awt_X11_XlibWrapper_XWindowEvent
XWindowEvent( (Display *) jlong_to_ptr(display), (Window)window, event_mask, (XEvent *) jlong_to_ptr(event_return));
}
static int filteredEventsCount = 0;
static int filteredEventsThreshold = -1;
static const int DEFAULT_THRESHOLD = 5;
#define KeyPressEventType 2
#define KeyReleaseEventType 3
static void checkBrokenInputMethod(XEvent * event, jboolean isEventFiltered) {
// Fix for JBR-2444
// By default filteredEventsThreshold == 5, you can turn it off with
// recreate.x11.input.method=false
if (filteredEventsThreshold < 0) {
filteredEventsThreshold = 0;
// read from VM-property
JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
jclass systemCls = (*env)->FindClass(env, "java/lang/System");
CHECK_NULL(systemCls);
jmethodID mid = (*env)->GetStaticMethodID(env, systemCls, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
CHECK_NULL(mid);
jstring name = (*env)->NewStringUTF(env, "recreate.x11.input.method");
CHECK_NULL(name);
jstring jvalue = (*env)->CallStaticObjectMethod(env, systemCls, mid, name);
if (jvalue == NULL) {
filteredEventsThreshold = DEFAULT_THRESHOLD;
} else {
const char * utf8string = (*env)->GetStringUTFChars(env, jvalue, NULL);
if (utf8string != NULL) {
const int parsedVal = atoi(utf8string);
if (parsedVal > 0)
filteredEventsThreshold = parsedVal;
else if (strncmp(utf8string, "true", 4) == 0)
filteredEventsThreshold = DEFAULT_THRESHOLD;
}
(*env)->ReleaseStringUTFChars(env, jvalue, utf8string);
}
(*env)->DeleteLocalRef(env, name);
}
if (filteredEventsThreshold <= 0)
return;
if (event->type == KeyPressEventType || event->type == KeyReleaseEventType) {
if (isEventFiltered) {
filteredEventsCount++;
} else {
filteredEventsCount = 0;
}
if (filteredEventsCount > filteredEventsThreshold) {
JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
JNU_CallStaticMethodByName(env, NULL, "sun/awt/X11InputMethod", "recreateAllXIC", "()V");
filteredEventsCount = 0;
}
}
}
/*
* Class: sun_awt_X11_XlibWrapper
* Method: XFilterEvent
@@ -724,8 +667,10 @@ JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XlibWrapper_XFilterEvent
return (jboolean)True;
}
#endif
jboolean isEventFiltered = (jboolean) XFilterEvent((XEvent *) jlong_to_ptr(ptr), (Window) window);
checkBrokenInputMethod((XEvent *)jlong_to_ptr(ptr), isEventFiltered);
XEvent* const xEvent = (XEvent *)jlong_to_ptr(ptr);
const jboolean isEventFiltered = (jboolean) XFilterEvent(xEvent, (Window) window);
return isEventFiltered;
}