Эх сурвалжийг харах

Fully reverse swkbd configuration structure and follow-up to swkbd implementation (#839)

* am: Fully reverse swkbd configuration structure

* Add documentation

* Remove explicit access modifiers

* Fix formatting

* Fix further formatting issues
jduncanator 6 жил өмнө
parent
commit
d562ba37a0

+ 18 - 0
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+    /// <summary>
+    /// Identifies the initial position of the cursor displayed in the area.
+    /// </summary>
+    enum InitialCursorPosition : uint
+    {
+        /// <summary>
+        /// Position the cursor at the beginning of the text
+        /// </summary>
+        Start,
+
+        /// <summary>
+        /// Position the cursor at the end of the text
+        /// </summary>
+        End
+    }
+}

+ 18 - 0
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+    /// <summary>
+    /// Identifies the text entry mode.
+    /// </summary>
+    enum InputFormMode : uint
+    {
+        /// <summary>
+        /// Displays the text entry area as a single-line field.
+        /// </summary>
+        SingleLine,
+
+        /// <summary>
+        /// Displays the text entry area as a multi-line field.
+        /// </summary>
+        MultiLine
+    }
+}

+ 56 - 0
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs

@@ -0,0 +1,56 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+    /// <summary>
+    /// Identifies prohibited character sets.
+    /// </summary>
+    [Flags]
+    enum InvalidCharFlags : uint
+    {
+        /// <summary>
+        /// No characters are prohibited.
+        /// </summary>
+        None = 0 << 1,
+
+        /// <summary>
+        /// Prohibits spaces.
+        /// </summary>
+        Space = 1 << 1,
+
+        /// <summary>
+        /// Prohibits the at (@) symbol.
+        /// </summary>
+        AtSymbol = 1 << 2,
+
+        /// <summary>
+        /// Prohibits the percent (%) symbol.
+        /// </summary>
+        Percent = 1 << 3,
+
+        /// <summary>
+        /// Prohibits the forward slash (/) symbol.
+        /// </summary>
+        ForwardSlash = 1 << 4,
+
+        /// <summary>
+        /// Prohibits the backward slash (\) symbol.
+        /// </summary>
+        BackSlash = 1 << 5,
+
+        /// <summary>
+        /// Prohibits numbers.
+        /// </summary>
+        Numbers = 1 << 6,
+
+        /// <summary>
+        /// Prohibits characters outside of those allowed in download codes.
+        /// </summary>
+        DownloadCode = 1 << 7,
+
+        /// <summary>
+        /// Prohibits characters outside of those allowed in Mii Nicknames.
+        /// </summary>
+        Username = 1 << 8
+    }
+}

+ 28 - 0
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs

@@ -0,0 +1,28 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+    /// <summary>
+    /// Identifies the variant of keyboard displayed on screen.
+    /// </summary>
+    enum KeyboardMode : uint
+    {
+        /// <summary>
+        /// A full alpha-numeric keyboard.
+        /// </summary>
+        Default,
+
+        /// <summary>
+        /// Number pad.
+        /// </summary>
+        NumbersOnly,
+
+        /// <summary>
+        /// QWERTY (and variants) keyboard only.
+        /// </summary>
+        LettersOnly,
+
+        /// <summary>
+        /// Unknown keyboard variant.
+        /// </summary>
+        Unknown
+    }
+}

+ 18 - 0
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+    /// <summary>
+    /// Identifies the display mode of text in a password field.
+    /// </summary>
+    enum PasswordMode : uint
+    {
+        /// <summary>
+        /// Display input characters.
+        /// </summary>
+        Disabled,
+
+        /// <summary>
+        /// Hide input characters.
+        /// </summary>
+        Enabled
+    }
+}

+ 36 - 20
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs

@@ -9,11 +9,11 @@ namespace Ryujinx.HLE.HOS.Applets
 {
     internal class SoftwareKeyboardApplet : IApplet
     {
-        private const string DEFAULT_NUMB = "1";
-        private const string DEFAULT_TEXT = "Ryujinx";
+        private const string DefaultNumb = "1";
+        private const string DefaultText = "Ryujinx";
 
-        private const int STANDARD_BUFFER_SIZE    = 0x7D8;
-        private const int INTERACTIVE_BUFFER_SIZE = 0x7D4;
+        private const int StandardBufferSize    = 0x7D8;
+        private const int InteractiveBufferSize = 0x7D4;
 
         private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized;
 
@@ -22,7 +22,8 @@ namespace Ryujinx.HLE.HOS.Applets
 
         private SoftwareKeyboardConfig _keyboardConfig;
 
-        private string _textValue = DEFAULT_TEXT;
+        private string   _textValue = DefaultText;
+        private Encoding _encoding  = Encoding.Unicode;
 
         public event EventHandler AppletStateChanged;
 
@@ -42,6 +43,11 @@ namespace Ryujinx.HLE.HOS.Applets
 
             _keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
 
+            if (_keyboardConfig.UseUtf8)
+            {
+                _encoding = Encoding.UTF8;
+            }
+
             _state = SoftwareKeyboardState.Ready;
 
             Execute();
@@ -58,9 +64,9 @@ namespace Ryujinx.HLE.HOS.Applets
         {
             // If the keyboard type is numbers only, we swap to a default
             // text that only contains numbers.
-            if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly)
+            if (_keyboardConfig.Mode == KeyboardMode.NumbersOnly)
             {
-                _textValue = DEFAULT_NUMB;
+                _textValue = DefaultNumb;
             }
 
             // If the max string length is 0, we set it to a large default
@@ -70,6 +76,15 @@ namespace Ryujinx.HLE.HOS.Applets
                 _keyboardConfig.StringLengthMax = 100;
             }
 
+            // If the game requests a string with a minimum length less
+            // than our default text, repeat our default text until we meet
+            // the minimum length requirement. 
+            // This should always be done before the text truncation step.
+            while (_textValue.Length < _keyboardConfig.StringLengthMin)
+            {
+                _textValue = String.Join(" ", _textValue, _textValue);
+            }
+
             // If our default text is longer than the allowed length,
             // we truncate it.
             if (_textValue.Length > _keyboardConfig.StringLengthMax)
@@ -77,7 +92,18 @@ namespace Ryujinx.HLE.HOS.Applets
                 _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax);
             }
 
-            if (!_keyboardConfig.CheckText)
+            // Does the application want to validate the text itself?
+            if (_keyboardConfig.CheckText)
+            {
+                // The application needs to validate the response, so we
+                // submit it to the interactive output buffer, and poll it
+                // for validation. Once validated, the application will submit
+                // back a validation status, which is handled in OnInteractiveDataPushIn.
+                _state = SoftwareKeyboardState.ValidationPending;
+
+                _interactiveSession.Push(BuildResponse(_textValue, true));
+            }
+            else
             {
                 // If the application doesn't need to validate the response,
                 // we push the data to the non-interactive output buffer
@@ -88,16 +114,6 @@ namespace Ryujinx.HLE.HOS.Applets
 
                 AppletStateChanged?.Invoke(this, null);
             }
-            else
-            {
-                // The application needs to validate the response, so we
-                // submit it to the interactive output buffer, and poll it
-                // for validation. Once validated, the application will submit
-                // back a validation status, which is handled in OnInteractiveDataPushIn.
-                _state = SoftwareKeyboardState.ValidationPending;
-
-                _interactiveSession.Push(BuildResponse(_textValue, true));
-            }
         }
 
         private void OnInteractiveData(object sender, EventArgs e)
@@ -136,12 +152,12 @@ namespace Ryujinx.HLE.HOS.Applets
 
         private byte[] BuildResponse(string text, bool interactive)
         {
-            int bufferSize = !interactive ? STANDARD_BUFFER_SIZE : INTERACTIVE_BUFFER_SIZE;
+            int bufferSize = interactive ? InteractiveBufferSize : StandardBufferSize;
 
             using (MemoryStream stream = new MemoryStream(new byte[bufferSize]))
             using (BinaryWriter writer = new BinaryWriter(stream))
             {
-                byte[] output = Encoding.Unicode.GetBytes(text);
+                byte[] output = _encoding.GetBytes(text);
 
                 if (!interactive)
                 {

+ 117 - 12
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs

@@ -2,32 +2,137 @@
 
 namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
 {
-    // TODO(jduncanator): Define all fields
-    [StructLayout(LayoutKind.Explicit)]
+    /// <summary>
+    /// A structure that defines the configuration options of the software keyboard.
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
     struct SoftwareKeyboardConfig
     {
+        private const int SubmitTextLength   = 8;
+        private const int HeaderTextLength   = 64;        
+        private const int SubtitleTextLength = 128;
+        private const int GuideTextLength    = 256;
+
         /// <summary>
         /// Type of keyboard.
         /// </summary>
-        [FieldOffset(0x0)]
-        public SoftwareKeyboardType Type;
+        public KeyboardMode Mode;
+
+        /// <summary>
+        /// The string displayed in the Submit button.
+        /// </summary>
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)]
+        public string SubmitText;
+
+        /// <summary>
+        /// The character displayed in the left button of the numeric keyboard.
+        /// This is ignored when Mode is not set to NumbersOnly.
+        /// </summary>
+        public char LeftOptionalSymbolKey;
+
+        /// <summary>
+        /// The character displayed in the right button of the numeric keyboard.
+        /// This is ignored when Mode is not set to NumbersOnly.
+        /// </summary>
+        public char RightOptionalSymbolKey;
+
+        /// <summary>
+        /// When set, predictive typing is enabled making use of the system dictionary,
+        /// and any custom user dictionary.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool PredictionEnabled;
+
+        /// <summary>
+        /// Specifies prohibited characters that cannot be input into the text entry area.
+        /// </summary>
+        public InvalidCharFlags InvalidCharFlag;
+
+        /// <summary>
+        /// The initial position of the text cursor displayed in the text entry area.
+        /// </summary>
+        public InitialCursorPosition InitialCursorPosition;
+
+        /// <summary>
+        /// The string displayed in the header area of the keyboard.
+        /// </summary>
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)]
+        public string HeaderText;
+
+        /// <summary>
+        /// The string displayed in the subtitle area of the keyboard.
+        /// </summary>
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)]
+        public string SubtitleText;
+
+        /// <summary>
+        /// The placeholder string displayed in the text entry area when no text is entered.
+        /// </summary>
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)]
+        public string GuideText;
+
+        /// <summary>
+        /// When non-zero, specifies the maximum allowed length of the string entered into the text entry area.
+        /// </summary>
+        public int StringLengthMax;
+
+        /// <summary>
+        /// When non-zero, specifies the minimum allowed length of the string entered into the text entry area.
+        /// </summary>
+        public int StringLengthMin;
+
+        /// <summary>
+        /// When enabled, hides input characters as dots in the text entry area.
+        /// </summary>
+        public PasswordMode PasswordMode;
+
+        /// <summary>
+        /// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field.
+        /// </summary>
+        public InputFormMode InputFormMode;
+
+        /// <summary>
+        /// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool UseNewLine;
+
+        /// <summary>
+        /// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool UseUtf8;
+
+        /// <summary>
+        /// When set, the software keyboard will blur the game application rendered behind the keyboard.
+        /// </summary>
+        [MarshalAs(UnmanagedType.I1)]
+        public bool UseBlurBackground;
+
+        /// <summary>
+        /// Offset into the work buffer of the initial text when the keyboard is first displayed.
+        /// </summary>
+        public int InitialStringOffset;
+
+        /// <summary>
+        /// Length of the initial text.
+        /// </summary>
+        public int InitialStringLength;
 
         /// <summary>
-        /// When non-zero, specifies the max string length. When the input is too long, swkbd will stop accepting more input until text is deleted via the B button (Backspace).
+        /// Offset into the work buffer of the custom user dictionary.
         /// </summary>
-        [FieldOffset(0x3AC)]
-        public uint StringLengthMax;
+        public int CustomDictionaryOffset;
 
         /// <summary>
-        /// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button.
+        /// Number of entries in the custom user dictionary.
         /// </summary>
-        [FieldOffset(0x3B0)]
-        public uint StringLengthMaxExtended;
+        public int CustomDictionaryCount;
 
         /// <summary>
-        /// When set, the application will validate the entered text whilst the swkbd is still on screen.
+        /// When set, the text entered will be validated on the application side after the keyboard has been submitted.
         /// </summary>
-        [FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)]
+        [MarshalAs(UnmanagedType.I1)]
         public bool CheckText;
     }
 }

+ 4 - 1
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs

@@ -1,6 +1,9 @@
 namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
 {
-    internal enum SoftwareKeyboardState
+    /// <summary>
+    /// Identifies the software keyboard state.
+    /// </summary>
+    enum SoftwareKeyboardState
     {
         /// <summary>
         /// swkbd is uninitialized.

+ 0 - 20
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs

@@ -1,20 +0,0 @@
-namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
-{
-    internal enum SoftwareKeyboardType : uint
-    {
-        /// <summary>
-        /// Normal keyboard.
-        /// </summary>
-        Default = 0,
-
-        /// <summary>
-        /// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText.
-        /// </summary>
-        NumbersOnly = 1,
-
-        /// <summary>
-        /// QWERTY (and variants) keyboard only.
-        /// </summary>
-        LettersOnly = 2
-    }
-}