From bf568311a68e8f0b34cf21eb2481dd083c64a583 Mon Sep 17 00:00:00 2001 From: hujianwei Date: Thu, 10 Nov 2022 12:12:53 +0000 Subject: [PATCH] Improve handling of keysyms returned by input methods * text_input.c (struct _KeysymMap): New struct. (struct _TextInput): New field `keysym_map'. (ClearKeysymMap, InsertKeysym, RemoveKeysym, GetKeysym): New function. (InputDoLeave, HandleResourceDestroy): Clear the keysym map. (NoticeLeave): Fix coding style. (LookupString): Always prefer keysyms looked up. (XLTextInputDispatchCoreEvent): Store keysym into map upon KeyPress under the keycode, and use that keysym with the corresponding KeyRelease. --- text_input.c | 204 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 173 insertions(+), 31 deletions(-) diff --git a/text_input.c b/text_input.c index 8b911b1..e5b44fe 100644 --- a/text_input.c +++ b/text_input.c @@ -94,6 +94,7 @@ typedef struct _TextInput TextInput; typedef struct _TextInputState TextInputState; typedef struct _TextPosition TextPosition; typedef struct _PreeditBuffer PreeditBuffer; +typedef struct _KeysymMap KeysymMap; typedef enum _XimStyleKind XimStyleKind; @@ -113,6 +114,18 @@ enum PendingSurroundingText = (1 << 2), }; +struct _KeysymMap +{ + /* Packed map between keycodes and keysyms. */ + KeyCode *keycodes; + + /* The keysyms. */ + KeySym *keysyms; + + /* The number of keycodes and keysyms in this map. */ + int key_count; +}; + struct _PreeditBuffer { /* Buffer data. */ @@ -188,6 +201,10 @@ struct _TextInput /* Number of commit requests performed. */ uint32_t serial; + + /* Map between keys currently held down and keysyms they looked up + to. */ + KeysymMap keysym_map; }; /* Structure describing a list of TextInput resources associated with @@ -241,6 +258,93 @@ static XIMStyle xim_style; /* The order in which XIM input styles will be searched for. */ static XimStyleKind xim_style_order[5]; + + +static void +ClearKeysymMap (KeysymMap *map) +{ + XLFree (map->keycodes); + XLFree (map->keysyms); + map->keycodes = NULL; + map->keysyms = NULL; + map->key_count = 0; +} + +static void +InsertKeysym (KeysymMap *map, KeyCode keycode, KeySym keysym) +{ + int i; + + /* Insert keysym into map, under keycode. See if map already + contains the given keycode. */ + + for (i = 0; i < map->key_count; ++i) + { + if (map->keycodes[i] == keycode) + { + map->keysyms[i] = keysym; + return; + } + } + + /* Otherwise, add it to the end. */ + map->key_count++; + map->keycodes + = XLRealloc (map->keycodes, + map->key_count * sizeof *map->keycodes); + map->keysyms + = XLRealloc (map->keysyms, + map->key_count * sizeof *map->keysyms); + map->keycodes[map->key_count - 1] = keycode; + map->keysyms[map->key_count - 1] = keysym; +} + +static void +RemoveKeysym (KeysymMap *map, KeyCode keycode) +{ + int i; + + /* Find where the keycode is located in the map. */ + + for (i = 0; i < map->key_count; ++i) + { + if (map->keycodes[i] == keycode) + { + /* Remove the keycode from the list and shrink it. */ + memcpy (&map->keycodes[i], &map->keycodes[i + 1], + (map->key_count - (i + 1)) * sizeof *map->keycodes); + memcpy (&map->keysyms[i], &map->keysyms[i + 1], + (map->key_count - (i + 1)) * sizeof *map->keysyms); + + map->key_count -= 1; + map->keycodes + = XLRealloc (map->keycodes, + map->key_count * sizeof *map->keycodes); + map->keysyms + = XLRealloc (map->keysyms, + map->key_count * sizeof *map->keysyms); + + return; + } + } +} + +static KeySym +GetKeysym (KeysymMap *map, KeyCode keycode) +{ + int i; + + for (i = 0; i < map->key_count; ++i) + { + if (map->keycodes[i] == keycode) + return map->keysyms[i]; + } + + return 0; +} + + + static int CurrentCursorX (TextInput *input) { @@ -929,6 +1033,11 @@ InputDoLeave (TextInput *input, Surface *old_surface) if (input->current_state.surrounding_text) XLFree (input->current_state.surrounding_text); + /* Clear the keysym-keycode table. Correlating key release with key + press events is no longer important, as a leave event has been + sent to the seat. */ + ClearKeysymMap (&input->keysym_map); + memset (&input->current_state, 0, sizeof input->current_state); } @@ -986,6 +1095,9 @@ HandleResourceDestroy (struct wl_resource *resource) if (input->buffer) FreePreeditBuffer (input->buffer); + /* Destroy the map of pressed keycodes to keysyms. */ + ClearKeysymMap (&input->keysym_map); + /* Free the text input itself. */ XLFree (input); } @@ -1081,21 +1193,21 @@ NoticeLeave (TextInputClientInfo *info) DebugPrint ("client info: %p", info); - input = info->inputs.next; - while (input != &info->inputs) - { - DebugPrint ("sending leave to text input %p", input); + input = info->inputs.next; + while (input != &info->inputs) + { + DebugPrint ("sending leave to text input %p", input); - /* Otherwise, if info->focus_surface->resource is still - there, send the leave event to each text input. */ - if (info->focus_surface->resource) - /* Send the enter event to each text input. */ - zwp_text_input_v3_send_leave (input->resource, - info->focus_surface->resource); - InputDoLeave (input, info->focus_surface); + /* Otherwise, if info->focus_surface->resource is still + there, send the leave event to each text input. */ + if (info->focus_surface->resource) + /* Send the enter event to each text input. */ + zwp_text_input_v3_send_leave (input->resource, + info->focus_surface->resource); + InputDoLeave (input, info->focus_surface); - input = input->next; - } + input = input->next; + } /* And clear the focus surface. */ info->focus_surface = NULL; @@ -3157,10 +3269,14 @@ LookupString (TextInput *input, XEvent *event, KeySym *keysym_return) DebugPrint ("status is: %d", (int) status); /* If no string was returned, return False. Otherwise, convert the - string to UTF-8 and commit it. */ - if (status != XLookupChars && status != XLookupBoth) + string to UTF-8 and commit it. However, use the keysym if both a + keysym and string were looked up, as a keysym allows modifiers to + be correctly dispatched to the seat and avoids doing potentially + expensive character conversion. */ + if (status != XLookupChars) { - if (status == XLookupKeySym && keysym_return) + if ((status == XLookupKeySym + || status == XLookupBoth) && keysym_return) /* Return the keysym if it was looked up. */ *keysym_return = keysym; @@ -3175,21 +3291,6 @@ LookupString (TextInput *input, XEvent *event, KeySym *keysym_return) /* Convert the string. */ buffer = ConvertString (buffer, nbytes, &buffer_size); - /* If the string happens to consist of only 1 control character and - a keysym was also found, give preference to the keysym. */ - if (buffer_size == 1 && status == XLookupBoth - && buffer[0] > 0 && buffer[0] < 32) - { - DebugPrint ("using keysym in preference to single control char"); - - XFree (buffer); - - if (keysym_return) - *keysym_return = keysym; - - return False; - } - if (buffer) CommitString (input, buffer, buffer_size); XFree (buffer); @@ -3328,6 +3429,47 @@ XLTextInputDispatchCoreEvent (Surface *surface, XEvent *event) DebugPrint ("lookup failed; dispatching event to seat; " "keysym is: %lu", keysym); + /* If the event is a KeyPress event and a keysym was + looked up, record the keysym in the text input's + keycode-keysym table, so the correct keycode can be + looked up upon the next KeyRelease event. X input + methods tend not to filter the KeyRelease events, so + the KeyRelease event for an event that changed the + keysym will be sent with the wrong keycode, which + does not matter much with X programs, but leads to + Wayland programs constantly autorepeating the keycode + for which a KeyPress event was sent. */ + + if (event->xkey.type == KeyPress && keysym) + { + DebugPrint ("inserting keysym %lu into map under %u", + keysym, event->xkey.keycode); + InsertKeysym (&input->keysym_map, event->xkey.keycode, + keysym); + } + else if (event->xkey.type == KeyRelease) + { + /* If no keysym was committed by the input method, + use the keysym provided in the last KeyPress + event for the keycode. Otherwise, the input + method probably knows better than us, so use the + keysym provided by the input method. */ + if (!keysym) + { + keysym = GetKeysym (&input->keysym_map, + event->xkey.keycode); + DebugPrint ("obtained keysym %lu for keycode %u" + " while processing KeyRelease event", + keysym, event->xkey.keycode); + } + + DebugPrint ("removing keycode %u from map", + event->xkey.keycode); + + /* Remove the keycode from the keysym map. */ + RemoveKeysym (&input->keysym_map, event->xkey.keycode); + } + XLSeatDispatchCoreKeyEvent (im_seat, surface, event, keysym); } }