From 18d0711ff391ceee6e8c8dfe57960a16a80bcbdf Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 17 Aug 2022 13:16:26 +0200 Subject: [PATCH] fix(linux): Fix reordering of output This change implements a commit queue which allows to control the order of the output. This ensures that any backspace we generate will be processed before the character we're adding. Requires changes in ibus (surrounding text fix (see #7072) and prefilter change) Fixes #1489, #4028, #4029, #4030, #4505, #5510, #6639 --- linux/ibus-keyman/src/engine.c | 112 +++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 33 deletions(-) diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index eda666adaa..39a56a2013 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -51,32 +51,50 @@ #define KEYMAN_LALT 56 // 0x38 #define KEYMAN_RCTRL 97 // 0x61 #define KEYMAN_RALT 100 // 0x64 +#define KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL 202 +#define KEYMAN_NOCHAR_KEYSYM (0xffff | 0x1000000) // Unicode NOCHAR typedef struct _IBusKeymanEngine IBusKeymanEngine; typedef struct _IBusKeymanEngineClass IBusKeymanEngineClass; -struct _IBusKeymanEngine { - IBusEngine parent; +#define MAX_QUEUE_SIZE 100 - /* members */ - km_kbp_keyboard *keyboard; - km_kbp_state *state; - gchar *ldmlfile; - gchar *kb_name; - gchar *char_buffer; - gboolean lctrl_pressed; - gboolean rctrl_pressed; - gboolean lalt_pressed; - gboolean ralt_pressed; - gboolean emitting_keystroke; - IBusProperty *status_prop; - IBusPropList *prop_list; +struct commit_queue_item { + gchar *char_buffer; + gboolean emitting_keystroke; + guint keyval; + guint keycode; + guint state; +}; + +struct _IBusKeymanEngine { + IBusEngine parent; + + /* members */ + km_kbp_keyboard *keyboard; + km_kbp_state *state; + gchar *ldmlfile; + gchar *kb_name; + gchar *char_buffer; + gboolean lctrl_pressed; + gboolean rctrl_pressed; + gboolean lalt_pressed; + gboolean ralt_pressed; + gboolean emitting_keystroke; + guint event_keyval; + guint event_keycode; + guint event_state; + IBusProperty *status_prop; + IBusPropList *prop_list; #ifdef GDK_WINDOWING_X11 - Display *xdisplay; + Display *xdisplay; #endif #ifdef GDK_WINDOWING_WAYLAND - GdkWaylandDisplay *wldisplay; + GdkWaylandDisplay *wldisplay; #endif + + struct commit_queue_item commit_queue[MAX_QUEUE_SIZE]; + int queue_end; }; struct _IBusKeymanEngineClass { @@ -609,12 +627,8 @@ process_persist_action( return TRUE; } -static gboolean process_emit_keystroke_action(IBusKeymanEngine *keyman) { - if (keyman->char_buffer != NULL) { - ibus_keyman_engine_commit_string(keyman, keyman->char_buffer); - g_free(keyman->char_buffer); - keyman->char_buffer = NULL; - } +static gboolean +process_emit_keystroke_action(IBusKeymanEngine *keyman) { keyman->emitting_keystroke = TRUE; return TRUE; } @@ -648,17 +662,41 @@ process_capslock_action( return TRUE; } -static gboolean process_end_action(IBusKeymanEngine *keyman) { - if (keyman->char_buffer != NULL) { - ibus_keyman_engine_commit_string(keyman, keyman->char_buffer); - g_free(keyman->char_buffer); - keyman->char_buffer = NULL; - } - if (keyman->emitting_keystroke) { - keyman->emitting_keystroke = FALSE; - return FALSE; +static void +commit_text(IBusKeymanEngine *keyman) { + if (keyman->queue_end > 0) { + if (keyman->commit_queue[0].char_buffer != NULL) { + ibus_keyman_engine_commit_string(keyman, keyman->commit_queue[0].char_buffer); + g_free(keyman->commit_queue[0].char_buffer); + } + if (keyman->commit_queue[0].emitting_keystroke) { + ibus_engine_forward_key_event(keyman, keyman->commit_queue[0].keyval, keyman->commit_queue[0].keycode, keyman->commit_queue[0].state); + } + keyman->queue_end--; + memmove(keyman->commit_queue, &keyman->commit_queue[1], sizeof(struct commit_queue_item) * keyman->queue_end); } +} +static gboolean +process_end_action(IBusKeymanEngine *keyman) { + keyman->commit_queue[keyman->queue_end].char_buffer = keyman->char_buffer; + keyman->commit_queue[keyman->queue_end].emitting_keystroke = keyman->emitting_keystroke; + keyman->commit_queue[keyman->queue_end].keyval = keyman->event_keyval; + keyman->commit_queue[keyman->queue_end].keycode = keyman->event_keycode; + keyman->commit_queue[keyman->queue_end].state = keyman->event_state; + keyman->queue_end++; + if (keyman->queue_end >= MAX_QUEUE_SIZE) { + g_error("Overflow of keyman commit_queue!"); + keyman->queue_end = MAX_QUEUE_SIZE - 1; + } + keyman->char_buffer = NULL; + keyman->emitting_keystroke = FALSE; + + // Forward a fake key event to get the correct order of events so that any backspace key we + // generated will be processed before the character we're adding. We need to send a + // valid keyval/keycode combination so that it doesn't get swallowed by GTK but which + // isn't very likely used in real keyboards. F24 seems to work for that. + ibus_engine_forward_key_event(keyman, KEYMAN_NOCHAR_KEYSYM, KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL, IBUS_PREFILTER_MASK); return TRUE; } @@ -724,6 +762,9 @@ ibus_keyman_engine_process_key_event( guint state ) { IBusKeymanEngine *keyman = (IBusKeymanEngine *)engine; + keyman->event_keyval = keyval; + keyman->event_keycode = keycode; + keyman->event_state = state; gboolean isKeyDown = !(state & IBUS_RELEASE_MASK); @@ -732,6 +773,11 @@ ibus_keyman_engine_process_key_event( "DAR: ibus_keyman_engine_process_key_event - keyval=0x%02x keycode=0x%02x, state=0x%02x, isKeyDown=%d", keyval, keycode, state, isKeyDown); + if (keycode == KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL && (state & IBUS_PREFILTER_MASK)) { + commit_text(keyman); + return TRUE; + } + // REVIEW: why don't we handle these keys? switch (keycode) { case KEYMAN_LCTRL: @@ -805,7 +851,7 @@ ibus_keyman_engine_process_key_event( const km_kbp_action_item *action_items = km_kbp_state_action_items(keyman->state, &num_action_items); if (!process_actions(engine, action_items, num_action_items)) - return FALSE; + return TRUE; context = km_kbp_state_context(keyman->state); g_message("after processing all actions");