aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs66
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs13
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs18
-rw-r--r--Ryujinx.HLE/IHostUiHandler.cs14
-rw-r--r--Ryujinx.HLE/Switch.cs2
-rw-r--r--Ryujinx/Ui/GtkHostUiHandler.cs69
-rw-r--r--Ryujinx/Ui/InputDialog.cs69
-rw-r--r--Ryujinx/Ui/MainWindow.cs9
8 files changed, 245 insertions, 15 deletions
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
index e142838c..000d1193 100644
--- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
@@ -1,4 +1,5 @@
-using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using System;
using System.IO;
@@ -9,9 +10,10 @@ namespace Ryujinx.HLE.HOS.Applets
{
internal class SoftwareKeyboardApplet : IApplet
{
- private const string DefaultNumb = "1";
private const string DefaultText = "Ryujinx";
+ private readonly Switch _device;
+
private const int StandardBufferSize = 0x7D8;
private const int InteractiveBufferSize = 0x7D4;
@@ -21,13 +23,18 @@ namespace Ryujinx.HLE.HOS.Applets
private AppletSession _interactiveSession;
private SoftwareKeyboardConfig _keyboardConfig;
+ private byte[] _transferMemory;
- private string _textValue = DefaultText;
+ private string _textValue = null;
+ private bool _okPressed = false;
private Encoding _encoding = Encoding.Unicode;
public event EventHandler AppletStateChanged;
- public SoftwareKeyboardApplet(Horizon system) { }
+ public SoftwareKeyboardApplet(Horizon system)
+ {
+ _device = system.Device;
+ }
public ResultCode Start(AppletSession normalSession,
AppletSession interactiveSession)
@@ -39,9 +46,20 @@ namespace Ryujinx.HLE.HOS.Applets
var launchParams = _normalSession.Pop();
var keyboardConfig = _normalSession.Pop();
- var transferMemory = _normalSession.Pop();
- _keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
+ if (keyboardConfig.Length < Marshal.SizeOf<SoftwareKeyboardConfig>())
+ {
+ Logger.PrintError(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf<SoftwareKeyboardConfig>():x}. Got {keyboardConfig.Length:x}");
+ }
+ else
+ {
+ _keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
+ }
+
+ if (!_normalSession.TryPop(out _transferMemory))
+ {
+ Logger.PrintError(LogClass.ServiceAm, "SwKbd Transfer Memory is null");
+ }
if (_keyboardConfig.UseUtf8)
{
@@ -62,11 +80,13 @@ namespace Ryujinx.HLE.HOS.Applets
private void Execute()
{
- // If the keyboard type is numbers only, we swap to a default
- // text that only contains numbers.
- if (_keyboardConfig.Mode == KeyboardMode.NumbersOnly)
+ string initialText = null;
+
+ // Initial Text is always encoded as a UTF-16 string in the work buffer (passed as transfer memory)
+ // InitialStringOffset points to the memory offset and InitialStringLength is the number of UTF-16 characters
+ if (_transferMemory != null && _keyboardConfig.InitialStringLength > 0)
{
- _textValue = DefaultNumb;
+ initialText = Encoding.Unicode.GetString(_transferMemory, _keyboardConfig.InitialStringOffset, 2 * _keyboardConfig.InitialStringLength);
}
// If the max string length is 0, we set it to a large default
@@ -76,6 +96,30 @@ namespace Ryujinx.HLE.HOS.Applets
_keyboardConfig.StringLengthMax = 100;
}
+ var args = new SoftwareKeyboardUiArgs
+ {
+ HeaderText = _keyboardConfig.HeaderText,
+ SubtitleText = _keyboardConfig.SubtitleText,
+ GuideText = _keyboardConfig.GuideText,
+ SubmitText = (!string.IsNullOrWhiteSpace(_keyboardConfig.SubmitText) ? _keyboardConfig.SubmitText : "OK"),
+ StringLengthMin = _keyboardConfig.StringLengthMin,
+ StringLengthMax = _keyboardConfig.StringLengthMax,
+ InitialText = initialText
+ };
+
+ // Call the configured GUI handler to get user's input
+ if (_device.UiHandler == null)
+ {
+ Logger.PrintWarning(LogClass.Application, $"GUI Handler is not set. Falling back to default");
+ _okPressed = true;
+ }
+ else
+ {
+ _okPressed = _device.UiHandler.DisplayInputDialog(args, out _textValue);
+ }
+
+ _textValue ??= initialText ?? DefaultText;
+
// 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.
@@ -162,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Applets
if (!interactive)
{
// Result Code
- writer.Write((uint)0);
+ writer.Write(_okPressed ? 0U : 1U);
}
else
{
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
new file mode 100644
index 00000000..d24adec3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Applets
+{
+ public struct SoftwareKeyboardUiArgs
+ {
+ public string HeaderText;
+ public string SubtitleText;
+ public string InitialText;
+ public string GuideText;
+ public string SubmitText;
+ public int StringLengthMin;
+ public int StringLengthMax;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs
index 564bde09..bc0e4d8a 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs
@@ -1,4 +1,5 @@
-using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
@@ -36,10 +37,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
{
bool unknown = context.RequestData.ReadBoolean();
long size = context.RequestData.ReadInt64();
+ int handle = context.Request.HandleDesc.ToCopy[0];
- // NOTE: We don't support TransferMemory for now.
+ KTransferMemory transferMem = context.Process.HandleTable.GetObject<KTransferMemory>(handle);
- MakeObject(context, new IStorage(new byte[size]));
+ if (transferMem == null)
+ {
+ Logger.PrintWarning(LogClass.ServiceAm, $"Invalid TransferMemory Handle: {handle:X}");
+
+ return ResultCode.Success; // TODO: Find correct error code
+ }
+
+ var data = new byte[transferMem.Size];
+ context.Memory.Read(transferMem.Address, data);
+
+ MakeObject(context, new IStorage(data));
return ResultCode.Success;
}
diff --git a/Ryujinx.HLE/IHostUiHandler.cs b/Ryujinx.HLE/IHostUiHandler.cs
new file mode 100644
index 00000000..13b4b4c1
--- /dev/null
+++ b/Ryujinx.HLE/IHostUiHandler.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Applets;
+
+namespace Ryujinx.HLE
+{
+ public interface IHostUiHandler
+ {
+ /// <summary>
+ /// Displays an Input Dialog box to the user and blocks until text is entered.
+ /// </summary>
+ /// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
+ /// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
+ bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText);
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs
index 2e1a4b66..0bdcdabd 100644
--- a/Ryujinx.HLE/Switch.cs
+++ b/Ryujinx.HLE/Switch.cs
@@ -37,6 +37,8 @@ namespace Ryujinx.HLE
public Hid Hid { get; private set; }
+ public IHostUiHandler UiHandler { get; set; }
+
public bool EnableDeviceVsync { get; set; } = true;
public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, IRenderer renderer, IAalOutput audioOut)
diff --git a/Ryujinx/Ui/GtkHostUiHandler.cs b/Ryujinx/Ui/GtkHostUiHandler.cs
new file mode 100644
index 00000000..7b7b3647
--- /dev/null
+++ b/Ryujinx/Ui/GtkHostUiHandler.cs
@@ -0,0 +1,69 @@
+using Gtk;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE;
+using Ryujinx.HLE.HOS.Applets;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Ui
+{
+ internal class GtkHostUiHandler : IHostUiHandler
+ {
+ private readonly Window _parent;
+
+ public GtkHostUiHandler(Window parent)
+ {
+ _parent = parent;
+ }
+
+ public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)
+ {
+ ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
+ bool okPressed = false;
+ bool error = false;
+ string inputText = args.InitialText ?? "";
+
+ Application.Invoke(delegate
+ {
+ try
+ {
+ var swkbdDialog = new InputDialog(_parent)
+ {
+ Title = "Software Keyboard",
+ Text = args.HeaderText,
+ SecondaryText = args.SubtitleText
+ };
+
+ swkbdDialog.InputEntry.Text = inputText;
+ swkbdDialog.InputEntry.PlaceholderText = args.GuideText;
+ swkbdDialog.OkButton.Label = args.SubmitText;
+
+ swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
+
+ if (swkbdDialog.Run() == (int)ResponseType.Ok)
+ {
+ inputText = swkbdDialog.InputEntry.Text;
+ okPressed = true;
+ }
+
+ swkbdDialog.Dispose();
+ }
+ catch (Exception e)
+ {
+ error = true;
+ Logger.PrintError(LogClass.Application, $"Error displaying Software Keyboard: {e}");
+ }
+ finally
+ {
+ dialogCloseEvent.Set();
+ }
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ userText = error ? null : inputText;
+
+ return error || okPressed;
+ }
+ }
+}
diff --git a/Ryujinx/Ui/InputDialog.cs b/Ryujinx/Ui/InputDialog.cs
new file mode 100644
index 00000000..a8dc80bf
--- /dev/null
+++ b/Ryujinx/Ui/InputDialog.cs
@@ -0,0 +1,69 @@
+using Gtk;
+using System;
+
+namespace Ryujinx.Ui
+{
+ public class InputDialog : MessageDialog
+ {
+ private int _inputMin, _inputMax;
+ private Predicate<int> _checkLength;
+ private Label _validationInfo;
+
+ public Entry InputEntry { get; }
+ public Button OkButton { get; }
+ public Button CancelButton { get; }
+
+ public InputDialog(Window parent)
+ : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null)
+ {
+ SetDefaultSize(300, 0);
+
+ _validationInfo = new Label() { Visible = false };
+
+ InputEntry = new Entry() { Visible = true };
+ InputEntry.Activated += (object sender, EventArgs e) => { if (OkButton.IsSensitive) Respond(ResponseType.Ok); };
+ InputEntry.Changed += OnInputChanged;
+
+ OkButton = (Button)AddButton("OK", ResponseType.Ok);
+ CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel);
+
+ ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
+ ((Box)MessageArea).PackEnd(InputEntry, true, true, 4);
+
+ SetInputLengthValidation(0, int.MaxValue); // disable by default
+ }
+
+ public void SetInputLengthValidation(int min, int max)
+ {
+ _inputMin = Math.Min(min, max);
+ _inputMax = Math.Max(min, max);
+
+ _validationInfo.Visible = false;
+
+ if (_inputMin <= 0 && _inputMax == int.MaxValue) // disable
+ {
+ _validationInfo.Visible = false;
+ _checkLength = (length) => true;
+ }
+ else if (_inputMin > 0 && _inputMax == int.MaxValue)
+ {
+ _validationInfo.Visible = true;
+ _validationInfo.Markup = $"<i>Must be at least {_inputMin} characters long</i>";
+ _checkLength = (length) => _inputMin <= length;
+ }
+ else
+ {
+ _validationInfo.Visible = true;
+ _validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
+ _checkLength = (length) => _inputMin <= length && length <= _inputMax;
+ }
+
+ OnInputChanged(this, EventArgs.Empty);
+ }
+
+ private void OnInputChanged(object sender, EventArgs e)
+ {
+ OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 42870107..2e288ac9 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -5,6 +5,7 @@ using LibHac.Ns;
using Ryujinx.Audio;
using Ryujinx.Common.Logging;
using Ryujinx.Configuration;
+using Ryujinx.Configuration.System;
using Ryujinx.Debugger.Profiler;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL;
@@ -31,6 +32,7 @@ namespace Ryujinx.Ui
private static HLE.Switch _emulationContext;
private static GlRenderer _glWidget;
+ private static GtkHostUiHandler _uiHandler;
private static AutoResetEvent _deviceExitStatus = new AutoResetEvent(false);
@@ -191,6 +193,8 @@ namespace Ryujinx.Ui
Task.Run(RefreshFirmwareLabel);
_statusBar.Hide();
+
+ _uiHandler = new GtkHostUiHandler(this);
}
private void MainWindow_WindowStateEvent(object o, WindowStateEventArgs args)
@@ -318,7 +322,10 @@ namespace Ryujinx.Ui
{
_virtualFileSystem.Reload();
- HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, InitializeRenderer(), InitializeAudioEngine());
+ HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, InitializeRenderer(), InitializeAudioEngine())
+ {
+ UiHandler = _uiHandler
+ };
instance.Initialize();