Atlas - SDL_gdktextinput.cpp
Home / ext / SDL / src / video / gdk Lines: 1 | Size: 10229 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21/* 22 Screen keyboard and text input backend 23 for GDK platforms. 24*/ 25#include "SDL_internal.h" 26#include "SDL_gdktextinput.h" 27 28#ifdef SDL_GDK_TEXTINPUT 29 30// GDK headers are weird here 31#ifndef WIN32_LEAN_AND_MEAN 32#define WIN32_LEAN_AND_MEAN 33#endif 34#include <Windows.h> 35#include <XGameUI.h> 36#include <XUser.h> 37 38#ifdef __cplusplus 39extern "C" { 40#endif 41 42#include "../../events/SDL_keyboard_c.h" 43#include "../windows/SDL_windowsvideo.h" 44 45// TODO: Have a separate task queue for text input perhaps? 46static XTaskQueueHandle g_TextTaskQueue = NULL; 47// Global because there can be only one text entry shown at once. 48static XAsyncBlock *g_TextBlock = NULL; 49 50// Creation parameters 51static bool g_DidRegisterHints = false; 52static char *g_TitleText = NULL; 53static char *g_DescriptionText = NULL; 54static char *g_DefaultText = NULL; 55static const Sint32 g_DefaultTextInputScope = (Sint32)XGameUiTextEntryInputScope::Default; 56static Sint32 g_TextInputScope = g_DefaultTextInputScope; 57static const Sint32 g_DefaultMaxTextLength = 1024; // as per doc: maximum allowed amount on consoles 58static Sint32 g_MaxTextLength = g_DefaultMaxTextLength; 59 60static void SDLCALL GDK_InternalHintCallback( 61 void *userdata, 62 const char *name, 63 const char *oldValue, 64 const char *newValue) 65{ 66 if (!userdata) { 67 return; 68 } 69 70 // oldValue is ignored because we store it ourselves. 71 // name is ignored because we deduce it from userdata 72 73 if (userdata == &g_TextInputScope || userdata == &g_MaxTextLength) { 74 // int32 hint 75 Sint32 intValue = (!newValue || newValue[0] == '\0') ? 0 : SDL_atoi(newValue); 76 if (userdata == &g_MaxTextLength && intValue <= 0) { 77 intValue = g_DefaultMaxTextLength; 78 } else if (userdata == &g_TextInputScope && intValue < 0) { 79 intValue = g_DefaultTextInputScope; 80 } 81 82 *(Sint32 *)userdata = intValue; 83 } else { 84 // string hint 85 if (!newValue || newValue[0] == '\0') { 86 // treat empty or NULL strings as just NULL for this impl 87 SDL_free(*(char **)userdata); 88 *(char **)userdata = NULL; 89 } else { 90 char *newString = SDL_strdup(newValue); 91 if (newString) { 92 // free previous value and write the new one 93 SDL_free(*(char **)userdata); 94 *(char **)userdata = newString; 95 } 96 } 97 } 98} 99 100static bool GDK_InternalEnsureTaskQueue(void) 101{ 102 if (!g_TextTaskQueue) { 103 if (!SDL_GetGDKTaskQueue(&g_TextTaskQueue)) { 104 // SetError will be done for us. 105 return false; 106 } 107 } 108 return true; 109} 110 111static void CALLBACK GDK_InternalTextEntryCallback(XAsyncBlock *asyncBlock) 112{ 113 HRESULT hR = S_OK; 114 Uint32 resultSize = 0; 115 Uint32 resultUsed = 0; 116 char *resultBuffer = NULL; 117 118 // The keyboard will be already hidden when we reach this code 119 120 if (FAILED(hR = XGameUiShowTextEntryResultSize( 121 asyncBlock, 122 &resultSize))) { 123 SDL_SetError("XGameUiShowTextEntryResultSize failure with HRESULT of %08X", hR); 124 } else if (resultSize > 0) { 125 // +1 to be super sure that the buffer will be null terminated 126 resultBuffer = (char *)SDL_calloc(1 + (size_t)resultSize, sizeof(*resultBuffer)); 127 if (resultBuffer) { 128 // still pass the original size that we got from ResultSize 129 if (FAILED(hR = XGameUiShowTextEntryResult( 130 asyncBlock, 131 resultSize, 132 resultBuffer, 133 &resultUsed))) { 134 SDL_SetError("XGameUiShowTextEntryResult failure with HRESULT of %08X", hR); 135 } 136 // check that we have some text and that we weren't cancelled 137 else if (resultUsed > 0 && resultBuffer[0] != '\0') { 138 // it's null terminated so it's fine 139 SDL_SendKeyboardText(resultBuffer); 140 } 141 // we're done with the buffer 142 SDL_free(resultBuffer); 143 resultBuffer = NULL; 144 } 145 } 146 147 // free the async block after we're done 148 SDL_free(asyncBlock); 149 asyncBlock = NULL; 150 g_TextBlock = NULL; // once we do this we're fully done with the keyboard 151 152 SDL_SendScreenKeyboardHidden(); 153} 154 155void GDK_EnsureHints(void) 156{ 157 if (g_DidRegisterHints == false) { 158 SDL_AddHintCallback( 159 SDL_HINT_GDK_TEXTINPUT_TITLE, 160 GDK_InternalHintCallback, 161 &g_TitleText); 162 SDL_AddHintCallback( 163 SDL_HINT_GDK_TEXTINPUT_DESCRIPTION, 164 GDK_InternalHintCallback, 165 &g_DescriptionText); 166 SDL_AddHintCallback( 167 SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT, 168 GDK_InternalHintCallback, 169 &g_DefaultText); 170 SDL_AddHintCallback( 171 SDL_HINT_GDK_TEXTINPUT_SCOPE, 172 GDK_InternalHintCallback, 173 &g_TextInputScope); 174 SDL_AddHintCallback( 175 SDL_HINT_GDK_TEXTINPUT_MAX_LENGTH, 176 GDK_InternalHintCallback, 177 &g_MaxTextLength); 178 g_DidRegisterHints = true; 179 } 180} 181 182bool GDK_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 183{ 184 /* 185 * Currently a stub, since all input is handled by the virtual keyboard, 186 * but perhaps when implementing XGameUiTextEntryOpen in the future 187 * you will need this. 188 * 189 * Also XGameUiTextEntryOpen docs say that it is 190 * "not implemented on desktop" so... no thanks. 191 * 192 * Right now this function isn't implemented on Desktop 193 * and seems to be present only in the docs? So I didn't bother. 194 */ 195 return true; 196} 197 198bool GDK_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) 199{ 200 // See notice in GDK_StartTextInput 201 return true; 202} 203 204bool GDK_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) 205{ 206 /* 207 * XGameUiShowTextEntryAsync does not allow you to set 208 * the position of the virtual keyboard window. 209 * 210 * However, XGameUiTextEntryOpen seems to allow that, 211 * but again, see notice in GDK_StartTextInput. 212 * 213 * Right now it's a stub which may be useful later. 214 */ 215 return true; 216} 217 218bool GDK_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window) 219{ 220 // See notice in GDK_StartTextInput 221 return true; 222} 223 224bool GDK_HasScreenKeyboardSupport(SDL_VideoDevice *_this) 225{ 226 // Currently always true for this input method 227 return true; 228} 229 230void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) 231{ 232 /* 233 * There is XGameUiTextEntryOpen but it's only in online docs, 234 * My October Update 1 GDKX installation does not have this function defined 235 * and as such I decided not to use it at all, since some folks might use even older GDKs. 236 * 237 * That means the only text input option for us is a simple virtual keyboard widget. 238 */ 239 240 HRESULT hR = S_OK; 241 242 if (g_TextBlock) { 243 // already showing the keyboard 244 return; 245 } 246 247 if (!GDK_InternalEnsureTaskQueue()) { 248 // unable to obtain the SDL GDK queue 249 return; 250 } 251 252 g_TextBlock = (XAsyncBlock *)SDL_calloc(1, sizeof(*g_TextBlock)); 253 if (!g_TextBlock) { 254 return; 255 } 256 257 XGameUiTextEntryInputScope scope; 258 switch (SDL_GetTextInputType(props)) { 259 default: 260 case SDL_TEXTINPUT_TYPE_TEXT: 261 scope = (XGameUiTextEntryInputScope)g_TextInputScope; 262 break; 263 case SDL_TEXTINPUT_TYPE_TEXT_NAME: 264 scope = XGameUiTextEntryInputScope::Default; 265 break; 266 case SDL_TEXTINPUT_TYPE_TEXT_EMAIL: 267 scope = XGameUiTextEntryInputScope::EmailSmtpAddress; 268 break; 269 case SDL_TEXTINPUT_TYPE_TEXT_USERNAME: 270 scope = XGameUiTextEntryInputScope::Default; 271 break; 272 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN: 273 scope = XGameUiTextEntryInputScope::Password; 274 break; 275 case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE: 276 scope = XGameUiTextEntryInputScope::Default; 277 break; 278 case SDL_TEXTINPUT_TYPE_NUMBER: 279 scope = XGameUiTextEntryInputScope::Number; 280 break; 281 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: 282 // FIXME: Password or number scope? 283 scope = XGameUiTextEntryInputScope::Number; 284 break; 285 case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE: 286 scope = XGameUiTextEntryInputScope::Number; 287 break; 288 } 289 290 g_TextBlock->queue = g_TextTaskQueue; 291 g_TextBlock->context = _this; 292 g_TextBlock->callback = GDK_InternalTextEntryCallback; 293 if (SUCCEEDED(hR = XGameUiShowTextEntryAsync( 294 g_TextBlock, 295 g_TitleText, 296 g_DescriptionText, 297 g_DefaultText, 298 scope, 299 (uint32_t)g_MaxTextLength))) { 300 SDL_SendScreenKeyboardShown(); 301 } else { 302 SDL_free(g_TextBlock); 303 g_TextBlock = NULL; 304 SDL_SetError("XGameUiShowTextEntryAsync failure with HRESULT of %08X", hR); 305 } 306} 307 308void GDK_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window) 309{ 310 if (g_TextBlock) { 311 XAsyncCancel(g_TextBlock); 312 // the completion callback will free the block 313 } 314} 315 316#ifdef __cplusplus 317} 318#endif 319 320#endif 321[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.