Initial swkbd implementation (#826)
* am: Initial swkbd implementation Currently only implements the full screen keyboard, inline keyboard will come later. * Remove unnecessary logging * Miscellaneous tidy up * am: Always pop incoming interactive session data * am: Add a reminder to implement the full config struct * am: Check for a max length of zero We should only limit/truncate text when the max length is set to a non-zero value. * Add documentation * am: Return IStorage not available when queue is empty We should be returning the appropriate error code when the FIFO is empty, rather than just throwing an exception and killing the emulator. * Fix typo * Code style changes
This commit is contained in:
parent
79abc6ed93
commit
ee81ab547e
14 changed files with 522 additions and 38 deletions
|
@ -11,35 +11,50 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
{
|
||||
private IApplet _applet;
|
||||
|
||||
private AppletFifo<byte[]> _inData;
|
||||
private AppletFifo<byte[]> _outData;
|
||||
private AppletSession _normalSession;
|
||||
private AppletSession _interactiveSession;
|
||||
|
||||
private KEvent _stateChangedEvent;
|
||||
private KEvent _normalOutDataEvent;
|
||||
private KEvent _interactiveOutDataEvent;
|
||||
|
||||
public ILibraryAppletAccessor(AppletId appletId, Horizon system)
|
||||
{
|
||||
_stateChangedEvent = new KEvent(system);
|
||||
_stateChangedEvent = new KEvent(system);
|
||||
_normalOutDataEvent = new KEvent(system);
|
||||
_interactiveOutDataEvent = new KEvent(system);
|
||||
|
||||
_applet = AppletManager.Create(appletId, system);
|
||||
_inData = new AppletFifo<byte[]>();
|
||||
_outData = new AppletFifo<byte[]>();
|
||||
|
||||
_applet.AppletStateChanged += OnAppletStateChanged;
|
||||
_applet = AppletManager.Create(appletId, system);
|
||||
|
||||
_normalSession = new AppletSession();
|
||||
_interactiveSession = new AppletSession();
|
||||
|
||||
_applet.AppletStateChanged += OnAppletStateChanged;
|
||||
_normalSession.DataAvailable += OnNormalOutData;
|
||||
_interactiveSession.DataAvailable += OnInteractiveOutData;
|
||||
|
||||
Logger.PrintInfo(LogClass.ServiceAm, $"Applet '{appletId}' created.");
|
||||
}
|
||||
|
||||
private void OnAppletStateChanged(object sender, EventArgs e)
|
||||
{
|
||||
_stateChangedEvent.ReadableEvent.Signal();
|
||||
_stateChangedEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
private void OnNormalOutData(object sender, EventArgs e)
|
||||
{
|
||||
_normalOutDataEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
private void OnInteractiveOutData(object sender, EventArgs e)
|
||||
{
|
||||
_interactiveOutDataEvent.WritableEvent.Signal();
|
||||
}
|
||||
|
||||
[Command(0)]
|
||||
// GetAppletStateChangedEvent() -> handle<copy>
|
||||
public ResultCode GetAppletStateChangedEvent(ServiceCtx context)
|
||||
{
|
||||
_stateChangedEvent.ReadableEvent.Signal();
|
||||
|
||||
if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
|
@ -54,7 +69,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
// Start()
|
||||
public ResultCode Start(ServiceCtx context)
|
||||
{
|
||||
return (ResultCode)_applet.Start(_inData, _outData);
|
||||
return (ResultCode)_applet.Start(_normalSession.GetConsumer(),
|
||||
_interactiveSession.GetConsumer());
|
||||
}
|
||||
|
||||
[Command(30)]
|
||||
|
@ -70,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
{
|
||||
IStorage data = GetObject<IStorage>(context, 0);
|
||||
|
||||
_inData.Push(data.Data);
|
||||
_normalSession.Push(data.Data);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -79,10 +95,70 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||
// PopOutData() -> object<nn::am::service::IStorage>
|
||||
public ResultCode PopOutData(ServiceCtx context)
|
||||
{
|
||||
byte[] data = _outData.Pop();
|
||||
if(_normalSession.TryPop(out byte[] data))
|
||||
{
|
||||
MakeObject(context, new IStorage(data));
|
||||
|
||||
_normalOutDataEvent.WritableEvent.Clear();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.NotAvailable;
|
||||
}
|
||||
|
||||
[Command(103)]
|
||||
// PushInteractiveInData(object<nn::am::service::IStorage>)
|
||||
public ResultCode PushInteractiveInData(ServiceCtx context)
|
||||
{
|
||||
IStorage data = GetObject<IStorage>(context, 0);
|
||||
|
||||
_interactiveSession.Push(data.Data);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(104)]
|
||||
// PopInteractiveOutData() -> object<nn::am::service::IStorage>
|
||||
public ResultCode PopInteractiveOutData(ServiceCtx context)
|
||||
{
|
||||
if(_interactiveSession.TryPop(out byte[] data))
|
||||
{
|
||||
MakeObject(context, new IStorage(data));
|
||||
|
||||
_interactiveOutDataEvent.WritableEvent.Clear();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
return ResultCode.NotAvailable;
|
||||
}
|
||||
|
||||
[Command(105)]
|
||||
// GetPopOutDataEvent() -> handle<copy>
|
||||
public ResultCode GetPopOutDataEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(106)]
|
||||
// GetPopInteractiveOutDataEvent() -> handle<copy>
|
||||
public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
|
||||
|
||||
MakeObject(context, new IStorage(data));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,5 +29,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(11)]
|
||||
// CreateTransferMemoryStorage(b8, u64, handle<copy>) -> object<nn::am::service::IStorage>
|
||||
public ResultCode CreateTransferMemoryStorage(ServiceCtx context)
|
||||
{
|
||||
bool unknown = context.RequestData.ReadBoolean();
|
||||
long size = context.RequestData.ReadInt64();
|
||||
|
||||
// NOTE: We don't support TransferMemory for now.
|
||||
|
||||
MakeObject(context, new IStorage(new byte[size]));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,11 +5,26 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
{
|
||||
internal class AppletFifo<T> : IEnumerable<T>
|
||||
internal class AppletFifo<T> : IAppletFifo<T>
|
||||
{
|
||||
private ConcurrentQueue<T> _dataQueue;
|
||||
|
||||
public int Count => _dataQueue.Count;
|
||||
public event EventHandler DataAvailable;
|
||||
|
||||
public bool IsSynchronized
|
||||
{
|
||||
get { return ((ICollection)_dataQueue).IsSynchronized; }
|
||||
}
|
||||
|
||||
public object SyncRoot
|
||||
{
|
||||
get { return ((ICollection)_dataQueue).SyncRoot; }
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _dataQueue.Count; }
|
||||
}
|
||||
|
||||
public AppletFifo()
|
||||
{
|
||||
|
@ -19,6 +34,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
public void Push(T item)
|
||||
{
|
||||
_dataQueue.Enqueue(item);
|
||||
|
||||
DataAvailable?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public bool TryAdd(T item)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Push(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
|
@ -36,6 +67,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
return _dataQueue.TryDequeue(out result);
|
||||
}
|
||||
|
||||
public bool TryTake(out T item)
|
||||
{
|
||||
return this.TryPop(out item);
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (_dataQueue.TryPeek(out T result))
|
||||
|
@ -66,6 +102,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
_dataQueue.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
this.CopyTo((T[])array, index);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _dataQueue.GetEnumerator();
|
||||
|
|
77
Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs
Normal file
77
Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
{
|
||||
internal class AppletSession
|
||||
{
|
||||
private IAppletFifo<byte[]> _inputData;
|
||||
private IAppletFifo<byte[]> _outputData;
|
||||
|
||||
public event EventHandler DataAvailable;
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return _inputData.Count; }
|
||||
}
|
||||
|
||||
public AppletSession()
|
||||
: this(new AppletFifo<byte[]>(),
|
||||
new AppletFifo<byte[]>())
|
||||
{ }
|
||||
|
||||
public AppletSession(
|
||||
IAppletFifo<byte[]> inputData,
|
||||
IAppletFifo<byte[]> outputData)
|
||||
{
|
||||
_inputData = inputData;
|
||||
_outputData = outputData;
|
||||
|
||||
_inputData.DataAvailable += OnDataAvailable;
|
||||
}
|
||||
|
||||
private void OnDataAvailable(object sender, EventArgs e)
|
||||
{
|
||||
DataAvailable?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public void Push(byte[] item)
|
||||
{
|
||||
if (!this.TryPush(item))
|
||||
{
|
||||
// TODO(jduncanator): Throw a proper exception
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPush(byte[] item)
|
||||
{
|
||||
return _outputData.TryAdd(item);
|
||||
}
|
||||
|
||||
public byte[] Pop()
|
||||
{
|
||||
if (this.TryPop(out byte[] item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Input data empty.");
|
||||
}
|
||||
|
||||
public bool TryPop(out byte[] item)
|
||||
{
|
||||
return _inputData.TryTake(out item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns an AppletSession that can be used at the
|
||||
/// other end of the pipe. Pushing data into this new session
|
||||
/// will put it in the first session's input buffer, and vice
|
||||
/// versa.
|
||||
/// </summary>
|
||||
public AppletSession GetConsumer()
|
||||
{
|
||||
return new AppletSession(this._outputData, this._inputData);
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs
Normal file
10
Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
{
|
||||
interface IAppletFifo<T> : IProducerConsumerCollection<T>
|
||||
{
|
||||
event EventHandler DataAvailable;
|
||||
}
|
||||
}
|
|
@ -24,11 +24,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
// Write(u64, buffer<bytes, 0x21>)
|
||||
public ResultCode Write(ServiceCtx context)
|
||||
{
|
||||
// TODO: Error conditions.
|
||||
long writePosition = context.RequestData.ReadInt64();
|
||||
|
||||
if (writePosition > _storage.Data.Length)
|
||||
{
|
||||
return ResultCode.OutOfBounds;
|
||||
}
|
||||
|
||||
(long position, long size) = context.Request.GetBufferType0x21();
|
||||
|
||||
size = Math.Min(size, _storage.Data.Length - writePosition);
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
long maxSize = _storage.Data.Length - writePosition;
|
||||
|
@ -50,23 +56,20 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||
// Read(u64) -> buffer<bytes, 0x22>
|
||||
public ResultCode Read(ServiceCtx context)
|
||||
{
|
||||
// TODO: Error conditions.
|
||||
long readPosition = context.RequestData.ReadInt64();
|
||||
|
||||
if (readPosition > _storage.Data.Length)
|
||||
{
|
||||
return ResultCode.OutOfBounds;
|
||||
}
|
||||
|
||||
(long position, long size) = context.Request.GetBufferType0x22();
|
||||
|
||||
byte[] data;
|
||||
size = Math.Min(size, _storage.Data.Length - readPosition);
|
||||
|
||||
if (_storage.Data.Length > size)
|
||||
{
|
||||
data = new byte[size];
|
||||
byte[] data = new byte[size];
|
||||
|
||||
Buffer.BlockCopy(_storage.Data, 0, data, 0, (int)size);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = _storage.Data;
|
||||
}
|
||||
Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size);
|
||||
|
||||
context.Memory.WriteBytes(position, data);
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ namespace Ryujinx.HLE.HOS.Services.Am
|
|||
|
||||
Success = 0,
|
||||
|
||||
NotAvailable = (2 << ErrorCodeShift) | ModuleId,
|
||||
NoMessages = (3 << ErrorCodeShift) | ModuleId,
|
||||
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
|
||||
OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
|
||||
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue