SDL: The text input state has been changed to be window-specific.

From 76631a09787a7f43d2151b496e7e23d03de9f3d7 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 22 Jun 2024 06:16:19 -0700
Subject: [PATCH] The text input state has been changed to be window-specific.

SDL_StartTextInput(), SDL_StopTextInput(), SDL_TextInputActive(), SDL_ClearComposition(), and SDL_SetTextInputRect() all now take a window parameter.

This change also fixes IME candidate positioning when SDL_SetTextInputRect() is called before SDL_StartTextInput(), as is recommended in the documentation.
---
 docs/README-migration.md                    |   2 +
 include/SDL3/SDL_keyboard.h                 |  42 ++++---
 src/core/android/SDL_android.c              |   4 +-
 src/core/haiku/SDL_BApp.h                   |   6 +-
 src/core/linux/SDL_fcitx.c                  |  22 ++--
 src/core/linux/SDL_fcitx.h                  |   2 +-
 src/core/linux/SDL_ibus.c                   |  30 ++---
 src/core/linux/SDL_ibus.h                   |   2 +-
 src/core/linux/SDL_ime.c                    |   6 +-
 src/core/linux/SDL_ime.h                    |   2 +-
 src/dynapi/SDL_dynapi_procs.h               |  10 +-
 src/events/SDL_keyboard.c                   |  12 +-
 src/video/SDL_sysvideo.h                    |  12 +-
 src/video/SDL_video.c                       |  71 +++++++-----
 src/video/android/SDL_androidkeyboard.c     |  10 +-
 src/video/android/SDL_androidkeyboard.h     |   1 -
 src/video/android/SDL_androidvideo.c        |   3 -
 src/video/android/SDL_androidvideo.h        |   1 -
 src/video/cocoa/SDL_cocoakeyboard.h         |   6 +-
 src/video/cocoa/SDL_cocoakeyboard.m         |  25 ++--
 src/video/cocoa/SDL_cocoavideo.m            |   2 +-
 src/video/emscripten/SDL_emscriptenevents.c |   7 +-
 src/video/gdk/SDL_gdktextinput.cpp          |  11 +-
 src/video/gdk/SDL_gdktextinput.h            |   8 +-
 src/video/haiku/SDL_bvideo.cc               |  14 ---
 src/video/n3ds/SDL_n3dsswkb.c               |   7 +-
 src/video/n3ds/SDL_n3dsswkb.h               |   4 +-
 src/video/uikit/SDL_uikitvideo.m            |   2 +-
 src/video/uikit/SDL_uikitview.m             |   8 +-
 src/video/uikit/SDL_uikitviewcontroller.h   |   2 +-
 src/video/uikit/SDL_uikitviewcontroller.m   |  16 +--
 src/video/wayland/SDL_waylandevents.c       |   6 +-
 src/video/wayland/SDL_waylandkeyboard.c     |  23 ++--
 src/video/wayland/SDL_waylandkeyboard.h     |   6 +-
 src/video/wayland/SDL_waylandvideo.c        |   2 +-
 src/video/windows/SDL_windowsevents.c       |  22 ++--
 src/video/windows/SDL_windowskeyboard.c     | 121 ++++++++++----------
 src/video/windows/SDL_windowskeyboard.h     |   8 +-
 src/video/windows/SDL_windowsvideo.c        |   2 +-
 src/video/winrt/SDL_winrtkeyboard.cpp       |   2 +-
 src/video/x11/SDL_x11events.c               |   8 +-
 src/video/x11/SDL_x11keyboard.c             |  35 +++---
 src/video/x11/SDL_x11keyboard.h             |   6 +-
 src/video/x11/SDL_x11video.c                |   2 +-
 test/checkkeys.c                            |  22 ++--
 test/checkkeysthreads.c                     |  12 +-
 test/testautomation_keyboard.c              |  41 ++++---
 test/testime.c                              |  10 +-
 48 files changed, 350 insertions(+), 328 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 496feceee15a0..3790bc49b563a 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -907,6 +907,8 @@ The following symbols have been removed:
 
 Text input is no longer automatically enabled when initializing video, you should call SDL_StartTextInput() when you want to receive text input and call SDL_StopTextInput() when you are done. Starting text input may shown an input method editor (IME) and cause key up/down events to be skipped, so should only be enabled when the application wants text input.
 
+The text input state hase been changed to be window-specific. SDL_StartTextInput(), SDL_StopTextInput(), SDL_TextInputActive(), and SDL_ClearComposition() all now take a window parameter.
+
 SDL_GetDefaultKeyFromScancode(), SDL_GetKeyFromScancode(), and SDL_GetScancodeFromKey() take an SDL_Keymod parameter and use that to provide the correct result based on keyboard modifier state.
 
 The following functions have been renamed:
diff --git a/include/SDL3/SDL_keyboard.h b/include/SDL3/SDL_keyboard.h
index 83576e208f307..7452461344588 100644
--- a/include/SDL3/SDL_keyboard.h
+++ b/include/SDL3/SDL_keyboard.h
@@ -353,59 +353,72 @@ extern SDL_DECLSPEC const char *SDLCALL SDL_GetKeyName(SDL_Keycode key);
 extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromName(const char *name);
 
 /**
- * Start accepting Unicode text input events.
+ * Start accepting Unicode text input events in a window.
  *
- * This function will start accepting Unicode text input events in the focused
- * SDL window, and start emitting SDL_TextInputEvent (SDL_EVENT_TEXT_INPUT)
- * and SDL_TextEditingEvent (SDL_EVENT_TEXT_EDITING) events. Please use this
- * function in pair with SDL_StopTextInput().
+ * This function will enable text input (SDL_EVENT_TEXT_INPUT and SDL_EVENT_TEXT_EDITING events) in the specified window. Please use this function paired with SDL_StopTextInput().
  *
  * Text input events are not received by default.
  *
- * On some platforms using this function activates the screen keyboard.
+ * On some platforms using this function shows the screen keyboard.
+ *
+ * \param window the window to enable text input.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_SetTextInputRect
  * \sa SDL_StopTextInput
+ * \sa SDL_TextInputActive
  */
-extern SDL_DECLSPEC void SDLCALL SDL_StartTextInput(void);
+extern SDL_DECLSPEC int SDLCALL SDL_StartTextInput(SDL_Window *window);
 
 /**
- * Check whether or not Unicode text input events are enabled.
+ * Check whether or not Unicode text input events are enabled for a window.
  *
+ * \param window the window to check.
  * \returns SDL_TRUE if text input events are enabled else SDL_FALSE.
  *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_StartTextInput
  */
-extern SDL_DECLSPEC SDL_bool SDLCALL SDL_TextInputActive(void);
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_TextInputActive(SDL_Window *window);
 
 /**
- * Stop receiving any text input events.
+ * Stop receiving any text input events in a window.
  *
- * Text input events are not received by default.
+ * If SDL_StartTextInput() showed the screen keyboard, this function will hide it.
+ *
+ * \param window the window to disable text input.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_StartTextInput
  */
-extern SDL_DECLSPEC void SDLCALL SDL_StopTextInput(void);
+extern SDL_DECLSPEC int SDLCALL SDL_StopTextInput(SDL_Window *window);
 
 /**
  * Dismiss the composition window/IME without disabling the subsystem.
  *
+ * \param window the window to affect.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_StartTextInput
  * \sa SDL_StopTextInput
  */
-extern SDL_DECLSPEC void SDLCALL SDL_ClearComposition(void);
+extern SDL_DECLSPEC int SDLCALL SDL_ClearComposition(SDL_Window *window);
 
 /**
  * Set the rectangle used to type Unicode text inputs.
  *
+ * This is often set to the extents of a text field within the window.
+ *
  * Native input methods will place a window with word suggestions near it,
  * without covering the text being inputted.
  *
@@ -417,6 +430,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ClearComposition(void);
  * **SDL_HINT_IME_SHOW_UI** to **1**, otherwise this function won't give you
  * any feedback.
  *
+ * \param window the window for which to set the text input rectangle.
  * \param rect the SDL_Rect structure representing the rectangle to receive
  *             text (ignored if NULL).
  * \returns 0 on success or a negative error code on failure; call
@@ -426,7 +440,7 @@ extern SDL_DECLSPEC void SDLCALL SDL_ClearComposition(void);
  *
  * \sa SDL_StartTextInput
  */
-extern SDL_DECLSPEC int SDLCALL SDL_SetTextInputRect(const SDL_Rect *rect);
+extern SDL_DECLSPEC int SDLCALL SDL_SetTextInputRect(SDL_Window *window, const SDL_Rect *rect);
 
 /**
  * Check whether the platform has screen keyboard support.
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index b33d420a1e2d5..a426506010612 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -1252,7 +1252,7 @@ JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
     JNIEnv *env, jclass jcls)
 {
     if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) {
-        SDL_StopTextInput();
+        SDL_StopTextInput(Android_Window);
         return JNI_TRUE;
     }
     return JNI_FALSE;
@@ -1263,7 +1263,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
     JNIEnv *env, jclass jcls)
 {
     /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
-    SDL_StopTextInput();
+    SDL_StopTextInput(Android_Window);
 }
 
 /* Touch */
diff --git a/src/core/haiku/SDL_BApp.h b/src/core/haiku/SDL_BApp.h
index 37384fdb699d0..e4f3b8dcce427 100644
--- a/src/core/haiku/SDL_BApp.h
+++ b/src/core/haiku/SDL_BApp.h
@@ -292,8 +292,11 @@ class SDL_BLooper : public BLooper
 
     void _HandleKey(BMessage *msg)
     {
+        SDL_Window *win;
+        int32 winID;
         int32 scancode, state; /* scancode, pressed/released */
         if (
+            !_GetWinID(msg, &winID) ||
             msg->FindInt32("key-state", &state) != B_OK ||
             msg->FindInt32("key-scancode", &scancode) != B_OK) {
             return;
@@ -306,7 +309,8 @@ class SDL_BLooper : public BLooper
         HAIKU_SetKeyState(scancode, state);
         SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, scancode, HAIKU_GetScancodeFromBeKey(scancode), state);
 
-        if (state == SDL_PRESSED && SDL_TextInputActive()) {
+        win = GetSDLWindow(winID);
+        if (state == SDL_PRESSED && SDL_TextInputActive(win)) {
             const int8 *keyUtf8;
             ssize_t count;
             if (msg->FindData("key-utf8", B_INT8_TYPE, (const void **)&keyUtf8, &count) == B_OK) {
diff --git a/src/core/linux/SDL_fcitx.c b/src/core/linux/SDL_fcitx.c
index 808054a05b255..bd0a8fb7b2dc3 100644
--- a/src/core/linux/SDL_fcitx.c
+++ b/src/core/linux/SDL_fcitx.c
@@ -211,7 +211,7 @@ static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *m
             SDL_SendEditingText("", 0, 0);
         }
 
-        SDL_Fcitx_UpdateTextRect(NULL);
+        SDL_Fcitx_UpdateTextRect(SDL_GetKeyboardFocus());
         return DBUS_HANDLER_RESULT_HANDLED;
     }
 
@@ -390,7 +390,7 @@ SDL_bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
                             DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
                             DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
         if (handled) {
-            SDL_Fcitx_UpdateTextRect(NULL);
+            SDL_Fcitx_UpdateTextRect(SDL_GetKeyboardFocus());
             return SDL_TRUE;
         }
     }
@@ -398,26 +398,22 @@ SDL_bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
     return SDL_FALSE;
 }
 
-void SDL_Fcitx_UpdateTextRect(const SDL_Rect *rect)
+void SDL_Fcitx_UpdateTextRect(SDL_Window *window)
 {
-    SDL_Window *focused_win = NULL;
     int x = 0, y = 0;
     SDL_Rect *cursor = &fcitx_client.cursor_rect;
 
-    if (rect) {
-        SDL_copyp(cursor, rect);
-    }
-
-    focused_win = SDL_GetKeyboardFocus();
-    if (!focused_win) {
+    if (!window) {
         return;
     }
 
-    SDL_GetWindowPosition(focused_win, &x, &y);
+    SDL_copyp(cursor, &window->text_input_rect);
+
+    SDL_GetWindowPosition(window, &x, &y);
 
 #ifdef SDL_VIDEO_DRIVER_X11
     {
-        SDL_PropertiesID props = SDL_GetWindowProperties(focused_win);
+        SDL_PropertiesID props = SDL_GetWindowProperties(window);
         Display *x_disp = (Display *)SDL_GetProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
         int x_screen = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_SCREEN_NUMBER, 0);
         Window x_win = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
@@ -431,7 +427,7 @@ void SDL_Fcitx_UpdateTextRect(const SDL_Rect *rect)
     if (cursor->x == -1 && cursor->y == -1 && cursor->w == 0 && cursor->h == 0) {
         /* move to bottom left */
         int w = 0, h = 0;
-        SDL_GetWindowSize(focused_win, &w, &h);
+        SDL_GetWindowSize(window, &w, &h);
         cursor->x = 0;
         cursor->y = h;
     }
diff --git a/src/core/linux/SDL_fcitx.h b/src/core/linux/SDL_fcitx.h
index 44ee053309f68..daddf8fd4e143 100644
--- a/src/core/linux/SDL_fcitx.h
+++ b/src/core/linux/SDL_fcitx.h
@@ -29,7 +29,7 @@ extern void SDL_Fcitx_Quit(void);
 extern void SDL_Fcitx_SetFocus(SDL_bool focused);
 extern void SDL_Fcitx_Reset(void);
 extern SDL_bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state);
-extern void SDL_Fcitx_UpdateTextRect(const SDL_Rect *rect);
+extern void SDL_Fcitx_UpdateTextRect(SDL_Window *window);
 extern void SDL_Fcitx_PumpEvents(void);
 
 #endif /* SDL_fcitx_h_ */
diff --git a/src/core/linux/SDL_ibus.c b/src/core/linux/SDL_ibus.c
index 9e514c302220c..370dff7d4b65c 100644
--- a/src/core/linux/SDL_ibus.c
+++ b/src/core/linux/SDL_ibus.c
@@ -261,7 +261,7 @@ static DBusHandlerResult IBus_MessageHandler(DBusConnection *conn, DBusMessage *
             }
         }
 
-        SDL_IBus_UpdateTextRect(NULL);
+        SDL_IBus_UpdateTextRect(SDL_GetKeyboardFocus());
 
         return DBUS_HANDLER_RESULT_HANDLED;
     }
@@ -480,9 +480,13 @@ static SDL_bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr)
         dbus->connection_flush(ibus_conn);
     }
 
-    SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
-    SDL_IBus_UpdateTextRect(NULL);
-
+    SDL_Window *window = SDL_GetKeyboardFocus();
+    if (SDL_TextInputActive(window)) {
+        SDL_IBus_SetFocus(SDL_TRUE);
+        SDL_IBus_UpdateTextRect(window);
+    } else {
+        SDL_IBus_SetFocus(SDL_FALSE);
+    }
     return result;
 }
 
@@ -670,31 +674,27 @@ SDL_bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
         }
     }
 
-    SDL_IBus_UpdateTextRect(NULL);
+    SDL_IBus_UpdateTextRect(SDL_GetKeyboardFocus());
 
     return (result != 0);
 }
 
-void SDL_IBus_UpdateTextRect(const SDL_Rect *rect)
+void SDL_IBus_UpdateTextRect(SDL_Window *window)
 {
-    SDL_Window *focused_win;
     int x = 0, y = 0;
     SDL_DBusContext *dbus;
 
-    if (rect) {
-        SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
-    }
-
-    focused_win = SDL_GetKeyboardFocus();
-    if (!focused_win) {
+    if (!window) {
         return;
     }
 
-    SDL_GetWindowPosition(focused_win, &x, &y);
+    SDL_copyp(&ibus_cursor_rect, &window->text_input_rect);
+
+    SDL_GetWindowPosition(window, &x, &y);
 
 #ifdef SDL_VIDEO_DRIVER_X11
     {
-        SDL_PropertiesID props = SDL_GetWindowProperties(focused_win);
+        SDL_PropertiesID props = SDL_GetWindowProperties(window);
         Display *x_disp = (Display *)SDL_GetProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
         int x_screen = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_SCREEN_NUMBER, 0);
         Window x_win = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
diff --git a/src/core/linux/SDL_ibus.h b/src/core/linux/SDL_ibus.h
index 377a9fc2bd36f..5ff90cb82de98 100644
--- a/src/core/linux/SDL_ibus.h
+++ b/src/core/linux/SDL_ibus.h
@@ -44,7 +44,7 @@ extern SDL_bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 st
 
 /* Update the position of IBus' candidate list. If rect is NULL then this will
    just reposition it relative to the focused window's new position. */
-extern void SDL_IBus_UpdateTextRect(const SDL_Rect *window_relative_rect);
+extern void SDL_IBus_UpdateTextRect(SDL_Window *window);
 
 /* Checks DBus for new IBus events, and calls SDL_SendKeyboardText /
    SDL_SendEditingText for each event it finds */
diff --git a/src/core/linux/SDL_ime.c b/src/core/linux/SDL_ime.c
index 752c3ab9b8c2b..7f2ba82392636 100644
--- a/src/core/linux/SDL_ime.c
+++ b/src/core/linux/SDL_ime.c
@@ -29,7 +29,7 @@ typedef void (*SDL_IME_Quit_t)(void);
 typedef void (*SDL_IME_SetFocus_t)(SDL_bool);
 typedef void (*SDL_IME_Reset_t)(void);
 typedef SDL_bool (*SDL_IME_ProcessKeyEvent_t)(Uint32, Uint32, Uint8 state);
-typedef void (*SDL_IME_UpdateTextRect_t)(const SDL_Rect *);
+typedef void (*SDL_IME_UpdateTextRect_t)(SDL_Window *window);
 typedef void (*SDL_IME_PumpEvents_t)(void);
 
 static SDL_IME_Init_t SDL_IME_Init_Real = NULL;
@@ -135,10 +135,10 @@ SDL_bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state)
     return SDL_FALSE;
 }
 
-void SDL_IME_UpdateTextRect(const SDL_Rect *rect)
+void SDL_IME_UpdateTextRect(SDL_Window *window)
 {
     if (SDL_IME_UpdateTextRect_Real) {
-        SDL_IME_UpdateTextRect_Real(rect);
+        SDL_IME_UpdateTextRect_Real(window);
     }
 }
 
diff --git a/src/core/linux/SDL_ime.h b/src/core/linux/SDL_ime.h
index f0ac778259afe..088ac53397821 100644
--- a/src/core/linux/SDL_ime.h
+++ b/src/core/linux/SDL_ime.h
@@ -29,7 +29,7 @@ extern void SDL_IME_Quit(void);
 extern void SDL_IME_SetFocus(SDL_bool focused);
 extern void SDL_IME_Reset(void);
 extern SDL_bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, Uint8 state);
-extern void SDL_IME_UpdateTextRect(const SDL_Rect *rect);
+extern void SDL_IME_UpdateTextRect(SDL_Window *window);
 extern void SDL_IME_PumpEvents(void);
 
 #endif /* SDL_ime_h_ */
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 09cc85694a0ce..bc9b6bfe7a307 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -86,7 +86,7 @@ SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_CleanupTLS,(void),(),)
 SDL_DYNAPI_PROC(int,SDL_ClearAudioStream,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_ClearClipboardData,(void),(),return)
-SDL_DYNAPI_PROC(void,SDL_ClearComposition,(void),(),)
+SDL_DYNAPI_PROC(int,SDL_ClearComposition,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_ClearError,(void),(),return)
 SDL_DYNAPI_PROC(int,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_CloseAudioDevice,(SDL_AudioDeviceID a),(a),)
@@ -779,7 +779,7 @@ SDL_DYNAPI_PROC(int,SDL_SetSurfaceColorspace,(SDL_Surface *a, SDL_Colorspace b),
 SDL_DYNAPI_PROC(int,SDL_SetSurfacePalette,(SDL_Surface *a, SDL_Palette *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetSurfaceRLE,(SDL_Surface *a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetTLS,(SDL_TLSID a, const void *b, SDL_TLSDestructorCallback c),(a,b,c),return)
-SDL_DYNAPI_PROC(int,SDL_SetTextInputRect,(const SDL_Rect *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_SetTextInputRect,(SDL_Window *a, const SDL_Rect *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetTextureAlphaMod,(SDL_Texture *a, Uint8 b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetTextureAlphaModFloat,(SDL_Texture *a, float b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetTextureBlendMode,(SDL_Texture *a, SDL_BlendMode b),(a,b),return)
@@ -821,17 +821,17 @@ SDL_DYNAPI_PROC(int,SDL_ShowWindow,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_ShowWindowSystemMenu,(SDL_Window *a, int b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SignalCondition,(SDL_Condition *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SoftStretch,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d, SDL_ScaleMode e),(a,b,c,d,e),return)
-SDL_DYNAPI_PROC(void,SDL_StartTextInput,(void),(),)
+SDL_DYNAPI_PROC(int,SDL_StartTextInput,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_StopHapticEffect,(SDL_Haptic *a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_StopHapticEffects,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_StopHapticRumble,(SDL_Haptic *a),(a),return)
-SDL_DYNAPI_PROC(void,SDL_StopTextInput,(void),(),)
+SDL_DYNAPI_PROC(int,SDL_StopTextInput,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_StorageReady,(SDL_Storage *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_SurfaceHasColorKey,(SDL_Surface *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_SurfaceHasRLE,(SDL_Surface *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SyncWindow,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(Sint64,SDL_TellIO,(SDL_IOStream *a),(a),return)
-SDL_DYNAPI_PROC(SDL_bool,SDL_TextInputActive,(void),(),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_TextInputActive,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Time,SDL_TimeFromWindows,(Uint32 a, Uint32 b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_TimeToDateTime,(SDL_Time a, SDL_DateTime *b, SDL_bool c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_TimeToWindows,(SDL_Time a, Uint32 *b, Uint32 *c),(a,b,c),)
diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c
index d490facae4536..21e581dcfde3c 100644
--- a/src/events/SDL_keyboard.c
+++ b/src/events/SDL_keyboard.c
@@ -303,9 +303,9 @@ int SDL_SetKeyboardFocus(SDL_Window *window)
         SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_LOST, 0, 0);
 
         /* Ensures IME compositions are committed */
-        if (SDL_TextInputActive()) {
+        if (SDL_TextInputActive(keyboard->focus)) {
             if (video && video->StopTextInput) {
-                video->StopTextInput(video);
+                video->StopTextInput(video, keyboard->focus);
             }
         }
     }
@@ -315,9 +315,9 @@ int SDL_SetKeyboardFocus(SDL_Window *window)
     if (keyboard->focus) {
         SDL_SendWindowEvent(keyboard->focus, SDL_EVENT_WINDOW_FOCUS_GAINED, 0, 0);
 
-        if (SDL_TextInputActive()) {
+        if (SDL_TextInputActive(keyboard->focus)) {
             if (video && video->StartTextInput) {
-                video->StartTextInput(video);
+                video->StartTextInput(video, keyboard->focus);
             }
         }
     }
@@ -598,7 +598,7 @@ int SDL_SendKeyboardText(const char *text)
     SDL_Keyboard *keyboard = &SDL_keyboard;
     int posted;
 
-    if (!SDL_TextInputActive()) {
+    if (!SDL_TextInputActive(keyboard->focus)) {
         return 0;
     }
 
@@ -632,7 +632,7 @@ int SDL_SendEditingText(const char *text, int start, int length)
     SDL_Keyboard *keyboard = &SDL_keyboard;
     int posted;
 
-    if (!SDL_TextInputActive()) {
+    if (!SDL_TextInputActive(keyboard->focus)) {
         return 0;
     }
 
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 9add48fcac2c7..b49a1501ce20a 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -101,6 +101,9 @@ struct SDL_Window
     SDL_bool is_destroying;
     SDL_bool is_dropping; /* drag/drop in progress, expecting SDL_SendDropComplete(). */
 
+    SDL_bool text_input_active;
+    SDL_Rect text_input_rect;
+
     SDL_Rect mouse_rect;
 
     SDL_HitTest hit_test;
@@ -322,10 +325,10 @@ struct SDL_VideoDevice
     int (*SuspendScreenSaver)(SDL_VideoDevice *_this);
 
     /* Text input */
-    void (*StartTextInput)(SDL_VideoDevice *_this);
-    void (*StopTextInput)(SDL_VideoDevice *_this);
-    int (*SetTextInputRect)(SDL_VideoDevice *_this, const SDL_Rect *rect);
-    void (*ClearComposition)(SDL_VideoDevice *_this);
+    int (*StartTextInput)(SDL_VideoDevice *_this, SDL_Window *window);
+    int (*StopTextInput)(SDL_VideoDevice *_this, SDL_Window *window);
+    int (*UpdateTextInputRect)(SDL_VideoDevice *_this, SDL_Window *window);
+    int (*ClearComposition)(SDL_VideoDevice *_this, SDL_Window *window);
 
     /* Screen keyboard */
     SDL_bool (*HasScreenKeyboardSupport)(SDL_VideoDevice *_this);
@@ -382,7 +385,6 @@ struct SDL_VideoDevice
     SDL_bool setting_display_mode;
     Uint32 device_caps;
     SDL_SystemTheme system_theme;
-    SDL_bool text_input_active;
 
     /* * * */
     /* Data used by the GL drivers */
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 2cfbfb96dac63..2ffb28ff4576c 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -4981,74 +4981,85 @@ void SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask)
 }
 #endif
 
-void SDL_StartTextInput(void)
+int SDL_StartTextInput(SDL_Window *window)
 {
-    if (!_this) {
-        return;
-    }
+    CHECK_WINDOW_MAGIC(window, -1);
 
     /* Show the on-screen keyboard, if desired */
     const char *hint = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD);
     if (((!hint || SDL_strcasecmp(hint, "auto") == 0) && !SDL_HasKeyboard()) ||
         SDL_GetStringBoolean(hint, SDL_FALSE)) {
-        SDL_Window *window = SDL_GetKeyboardFocus();
-        if (window && _this->ShowScreenKeyboard) {
+        if (_this->ShowScreenKeyboard) {
             _this->ShowScreenKeyboard(_this, window);
         }
     }
 
     /* Finally start the text input system */
     if (_this->StartTextInput) {
-        _this->StartTextInput(_this);
+        if (_this->StartTextInput(_this, window) < 0) {
+            return -1;
+        }
     }
-    _this->text_input_active = SDL_TRUE;
+    window->text_input_active = SDL_TRUE;
+    return 0;
 }
 
-void SDL_ClearComposition(void)
+SDL_bool SDL_TextInputActive(SDL_Window *window)
 {
-    if (_this && _this->ClearComposition) {
-        _this->ClearComposition(_this);
-    }
-}
+    CHECK_WINDOW_MAGIC(window, SDL_FALSE);
 
-SDL_bool SDL_TextInputActive(void)
-{
-    return _this && _this->text_input_active;
+    return window->text_input_active;
 }
 
-void SDL_StopTextInput(void)
+int SDL_StopTextInput(SDL_Window *window)
 {
-    if (!_this) {
-        return;
-    }
+    CHECK_WINDOW_MAGIC(window, -1);
 
     /* Stop the text input system */
     if (_this->StopTextInput) {
-        _this->StopTextInput(_this);
+        _this->StopTextInput(_this, window);
     }
-    _this->text_input_active = SDL_FALSE;
+    window->text_input_active = SDL_FALSE;
 
     /* Hide the on-screen keyboard, if desired */
     const char *hint = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD);
     if (((!hint || SDL_strcasecmp(hint, "auto") == 0) && !SDL_HasKeyboard()) ||
         SDL_GetStringBoolean(hint, SDL_FALSE)) {
-        SDL_Window *window = SDL_GetKeyboardFocus();
-        if (window && _this->HideScreenKeyboard) {
+        if (_this->HideScreenKeyboard) {
             _this->HideScreenKeyboard(_this, window);
         }
     }
+    return 0;
 }
 
-int SDL_SetTextInputRect(const SDL_Rect *rect)
+int SDL_SetTextInputRect(SDL_Window *window, const SDL_Rect *rect)
 {
-    if (!rect) {
-        return SDL_InvalidParamError("rect");
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (rect) {
+        SDL_copyp(&window->text_input_rect, rect);
+    } else {
+        SDL_zero(window->text_input_rect);
     }
 
-    if (_this && _this->SetTextInputRect) {
-        return _this->SetTextInputRect(_this, rect);
+    if (_this && _this->UpdateTextInputRect) {
+        if (_this->UpdateTextInputRect(_this, window) < 0) {
+            return -1;
+        }
     }
-    return SDL_Unsupported();
+    return 0;
+}
+
+int SDL_ClearComposition(SDL_Window *window)
+{
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (_this->ClearComposition) {
+        if (_this->ClearComposition(_this, window) < 0) {
+            return -1;
+        }
+    }
+    return 0;
 }
 
 SDL_bool SDL_HasScreenKeyboardSupport(void)
diff --git a/src/video/android/SDL_androidkeyboard.c b/src/video/android/SDL_androidkeyboard.c
index 45bf4021aa564..85a124725ce67 100644
--- a/src/video/android/SDL_androidkeyboard.c
+++ b/src/video/android/SDL_androidkeyboard.c
@@ -345,8 +345,7 @@ SDL_bool Android_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
 
 void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
 {
-    SDL_VideoData *videodata = _this->driverdata;
-    Android_JNI_ShowScreenKeyboard(&videodata->textRect);
+    Android_JNI_ShowScreenKeyboard(&window->text_input_rect);
     SDL_screen_keyboard_shown = SDL_TRUE;
 }
 
@@ -368,11 +367,4 @@ SDL_bool Android_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *windo
     return Android_JNI_IsScreenKeyboardShown();
 }
 
-int Android_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect)
-{
-    SDL_VideoData *videodata = _this->driverdata;
-    videodata->textRect = *rect;
-    return 0;
-}
-
 #endif /* SDL_VIDEO_DRIVER_ANDROID */
diff --git a/src/video/android/SDL_androidkeyboard.h b/src/video/android/SDL_androidkeyboard.h
index 48de9d481f2c9..bcbd948e1caa9 100644
--- a/src/video/android/SDL_androidkeyboard.h
+++ b/src/video/android/SDL_androidkeyboard.h
@@ -30,4 +30,3 @@ extern void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *windo
 extern void Android_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 extern void Android_RestoreScreenKeyboardOnResume(SDL_VideoDevice *_this, SDL_Window *window);
 extern SDL_bool Android_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
-extern int Android_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect);
diff --git a/src/video/android/SDL_androidvideo.c b/src/video/android/SDL_androidvideo.c
index 5924a027ecdd9..8e2cae7c00b9d 100644
--- a/src/video/android/SDL_androidvideo.c
+++ b/src/video/android/SDL_androidvideo.c
@@ -143,9 +143,6 @@ static SDL_VideoDevice *Android_CreateDevice(void)
     /* Screensaver */
     device->SuspendScreenSaver = Android_SuspendScreenSaver;
 
-    /* Text input */
-    device->SetTextInputRect = Android_SetTextInputRect;
-
     /* Screen keyboard */
     device->HasScreenKeyboardSupport = Android_HasScreenKeyboardSupport;
     device->ShowScreenKeyboard = Android_ShowScreenKeyboard;
diff --git a/src/video/android/SDL_androidvideo.h b/src/video/android/SDL_androidvideo.h
index f6dd54eb56073..b7043ffa8de9e 100644
--- a/src/video/android/SDL_androidvideo.h
+++ b/src/video/android/SDL_androidvideo.h
@@ -35,7 +35,6 @@ extern void Android_SetDarkMode(SDL_bool enabled);
 
 struct SDL_VideoData
 {
-    SDL_Rect textRect;
     int isPaused;
     int isPausing;
     int pauseAudio;
diff --git a/src/video/cocoa/SDL_cocoakeyboard.h b/src/video/cocoa/SDL_cocoakeyboard.h
index 1314ec67d857d..ccbc057b44b6f 100644
--- a/src/video/cocoa/SDL_cocoakeyboard.h
+++ b/src/video/cocoa/SDL_cocoakeyboard.h
@@ -27,9 +27,9 @@ extern void Cocoa_InitKeyboard(SDL_VideoDevice *_this);
 extern void Cocoa_HandleKeyEvent(SDL_VideoDevice *_this, NSEvent *event);
 extern void Cocoa_QuitKeyboard(SDL_VideoDevice *_this);
 
-extern void Cocoa_StartTextInput(SDL_VideoDevice *_this);
-extern void Cocoa_StopTextInput(SDL_VideoDevice *_this);
-extern int Cocoa_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect);
+extern int Cocoa_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+extern int Cocoa_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+extern int Cocoa_UpdateTextInputRect(SDL_VideoDevice *_this, SDL_Window *window);
 
 extern int Cocoa_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, SDL_bool grabbed);
 
diff --git a/src/video/cocoa/SDL_cocoakeyboard.m b/src/video/cocoa/SDL_cocoakeyboard.m
index f6ef52bf3ad57..b003d6910b488 100644
--- a/src/video/cocoa/SDL_cocoakeyboard.m
+++ b/src/video/cocoa/SDL_cocoakeyboard.m
@@ -48,7 +48,7 @@ @implementation SDLTranslatorResponder
 

(Patch may be truncated, please check the link at the top of this post.)