SDL: ime: ibus: Retrieve cursor position and selection

From dd7bed9ebc0e002551ac8bfbe7413cd3249473e5 Mon Sep 17 00:00:00 2001
From: Guldoman <[EMAIL REDACTED]>
Date: Thu, 5 May 2022 03:10:35 +0200
Subject: [PATCH] ime: ibus: Retrieve cursor position and selection

Also, if `SDL_HINT_IME_SUPPORT_EXTENDED_TEXT` is enabled, make use of
`SDL_TEXTEDITING_EXT` by sending the full preedit string.
---
 src/core/linux/SDL_ibus.c | 213 +++++++++++++++++++++++++++++---------
 1 file changed, 162 insertions(+), 51 deletions(-)

diff --git a/src/core/linux/SDL_ibus.c b/src/core/linux/SDL_ibus.c
index 60af0ba2925..dc0b928eb9d 100644
--- a/src/core/linux/SDL_ibus.c
+++ b/src/core/linux/SDL_ibus.c
@@ -22,6 +22,7 @@
 
 #ifdef HAVE_IBUS_IBUS_H
 #include "SDL.h"
+#include "SDL_hints.h"
 #include "SDL_syswm.h"
 #include "SDL_ibus.h"
 #include "SDL_dbus.h"
@@ -66,107 +67,217 @@ IBus_ModState(void)
     return ibus_mods;
 }
 
-static const char *
-IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
+static SDL_bool
+IBus_EnterVariant(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
+                  DBusMessageIter *inside, const char * struct_id, size_t id_size)
 {
-    /* The text we need is nested weirdly, use dbus-monitor to see the structure better */
-    const char *text = NULL;
-    const char *struct_id = NULL;
-    DBusMessageIter sub1, sub2;
-
+    DBusMessageIter sub;
     if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
-        return NULL;
+        return SDL_FALSE;
     }
-    
-    dbus->message_iter_recurse(iter, &sub1);
-    
-    if (dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) {
-        return NULL;
+
+    dbus->message_iter_recurse(iter, &sub);
+
+    if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
+        return SDL_FALSE;
     }
-    
-    dbus->message_iter_recurse(&sub1, &sub2);
-    
-    if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
-        return NULL;
+
+    dbus->message_iter_recurse(&sub, inside);
+
+    if (dbus->message_iter_get_arg_type(inside) != DBUS_TYPE_STRING) {
+        return SDL_FALSE;
     }
-    
-    dbus->message_iter_get_basic(&sub2, &struct_id);
-    if (!struct_id || SDL_strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) {
-        return NULL;
+
+    dbus->message_iter_get_basic(inside, &struct_id);
+    if (!struct_id || SDL_strncmp(struct_id, struct_id, id_size) != 0) {
+        return SDL_FALSE;
     }
-    
+    return SDL_TRUE;
+}
+
+static SDL_bool
+IBus_GetDecorationPosition(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
+                           Uint32 *start_pos, Uint32 *end_pos)
+{
+    DBusMessageIter sub1, sub2, array;
+
+    if (!IBus_EnterVariant(conn, iter, dbus, &sub1, "IBusText", sizeof("IBusText"))) {
+        return SDL_FALSE;
+    }
+
+    dbus->message_iter_next(&sub1);
+    dbus->message_iter_next(&sub1);
+    dbus->message_iter_next(&sub1);
+
+    if (!IBus_EnterVariant(conn, &sub1, dbus, &sub2, "IBusAttrList", sizeof("IBusAttrList"))) {
+        return SDL_FALSE;
+    }
+
     dbus->message_iter_next(&sub2);
     dbus->message_iter_next(&sub2);
-    
-    if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
+
+    if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_ARRAY) {
+        return SDL_FALSE;
+    }
+
+    dbus->message_iter_recurse(&sub2, &array);
+
+    while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_VARIANT) {
+        DBusMessageIter sub;
+        if (IBus_EnterVariant(conn, &array, dbus, &sub, "IBusAttribute", sizeof("IBusAttribute"))) {
+            Uint32 type;
+
+            dbus->message_iter_next(&sub);
+            dbus->message_iter_next(&sub);
+
+            /* From here on, the structure looks like this:                    */
+            /* Uint32 type: 1=underline, 2=foreground, 3=background            */
+            /* Uint32 value: for underline it's 0=NONE, 1=SINGLE, 2=DOUBLE,    */
+            /*                                    3=LOW,  4=ERROR              */
+            /*                 for foreground and background it's a color      */
+            /* Uint32 start_index: starting position for the style (utf8-char) */
+            /* Uint32 end_index: end position for the style (utf8-char)        */
+
+            dbus->message_iter_get_basic(&sub, &type);
+            /* We only use the background type to determine the selection */
+            if (type == 3) {
+                Uint32 start = -1;
+                dbus->message_iter_next(&sub);
+                dbus->message_iter_next(&sub);
+                if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {
+                    dbus->message_iter_get_basic(&sub, &start);
+                    dbus->message_iter_next(&sub);
+                    if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {
+                        dbus->message_iter_get_basic(&sub, end_pos);
+                        *start_pos = start;
+                        return SDL_TRUE;
+                    }
+                }
+            }
+        }
+        dbus->message_iter_next(&array);
+    }
+    return SDL_FALSE;
+}
+
+static const char *
+IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
+{
+    /* The text we need is nested weirdly, use dbus-monitor to see the structure better */
+    const char *text = NULL;
+    DBusMessageIter sub;
+
+    if (!IBus_EnterVariant(conn, iter, dbus, &sub, "IBusText", sizeof("IBusText"))) {
         return NULL;
     }
-    
-    dbus->message_iter_get_basic(&sub2, &text);
-    
+
+    dbus->message_iter_next(&sub);
+    dbus->message_iter_next(&sub);
+
+    if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
+        return NULL;
+    }
+    dbus->message_iter_get_basic(&sub, &text);
+
     return text;
 }
 
+static SDL_bool
+IBus_GetVariantCursorPos(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
+                         Uint32 *pos)
+{
+    dbus->message_iter_next(iter);
+
+    if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) {
+        return SDL_FALSE;
+    }
+
+    dbus->message_iter_get_basic(iter, pos);
+
+    return SDL_TRUE;
+}
+
 static DBusHandlerResult
 IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)
 {
     SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
-        
+
     if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")) {
         DBusMessageIter iter;
         const char *text;
 
         dbus->message_iter_init(msg, &iter);
-        
         text = IBus_GetVariantText(conn, &iter, dbus);
+
         if (text && *text) {
             char buf[SDL_TEXTINPUTEVENT_TEXT_SIZE];
             size_t text_bytes = SDL_strlen(text), i = 0;
-            
+
             while (i < text_bytes) {
                 size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
                 SDL_SendKeyboardText(buf);
-                
+
                 i += sz;
             }
         }
-        
+
         return DBUS_HANDLER_RESULT_HANDLED;
     }
-    
+
     if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")) {
         DBusMessageIter iter;
         const char *text;
 
         dbus->message_iter_init(msg, &iter);
         text = IBus_GetVariantText(conn, &iter, dbus);
-        
+
         if (text) {
-            char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
-            size_t text_bytes = SDL_strlen(text), i = 0;
-            size_t cursor = 0;
-            
-            do {
-                const size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
-                const size_t chars = SDL_utf8strlen(buf);
-                
-                SDL_SendEditingText(buf, cursor, chars);
+            if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE)) {
+                Uint32 pos, start_pos, end_pos;
+                SDL_bool has_pos = SDL_FALSE;
+                SDL_bool has_dec_pos = SDL_FALSE;
+
+                dbus->message_iter_init(msg, &iter);
+                has_dec_pos = IBus_GetDecorationPosition(conn, &iter, dbus, &start_pos, &end_pos);
+                if (!has_dec_pos)
+                {
+                    dbus->message_iter_init(msg, &iter);
+                    has_pos = IBus_GetVariantCursorPos(conn, &iter, dbus, &pos);
+                }
 
-                i += sz;
-                cursor += chars;
-            } while (i < text_bytes);
+                if(has_dec_pos) {
+                    SDL_SendEditingText(text, start_pos, end_pos - start_pos);
+                } else if (has_pos) {
+                    SDL_SendEditingText(text, pos, -1);
+                } else {
+                    SDL_SendEditingText(text, -1, -1);
+                }
+            } else {
+                char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
+                size_t text_bytes = SDL_strlen(text), i = 0;
+                size_t cursor = 0;
+
+                do {
+                    const size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
+                    const size_t chars = SDL_utf8strlen(buf);
+
+                    SDL_SendEditingText(buf, cursor, chars);
+                    i += sz;
+                    cursor += chars;
+                } while (i < text_bytes);
+            }
         }
-        
+
         SDL_IBus_UpdateTextRect(NULL);
-        
+
         return DBUS_HANDLER_RESULT_HANDLED;
     }
-    
+
     if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")) {
         SDL_SendEditingText("", 0, 0);
         return DBUS_HANDLER_RESULT_HANDLED;
     }
-    
+
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }