diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs new file mode 100644 index 000000000..727b6d27b --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the initial position of the cursor displayed in the area. + /// + enum InitialCursorPosition : uint + { + /// + /// Position the cursor at the beginning of the text + /// + Start, + + /// + /// Position the cursor at the end of the text + /// + End + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs new file mode 100644 index 000000000..c3ce2c125 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the text entry mode. + /// + enum InputFormMode : uint + { + /// + /// Displays the text entry area as a single-line field. + /// + SingleLine, + + /// + /// Displays the text entry area as a multi-line field. + /// + MultiLine + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs new file mode 100644 index 000000000..f3fd8ac85 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies prohibited character sets. + /// + [Flags] + enum InvalidCharFlags : uint + { + /// + /// No characters are prohibited. + /// + None = 0 << 1, + + /// + /// Prohibits spaces. + /// + Space = 1 << 1, + + /// + /// Prohibits the at (@) symbol. + /// + AtSymbol = 1 << 2, + + /// + /// Prohibits the percent (%) symbol. + /// + Percent = 1 << 3, + + /// + /// Prohibits the forward slash (/) symbol. + /// + ForwardSlash = 1 << 4, + + /// + /// Prohibits the backward slash (\) symbol. + /// + BackSlash = 1 << 5, + + /// + /// Prohibits numbers. + /// + Numbers = 1 << 6, + + /// + /// Prohibits characters outside of those allowed in download codes. + /// + DownloadCode = 1 << 7, + + /// + /// Prohibits characters outside of those allowed in Mii Nicknames. + /// + Username = 1 << 8 + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs new file mode 100644 index 000000000..e5418a6f3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the variant of keyboard displayed on screen. + /// + enum KeyboardMode : uint + { + /// + /// A full alpha-numeric keyboard. + /// + Default, + + /// + /// Number pad. + /// + NumbersOnly, + + /// + /// QWERTY (and variants) keyboard only. + /// + LettersOnly, + + /// + /// Unknown keyboard variant. + /// + Unknown + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs new file mode 100644 index 000000000..fc9e1ff8e --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the display mode of text in a password field. + /// + enum PasswordMode : uint + { + /// + /// Display input characters. + /// + Disabled, + + /// + /// Hide input characters. + /// + Enabled + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs index 22fbe8d0a..2780446a8 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs +++ b/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(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) { diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs index 183da774c..fd462382b 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs +++ b/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)] + /// + /// A structure that defines the configuration options of the software keyboard. + /// + [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; + /// /// Type of keyboard. /// - [FieldOffset(0x0)] - public SoftwareKeyboardType Type; + public KeyboardMode Mode; /// - /// 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). + /// The string displayed in the Submit button. /// - [FieldOffset(0x3AC)] - public uint StringLengthMax; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)] + public string SubmitText; /// - /// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button. + /// The character displayed in the left button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. /// - [FieldOffset(0x3B0)] - public uint StringLengthMaxExtended; + public char LeftOptionalSymbolKey; /// - /// When set, the application will validate the entered text whilst the swkbd is still on screen. + /// The character displayed in the right button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. /// - [FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)] + public char RightOptionalSymbolKey; + + /// + /// When set, predictive typing is enabled making use of the system dictionary, + /// and any custom user dictionary. + /// + [MarshalAs(UnmanagedType.I1)] + public bool PredictionEnabled; + + /// + /// Specifies prohibited characters that cannot be input into the text entry area. + /// + public InvalidCharFlags InvalidCharFlag; + + /// + /// The initial position of the text cursor displayed in the text entry area. + /// + public InitialCursorPosition InitialCursorPosition; + + /// + /// The string displayed in the header area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)] + public string HeaderText; + + /// + /// The string displayed in the subtitle area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)] + public string SubtitleText; + + /// + /// The placeholder string displayed in the text entry area when no text is entered. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)] + public string GuideText; + + /// + /// When non-zero, specifies the maximum allowed length of the string entered into the text entry area. + /// + public int StringLengthMax; + + /// + /// When non-zero, specifies the minimum allowed length of the string entered into the text entry area. + /// + public int StringLengthMin; + + /// + /// When enabled, hides input characters as dots in the text entry area. + /// + public PasswordMode PasswordMode; + + /// + /// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field. + /// + public InputFormMode InputFormMode; + + /// + /// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseNewLine; + + /// + /// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseUtf8; + + /// + /// When set, the software keyboard will blur the game application rendered behind the keyboard. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseBlurBackground; + + /// + /// Offset into the work buffer of the initial text when the keyboard is first displayed. + /// + public int InitialStringOffset; + + /// + /// Length of the initial text. + /// + public int InitialStringLength; + + /// + /// Offset into the work buffer of the custom user dictionary. + /// + public int CustomDictionaryOffset; + + /// + /// Number of entries in the custom user dictionary. + /// + public int CustomDictionaryCount; + + /// + /// When set, the text entered will be validated on the application side after the keyboard has been submitted. + /// + [MarshalAs(UnmanagedType.I1)] public bool CheckText; } } diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs index 42a2831ec..0f66fc9ba 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs @@ -1,6 +1,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard { - internal enum SoftwareKeyboardState + /// + /// Identifies the software keyboard state. + /// + enum SoftwareKeyboardState { /// /// swkbd is uninitialized. diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs deleted file mode 100644 index 4875da809..000000000 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard -{ - internal enum SoftwareKeyboardType : uint - { - /// - /// Normal keyboard. - /// - Default = 0, - - /// - /// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText. - /// - NumbersOnly = 1, - - /// - /// QWERTY (and variants) keyboard only. - /// - LettersOnly = 2 - } -} \ No newline at end of file