SDL: Added virtual keyboard support for Xbox (thanks @ts-13512)

From b5d4206b303207e35e43c4e807ef4c0013a5c385 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 10 Mar 2024 09:44:49 -0700
Subject: [PATCH] Added virtual keyboard support for Xbox (thanks @ts-13512)

---
 VisualC-GDK/SDL/SDL.vcxproj             |   1 +
 VisualC-GDK/SDL/SDL.vcxproj.filters     |   1 +
 src/video/windows/SDL_windowskeyboard.c |   5 ++
 src/video/windows/SDL_windowskeyboard.h |   1 +
 src/video/windows/SDL_windowsvideo.c    |   3 +-
 src/video/windows/SDL_xboxkeyboard.cpp  | 109 ++++++++++++++++++++++++
 6 files changed, 119 insertions(+), 1 deletion(-)
 create mode 100644 src/video/windows/SDL_xboxkeyboard.cpp

diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 53926d49a16d2..872aff162686f 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -839,6 +839,7 @@
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_xboxkeyboard.cpp" />
     <ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_lsx.c" />
     <ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_sse.c" />
     <ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb_std.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 39aac2b531dce..1e07dc754ef4b 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -215,6 +215,7 @@
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
     <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_xboxkeyboard.cpp" />
     <ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb.c" />
     <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfilesystem.c" />
     <ClCompile Include="..\..\src\video\SDL_video_capture.c" />
diff --git a/src/video/windows/SDL_windowskeyboard.c b/src/video/windows/SDL_windowskeyboard.c
index 46104649e88e6..7b7fa97711627 100644
--- a/src/video/windows/SDL_windowskeyboard.c
+++ b/src/video/windows/SDL_windowskeyboard.c
@@ -210,6 +210,11 @@ void WIN_ResetDeadKeys()
     }
 }
 
+SDL_bool WIN_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
+{
+    return SDL_FALSE;
+}
+
 void WIN_StartTextInput(SDL_VideoDevice *_this)
 {
 #ifndef SDL_DISABLE_WINDOWS_IME
diff --git a/src/video/windows/SDL_windowskeyboard.h b/src/video/windows/SDL_windowskeyboard.h
index 76f8233e3085b..7756f7181ec85 100644
--- a/src/video/windows/SDL_windowskeyboard.h
+++ b/src/video/windows/SDL_windowskeyboard.h
@@ -34,6 +34,7 @@ extern void WIN_StopTextInput(SDL_VideoDevice *_this);
 extern int WIN_SetTextInputRect(SDL_VideoDevice *_this, const SDL_Rect *rect);
 extern void WIN_ClearComposition(SDL_VideoDevice *_this);
 extern SDL_bool WIN_IsTextInputShown(SDL_VideoDevice *_this);
+extern SDL_bool WIN_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
 
 extern SDL_bool IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, struct SDL_VideoData *videodata);
 
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 7100159830eee..b6152d36291d0 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -251,9 +251,10 @@ static SDL_VideoDevice *WIN_CreateDevice(void)
     device->Vulkan_CreateSurface = WIN_Vulkan_CreateSurface;
 #endif
 
-#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     device->StartTextInput = WIN_StartTextInput;
     device->StopTextInput = WIN_StopTextInput;
+    device->HasScreenKeyboardSupport = WIN_HasScreenKeyboardSupport;
+#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     device->SetTextInputRect = WIN_SetTextInputRect;
     device->ClearComposition = WIN_ClearComposition;
     device->IsTextInputShown = WIN_IsTextInputShown;
diff --git a/src/video/windows/SDL_xboxkeyboard.cpp b/src/video/windows/SDL_xboxkeyboard.cpp
new file mode 100644
index 0000000000000..342a7cdfa289d
--- /dev/null
+++ b/src/video/windows/SDL_xboxkeyboard.cpp
@@ -0,0 +1,109 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 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"
+
+#if SDL_VIDEO_DRIVER_WINDOWS && (defined(__XBOXONE__) || defined(__XBOXSERIES__))
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <XAsync.h>
+#include <XGameUI.h>
+#include <XGameRuntime.h>
+
+extern "C" {
+#include "../../events/SDL_keyboard_c.h"
+#include "SDL_windowsvideo.h"
+}
+
+/* Max length passed to XGameUiShowTextEntryAsync */
+#define SDL_XBOX_VIRTUAL_KEYBOARD_MAX_TEXT_LENGTH 1024
+
+
+SDL_bool
+WIN_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
+{
+    return SDL_TRUE;
+}
+
+void
+WIN_StartTextInput(SDL_VideoDevice *_this)
+{
+    XAsyncBlock* asyncBlock = new XAsyncBlock;
+    asyncBlock->context = nullptr;
+    asyncBlock->queue = NULL;
+    asyncBlock->callback = [](XAsyncBlock* async)
+    {
+        async->context;
+        uint32_t textBufSize;
+        HRESULT hr = XGameUiShowTextEntryResultSize(async, &textBufSize);
+
+        if (FAILED(hr))
+        {
+            SDL_Log("XGameUiShowTextEntryResultSize failed: 0x%08X", hr);
+            return;
+        }
+
+        if (textBufSize == 0)
+        {
+            return;
+        }
+
+        char* textBuf = new char[textBufSize + 1];
+        if (textBuf == nullptr)
+        {
+            SDL_Log("Allocating text buffer with size: XGameUiShowTextEntryResultSize(%ul) failed!", textBufSize);
+            return;
+        }
+
+        hr = XGameUiShowTextEntryResult(async, textBufSize, textBuf, nullptr);
+
+        if (FAILED(hr))
+        {
+            SDL_Log("XGameUiShowTextEntryResult failed: 0x%08X", hr);
+            delete[] textBuf;
+            return;
+        }
+
+        SDL_SendKeyboardText(textBuf);
+
+        // Use the text buffer
+       delete[] textBuf;
+    };
+
+    // This can be further improved, title, description, InputScope can be exposed for the user to set.
+    HRESULT hr = XGameUiShowTextEntryAsync(
+        asyncBlock,
+        "Enter text",
+        "",
+        "",
+        XGameUiTextEntryInputScope::Default,
+        SDL_XBOX_VIRTUAL_KEYBOARD_MAX_TEXT_LENGTH
+    );
+}
+
+void
+WIN_StopTextInput(SDL_VideoDevice *_this)
+{
+}
+
+#endif /* SDL_VIDEO_DRIVER_WINDOWS */