SDL: Support key composing (i.e. dead keys) in Wayland driver (#4296)

From 5c78df9c2337746bb929337239d000f8a2e6560f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luis=20C=C3=A1ceres?= <[EMAIL REDACTED]>
Date: Wed, 14 Apr 2021 00:56:50 +0100
Subject: [PATCH] Support key composing (i.e. dead keys) in Wayland driver
 (#4296)

Based on an old patch by chw from the old Bugzilla issue tracker.

Authored-by: chw

Co-authored-by: Sam Lantinga <slouken@libsdl.org>
---
 src/video/wayland/SDL_waylanddyn.h      |  1 +
 src/video/wayland/SDL_waylandevents.c   | 56 +++++++++++++++++++++++--
 src/video/wayland/SDL_waylandevents_c.h |  2 +
 src/video/wayland/SDL_waylandsym.h      |  8 ++++
 4 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/src/video/wayland/SDL_waylanddyn.h b/src/video/wayland/SDL_waylanddyn.h
index 63b831f0a..55ac1ebf7 100644
--- a/src/video/wayland/SDL_waylanddyn.h
+++ b/src/video/wayland/SDL_waylanddyn.h
@@ -38,6 +38,7 @@ struct wl_shm;
 #include "wayland-cursor.h"
 #include "wayland-util.h"
 #include "xkbcommon/xkbcommon.h"
+#include "xkbcommon/xkbcommon-compose.h"
 
 #ifdef __cplusplus
 extern "C"
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 73b81c1e0..2657a276e 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -57,6 +57,7 @@
 #include <poll.h>
 #include <unistd.h>
 #include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
 
 /* Weston uses a ratio of 10 units per scroll tick */
 #define WAYLAND_WHEEL_AXIS_UNIT 10
@@ -624,7 +625,7 @@ keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
                        uint32_t format, int fd, uint32_t size)
 {
     struct SDL_WaylandInput *input = data;
-    char *map_str;
+    char *map_str, *locale;
 
     if (!data) {
         close(fd);
@@ -661,6 +662,30 @@ keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard,
         input->xkb.keymap = NULL;
         return;
     }
+
+    /*
+     * See https://blogs.s-osg.org/compose-key-support-weston/
+     * for further explanation on dead keys in Wayland.
+     */
+
+    /* Look up the preferred locale, falling back to "C" as default */
+    if (!(locale = SDL_getenv("LC_ALL")))
+        if (!(locale = SDL_getenv("LC_CTYPE")))
+            if (!(locale = SDL_getenv("LANG")))
+                locale = "C";
+    /* Set up XKB compose table */
+    input->xkb.compose_table = WAYLAND_xkb_compose_table_new_from_locale(input->display->xkb_context,
+                                              locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
+    if (input->xkb.compose_table) {
+        /* Set up XKB compose state */
+        input->xkb.compose_state = WAYLAND_xkb_compose_state_new(input->xkb.compose_table,
+                                              XKB_COMPOSE_STATE_NO_FLAGS);
+        if (!input->xkb.compose_state) {
+            fprintf(stderr, "could not create XKB compose state\n");
+            WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
+            input->xkb.compose_table = NULL;
+        }
+    }
 }
 
 static void
@@ -709,6 +734,7 @@ keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint
 {
     SDL_WindowData *window = input->keyboard_focus;
     const xkb_keysym_t *syms;
+    xkb_keysym_t sym;
 
     if (!window || window->keyboard_device != input || !input->xkb.state) {
         return SDL_FALSE;
@@ -718,15 +744,33 @@ keyboard_input_get_text(char text[8], const struct SDL_WaylandInput *input, uint
     if (WAYLAND_xkb_state_key_get_syms(input->xkb.state, key + 8, &syms) != 1) {
         return SDL_FALSE;
     }
+    sym = syms[0];
 
 #ifdef SDL_USE_IME
-    if (SDL_IME_ProcessKeyEvent(syms[0], key + 8)) {
+    if (SDL_IME_ProcessKeyEvent(sym, key + 8)) {
         *handled_by_ime = SDL_TRUE;
         return SDL_TRUE;
     }
 #endif
 
-    return WAYLAND_xkb_keysym_to_utf8(syms[0], text, 8) > 0;
+    if (WAYLAND_xkb_compose_state_feed(input->xkb.compose_state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
+        switch(WAYLAND_xkb_compose_state_get_status(input->xkb.compose_state)) {
+            case XKB_COMPOSE_COMPOSING:
+                *handled_by_ime = SDL_TRUE;
+                return SDL_TRUE;
+            case XKB_COMPOSE_CANCELLED:
+            default:
+                sym = XKB_KEY_NoSymbol;
+                break;
+            case XKB_COMPOSE_NOTHING:
+                break;
+            case XKB_COMPOSE_COMPOSED:
+                sym = WAYLAND_xkb_compose_state_get_one_sym(input->xkb.compose_state);
+                break;
+        }
+    }
+
+    return WAYLAND_xkb_keysym_to_utf8(sym, text, 8) > 0;
 }
 
 static void
@@ -1239,6 +1283,12 @@ void Wayland_display_destroy_input(SDL_VideoData *d)
     if (input->seat)
         wl_seat_destroy(input->seat);
 
+    if (input->xkb.compose_state)
+        WAYLAND_xkb_compose_state_unref(input->xkb.compose_state);
+
+    if (input->xkb.compose_table)
+        WAYLAND_xkb_compose_table_unref(input->xkb.compose_table);
+
     if (input->xkb.state)
         WAYLAND_xkb_state_unref(input->xkb.state);
 
diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h
index ac4fcceb3..f3604ac36 100644
--- a/src/video/wayland/SDL_waylandevents_c.h
+++ b/src/video/wayland/SDL_waylandevents_c.h
@@ -64,6 +64,8 @@ struct SDL_WaylandInput {
     struct {
         struct xkb_keymap *keymap;
         struct xkb_state *state;
+        struct xkb_compose_table *compose_table;
+        struct xkb_compose_state *compose_state;
     } xkb;
 
     /* information about axis events on current frame */
diff --git a/src/video/wayland/SDL_waylandsym.h b/src/video/wayland/SDL_waylandsym.h
index 5e9bafefa..278c4ff17 100644
--- a/src/video/wayland/SDL_waylandsym.h
+++ b/src/video/wayland/SDL_waylandsym.h
@@ -118,6 +118,14 @@ SDL_WAYLAND_SYM(enum xkb_state_component, xkb_state_update_mask, (struct xkb_sta
                       xkb_layout_index_t depressed_layout,\
                       xkb_layout_index_t latched_layout,\
                       xkb_layout_index_t locked_layout) )
+SDL_WAYLAND_SYM(struct xkb_compose_table *, xkb_compose_table_new_from_locale, (struct xkb_context *,\
+                      const char *locale, enum xkb_compose_compile_flags) )
+SDL_WAYLAND_SYM(void, xkb_compose_table_unref, (struct xkb_compose_table *) )
+SDL_WAYLAND_SYM(struct xkb_compose_state *, xkb_compose_state_new, (struct xkb_compose_table *, enum xkb_compose_state_flags) )
+SDL_WAYLAND_SYM(void, xkb_compose_state_unref, (struct xkb_compose_state *) )
+SDL_WAYLAND_SYM(enum xkb_compose_feed_result, xkb_compose_state_feed, (struct xkb_compose_state *, xkb_keysym_t) )
+SDL_WAYLAND_SYM(enum xkb_compose_status, xkb_compose_state_get_status, (struct xkb_compose_state *) )
+SDL_WAYLAND_SYM(xkb_keysym_t, xkb_compose_state_get_one_sym, (struct xkb_compose_state *) )
 SDL_WAYLAND_SYM(void, xkb_keymap_key_for_each, (struct xkb_keymap *, xkb_keymap_key_iter_t, void*) )
 SDL_WAYLAND_SYM(int, xkb_keymap_key_get_syms_by_level, (struct xkb_keymap *,
                                                         xkb_keycode_t,