Browse Source

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
Sam Lantinga 8 months ago
parent
commit
81f8e6aba6
36 changed files with 737 additions and 76 deletions
  1. 2 0
      Xcode/SDLTest/SDLTest.xcodeproj/project.pbxproj
  2. 6 3
      android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
  3. 6 2
      android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java
  4. 2 0
      include/SDL3/SDL_hints.h
  5. 82 0
      include/SDL3/SDL_keyboard.h
  6. 3 2
      src/core/android/SDL_android.c
  7. 1 1
      src/core/android/SDL_android.h
  8. 1 0
      src/dynapi/SDL_dynapi.sym
  9. 1 0
      src/dynapi/SDL_dynapi_overrides.h
  10. 1 0
      src/dynapi/SDL_dynapi_procs.h
  11. 1 1
      src/events/SDL_keyboard.c
  12. 8 2
      src/video/SDL_sysvideo.h
  13. 67 9
      src/video/SDL_video.c
  14. 101 3
      src/video/android/SDL_androidkeyboard.c
  15. 1 1
      src/video/android/SDL_androidkeyboard.h
  16. 1 1
      src/video/cocoa/SDL_cocoakeyboard.h
  17. 1 1
      src/video/cocoa/SDL_cocoakeyboard.m
  18. 36 3
      src/video/gdk/SDL_gdktextinput.cpp
  19. 2 2
      src/video/gdk/SDL_gdktextinput.h
  20. 1 1
      src/video/n3ds/SDL_n3dsswkb.c
  21. 1 1
      src/video/n3ds/SDL_n3dsswkb.h
  22. 31 2
      src/video/psp/SDL_pspvideo.c
  23. 1 1
      src/video/psp/SDL_pspvideo.h
  24. 1 1
      src/video/uikit/SDL_uikitviewcontroller.h
  25. 99 10
      src/video/uikit/SDL_uikitviewcontroller.m
  26. 41 3
      src/video/vita/SDL_vitavideo.c
  27. 1 1
      src/video/vita/SDL_vitavideo.h
  28. 62 4
      src/video/wayland/SDL_waylandkeyboard.c
  29. 1 1
      src/video/wayland/SDL_waylandkeyboard.h
  30. 1 1
      src/video/windows/SDL_windowskeyboard.c
  31. 1 1
      src/video/windows/SDL_windowskeyboard.h
  32. 1 1
      src/video/winrt/SDL_winrtevents_c.h
  33. 1 1
      src/video/winrt/SDL_winrtkeyboard.cpp
  34. 26 3
      src/video/x11/SDL_x11keyboard.c
  35. 2 2
      src/video/x11/SDL_x11keyboard.h
  36. 143 11
      test/testime.c

+ 2 - 0
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;

+ 6 - 3
android-project/app/src/main/java/org/libsdl/app/SDLActivity.java

@@ -1375,9 +1375,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
          */
         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 class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
             } else {
                 mTextEdit.setLayoutParams(params);
             }
+            mTextEdit.setInputType(input_type);
 
             mTextEdit.setVisibility(View.VISIBLE);
             mTextEdit.requestFocus();
@@ -1419,9 +1422,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
     /**
      * 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) {

+ 6 - 2
android-project/app/src/main/java/org/libsdl/app/SDLDummyEdit.java

@@ -12,6 +12,7 @@ import android.view.inputmethod.InputConnection;
 public class SDLDummyEdit extends View implements View.OnKeyListener
 {
     InputConnection ic;
+    int input_type;
 
     public SDLDummyEdit(Context context) {
         super(context);
@@ -20,6 +21,10 @@ public class SDLDummyEdit extends View implements View.OnKeyListener
         setOnKeyListener(this);
     }
 
+    public void setInputType(int input_type) {
+        this.input_type = input_type;
+    }
+
     @Override
     public boolean onCheckIsTextEditor() {
         return true;
@@ -51,8 +56,7 @@ public class SDLDummyEdit extends View implements View.OnKeyListener
     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 */;
 

+ 2 - 0
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)

+ 82 - 0
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.
  *

+ 3 - 2
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,

+ 1 - 1
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);

+ 1 - 0
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;

+ 1 - 0
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

+ 1 - 0
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)

+ 1 - 1
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);
             }
         }
     }

+ 8 - 2
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_ */

+ 67 - 9
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;

+ 101 - 3
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);
     }
 }
 

+ 1 - 1
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);

+ 1 - 1
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);
 

+ 1 - 1
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;

+ 36 - 3
src/video/gdk/SDL_gdktextinput.cpp

@@ -178,7 +178,7 @@ void GDK_EnsureHints(void)
     }
 }
 
-int GDK_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
+int GDK_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     /*
      * Currently a stub, since all input is handled by the virtual keyboard,
@@ -226,7 +226,7 @@ SDL_bool GDK_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return SDL_TRUE;
 }
 
-void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     /*
      * There is XGameUiTextEntryOpen but it's only in online docs,
@@ -253,6 +253,39 @@ void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
         return;
     }
 
+    XGameUiTextEntryInputScope scope;
+    switch (SDL_GetTextInputType(props)) {
+    default:
+    case SDL_TEXTINPUT_TYPE_TEXT:
+        scope = (XGameUiTextEntryInputScope)g_TextInputScope;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+        scope = XGameUiTextEntryInputScope::Default;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+        scope = XGameUiTextEntryInputScope::EmailSmtpAddress;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+        scope = XGameUiTextEntryInputScope::Default;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+        scope = XGameUiTextEntryInputScope::Password;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+        scope = XGameUiTextEntryInputScope::Default;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER:
+        scope = XGameUiTextEntryInputScope::Number;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+        // FIXME: Password or number scope?
+        scope = XGameUiTextEntryInputScope::Number;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+        scope = XGameUiTextEntryInputScope::Number;
+        break;
+    }
+
     g_TextBlock->queue = g_TextTaskQueue;
     g_TextBlock->context = _this;
     g_TextBlock->callback = GDK_InternalTextEntryCallback;
@@ -261,7 +294,7 @@ void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
                    g_TitleText,
                    g_DescriptionText,
                    g_DefaultText,
-                   (XGameUiTextEntryInputScope)g_TextInputScope,
+                   scope,
                    (uint32_t)g_MaxTextLength))) {
         SDL_free(g_TextBlock);
         g_TextBlock = NULL;

+ 2 - 2
src/video/gdk/SDL_gdktextinput.h

@@ -32,13 +32,13 @@ extern "C" {
 
 void GDK_EnsureHints(void);
 
-int GDK_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+int GDK_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 int GDK_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 int GDK_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
 int GDK_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window);
 
 SDL_bool GDK_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+void GDK_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 void GDK_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 SDL_bool GDK_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
 

+ 1 - 1
src/video/n3ds/SDL_n3dsswkb.c

@@ -50,7 +50,7 @@ SDL_bool N3DS_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return SDL_TRUE;
 }
 
-int N3DS_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
+int N3DS_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     char buffer[BUFFER_SIZE];
     SwkbdButton button_pressed;

+ 1 - 1
src/video/n3ds/SDL_n3dsswkb.h

@@ -30,7 +30,7 @@ void N3DS_SwkbQuit();
 
 SDL_bool N3DS_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
 
-int N3DS_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+int N3DS_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 int N3DS_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 
 #endif /* SDL_n3dskeyboard_h_ */

+ 31 - 2
src/video/psp/SDL_pspvideo.c

@@ -249,7 +249,7 @@ SDL_bool PSP_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return SDL_TRUE;
 }
 
-void PSP_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void PSP_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     char list[0x20000] __attribute__((aligned(64)));  // Needed for sceGuStart to work
     int i;
@@ -266,7 +266,36 @@ void PSP_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
     data.language = PSP_UTILITY_OSK_LANGUAGE_DEFAULT;
     data.lines = 1;
     data.unk_24 = 1;
-    data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+    switch (SDL_GetTextInputType(props)) {
+    default:
+    case SDL_TEXTINPUT_TYPE_TEXT:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_ALL;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_LATIN_DIGIT;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_LATIN_DIGIT;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+        data.inputtype = PSP_UTILITY_OSK_INPUTTYPE_LATIN_DIGIT;
+        break;
+    }
     data.desc = NULL;
     data.intext = NULL;
     data.outtextlength = input_text_length;

+ 1 - 1
src/video/psp/SDL_pspvideo.h

@@ -74,7 +74,7 @@ int PSP_GL_DeleteContext(SDL_VideoDevice *_this, SDL_GLContext context);
 
 /* PSP on screen keyboard */
 SDL_bool PSP_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-void PSP_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+void PSP_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 void PSP_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 SDL_bool PSP_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
 

+ 1 - 1
src/video/uikit/SDL_uikitviewcontroller.h

@@ -88,7 +88,7 @@
 
 #ifdef SDL_IPHONE_KEYBOARD
 SDL_bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-void UIKit_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+void UIKit_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 void UIKit_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 SDL_bool UIKit_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
 int UIKit_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);

+ 99 - 10
src/video/uikit/SDL_uikitviewcontroller.m

@@ -278,15 +278,6 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
     textField.text = obligateForBackspace;
     committedText = textField.text;
 
-    /* set UITextInputTrait properties, mostly to defaults */
-    textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
-    textField.autocorrectionType = UITextAutocorrectionTypeNo;
-    textField.enablesReturnKeyAutomatically = NO;
-    textField.keyboardAppearance = UIKeyboardAppearanceDefault;
-    textField.keyboardType = UIKeyboardTypeDefault;
-    textField.returnKeyType = UIReturnKeyDefault;
-    textField.secureTextEntry = NO;
-
     textField.hidden = YES;
     keyboardVisible = NO;
 
@@ -393,6 +384,103 @@ static void SDLCALL SDL_HideHomeIndicatorHintChanged(void *userdata, const char
                     object:nil];
 }
 
+- (void)setKeyboardProperties:(SDL_PropertiesID) props
+{
+    textField.secureTextEntry = NO;
+
+    switch (SDL_GetTextInputType(props)) {
+    default:
+    case SDL_TEXTINPUT_TYPE_TEXT:
+        textField.keyboardType = UIKeyboardTypeDefault;
+        textField.textContentType = nil;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+        textField.keyboardType = UIKeyboardTypeDefault;
+        textField.textContentType = UITextContentTypeName;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+        textField.keyboardType = UIKeyboardTypeEmailAddress;
+        textField.textContentType = UITextContentTypeEmailAddress;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+        textField.keyboardType = UIKeyboardTypeDefault;
+        if (@available(iOS 11.0, *)) {
+            textField.textContentType = UITextContentTypeUsername;
+        } else {
+            textField.textContentType = nil;
+        }
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+        textField.keyboardType = UIKeyboardTypeDefault;
+        if (@available(iOS 11.0, *)) {
+            textField.textContentType = UITextContentTypePassword;
+        } else {
+            textField.textContentType = nil;
+        }
+        textField.secureTextEntry = YES;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+        textField.keyboardType = UIKeyboardTypeDefault;
+        if (@available(iOS 11.0, *)) {
+            textField.textContentType = UITextContentTypePassword;
+        } else {
+            textField.textContentType = nil;
+        }
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER:
+        textField.keyboardType = UIKeyboardTypeNumberPad;
+        textField.textContentType = nil;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+        textField.keyboardType = UIKeyboardTypeNumberPad;
+        if (@available(iOS 12.0, *)) {
+            textField.textContentType = UITextContentTypeOneTimeCode;
+        } else {
+            textField.textContentType = nil;
+        }
+        textField.secureTextEntry = YES;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+        textField.keyboardType = UIKeyboardTypeNumberPad;
+        if (@available(iOS 12.0, *)) {
+            textField.textContentType = UITextContentTypeOneTimeCode;
+        } else {
+            textField.textContentType = nil;
+        }
+        break;
+    }
+
+    switch (SDL_GetTextInputCapitalization(props)) {
+    default:
+    case SDL_CAPITALIZE_NONE:
+        textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
+        break;
+    case SDL_CAPITALIZE_LETTERS:
+        textField.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
+        break;
+    case SDL_CAPITALIZE_WORDS:
+        textField.autocapitalizationType = UITextAutocapitalizationTypeWords;
+        break;
+    case SDL_CAPITALIZE_SENTENCES:
+        textField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
+        break;
+    }
+
+    if (SDL_GetTextInputAutocorrect(props)) {
+        textField.autocorrectionType = UITextAutocorrectionTypeYes;
+        textField.spellCheckingType = UITextSpellCheckingTypeYes;
+    } else {
+        textField.autocorrectionType = UITextAutocorrectionTypeNo;
+        textField.spellCheckingType = UITextSpellCheckingTypeNo;
+    }
+
+    if (SDL_GetTextInputMultiline(props)) {
+        textField.enablesReturnKeyAutomatically = YES;
+    } else {
+        textField.enablesReturnKeyAutomatically = NO;
+    }
+}
+
 /* reveal onscreen virtual keyboard */
 - (void)showKeyboard
 {
@@ -596,10 +684,11 @@ SDL_bool UIKit_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return SDL_TRUE;
 }
 
-void UIKit_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void UIKit_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     @autoreleasepool {
         SDL_uikitviewcontroller *vc = GetWindowViewController(window);
+        [vc setKeyboardProperties:props];
         [vc showKeyboard];
     }
 }

+ 41 - 3
src/video/vita/SDL_vitavideo.c

@@ -411,7 +411,7 @@ void VITA_ImeEventHandler(void *arg, const SceImeEventData *e)
 }
 #endif
 
-void VITA_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void VITA_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     SDL_VideoData *videodata = _this->internal;
     SceInt32 res;
@@ -427,8 +427,46 @@ void VITA_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
 
     param.supportedLanguages = SCE_IME_LANGUAGE_ENGLISH_US;
     param.languagesForced = SCE_FALSE;
-    param.type = SCE_IME_TYPE_DEFAULT;
-    param.option = SCE_IME_OPTION_NO_ASSISTANCE;
+    switch (SDL_GetTextInputType(props)) {
+    default:
+    case SDL_TEXTINPUT_TYPE_TEXT:
+        param.type = SCE_IME_TYPE_DEFAULT;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+        param.type = SCE_IME_TYPE_DEFAULT;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+        param.type = SCE_IME_TYPE_MAIL;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+        param.type = SCE_IME_TYPE_DEFAULT;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+        param.type = SCE_IME_TYPE_DEFAULT;
+        break;
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+        param.type = SCE_IME_TYPE_DEFAULT;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER:
+        param.type = SCE_IME_TYPE_NUMBER;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+        param.type = SCE_IME_TYPE_NUMBER;
+        break;
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+        param.type = SCE_IME_TYPE_NUMBER;
+        break;
+    }
+    param.option = 0;
+    if (SDL_GetTextInputCapitalization(props) != SDL_CAPITALIZE_SENTENCES) {
+        param.option |= SCE_IME_OPTION_NO_AUTO_CAPITALIZATION;
+    }
+    if (!SDL_GetTextInputAutocorrect(props)) {
+        param.option |= SCE_IME_OPTION_NO_ASSISTANCE;
+    }
+    if (SDL_GetTextInputMultiline(props)) {
+        param.option |= SCE_IME_OPTION_MULTILINE;
+    }
     param.inputTextBuffer = libime_out;
     param.maxTextLength = SCE_IME_MAX_TEXT_LENGTH;
     param.handler = VITA_ImeEventHandler;

+ 1 - 1
src/video/vita/SDL_vitavideo.h

@@ -97,7 +97,7 @@ int VITA_GLES_DeleteContext(SDL_VideoDevice *_this, SDL_GLContext context);
 
 /* VITA on screen keyboard */
 SDL_bool VITA_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-void VITA_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+void VITA_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 void VITA_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 SDL_bool VITA_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
 

+ 62 - 4
src/video/wayland/SDL_waylandkeyboard.c

@@ -51,7 +51,7 @@ void Wayland_QuitKeyboard(SDL_VideoDevice *_this)
 #endif
 }
 
-int Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
+int Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     SDL_VideoData *internal = _this->internal;
     struct SDL_WaylandInput *input = internal->input;
@@ -59,13 +59,71 @@ int Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
     if (internal->text_input_manager) {
         if (input && input->text_input) {
             const SDL_Rect *rect = &input->text_input->cursor_rect;
+            enum zwp_text_input_v3_content_hint hint = ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE;
+            enum zwp_text_input_v3_content_purpose purpose;
+
+            switch (SDL_GetTextInputType(props)) {
+            default:
+            case SDL_TEXTINPUT_TYPE_TEXT:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
+                break;
+            case SDL_TEXTINPUT_TYPE_TEXT_NAME:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME;
+                break;
+            case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL;
+                break;
+            case SDL_TEXTINPUT_TYPE_TEXT_USERNAME:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL;
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
+                break;
+            case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
+                hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
+                break;
+            case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD;
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
+                break;
+            case SDL_TEXTINPUT_TYPE_NUMBER:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER;
+                break;
+            case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
+                hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_HIDDEN_TEXT | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA);
+                break;
+            case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+                purpose = ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN;
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_SENSITIVE_DATA;
+                break;
+            }
+
+            switch (SDL_GetTextInputCapitalization(props)) {
+            default:
+            case SDL_CAPITALIZE_NONE:
+                break;
+            case SDL_CAPITALIZE_LETTERS:
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_UPPERCASE;
+                break;
+            case SDL_CAPITALIZE_WORDS:
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_TITLECASE;
+                break;
+            case SDL_CAPITALIZE_SENTENCES:
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION;
+                break;
+            }
+
+            if (SDL_GetTextInputAutocorrect(props)) {
+                hint |= (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK);
+            }
+            if (SDL_GetTextInputMultiline(props)) {
+                hint |= ZWP_TEXT_INPUT_V3_CONTENT_HINT_MULTILINE;
+            }
 
             zwp_text_input_v3_enable(input->text_input->text_input);
 
             /* Now that it's enabled, set the input properties */
-            zwp_text_input_v3_set_content_type(input->text_input->text_input,
-                                               ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
-                                               ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
+            zwp_text_input_v3_set_content_type(input->text_input->text_input, hint, purpose);
             if (!SDL_RectEmpty(rect)) {
                 /* This gets reset on enable so we have to cache it */
                 zwp_text_input_v3_set_cursor_rectangle(input->text_input->text_input,

+ 1 - 1
src/video/wayland/SDL_waylandkeyboard.h

@@ -32,7 +32,7 @@ typedef struct SDL_WaylandTextInput
 
 extern int Wayland_InitKeyboard(SDL_VideoDevice *_this);
 extern void Wayland_QuitKeyboard(SDL_VideoDevice *_this);
-extern int Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+extern int Wayland_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern int Wayland_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 extern int Wayland_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
 extern SDL_bool Wayland_HasScreenKeyboardSupport(SDL_VideoDevice *_this);

+ 1 - 1
src/video/windows/SDL_windowskeyboard.c

@@ -196,7 +196,7 @@ void WIN_ResetDeadKeys(void)
     }
 }
 
-int WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
+int WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     WIN_ResetDeadKeys();
 

+ 1 - 1
src/video/windows/SDL_windowskeyboard.h

@@ -29,7 +29,7 @@ extern void WIN_QuitKeyboard(SDL_VideoDevice *_this);
 
 extern void WIN_ResetDeadKeys(void);
 
-extern int WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+extern int WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern int WIN_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 extern int WIN_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
 extern int WIN_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window);

+ 1 - 1
src/video/winrt/SDL_winrtevents_c.h

@@ -69,7 +69,7 @@ extern void WINRT_ProcessCharacterReceivedEvent(SDL_Window *window, Windows::UI:
 #if NTDDI_VERSION >= NTDDI_WIN10
 extern void WINTRT_InitialiseInputPaneEvents(SDL_VideoDevice *_this);
 extern SDL_bool WINRT_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-extern void WINRT_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+extern void WINRT_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern void WINRT_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 extern SDL_bool WINRT_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
 #endif // NTDDI_VERSION >= ...

+ 1 - 1
src/video/winrt/SDL_winrtkeyboard.cpp

@@ -148,7 +148,7 @@ SDL_bool WINRT_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return SDL_TRUE;
 }
 
-void WINRT_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void WINRT_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     using namespace Windows::UI::ViewManagement;
     InputPane ^ inputPane = InputPane::GetForCurrentView();

+ 26 - 3
src/video/x11/SDL_x11keyboard.c

@@ -421,7 +421,7 @@ static void X11_ResetXIM(SDL_VideoDevice *_this, SDL_Window *window)
 #endif
 }
 
-int X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window)
+int X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     X11_ResetXIM(_this, window);
 
@@ -451,7 +451,7 @@ SDL_bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this)
     return videodata->is_steam_deck;
 }
 
-void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
+void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
 {
     SDL_VideoData *videodata = _this->internal;
 
@@ -459,10 +459,33 @@ void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window)
         /* For more documentation of the URL parameters, see:
          * https://partner.steamgames.com/doc/api/ISteamUtils#ShowFloatingGamepadTextInput
          */
+        const int k_EFloatingGamepadTextInputModeModeSingleLine = 0;    // Enter dismisses the keyboard
+        const int k_EFloatingGamepadTextInputModeModeMultipleLines = 1; // User needs to explicitly dismiss the keyboard
+        const int k_EFloatingGamepadTextInputModeModeEmail = 2;         // Keyboard is displayed in a special mode that makes it easier to enter emails
+        const int k_EFloatingGamepadTextInputModeModeNumeric = 3;       // Numeric keypad is shown
         char deeplink[128];
+        int mode;
+
+        switch (SDL_GetTextInputType(props)) {
+        case SDL_TEXTINPUT_TYPE_TEXT_EMAIL:
+            mode = k_EFloatingGamepadTextInputModeModeEmail;
+            break;
+        case SDL_TEXTINPUT_TYPE_NUMBER:
+        case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN:
+        case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE:
+            mode = k_EFloatingGamepadTextInputModeModeNumeric;
+            break;
+        default:
+            if (SDL_GetTextInputMultiline(props)) {
+                mode = k_EFloatingGamepadTextInputModeModeMultipleLines;
+            } else {
+                mode = k_EFloatingGamepadTextInputModeModeSingleLine;
+            }
+            break;
+        }
         (void)SDL_snprintf(deeplink, sizeof(deeplink),
                            "steam://open/keyboard?XPosition=0&YPosition=0&Width=0&Height=0&Mode=%d",
-                           SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE) ? 0 : 1);
+                           mode);
         SDL_OpenURL(deeplink);
         videodata->steam_keyboard_open = SDL_TRUE;
     }

+ 2 - 2
src/video/x11/SDL_x11keyboard.h

@@ -26,11 +26,11 @@
 extern int X11_InitKeyboard(SDL_VideoDevice *_this);
 extern void X11_UpdateKeymap(SDL_VideoDevice *_this, SDL_bool send_event);
 extern void X11_QuitKeyboard(SDL_VideoDevice *_this);
-extern int X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window);
+extern int X11_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern int X11_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
 extern int X11_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
 extern SDL_bool X11_HasScreenKeyboardSupport(SDL_VideoDevice *_this);
-extern void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
+extern void X11_ShowScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
 extern void X11_HideScreenKeyboard(SDL_VideoDevice *_this, SDL_Window *window);
 extern SDL_bool X11_IsScreenKeyboardShown(SDL_VideoDevice *_this, SDL_Window *window);
 extern KeySym X11_KeyCodeToSym(SDL_VideoDevice *_this, KeyCode, unsigned char group, unsigned int mod_mask);

+ 143 - 11
test/testime.c

@@ -15,6 +15,7 @@
 
 #include <SDL3/SDL.h>
 #include <SDL3/SDL_main.h>
+#include <SDL3/SDL_test_font.h>
 #ifdef HAVE_SDL_TTF
 #include "SDL_ttf.h"
 #endif
@@ -40,6 +41,11 @@
 #endif
 #define MAX_TEXT_LENGTH 256
 
+#define WINDOW_WIDTH    640
+#define WINDOW_HEIGHT   480
+
+#define MARGIN 32.0f
+#define LINE_HEIGHT (FONT_CHARACTER_SIZE + 4.0f)
 #define CURSOR_BLINK_INTERVAL_MS    500
 
 typedef struct
@@ -47,6 +53,10 @@ typedef struct
     SDL_Window *window;
     SDL_Renderer *renderer;
     int rendererID;
+    SDL_bool settings_visible;
+    SDL_Texture *settings_icon;
+    SDL_FRect settings_rect;
+    SDL_PropertiesID text_settings;
     SDL_FRect textRect;
     SDL_FRect markedRect;
     char text[MAX_TEXT_LENGTH];
@@ -68,6 +78,33 @@ static const SDL_Color backColor = { 255, 255, 255, 255 };
 static const SDL_Color textColor = { 0, 0, 0, 255 };
 static SDL_BlendMode highlight_mode;
 
+static const struct
+{
+    const char *label;
+    const char *setting;
+    int value;
+} settings[] = {
+    { "Text",                   SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT },
+    { "Name",                   SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_NAME },
+    { "E-mail",                 SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_EMAIL },
+    { "Username",               SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_USERNAME },
+    { "Password (hidden)",      SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN },
+    { "Password (visible)",     SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE },
+    { "Number",                 SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER },
+    { "Numeric PIN (hidden)",   SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN },
+    { "Numeric PIN (visible)",  SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE },
+    { "",                       NULL },
+    { "No capitalization",      SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE },
+    { "Capitalize sentences",   SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_SENTENCES },
+    { "Capitalize words",       SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_WORDS },
+    { "All caps",               SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_LETTERS },
+    { "",                       NULL },
+    { "Auto-correct OFF",       SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, SDL_FALSE },
+    { "Auto-correct ON",        SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, SDL_TRUE },
+    { "Multiline OFF",          SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, SDL_FALSE },
+    { "Multiline ON",           SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, SDL_TRUE }
+};
+
 #ifdef HAVE_SDL_TTF
 static TTF_Font *font;
 #else
@@ -487,7 +524,9 @@ static void InitInput(WindowState *ctx)
     ctx->textRect.h = 50.0f;
     ctx->markedRect = ctx->textRect;
 
-    SDL_StartTextInput(ctx->window);
+    ctx->text_settings = SDL_CreateProperties();
+
+    SDL_StartTextInputWithProperties(ctx->window, ctx->text_settings);
 }
 
 
@@ -692,6 +731,7 @@ static void CleanupVideo(void)
 
         SDL_StopTextInput(ctx->window);
         ClearCandidates(ctx);
+        SDL_DestroyProperties(ctx->text_settings);
     }
 #ifdef HAVE_SDL_TTF
     TTF_CloseFont(font);
@@ -701,11 +741,85 @@ static void CleanupVideo(void)
 #endif
 }
 
+static void DrawSettingsButton(WindowState *ctx)
+{
+    SDL_Renderer *renderer = ctx->renderer;
+
+    SDL_RenderTexture(renderer, ctx->settings_icon, NULL, &ctx->settings_rect);
+}
+
+static void ToggleSettings(WindowState *ctx)
+{
+    if (ctx->settings_visible) {
+        ctx->settings_visible = SDL_FALSE;
+        SDL_StartTextInputWithProperties(ctx->window, ctx->text_settings);
+    } else {
+        SDL_StopTextInput(ctx->window);
+        ctx->settings_visible = SDL_TRUE;
+    }
+}
+
+static void DrawSettings(WindowState *ctx)
+{
+    SDL_Renderer *renderer = ctx->renderer;
+    SDL_FRect checkbox;
+    int i;
+
+    checkbox.x = MARGIN;
+    checkbox.y = MARGIN;
+    checkbox.w = (float)FONT_CHARACTER_SIZE;
+    checkbox.h = (float)FONT_CHARACTER_SIZE;
+
+    for (i = 0; i < SDL_arraysize(settings); ++i) {
+        if (settings[i].setting) {
+            int value = (int)SDL_GetNumberProperty(ctx->text_settings, settings[i].setting, 0);
+            if (value == settings[i].value) {
+                SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
+                SDL_RenderFillRect(renderer, &checkbox);
+            }
+            SDL_SetRenderDrawColor(renderer, backColor.r, backColor.g, backColor.b, backColor.a);
+            SDL_RenderRect(renderer, &checkbox);
+            SDLTest_DrawString(renderer, checkbox.x + checkbox.w + 8.0f, checkbox.y, settings[i].label);
+        }
+        checkbox.y += LINE_HEIGHT;
+    }
+}
+
+static void ClickSettings(WindowState *ctx, float x, float y)
+{
+    int setting = (int)SDL_floorf((y - MARGIN) / LINE_HEIGHT);
+    if (setting >= 0 && setting < SDL_arraysize(settings)) {
+        SDL_SetNumberProperty(ctx->text_settings, settings[setting].setting, settings[setting].value);
+    }
+}
+
 static void RedrawWindow(WindowState *ctx)
 {
     SDL_Renderer *renderer = ctx->renderer;
     int rendererID = ctx->rendererID;
     SDL_FRect drawnTextRect, cursorRect, underlineRect;
+    char text[MAX_TEXT_LENGTH];
+
+    DrawSettingsButton(ctx);
+
+    if (ctx->settings_visible) {
+        DrawSettings(ctx);
+        return;
+    }
+
+    /* Hide the text if it's a password */
+    switch ((SDL_TextInputType)SDL_GetNumberProperty(ctx->text_settings, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT)) {
+    case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN:
+    case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: {
+        size_t len = SDL_utf8strlen(ctx->text);
+        SDL_memset(text, '*', len);
+        text[len] = '\0';
+        break;
+    }
+    default:
+        SDL_strlcpy(text, ctx->text, sizeof(text));
+        break;
+    }
 
     SDL_SetRenderDrawColor(renderer, backColor.r, backColor.g, backColor.b, backColor.a);
     SDL_RenderFillRect(renderer, &ctx->textRect);
@@ -716,9 +830,9 @@ static void RedrawWindow(WindowState *ctx)
     drawnTextRect.w = 0.0f;
     drawnTextRect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE;
 
-    if (ctx->text[0]) {
+    if (text[0]) {
 #ifdef HAVE_SDL_TTF
-        SDL_Surface *textSur = TTF_RenderUTF8_Blended(font, ctx->text, textColor);
+        SDL_Surface *textSur = TTF_RenderUTF8_Blended(font, text, textColor);
         SDL_Texture *texture;
 
         /* Vertically center text */
@@ -732,7 +846,7 @@ static void RedrawWindow(WindowState *ctx)
         SDL_RenderTexture(renderer, texture, NULL, &drawnTextRect);
         SDL_DestroyTexture(texture);
 #else
-        char *utext = ctx->text;
+        char *utext = text;
         Uint32 codepoint;
         size_t len;
         SDL_FRect dstrect;
@@ -756,13 +870,6 @@ static void RedrawWindow(WindowState *ctx)
     /* The marked text rectangle is the text area that hasn't been filled by committed text */
     ctx->markedRect.x = ctx->textRect.x + drawnTextRect.w;
     ctx->markedRect.w = ctx->textRect.w - drawnTextRect.w;
-    if (ctx->markedRect.w < 0) {
-        /* Stop text input because we cannot hold any more characters */
-        SDL_StopTextInput(ctx->window);
-        return;
-    } else {
-        SDL_StartTextInput(ctx->window);
-    }
 
     /* Update the drawn text rectangle for composition text, after the committed text */
     drawnTextRect.x += drawnTextRect.w;
@@ -974,10 +1081,18 @@ int main(int argc, char *argv[])
         WindowState *ctx = &windowstate[i];
         SDL_Window *window = state->windows[i];
         SDL_Renderer *renderer = state->renderers[i];
+        int icon_w = 0, icon_h = 0;
+
+        SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX, SDL_SCALEMODE_LINEAR);
 
         ctx->window = window;
         ctx->renderer = renderer;
         ctx->rendererID = i;
+        ctx->settings_icon = LoadTexture(renderer, "icon.bmp", SDL_TRUE, &icon_w, &icon_h);
+        ctx->settings_rect.x = (float)WINDOW_WIDTH - icon_w - MARGIN;
+        ctx->settings_rect.y = MARGIN;
+        ctx->settings_rect.w = (float)icon_w;
+        ctx->settings_rect.h = (float)icon_h;
 
         InitInput(ctx);
 
@@ -999,6 +1114,23 @@ int main(int argc, char *argv[])
         while (SDL_PollEvent(&event)) {
             SDLTest_CommonEvent(state, &event, &done);
             switch (event.type) {
+            case SDL_EVENT_MOUSE_BUTTON_UP: {
+                SDL_FPoint point;
+                WindowState *ctx = GetWindowStateForWindowID(event.button.windowID);
+                if (!ctx) {
+                    break;
+                }
+
+                SDL_ConvertEventToRenderCoordinates(ctx->renderer, &event);
+                point.x = event.button.x;
+                point.y = event.button.y;
+                if (SDL_PointInRectFloat(&point, &ctx->settings_rect)) {
+                    ToggleSettings(ctx);
+                } else if (ctx->settings_visible) {
+                    ClickSettings(ctx, point.x, point.y);
+                }
+                break;
+            }
             case SDL_EVENT_KEY_DOWN: {
                 WindowState *ctx = GetWindowStateForWindowID(event.key.windowID);
                 if (!ctx) {