SDL: Added SDL_StartTextInputWithProperties()

From 81f8e6aba6b3b73e44767a61f37e8f72b18ce3e4 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 2 Aug 2024 06:56:51 -0700
Subject: [PATCH] Added SDL_StartTextInputWithProperties()

This allows you to customize the text input so you can have numeric text entry, hidden passwords, etc.

Fixes https://github.com/libsdl-org/SDL/issues/7101
Fixes https://github.com/libsdl-org/SDL/issues/7965
Fixes https://github.com/libsdl-org/SDL/issues/9439
---
 .../SDLTest/SDLTest.xcodeproj/project.pbxproj |   2 +
 .../main/java/org/libsdl/app/SDLActivity.java |   9 +-
 .../java/org/libsdl/app/SDLDummyEdit.java     |   8 +-
 include/SDL3/SDL_hints.h                      |   2 +
 include/SDL3/SDL_keyboard.h                   |  82 ++++++++++
 src/core/android/SDL_android.c                |   5 +-
 src/core/android/SDL_android.h                |   2 +-
 src/dynapi/SDL_dynapi.sym                     |   1 +
 src/dynapi/SDL_dynapi_overrides.h             |   1 +
 src/dynapi/SDL_dynapi_procs.h                 |   1 +
 src/events/SDL_keyboard.c                     |   2 +-
 src/video/SDL_sysvideo.h                      |  10 +-
 src/video/SDL_video.c                         |  76 ++++++++-
 src/video/android/SDL_androidkeyboard.c       | 104 +++++++++++-
 src/video/android/SDL_androidkeyboard.h       |   2 +-
 src/video/cocoa/SDL_cocoakeyboard.h           |   2 +-
 src/video/cocoa/SDL_cocoakeyboard.m           |   2 +-
 src/video/gdk/SDL_gdktextinput.cpp            |  39 ++++-
 src/video/gdk/SDL_gdktextinput.h              |   4 +-
 src/video/n3ds/SDL_n3dsswkb.c                 |   2 +-
 src/video/n3ds/SDL_n3dsswkb.h                 |   2 +-
 src/video/psp/SDL_pspvideo.c                  |  33 +++-
 src/video/psp/SDL_pspvideo.h                  |   2 +-
 src/video/uikit/SDL_uikitviewcontroller.h     |   2 +-
 src/video/uikit/SDL_uikitviewcontroller.m     | 109 +++++++++++--
 src/video/vita/SDL_vitavideo.c                |  44 ++++-
 src/video/vita/SDL_vitavideo.h                |   2 +-
 src/video/wayland/SDL_waylandkeyboard.c       |  66 +++++++-
 src/video/wayland/SDL_waylandkeyboard.h       |   2 +-
 src/video/windows/SDL_windowskeyboard.c       |   2 +-
 src/video/windows/SDL_windowskeyboard.h       |   2 +-
 src/video/winrt/SDL_winrtevents_c.h           |   2 +-
 src/video/winrt/SDL_winrtkeyboard.cpp         |   2 +-
 src/video/x11/SDL_x11keyboard.c               |  29 +++-
 src/video/x11/SDL_x11keyboard.h               |   4 +-
 test/testime.c                                | 154 ++++++++++++++++--
 36 files changed, 737 insertions(+), 76 deletions(-)

diff --git a/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj b/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj
index 0d28405ff1966..a3b389edca9c0 100644
--- a/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj
+++ b/Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj
@@ -185,6 +185,7 @@
 		F3C17D3928E424B800E1A26D /* sample.wav in Resources */ = {isa = PBXBuildFile; fileRef = 00794E6209D20839003FC8A1 /* sample.wav */; };
 		F3C17D3B28E4252900E1A26D /* icon.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; };
 		F3C2CAC62C5C8BD6004D7998 /* unifont-15.1.05.hex in Resources */ = {isa = PBXBuildFile; fileRef = F3C2CAC52C5C8BD6004D7998 /* unifont-15.1.05.hex */; };
+		F3C2CB072C5D3FB2004D7998 /* icon.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 00794E5D09D20839003FC8A1 /* icon.bmp */; };
 		F3CB56892A7895F800766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; };
 		F3CB568A2A7895F800766177 /* SDL3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		F3CB568C2A7896BF00766177 /* SDL3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 003FA643093FFD41000C53B3 /* SDL3.framework */; };
@@ -3026,6 +3027,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				F3C2CB072C5D3FB2004D7998 /* icon.bmp in Resources */,
 				F3C2CAC62C5C8BD6004D7998 /* unifont-15.1.05.hex in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
index 6cfbc904f9598..134a3dfdcf604 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
@@ -1375,9 +1375,11 @@ static class ShowTextInputTask implements Runnable {
          */
         static final int HEIGHT_PADDING = 15;
 
+        public int input_type;
         public int x, y, w, h;
 
-        public ShowTextInputTask(int x, int y, int w, int h) {
+        public ShowTextInputTask(int input_type, int x, int y, int w, int h) {
+            this.input_type = input_type;
             this.x = x;
             this.y = y;
             this.w = w;
@@ -1405,6 +1407,7 @@ public void run() {
             } else {
                 mTextEdit.setLayoutParams(params);
             }
+            mTextEdit.setInputType(input_type);
 
             mTextEdit.setVisibility(View.VISIBLE);
             mTextEdit.requestFocus();
@@ -1419,9 +1422,9 @@ public void run() {
     /**
      * This method is called by SDL using JNI.
      */
-    public static boolean showTextInput(int x, int y, int w, int h) {
+    public static boolean showTextInput(int input_type, int x, int y, int w, int h) {
         // Transfer the task to the main thread as a Runnable
-        return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
+        return mSingleton.commandHandler.post(new ShowTextInputTask(input_type, x, y, w, h));
     }
 
     public static boolean isTextInputEvent(KeyEvent event) {
diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java b/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
index dca28145ec19c..40e556ff41066 100644
--- a/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
+++ b/android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
@@ -12,6 +12,7 @@
 public class SDLDummyEdit extends View implements View.OnKeyListener
 {
     InputConnection ic;
+    int input_type;
 
     public SDLDummyEdit(Context context) {
         super(context);
@@ -20,6 +21,10 @@ public SDLDummyEdit(Context context) {
         setOnKeyListener(this);
     }
 
+    public void setInputType(int input_type) {
+        this.input_type = input_type;
+    }
+
     @Override
     public boolean onCheckIsTextEditor() {
         return true;
@@ -51,8 +56,7 @@ public boolean onKeyPreIme (int keyCode, KeyEvent event) {
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
         ic = new SDLInputConnection(this, true);
 
-        outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
-                             InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+        outAttrs.inputType = input_type;
         outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |
                               EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
 
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index eb14e7a2c6421..79aa0089e3788 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2620,6 +2620,8 @@ extern "C" {
  * A variable to control whether the return key on the soft keyboard should
  * hide the soft keyboard on Android and iOS.
  *
+ * This hint sets the default value of SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN.
+ *
  * The variable can be set to the following values:
  *
  * - "0": The return key will be handled as a key event. (default)
diff --git a/include/SDL3/SDL_keyboard.h b/include/SDL3/SDL_keyboard.h
index b5453a0a15408..67c4f5842e037 100644
--- a/include/SDL3/SDL_keyboard.h
+++ b/include/SDL3/SDL_keyboard.h
@@ -367,11 +367,93 @@ extern SDL_DECLSPEC SDL_Keycode SDLCALL SDL_GetKeyFromName(const char *name);
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_SetTextInputArea
+ * \sa SDL_StartTextInputWithProperties
  * \sa SDL_StopTextInput
  * \sa SDL_TextInputActive
  */
 extern SDL_DECLSPEC int SDLCALL SDL_StartTextInput(SDL_Window *window);
 
+/**
+ * Text input type.
+ *
+ * These are the valid values for SDL_PROP_TEXTINPUT_TYPE_NUMBER. Not every value is valid on every platform, but where a value isn't supported, a reasonable fallback will be used.
+ *
+ * \since This enum is available since SDL 3.0.0.
+ *
+ * \sa SDL_StartTextInputWithProperties
+ */
+typedef enum SDL_TextInputType
+{
+    SDL_TEXTINPUT_TYPE_TEXT,                        /**< The input is text */
+    SDL_TEXTINPUT_TYPE_TEXT_NAME,                   /**< The input is a person's name */
+    SDL_TEXTINPUT_TYPE_TEXT_EMAIL,                  /**< The input is an e-mail address */
+    SDL_TEXTINPUT_TYPE_TEXT_USERNAME,               /**< The input is a username */
+    SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN,        /**< The input is a secure password that is hidden */
+    SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE,       /**< The input is a secure password that is visible */
+    SDL_TEXTINPUT_TYPE_NUMBER,                      /**< The input is a number */
+    SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN,      /**< The input is a secure PIN that is hidden */
+    SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE      /**< The input is a secure PIN that is visible */
+} SDL_TextInputType;
+
+/**
+ * Auto capitalization type.
+ *
+ * These are the valid values for SDL_PROP_TEXTINPUT_AUTOCAPITALIZATION_NUMBER. Not every value is valid on every platform, but where a value isn't supported, a reasonable fallback will be used.
+ *
+ * \since This enum is available since SDL 3.0.0.
+ *
+ * \sa SDL_StartTextInputWithProperties
+ */
+typedef enum SDL_Capitalization
+{
+    SDL_CAPITALIZE_NONE,        /**< No auto-capitalization will be done */
+    SDL_CAPITALIZE_SENTENCES,   /**< The first letter of sentences will be capitalized */
+    SDL_CAPITALIZE_WORDS,       /**< The first letter of words will be capitalized */
+    SDL_CAPITALIZE_LETTERS      /**< All letters will be capitalized */
+} SDL_Capitalization;
+
+/**
+ * Start accepting Unicode text input events in a window, with properties describing the input.
+ *
+ * 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 shows the screen keyboard.
+ *
+ * These are the supported properties:
+ *
+ * - `SDL_PROP_TEXTINPUT_TYPE_NUMBER` - an SDL_TextInputType value that describes text being input, defaults to SDL_TEXTINPUT_TYPE_TEXT.
+ * - `SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER` - an SDL_Capitalization value that describes how text should be capitalized, defaults to SDL_CAPITALIZE_NONE.
+ * - `SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN` - true to enable auto completion and auto correction.
+ * - `SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN` - true if multiple lines of text are allowed. This defaults to true if SDL_HINT_RETURN_KEY_HIDES_IME is "0" or is not set, and defaults to false if SDL_HINT_RETURN_KEY_HIDES_IME is "1".
+ *
+ * On Android you can directly specify the input type:
+ *
+ * - `SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER` - the text input type to use, overriding other properties. This is documented at https://developer.android.com/reference/android/text/InputType
+ *
+ * \param window the window to enable text input.
+ * \param props the properties to use.
+ * \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_SetTextInputArea
+ * \sa SDL_StartTextInput
+ * \sa SDL_StopTextInput
+ * \sa SDL_TextInputActive
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_StartTextInputWithProperties(SDL_Window *window, SDL_PropertiesID props);
+
+#define SDL_PROP_TEXTINPUT_TYPE_NUMBER                  "SDL.textinput.type"
+#define SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER        "SDL.textinput.capitalization"
+#define SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN          "SDL.textinput.autocorrect"
+#define SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN            "SDL.textinput.multiline"
+#define SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER     "SDL.textinput.android.inputtype"
+
 /**
  * Check whether or not Unicode text input events are enabled for a window.
  *
diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c
index c0c73bcf6ceb4..bf0f30b986114 100644
--- a/src/core/android/SDL_android.c
+++ b/src/core/android/SDL_android.c
@@ -645,7 +645,7 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
     midSetSystemCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setSystemCursor", "(I)Z");
     midSetWindowStyle = (*env)->GetStaticMethodID(env, mActivityClass, "setWindowStyle", "(Z)V");
     midShouldMinimizeOnFocusLoss = (*env)->GetStaticMethodID(env, mActivityClass, "shouldMinimizeOnFocusLoss", "()Z");
-    midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
+    midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIIII)Z");
     midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z");
     midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
     midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z");
@@ -2044,10 +2044,11 @@ int Android_JNI_SuspendScreenSaver(SDL_bool suspend)
     return Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
 }
 
-void Android_JNI_ShowScreenKeyboard(SDL_Rect *inputRect)
+void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect)
 {
     JNIEnv *env = Android_JNI_GetEnv();
     (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
+                                    input_type,
                                     inputRect->x,
                                     inputRect->y,
                                     inputRect->w,
diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h
index ef0df18e30b19..6950ef2c76ebc 100644
--- a/src/core/android/SDL_android.h
+++ b/src/core/android/SDL_android.h
@@ -63,7 +63,7 @@ extern void Android_JNI_MinizeWindow(void);
 extern SDL_bool Android_JNI_ShouldMinimizeOnFocusLoss(void);
 
 extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]);
-extern void Android_JNI_ShowScreenKeyboard(SDL_Rect *inputRect);
+extern void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect);
 extern void Android_JNI_HideScreenKeyboard(void);
 extern SDL_bool Android_JNI_IsScreenKeyboardShown(void);
 extern ANativeWindow *Android_JNI_GetNativeWindow(void);
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index bf7a2276b3153..38aad316a8e65 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -816,6 +816,7 @@ SDL3_0.0.0 {
     SDL_SignalCondition;
     SDL_SignalSemaphore;
     SDL_StartTextInput;
+    SDL_StartTextInputWithProperties;
     SDL_StepUTF8;
     SDL_StopHapticEffect;
     SDL_StopHapticEffects;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 96d40eaa36afc..004eee31cbb28 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -841,6 +841,7 @@
 #define SDL_SignalCondition SDL_SignalCondition_REAL
 #define SDL_SignalSemaphore SDL_SignalSemaphore_REAL
 #define SDL_StartTextInput SDL_StartTextInput_REAL
+#define SDL_StartTextInputWithProperties SDL_StartTextInputWithProperties_REAL
 #define SDL_StepUTF8 SDL_StepUTF8_REAL
 #define SDL_StopHapticEffect SDL_StopHapticEffect_REAL
 #define SDL_StopHapticEffects SDL_StopHapticEffects_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 749af3ce25ff2..6cdf7ff094b65 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -851,6 +851,7 @@ SDL_DYNAPI_PROC(int,SDL_ShowWindowSystemMenu,(SDL_Window *a, int b, int c),(a,b,
 SDL_DYNAPI_PROC(int,SDL_SignalCondition,(SDL_Condition *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SignalSemaphore,(SDL_Semaphore *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_StartTextInput,(SDL_Window *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_StartTextInputWithProperties,(SDL_Window *a, SDL_PropertiesID b),(a,b),return)
 SDL_DYNAPI_PROC(Uint32,SDL_StepUTF8,(const char **a, size_t *b),(a,b),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)
diff --git a/src/events/SDL_keyboard.c b/src/events/SDL_keyboard.c
index 63c503d149c57..78abd861394b9 100644
--- a/src/events/SDL_keyboard.c
+++ b/src/events/SDL_keyboard.c
@@ -327,7 +327,7 @@ int SDL_SetKeyboardFocus(SDL_Window *window)
 
         if (SDL_TextInputActive(keyboard->focus)) {
             if (video && video->StartTextInput) {
-                video->StartTextInput(video, keyboard->focus);
+                video->StartTextInput(video, keyboard->focus, keyboard->focus->text_input_props);
             }
         }
     }
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index 2be73c9b5614f..6554191225eab 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -106,6 +106,7 @@ struct SDL_Window
     int safe_inset_bottom;
     SDL_Rect safe_rect;
 
+    SDL_PropertiesID text_input_props;
     SDL_bool text_input_active;
     SDL_Rect text_input_rect;
     int text_input_cursor;
@@ -331,14 +332,14 @@ struct SDL_VideoDevice
     int (*SuspendScreenSaver)(SDL_VideoDevice *_this);
 
     /* Text input */
-    int (*StartTextInput)(SDL_VideoDevice *_this, SDL_Window *window);
+    int (*StartTextInput)(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
     int (*StopTextInput)(SDL_VideoDevice *_this, SDL_Window *window);
     int (*UpdateTextInputArea)(SDL_VideoDevice *_this, SDL_Window *window);
     int (*ClearComposition)(SDL_VideoDevice *_this, SDL_Window *window);
 
     /* Screen keyboard */
     SDL_bool (*HasScreenKeyboardSupport)(SDL_VideoDevice *_this);
-    void (*ShowScreenKeyboard)(SDL_VideoDevice *_this, SDL_Window *window);
+    void (*ShowScreenKeyboard)(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
     void (*HideScreenKeyboard)(SDL_VideoDevice *_this, SDL_Window *window);
     SDL_bool (*IsScreenKeyboardShown)(SDL_VideoDevice *_this, SDL_Window *window);
 
@@ -565,4 +566,9 @@ extern SDL_bool SDL_ShouldAllowTopmost(void);
 
 extern void SDL_ToggleDragAndDropSupport(void);
 
+extern SDL_TextInputType SDL_GetTextInputType(SDL_PropertiesID props);
+extern SDL_Capitalization SDL_GetTextInputCapitalization(SDL_PropertiesID props);
+extern SDL_bool SDL_GetTextInputAutocorrect(SDL_PropertiesID props);
+extern SDL_bool SDL_GetTextInputMultiline(SDL_PropertiesID props);
+
 #endif /* SDL_sysvideo_h_ */
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 484f84f13ef0f..fa6707f99e2da 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -4011,6 +4011,7 @@ void SDL_DestroyWindow(SDL_Window *window)
         SDL_HideWindow(window);
     }
 
+    SDL_DestroyProperties(window->text_input_props);
     SDL_DestroyProperties(window->props);
 
     /* Clear the modal status, but don't unset the parent, as it may be
@@ -5129,23 +5130,80 @@ void SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask)
 }
 #endif
 
-int SDL_StartTextInput(SDL_Window *window)
+SDL_TextInputType SDL_GetTextInputType(SDL_PropertiesID props)
 {
-    CHECK_WINDOW_MAGIC(window, -1);
+    return (SDL_TextInputType)SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT);
+}
 
-    /* Show the on-screen keyboard, if desired */
+SDL_Capitalization SDL_GetTextInputCapitalization(SDL_PropertiesID props)
+{
+    return (SDL_Capitalization)SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE);
+}
+
+SDL_bool SDL_GetTextInputAutocorrect(SDL_PropertiesID props)
+{
+    return SDL_GetBooleanProperty(props, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, SDL_FALSE);
+}
+
+SDL_bool SDL_GetTextInputMultiline(SDL_PropertiesID props)
+{
+    if (SDL_HasProperty(props, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN)) {
+        return SDL_GetBooleanProperty(props, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, SDL_FALSE);
+    }
+
+    if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) {
+        return SDL_FALSE;
+    } else {
+        return SDL_TRUE;
+    }
+}
+
+static SDL_bool AutoShowingScreenKeyboard(void)
+{
     const char *hint = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD);
     if (((!hint || SDL_strcasecmp(hint, "auto") == 0) && !SDL_HasKeyboard()) ||
         SDL_GetStringBoolean(hint, SDL_FALSE)) {
+        return SDL_TRUE;
+    } else {
+        return SDL_FALSE;
+    }
+}
+
+int SDL_StartTextInput(SDL_Window *window)
+{
+    return SDL_StartTextInputWithProperties(window, 0);
+}
+
+int SDL_StartTextInputWithProperties(SDL_Window *window, SDL_PropertiesID props)
+{
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (window->text_input_props) {
+        SDL_DestroyProperties(window->text_input_props);
+        window->text_input_props = 0;
+    }
+
+    if (props) {
+        window->text_input_props = SDL_CreateProperties();
+        if (!window->text_input_props) {
+            return -1;
+        }
+        if (SDL_CopyProperties(props, window->text_input_props) < 0) {
+            return -1;
+        }
+    }
+
+    /* Show the on-screen keyboard, if desired */
+    if (AutoShowingScreenKeyboard() && !SDL_ScreenKeyboardShown(window)) {
         if (_this->ShowScreenKeyboard) {
-            _this->ShowScreenKeyboard(_this, window);
+            _this->ShowScreenKeyboard(_this, window, props);
         }
     }
 
     if (!window->text_input_active) {
         /* Finally start the text input system */
         if (_this->StartTextInput) {
-            if (_this->StartTextInput(_this, window) < 0) {
+            if (_this->StartTextInput(_this, window, props) < 0) {
                 return -1;
             }
         }
@@ -5174,9 +5232,7 @@ int SDL_StopTextInput(SDL_Window *window)
     }
 
     /* 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)) {
+    if (AutoShowingScreenKeyboard() && SDL_ScreenKeyboardShown(window)) {
         if (_this->HideScreenKeyboard) {
             _this->HideScreenKeyboard(_this, window);
         }
@@ -5239,7 +5295,9 @@ SDL_bool SDL_HasScreenKeyboardSupport(void)
 
 SDL_bool SDL_ScreenKeyboardShown(SDL_Window *window)
 {
-    if (window && _this && _this->IsScreenKeyboardShown) {
+    CHECK_WINDOW_MAGIC(window, SDL_FALSE);
+
+    if (_this->IsScreenKeyboardShown) {
         return _this->IsScreenKeyboardShown(_this, window);
     }
     return SDL_FALSE;
diff --git a/src/video/android/SDL_androidkeyboard.c b/src/video/android/SDL_androidkeyboard.c
index 85a124725ce67..84da48166f044 100644
--- a/src/video/android/SDL_androidkeyboard.c
+++ b/src/video/android/SDL_androidkeyboard.c
@@ -30,6 +30,46 @@
 
 #include "../../core/android/SDL_android.h"
 
+#define TYPE_CLASS_TEXT                         0x00000001
+#define TYPE_CLASS_NUMBER                       0x00000002
+#define TYPE_CLASS_PHONE                        0x00000003
+#define TYPE_CLASS_DATETIME                     0x00000004
+
+#define TYPE_DATETIME_VARIATION_NORMAL          0x00000000
+#define TYPE_DATETIME_VARIATION_DATE            0x00000010
+#define TYPE_DATETIME_VARIATION_TIME            0x00000020
+
+#define TYPE_NUMBER_VARIATION_NORMAL            0x00000000
+#define TYPE_NUMBER_VARIATION_PASSWORD          0x00000010
+#define TYPE_NUMBER_FLAG_SIGNED                 0x00001000
+#define TYPE_NUMBER_FLAG_DECIMAL                0x00002000
+
+#define TYPE_TEXT_FLAG_CAP_CHARACTERS           0x00001000
+#define TYPE_TEXT_FLAG_CAP_WORDS                0x00002000
+#define TYPE_TEXT_FLAG_CAP_SENTENCES            0x00004000
+#define TYPE_TEXT_FLAG_AUTO_CORRECT             0x00008000
+#define TYPE_TEXT_FLAG_AUTO_COMPLETE            0x00010000
+#define TYPE_TEXT_FLAG_MULTI_LINE               0x00020000
+#define TYPE_TEXT_FLAG_IME_MULTI_LINE           0x00040000
+#define TYPE_TEXT_FLAG_NO_SUGGESTIONS           0x00080000
+
+#define TYPE_TEXT_VARIATION_NORMAL              0x00000000
+#define TYPE_TEXT_VARIATION_URI                 0x00000010
+#define TYPE_TEXT_VARIATION_EMAIL_ADDRESS       0x00000020
+#define TYPE_TEXT_VARIATION_EMAIL_SUBJECT       0x00000030
+#define TYPE_TEXT_VARIATION_SHORT_MESSAGE       0x00000040
+#define TYPE_TEXT_VARIATION_LONG_MESSAGE        0x00000050
+#define TYPE_TEXT_VARIATION_PERSON_NAME         0x00000060
+#define TYPE_TEXT_VARIATION_POSTAL_ADDRESS      0x00000070
+#define TYPE_TEXT_VARIATION_PASSWORD            0x00000080
+#define TYPE_TEXT_VARIATION_VISIBLE_PASSWORD    0x00000090
+#define TYPE_TEXT_VARIATION_WEB_EDIT_TEXT       0x000000a0
+#define TYPE_TEXT_VARIATION_FILTER              0x000000b0
+#define TYPE_TEXT_VARIATION_PHONETIC            0x000000c0
+#define TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS   0x000000d0
+#define TYPE_TEXT_VARIATION_WEB_PASSWORD        0x000000e0
+
+
 static SDL_Scancode Android_Keycodes[] = {
     SDL_SCANCODE_UNKNOWN,          /* AKEYCODE_UNKNOWN */
     SDL_SCANCODE_SOFTLEFT,         /* AKEYCODE_SOFT_LEFT */
@@ -343,9 +383,67 @@ SDL_bool Android_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return SDL_TRUE;
 }
 
-void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
-    Android_JNI_ShowScreenKeyboard(&window->text_input_rect);
+    int input_type = 0;
+    if (SDL_HasProperty(props, SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER)) {
+        input_type = (int)SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_ANDROID_INPUTTYPE_NUMBER, 0);
+    } else {
+        switch (SDL_GetTextInputType(props)) {
+        default:
+        case SDL_TEXTINPUT_TYPE_TEXT:
+            input_type = (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_NORMAL);
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+            input_type = (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PERSON_NAME);
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+            input_type = (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+            input_type = (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_NORMAL);
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+            input_type = (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD);
+            break;
+        case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+            input_type = (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER:
+            input_type = (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_NORMAL);
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+            input_type = (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD);
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+            input_type = (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_NORMAL);
+            break;
+        }
+
+        switch (SDL_GetTextInputCapitalization(props)) {
+        default:
+        case SDL_CAPITALIZE_NONE:
+            break;
+        case SDL_CAPITALIZE_LETTERS:
+            input_type |= TYPE_TEXT_FLAG_CAP_CHARACTERS;
+            break;
+        case SDL_CAPITALIZE_WORDS:
+            input_type |= TYPE_TEXT_FLAG_CAP_WORDS;
+            break;
+        case SDL_CAPITALIZE_SENTENCES:
+            input_type |= TYPE_TEXT_FLAG_CAP_SENTENCES;
+            break;
+        }
+
+        if (SDL_GetTextInputAutocorrect(props)) {
+            input_type |= (TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_AUTO_COMPLETE);
+        }
+
+        if (SDL_GetTextInputMultiline(props)) {
+            input_type |= TYPE_TEXT_FLAG_MULTI_LINE;
+        }
+    }
+    Android_JNI_ShowScreenKeyboard(input_type, &window->text_input_rect);
     SDL_screen_keyboard_shown = SDL_TRUE;
 }
 
@@ -358,7 +456,7 @@ void Android_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
 void Android_RestoreScreenKeyboardOnResume(SDL_VideoDevice *_this, SDL_Window *window)
 {
     if (SDL_screen_keyboard_shown) {
-        Android_ShowScreenKeyboard(_this, window);
+        Android_ShowScreenKeyboard(_this, window, window->text_input_props);
     }
 }
 
diff --git a/src/video/android/SDL_androidkeyboard.h b/src/video/android/SDL_androidkeyboard.h
index bcbd948e1caa9..c7b70090b8b7e 100644
--- a/src/video/android/SDL_androidkeyboard.h
+++ b/src/video/android/SDL_androidkeyboard.h
@@ -26,7 +26,7 @@ extern int Android_OnKeyDown(int keycode);
 extern int Android_OnKeyUp(int keycode);
 
 extern SDL_bool Android_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-extern void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+extern void Android_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 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);
diff --git a/src/video/cocoa/SDL_cocoakeyboard.h b/src/video/cocoa/SDL_cocoakeyboard.h
index 85a409258a3a7..0103e1a8a336f 100644
--- a/src/video/cocoa/SDL_cocoakeyboard.h
+++ b/src/video/cocoa/SDL_cocoakeyboard.h
@@ -27,7 +27,7 @@ extern void Cocoa_InitKeyboard(SDL_VideoDevice *_this);
 extern void Cocoa_HandleKeyEvent(SDL_VideoDevice *_this, NSEvent *event);
 extern void Cocoa_QuitKeyboard(SDL_VideoDevice *_this);
 
-extern int Cocoa_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+extern int Cocoa_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern int Cocoa_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 extern int Cocoa_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
 
diff --git a/src/video/cocoa/SDL_cocoakeyboard.m b/src/video/cocoa/SDL_cocoakeyboard.m
index 685d6752df7e7..1bc0664da7ed8 100644
--- a/src/video/cocoa/SDL_cocoakeyboard.m
+++ b/src/video/cocoa/SDL_cocoakeyboard.m
@@ -381,7 +381,7 @@ void Cocoa_InitKeyboard(SDL_VideoDevice *_this)
     SDL_ToggleModState(SDL_KMOD_CAPS, (data.modifierFlags & NSEventModifierFlagCapsLock) ? SDL_TRUE : SDL_FALSE);
 }
 
-int Cocoa_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
+int Cocoa_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     @autoreleasepool {
         NSView *parentView;
diff --git a/src/video/gdk/SDL_gdktextinput.cpp b/src/video/gdk/SDL_gdktextinput.cpp
index 7af1b381a0409..0c6b1d30c10be 100644
--- a/src/video/gdk/SDL_gdktextinput.cpp
+++ b/src/video/gdk/SDL_gdktextinput.cpp
@@ -178,7 +178,7 @@ void GDK_EnsureHints(void)
     }
 }
 
-int GDK_StartTextInput(SDL_VideoD

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