JBR-7662: Fix key repeat manager sometimes not cancelling properly [WLToolkit]

(cherry picked from commit e1b1116bb9)
This commit is contained in:
Nikita Tsarev
2024-09-30 10:33:37 +02:00
committed by Nikita Gubarkov
parent c73769e20a
commit c901b53ba3
2 changed files with 42 additions and 3 deletions

View File

@@ -38,13 +38,16 @@ class WLKeyboard {
} }
private class KeyRepeatManager { private class KeyRepeatManager {
// Methods and fields should only be accessed from the EDT
private final Timer timer = new Timer("WLKeyboard.KeyRepeatManager", true); private final Timer timer = new Timer("WLKeyboard.KeyRepeatManager", true);
private TimerTask currentRepeatTask; private TimerTask currentRepeatTask;
private int delayBeforeRepeatMillis; private int delayBeforeRepeatMillis;
private int delayBetweenRepeatMillis; private int delayBetweenRepeatMillis;
// called from native code
void setRepeatInfo(int charsPerSecond, int delayMillis) { void setRepeatInfo(int charsPerSecond, int delayMillis) {
// this function receives (0, 0) when key repeat is disabled // this function receives (0, 0) when key repeat is disabled
assert EventQueue.isDispatchThread();
this.delayBeforeRepeatMillis = delayMillis; this.delayBeforeRepeatMillis = delayMillis;
if (charsPerSecond > 0) { if (charsPerSecond > 0) {
this.delayBetweenRepeatMillis = (int) (1000.0 / charsPerSecond); this.delayBetweenRepeatMillis = (int) (1000.0 / charsPerSecond);
@@ -59,26 +62,33 @@ class WLKeyboard {
return this.delayBeforeRepeatMillis > 0 || this.delayBetweenRepeatMillis > 0; return this.delayBeforeRepeatMillis > 0 || this.delayBetweenRepeatMillis > 0;
} }
// called from native code
void cancelRepeat() { void cancelRepeat() {
assert EventQueue.isDispatchThread();
if (currentRepeatTask != null) { if (currentRepeatTask != null) {
currentRepeatTask.cancel(); currentRepeatTask.cancel();
currentRepeatTask = null; currentRepeatTask = null;
} }
} }
// called from native code
void startRepeat(long timestamp, int keycode) { void startRepeat(long timestamp, int keycode) {
assert EventQueue.isDispatchThread();
cancelRepeat(); cancelRepeat();
if (keycode == 0 || !isRepeatEnabled()) { if (keycode == 0 || !isRepeatEnabled()) {
return; return;
} }
long delta = timestamp - System.currentTimeMillis(); long delta = timestamp - System.currentTimeMillis();
currentRepeatTask = new TimerTask() { currentRepeatTask = new TimerTask() {
@Override @Override
public void run() { public void run() {
try { try {
EventQueue.invokeAndWait(() -> { EventQueue.invokeAndWait(() -> {
if (this == currentRepeatTask) {
handleKeyPress(delta + System.currentTimeMillis(), keycode, true); handleKeyPress(delta + System.currentTimeMillis(), keycode, true);
}
}); });
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
@@ -136,6 +146,7 @@ class WLKeyboard {
} }
public void onLostFocus() { public void onLostFocus() {
assert EventQueue.isDispatchThread();
keyRepeatManager.cancelRepeat(); keyRepeatManager.cancelRepeat();
cancelCompose(); cancelCompose();
} }

View File

@@ -1204,6 +1204,9 @@ getJavaKeyCharForKeycode(xkb_keycode_t xkbKeycode) {
// Posts an XKB keysym as KEY_TYPED events, without consulting the current compose state. // Posts an XKB keysym as KEY_TYPED events, without consulting the current compose state.
static void static void
handleKeyTypeNoCompose(long timestamp, xkb_keycode_t xkbKeycode) { handleKeyTypeNoCompose(long timestamp, xkb_keycode_t xkbKeycode) {
#ifdef WL_KEYBOARD_DEBUG
fprintf(stderr, "handleKeyTypeNoCompose: xkbKeycode = %d\n", xkbKeycode);
#endif
int bufSize = xkb.state_key_get_utf8(keyboard.state, xkbKeycode, NULL, 0) + 1; int bufSize = xkb.state_key_get_utf8(keyboard.state, xkbKeycode, NULL, 0) + 1;
char buf[bufSize]; char buf[bufSize];
xkb.state_key_get_utf8(keyboard.state, xkbKeycode, buf, bufSize); xkb.state_key_get_utf8(keyboard.state, xkbKeycode, buf, bufSize);
@@ -1213,8 +1216,17 @@ handleKeyTypeNoCompose(long timestamp, xkb_keycode_t xkbKeycode) {
// Handles generating KEY_TYPED events for an XKB keysym, translating it using the active compose state // Handles generating KEY_TYPED events for an XKB keysym, translating it using the active compose state
static void static void
handleKeyType(long timestamp, xkb_keycode_t xkbKeycode) { handleKeyType(long timestamp, xkb_keycode_t xkbKeycode) {
#ifdef WL_KEYBOARD_DEBUG
fprintf(stderr, "handleKeyType(xkbKeycode = %d)\n", xkbKeycode);
#endif
xkb_keysym_t keysym = xkb.state_key_get_one_sym(keyboard.state, xkbKeycode); xkb_keysym_t keysym = xkb.state_key_get_one_sym(keyboard.state, xkbKeycode);
#ifdef WL_KEYBOARD_DEBUG
char buf[256];
xkb.keysym_get_name(keysym, buf, sizeof buf);
fprintf(stderr, "handleKeyType: keysym = %d (%s)\n", keysym, buf);
#endif
if (!keyboard.composeState || if (!keyboard.composeState ||
(xkb.compose_state_feed(keyboard.composeState, keysym) == XKB_COMPOSE_FEED_IGNORED)) { (xkb.compose_state_feed(keyboard.composeState, keysym) == XKB_COMPOSE_FEED_IGNORED)) {
handleKeyTypeNoCompose(timestamp, xkbKeycode); handleKeyTypeNoCompose(timestamp, xkbKeycode);
@@ -1250,12 +1262,24 @@ handleKeyType(long timestamp, xkb_keycode_t xkbKeycode) {
// 2. From the key repeat manager. In this case, isRepeat = true and isPressed = true. // 2. From the key repeat manager. In this case, isRepeat = true and isPressed = true.
static void static void
handleKey(long timestamp, uint32_t keycode, bool isPressed, bool isRepeat) { handleKey(long timestamp, uint32_t keycode, bool isPressed, bool isRepeat) {
#ifdef WL_KEYBOARD_DEBUG
fprintf(stderr, "handleKey(keycode = %d, isPressed = %d, isRepeat = %d)\n", keycode, isPressed, isRepeat);
#endif
JNIEnv *env = getEnv(); JNIEnv *env = getEnv();
xkb_keycode_t xkbKeycode = keycode + 8; xkb_keycode_t xkbKeycode = keycode + 8;
bool keyRepeats = xkb.keymap_key_repeats(keyboard.keymap, xkbKeycode);
xkb_keysym_t keysym = translateKeycodeToKeysym(keycode, TRANSLATE_USING_ACTIVE_LAYOUT); xkb_keysym_t keysym = translateKeycodeToKeysym(keycode, TRANSLATE_USING_ACTIVE_LAYOUT);
xkb_keysym_t qwertyKeysym = translateKeycodeToKeysym(keycode, TRANSLATE_USING_QWERTY); xkb_keysym_t qwertyKeysym = translateKeycodeToKeysym(keycode, TRANSLATE_USING_QWERTY);
#ifdef WL_KEYBOARD_DEBUG
char buf[256];
xkb.keysym_get_name(keysym, buf, sizeof buf);
fprintf(stderr, "handleKey: keysym = %d (%s)\n", keysym, buf);
xkb.keysym_get_name(qwertyKeysym, buf, sizeof buf);
fprintf(stderr, "handleKey: qwertyKeysym = %d (%s)\n", qwertyKeysym, buf);
#endif
int javaKeyCode, javaExtendedKeyCode, javaKeyLocation; int javaKeyCode, javaExtendedKeyCode, javaKeyLocation;
// If the national layouts support is enabled, and the current keyboard is not ascii-capable, // If the national layouts support is enabled, and the current keyboard is not ascii-capable,
@@ -1288,6 +1312,10 @@ handleKey(long timestamp, uint32_t keycode, bool isPressed, bool isRepeat) {
} }
} }
#ifdef WL_KEYBOARD_DEBUG
fprintf(stderr, "handleKey: javaKeyCode = %d\n", javaKeyCode);
#endif
struct WLKeyEvent event = { struct WLKeyEvent event = {
.timestamp = timestamp, .timestamp = timestamp,
.id = isPressed ? java_awt_event_KeyEvent_KEY_PRESSED : java_awt_event_KeyEvent_KEY_RELEASED, .id = isPressed ? java_awt_event_KeyEvent_KEY_PRESSED : java_awt_event_KeyEvent_KEY_RELEASED,
@@ -1303,11 +1331,11 @@ handleKey(long timestamp, uint32_t keycode, bool isPressed, bool isRepeat) {
if (isPressed) { if (isPressed) {
handleKeyType(timestamp, xkbKeycode); handleKeyType(timestamp, xkbKeycode);
if (!isRepeat && xkb.keymap_key_repeats(keyboard.keymap, xkbKeycode)) { if (!isRepeat && keyRepeats) {
(*env)->CallVoidMethod(env, keyboard.keyRepeatManager, startRepeatMID, timestamp, keycode); (*env)->CallVoidMethod(env, keyboard.keyRepeatManager, startRepeatMID, timestamp, keycode);
JNU_CHECK_EXCEPTION(env); JNU_CHECK_EXCEPTION(env);
} }
} else { } else if (keyRepeats) {
(*env)->CallVoidMethod(env, keyboard.keyRepeatManager, cancelRepeatMID); (*env)->CallVoidMethod(env, keyboard.keyRepeatManager, cancelRepeatMID);
JNU_CHECK_EXCEPTION(env); JNU_CHECK_EXCEPTION(env);
} }