SDL: Dynamically allocate long text for SDL_EVENT_TEXT_INPUT events

From 75ea3a8d32dcdc3d8784c12e39719d8d4b403df4 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 4 Nov 2023 16:53:25 -0700
Subject: [PATCH] Dynamically allocate long text for SDL_EVENT_TEXT_INPUT
 events

This prevents input text from being split across Unicode combining or modifier characters, and in practice allocations will rarely happen.
---
 docs/README-migration.md  |  2 +-
 include/SDL3/SDL_events.h |  3 ++-
 src/events/SDL_events.c   | 10 +++++++++-
 src/events/SDL_keyboard.c | 18 +++++++++---------
 test/checkkeys.c          |  1 +
 test/checkkeysthreads.c   |  1 +
 test/testcontroller.c     |  1 +
 test/testime.c            |  1 +
 8 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index bbe1fe56d83f..26026372a5f8 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -298,7 +298,7 @@ The timestamp_us member of the sensor events has been renamed sensor_timestamp a
 
 You should set the event.common.timestamp field before passing an event to SDL_PushEvent(). If the timestamp is 0 it will be filled in with SDL_GetTicksNS().
 
-You should call SDL_CleanupEvent() after handling SDL_EVENT_DROP_FILE, SDL_EVENT_DROP_TEXT, SDL_EVENT_SYSWM, and SDL_EVENT_TEXT_EDITING. This cleans up the memory associated with those events, and you no longer have to free the data yourself.
+You should call SDL_CleanupEvent() after handling SDL_EVENT_DROP_FILE, SDL_EVENT_DROP_TEXT, SDL_EVENT_SYSWM, SDL_EVENT_TEXT_EDITING, and SDL_EVENT_TEXT_INPUT. This cleans up the memory associated with those events, and you no longer have to free the data yourself.
 
 Mouse events use floating point values for mouse coordinates and relative motion values. You can get sub-pixel motion depending on the platform and display scaling.
 
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index ef847078aac5..92be1352ba8b 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -284,7 +284,8 @@ typedef struct SDL_TextInputEvent
     Uint32 type;                                /**< ::SDL_EVENT_TEXT_INPUT */
     Uint64 timestamp;                           /**< In nanoseconds, populated using SDL_GetTicksNS() */
     SDL_WindowID windowID;                      /**< The window with keyboard focus, if any */
-    char text[SDL_TEXTINPUTEVENT_TEXT_SIZE];    /**< The input text */
+    char *text;                                 /**< The input text */
+    char short_text[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; /**< Memory space for short input text, use 'text' instead */
 } SDL_TextInputEvent;
 
 /**
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index c6616fe462b8..baac6188b6b7 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -470,11 +470,13 @@ static void SDL_LogEvent(const SDL_Event *event)
 
 static void SDL_CopyEvent(SDL_Event *dst, SDL_Event *src)
 {
-    *dst = *src;
+    SDL_copyp(dst, src);
 
     /* Pointers to internal static data must be updated when copying. */
     if (src->type == SDL_EVENT_TEXT_EDITING && src->edit.text == src->edit.short_text) {
         dst->edit.text = dst->edit.short_text;
+    } else if (src->type == SDL_EVENT_TEXT_INPUT && src->text.text == src->text.short_text) {
+        dst->text.text = dst->text.short_text;
     } else if (src->type == SDL_EVENT_DROP_TEXT && src->drop.data == src->drop.short_data) {
         dst->drop.data = dst->drop.short_data;
     }
@@ -1106,6 +1108,12 @@ void SDL_CleanupEvent(SDL_Event *event)
             event->edit.text = NULL;
         }
         break;
+    case SDL_EVENT_TEXT_INPUT:
+        if (event->text.text && event->text.text != event->text.short_text) {
+            SDL_free(event->text.text);
+            event->text.text = NULL;
+        }
+        break;
     default:
         break;
     }
diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c
index 49f38b9f490d..2e69aac027cd 100644
--- a/src/events/SDL_keyboard.c
+++ b/src/events/SDL_keyboard.c
@@ -1071,19 +1071,19 @@ int SDL_SendKeyboardText(const char *text)
     posted = 0;
     if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
         SDL_Event event;
-        size_t pos = 0, advance, length = SDL_strlen(text);
-
         event.type = SDL_EVENT_TEXT_INPUT;
         event.common.timestamp = 0;
         event.text.windowID = keyboard->focus ? keyboard->focus->id : 0;
-        while (pos < length) {
-            advance = SDL_utf8strlcpy(event.text.text, text + pos, SDL_arraysize(event.text.text));
-            if (!advance) {
-                break;
-            }
-            pos += advance;
-            posted |= (SDL_PushEvent(&event) > 0);
+
+        size_t len = SDL_strlen(text);
+        if (len < sizeof(event.text.short_text)) {
+            SDL_memcpy(event.text.short_text, text, len + 1);
+            event.text.text = event.text.short_text;
+        } else {
+            event.text.text = SDL_strdup(text);
         }
+
+        posted = (SDL_PushEvent(&event) > 0);
     }
     return posted;
 }
diff --git a/test/checkkeys.c b/test/checkkeys.c
index 11fd5b6ced63..4894f6436874 100644
--- a/test/checkkeys.c
+++ b/test/checkkeys.c
@@ -186,6 +186,7 @@ static void loop(void)
         case SDL_EVENT_TEXT_INPUT:
             PrintText("INPUT", event.text.text);
             SDLTest_TextWindowAddText(textwin, "%s", event.text.text);
+            SDL_CleanupEvent(&event);
             break;
         case SDL_EVENT_FINGER_DOWN:
             if (SDL_TextInputActive()) {
diff --git a/test/checkkeysthreads.c b/test/checkkeysthreads.c
index 64e0dc418d34..cae8f48e849c 100644
--- a/test/checkkeysthreads.c
+++ b/test/checkkeysthreads.c
@@ -187,6 +187,7 @@ static void loop(void)
             break;
         case SDL_EVENT_TEXT_INPUT:
             PrintText("INPUT", event.text.text);
+            SDL_CleanupEvent(&event);
             break;
         case SDL_EVENT_MOUSE_BUTTON_DOWN:
             /* Left button quits the app, other buttons toggles text input */
diff --git a/test/testcontroller.c b/test/testcontroller.c
index dd4b9819d95f..cdcd28ed2fbc 100644
--- a/test/testcontroller.c
+++ b/test/testcontroller.c
@@ -1728,6 +1728,7 @@ static void loop(void *arg)
                     AddControllerNameText(event.text.text);
                 }
             }
+            SDL_CleanupEvent(&event);
             break;
         case SDL_EVENT_QUIT:
             done = SDL_TRUE;
diff --git a/test/testime.c b/test/testime.c
index 58010bd485e2..e2353db21171 100644
--- a/test/testime.c
+++ b/test/testime.c
@@ -770,6 +770,7 @@ int main(int argc, char *argv[])
                 /* is committed */
                 markedText[0] = 0;
                 Redraw();
+                SDL_CleanupEvent(&event);
                 break;
 
             case SDL_EVENT_TEXT_EDITING: