diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs
new file mode 100644
index 0000000000..727b6d27b1
--- /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 0000000000..c3ce2c1251
--- /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 0000000000..f3fd8ac85d
--- /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 0000000000..e5418a6f34
--- /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 0000000000..fc9e1ff8e2
--- /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 22fbe8d0a8..2780446a8b 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 183da774ce..fd462382bc 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 42a2831ec9..0f66fc9bac 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 4875da8098..0000000000
--- 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