SDL: gdk: Virtual keyboard and text input backend

From c886e8067507b3de47b3a7ad6a37aa8cac12e6f1 Mon Sep 17 00:00:00 2001
From: Nikita Krapivin <[EMAIL REDACTED]>
Date: Thu, 1 Jun 2023 01:09:34 +0500
Subject: [PATCH] gdk: Virtual keyboard and text input backend

---
 VisualC-GDK/SDL/SDL.vcxproj                   |   5 +
 VisualC-GDK/SDL/SDL.vcxproj.filters           |   9 +
 include/SDL3/SDL_hints.h                      | 104 ++++++
 .../build_config/SDL_build_config_wingdk.h    |   3 +
 include/build_config/SDL_build_config_xbox.h  |   2 +
 src/video/gdk/SDL_gdktextinput.cpp            | 304 ++++++++++++++++++
 src/video/gdk/SDL_gdktextinput.h              |  51 +++
 src/video/windows/SDL_windowsvideo.c          |  19 ++
 8 files changed, 497 insertions(+)
 create mode 100644 src/video/gdk/SDL_gdktextinput.cpp
 create mode 100644 src/video/gdk/SDL_gdktextinput.h

diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index f9768a123877..6d898f98eaa9 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -485,6 +485,7 @@
     <ClInclude Include="..\..\src\video\dummy\SDL_nullevents_c.h" />
     <ClInclude Include="..\..\src\video\dummy\SDL_nullframebuffer_c.h" />
     <ClInclude Include="..\..\src\video\dummy\SDL_nullvideo.h" />
+    <ClInclude Include="..\..\src\video\gdk\SDL_gdktextinput.h" />
     <ClInclude Include="..\..\src\video\khronos\vulkan\vk_icd.h" />
     <ClInclude Include="..\..\src\video\khronos\vulkan\vk_layer.h" />
     <ClInclude Include="..\..\src\video\khronos\vulkan\vk_platform.h" />
@@ -738,6 +739,10 @@
     <ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
     <ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
+    <ClCompile Include="..\..\src\video\gdk\SDL_gdktextinput.cpp">
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|Gaming.Desktop.x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+      <PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Gaming.Desktop.x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
+    </ClCompile>
     <ClCompile Include="..\..\src\video\SDL_blit.c" />
     <ClCompile Include="..\..\src\video\SDL_blit_0.c" />
     <ClCompile Include="..\..\src\video\SDL_blit_1.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 443e589f9870..769c45215ba8 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -172,6 +172,9 @@
     <Filter Include="core\gdk">
       <UniqueIdentifier>{3ab60a46-e18e-450a-a916-328fb8547e59}</UniqueIdentifier>
     </Filter>
+    <Filter Include="video\gdk">
+      <UniqueIdentifier>{3ad16a8a-0ed8-439c-a771-383af2e2867f}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\include\SDL3\SDL_begin_code.h">
@@ -821,6 +824,9 @@
     <ClInclude Include="..\..\src\render\direct3d12\SDL_render_d3d12_xbox.h">
       <Filter>render\direct3d12</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\video\gdk\SDL_gdktextinput.h">
+      <Filter>video\gdk</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\thread\generic\SDL_sysrwlock_c.h">
       <Filter>thread\generic</Filter>
     </ClInclude>
@@ -1367,6 +1373,9 @@
     <ClCompile Include="..\..\src\core\windows\pch_cpp.cpp">
       <Filter>core\windows</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\video\gdk\SDL_gdktextinput.cpp">
+      <Filter>video\gdk</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c">
       <Filter>thread\windows</Filter>
     </ClCompile>
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index d8a8416650c7..daf4d057c5c0 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2413,6 +2413,110 @@ extern "C" {
 #define SDL_HINT_TRACKPAD_IS_TOUCH_ONLY "SDL_TRACKPAD_IS_TOUCH_ONLY"
 
 
+/**
+ *  \brief  Sets the title of the TextInput window on GDK platforms.
+ *
+ *  On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the
+ *  standard SDL text input and virtual keyboard capabilities
+ *  to get text from the user.
+ *
+ *  This hint allows you to customize the virtual keyboard
+ *  window that will be shown to the user.
+ *
+ *  Set this hint to change the title of the window.
+ *
+ *  This hint will not affect a window that is already being
+ *  shown to the user. It will only affect new input windows.
+ *
+ *  This hint is available only if SDL_GDK_TEXTINPUT defined.
+ */
+#define SDL_HINT_GDK_TEXTINPUT_TITLE "SDL_GDK_TEXTINPUT_TITLE"
+
+/**
+ *  \brief  Sets the description of the TextInput window on GDK platforms.
+ *
+ *  On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the
+ *  standard SDL text input and virtual keyboard capabilities
+ *  to get text from the user.
+ *
+ *  This hint allows you to customize the virtual keyboard
+ *  window that will be shown to the user.
+ *
+ *  Set this hint to change the description of the window.
+ *
+ *  This hint will not affect a window that is already being
+ *  shown to the user. It will only affect new input windows.
+ *
+ *  This hint is available only if SDL_GDK_TEXTINPUT defined.
+ */
+#define SDL_HINT_GDK_TEXTINPUT_DESCRIPTION "SDL_GDK_TEXTINPUT_DESCRIPTION"
+
+/**
+ *  \brief  Sets the default text of the TextInput window on GDK platforms.
+ *
+ *  On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the
+ *  standard SDL text input and virtual keyboard capabilities
+ *  to get text from the user.
+ *
+ *  This hint allows you to customize the virtual keyboard
+ *  window that will be shown to the user.
+ *
+ *  Set this hint to change the default text value of the window.
+ *
+ *  This hint will not affect a window that is already being
+ *  shown to the user. It will only affect new input windows.
+ *
+ *  This hint is available only if SDL_GDK_TEXTINPUT defined.
+ */
+#define SDL_HINT_GDK_TEXTINPUT_DEFAULT "SDL_GDK_TEXTINPUT_DEFAULT"
+
+/**
+ *  \brief  Sets the input scope of the TextInput window on GDK platforms.
+ *
+ *  On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the
+ *  standard SDL text input and virtual keyboard capabilities
+ *  to get text from the user.
+ *
+ *  This hint allows you to customize the virtual keyboard
+ *  window that will be shown to the user.
+ *
+ *  Set this hint to change the XGameUiTextEntryInputScope value
+ *  that will be passed to the window creation function.
+ *
+ *  The value must be a stringified integer,
+ *  for example "0" for XGameUiTextEntryInputScope::Default.
+ *
+ *  This hint will not affect a window that is already being
+ *  shown to the user. It will only affect new input windows.
+ *
+ *  This hint is available only if SDL_GDK_TEXTINPUT defined.
+ */
+#define SDL_HINT_GDK_TEXTINPUT_SCOPE "SDL_GDK_TEXTINPUT_SCOPE"
+
+/**
+ *  \brief  Sets the maximum input length of the TextInput window on GDK platforms.
+ *
+ *  On GDK, if SDL_GDK_TEXTINPUT is defined, you can use the
+ *  standard SDL text input and virtual keyboard capabilities
+ *  to get text from the user.
+ *
+ *  This hint allows you to customize the virtual keyboard
+ *  window that will be shown to the user.
+ *
+ *  Set this hint to change the maximum allowed input
+ *  length of the text box in the virtual keyboard window.
+ *
+ *  The value must be a stringified integer,
+ *  for example "10" to allow for up to 10 characters of text input.
+ *
+ *  This hint will not affect a window that is already being
+ *  shown to the user. It will only affect new input windows.
+ *
+ *  This hint is available only if SDL_GDK_TEXTINPUT defined.
+ */
+#define SDL_HINT_GDK_TEXTINPUT_MAX_LENGTH "SDL_GDK_TEXTINPUT_MAX_LENGTH"
+
+
 /**
  *  \brief  An enumeration of hint priorities
  */
diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h
index 54b0359c23cd..47adef5068e1 100644
--- a/include/build_config/SDL_build_config_wingdk.h
+++ b/include/build_config/SDL_build_config_wingdk.h
@@ -245,4 +245,7 @@
 /* Enable filesystem support */
 #define SDL_FILESYSTEM_WINDOWS  1
 
+/* Use the (inferior) GDK text input method for GDK platforms */
+/*#define SDL_GDK_TEXTINPUT 1*/
+
 #endif /* SDL_build_config_wingdk_h_ */
diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h
index 4e441895f77d..622a8bc4adfe 100644
--- a/include/build_config/SDL_build_config_xbox.h
+++ b/include/build_config/SDL_build_config_xbox.h
@@ -231,5 +231,7 @@
 
 /* Disable IME as not supported yet (TODO: Xbox IME?) */
 #define SDL_DISABLE_WINDOWS_IME 1
+/* Use the (inferior) GDK text input method for GDK platforms */
+#define SDL_GDK_TEXTINPUT 1
 
 #endif /* SDL_build_config_wingdk_h_ */
diff --git a/src/video/gdk/SDL_gdktextinput.cpp b/src/video/gdk/SDL_gdktextinput.cpp
new file mode 100644
index 000000000000..934a029eb290
--- /dev/null
+++ b/src/video/gdk/SDL_gdktextinput.cpp
@@ -0,0 +1,304 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+/*
+  Screen keyboard and text input backend
+  for GDK platforms.
+*/
+#include "SDL_internal.h"
+#include "SDL_gdktextinput.h"
+
+#ifdef SDL_GDK_TEXTINPUT
+
+/* GDK headers are weird here */
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <Windows.h>
+#include <XGameUI.h>
+#include <XUser.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../../events/SDL_keyboard_c.h"
+#include "../windows/SDL_windowsvideo.h"
+
+/* TODO: Have a separate task queue for text input perhaps? */
+static XTaskQueueHandle g_TextTaskQueue = NULL;
+/* Global because there can be only one text entry shown at once. */
+static XAsyncBlock *g_TextBlock = NULL;
+
+/* Creation parameters */
+static SDL_bool g_DidRegisterHints = SDL_FALSE;
+static char *g_TitleText = NULL;
+static char *g_DescriptionText = NULL;
+static char *g_DefaultText = NULL;
+static const Sint32 g_DefaultTextInputScope = (Sint32)XGameUiTextEntryInputScope::Default;
+static Sint32 g_TextInputScope = g_DefaultTextInputScope;
+static const Sint32 g_DefaultMaxTextLength = 1024; /* as per doc: maximum allowed amount on consoles */
+static Sint32 g_MaxTextLength = g_DefaultMaxTextLength;
+
+static void SDLCALL GDK_InternalHintCallback(
+    void *userdata,
+    const char *name,
+    const char *oldValue,
+    const char *newValue)
+{
+    if (userdata == NULL) {
+        return;
+    }
+
+    /* oldValue is ignored because we store it ourselves. */
+    /* name is ignored because we deduce it from userdata */
+
+    if (userdata == &g_TextInputScope || userdata == &g_MaxTextLength) {
+        /* int32 hint */
+        Sint32 intValue = (newValue == NULL || newValue[0] == '\0') ? 0 : SDL_atoi(newValue);
+        if (userdata == &g_MaxTextLength && intValue <= 0) {
+            intValue = g_DefaultMaxTextLength;
+        } else if (userdata == &g_TextInputScope && intValue < 0) {
+            intValue = g_DefaultTextInputScope;
+        }
+
+        *(Sint32 *)userdata = intValue;
+    } else {
+        /* string hint */
+        if (newValue == NULL || newValue[0] == '\0') {
+            /* treat empty or NULL strings as just NULL for this impl */
+            SDL_free(*(char **)userdata);
+            *(char **)userdata = NULL;
+        } else {
+            char *newString = SDL_strdup(newValue);
+            if (newString == NULL) {
+                /* couldn't strdup, oh well */
+                SDL_OutOfMemory();
+            } else {
+                /* free previous value and write the new one */
+                SDL_free(*(char **)userdata);
+                *(char **)userdata = newString;
+            }
+        }
+    }
+}
+
+static int GDK_InternalEnsureTaskQueue(void)
+{
+    if (g_TextTaskQueue == NULL) {
+        if (SDL_GDKGetTaskQueue(&g_TextTaskQueue) < 0) {
+            /* SetError will be done for us. */
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+static void CALLBACK GDK_InternalTextEntryCallback(XAsyncBlock *asyncBlock)
+{
+    HRESULT hR = S_OK;
+    Uint32 resultSize = 0;
+    Uint32 resultUsed = 0;
+    char *resultBuffer = NULL;
+
+    /* The keyboard will be already hidden when we reach this code */
+
+    if (FAILED(hR = XGameUiShowTextEntryResultSize(
+                   asyncBlock,
+                   &resultSize))) {
+        SDL_SetError("XGameUiShowTextEntryResultSize failure with HRESULT of %08X", hR);
+    } else if (resultSize > 0) {
+        /* +1 to be super sure that the buffer will be null terminated */
+        resultBuffer = (char *)SDL_calloc(sizeof(*resultBuffer), 1 + (size_t)resultSize);
+        if (resultBuffer == NULL) {
+            SDL_OutOfMemory();
+        } else {
+            /* still pass the original size that we got from ResultSize */
+            if (FAILED(hR = XGameUiShowTextEntryResult(
+                           asyncBlock,
+                           resultSize,
+                           resultBuffer,
+                           &resultUsed))) {
+                SDL_SetError("XGameUiShowTextEntryResult failure with HRESULT of %08X", hR);
+            }
+            /* check that we have some text and that we weren't cancelled */
+            else if (resultUsed > 0 && resultBuffer[0] != '\0') {
+                /* it's null terminated so it's fine */
+                SDL_SendKeyboardText(resultBuffer);
+            }
+            /* we're done with the buffer */
+            SDL_free(resultBuffer);
+            resultBuffer = NULL;
+        }
+    }
+
+    /* free the async block after we're done */
+    SDL_free(asyncBlock);
+    asyncBlock = NULL;
+    g_TextBlock = NULL; /* once we do this we're fully done with the keyboard */
+}
+
+void GDK_EnsureHints(void)
+{
+    if (g_DidRegisterHints == SDL_FALSE) {
+        SDL_AddHintCallback(
+            SDL_HINT_GDK_TEXTINPUT_TITLE,
+            GDK_InternalHintCallback,
+            &g_TitleText);
+        SDL_AddHintCallback(
+            SDL_HINT_GDK_TEXTINPUT_DESCRIPTION,
+            GDK_InternalHintCallback,
+            &g_DescriptionText);
+        SDL_AddHintCallback(
+            SDL_HINT_GDK_TEXTINPUT_DEFAULT,
+            GDK_InternalHintCallback,
+            &g_DefaultText);
+        SDL_AddHintCallback(
+            SDL_HINT_GDK_TEXTINPUT_SCOPE,
+            GDK_InternalHintCallback,
+            &g_TextInputScope);
+        SDL_AddHintCallback(
+            SDL_HINT_GDK_TEXTINPUT_MAX_LENGTH,
+            GDK_InternalHintCallback,
+            &g_MaxTextLength);
+        g_DidRegisterHints = SDL_TRUE;
+    }
+}
+
+void GDK_StartTextInput(SDL_VideoDevice *_this)
+{
+    /*
+     * Currently a stub, since all input is handled by the virtual keyboard,
+     * but perhaps when implementing XGameUiTextEntryOpen in the future
+     * you will need this.
+     *
+     * Also XGameUiTextEntryOpen docs say that it is
+     * "not implemented on desktop" so... no thanks.
+     *
+     * Right now this function isn't implemented on Desktop
+     * and seems to be present only in the docs? So I didn't bother.
+     */
+}
+
+void GDK_StopTextInput(SDL_VideoDevice *_this)
+{
+    /* See notice in GDK_StartTextInput */
+}
+
+int GDK_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect)
+{
+    /*
+     * XGameUiShowTextEntryAsync does not allow you to set
+     * the position of the virtual keyboard window.
+     *
+     * However, XGameUiTextEntryOpen seems to allow that,
+     * but again, see notice in GDK_StartTextInput.
+     *
+     * Right now it's a stub which may be useful later.
+     */
+    return 0;
+}
+
+void GDK_ClearComposition(SDL_VideoDevice *_this)
+{
+    /* See notice in GDK_StartTextInput */
+}
+
+SDL_bool GDK_IsTextInputShown(SDL_VideoDevice *_this)
+{
+    /*
+     * The XGameUiShowTextEntryAsync window
+     * does specify potential input candidates
+     * just below the text box, so technically
+     * this is true whenever the window is shown.
+     */
+    return (g_TextBlock != NULL) ? SDL_TRUE : SDL_FALSE;
+}
+
+SDL_bool GDK_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
+{
+    /* Currently always true for this input method */
+    return SDL_TRUE;
+}
+
+void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+{
+    /*
+     * There is XGameUiTextEntryOpen but it's only in online docs,
+     * My October Update 1 GDKX installation does not have this function defined
+     * and as such I decided not to use it at all, since some folks might use even older GDKs.
+     *
+     * That means the only text input option for us is a simple virtual keyboard widget.
+     */
+
+    HRESULT hR = S_OK;
+
+    if (g_TextBlock != NULL) {
+        /* already showing the keyboard */
+        return;
+    }
+
+    if (GDK_InternalEnsureTaskQueue() < 0) {
+        /* unable to obtain the SDL GDK queue */
+        return;
+    }
+
+    g_TextBlock = (XAsyncBlock *)SDL_calloc(1, sizeof(*g_TextBlock));
+    if (g_TextBlock == NULL) {
+        SDL_OutOfMemory();
+        return;
+    }
+
+    g_TextBlock->queue = g_TextTaskQueue;
+    g_TextBlock->context = _this;
+    g_TextBlock->callback = GDK_InternalTextEntryCallback;
+    if (FAILED(hR = XGameUiShowTextEntryAsync(
+                   g_TextBlock,
+                   g_TitleText,
+                   g_DescriptionText,
+                   g_DefaultText,
+                   (XGameUiTextEntryInputScope)g_TextInputScope,
+                   (uint32_t)g_MaxTextLength))) {
+        SDL_free(g_TextBlock);
+        g_TextBlock = NULL;
+        SDL_SetError("XGameUiShowTextEntryAsync failure with HRESULT of %08X", hR);
+    }
+}
+
+void GDK_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+{
+    if (g_TextBlock != NULL) {
+        XAsyncCancel(g_TextBlock);
+        /* the completion callback will free the block */
+    }
+}
+
+SDL_bool GDK_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window)
+{
+    /* See notice in GDK_IsTextInputShown */
+    return GDK_IsTextInputShown(_this);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/video/gdk/SDL_gdktextinput.h b/src/video/gdk/SDL_gdktextinput.h
new file mode 100644
index 000000000000..41f771788187
--- /dev/null
+++ b/src/video/gdk/SDL_gdktextinput.h
@@ -0,0 +1,51 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifndef SDL_gdktextinput_h_
+#define SDL_gdktextinput_h_
+
+#ifdef SDL_GDK_TEXTINPUT
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "../SDL_sysvideo.h"
+
+void GDK_EnsureHints(void);
+
+void GDK_StartTextInput(SDL_VideoDevice *_this);
+void GDK_StopTextInput(SDL_VideoDevice *_this);
+int GDK_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect);
+void GDK_ClearComposition(SDL_VideoDevice *_this);
+SDL_bool GDK_IsTextInputShown(SDL_VideoDevice *_this);
+
+SDL_bool GDK_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
+void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+void GDK_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+SDL_bool GDK_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+
+#endif
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 93427e9710f0..b1617385b6b2 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -31,6 +31,10 @@
 #include "SDL_windowsshape.h"
 #include "SDL_windowsvulkan.h"
 
+#ifdef SDL_GDK_TEXTINPUT
+#include "../gdk/SDL_gdktextinput.h"
+#endif
+
 /* #define HIGHDPI_DEBUG */
 
 /* Initialization/Query functions */
@@ -258,6 +262,21 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
     device->HasClipboardText = WIN_HasClipboardText;
 #endif
 
+#ifdef SDL_GDK_TEXTINPUT
+    GDK_EnsureHints();
+
+    device->StartTextInput = GDK_StartTextInput;
+    device->StopTextInput = GDK_StopTextInput;
+    device->SetTextInputRect = GDK_SetTextInputRect;
+    device->ClearComposition = GDK_ClearComposition;
+    device->IsTextInputShown = GDK_IsTextInputShown;
+
+    device->HasScreenKeyboardSupport = GDK_HasScreenKeyboardSupport;
+    device->ShowScreenKeyboard = GDK_ShowScreenKeyboard;
+    device->HideScreenKeyboard = GDK_HideScreenKeyboard;
+    device->IsScreenKeyboardShown = GDK_IsScreenKeyboardShown;
+#endif
+
     device->free = WIN_DeleteDevice;
 
     device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT;