Implement application switching
Co-authored-by: Alula <6276139+alula@users.noreply.github.com>
This commit is contained in:
parent
ffcc13e1cc
commit
f67cf6a87c
@ -37,6 +37,9 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
Guide,
|
||||
Misc1,
|
||||
|
||||
Home = Guide,
|
||||
Capture = Misc1,
|
||||
|
||||
// Xbox Elite paddle
|
||||
Paddle1,
|
||||
Paddle2,
|
||||
|
@ -10,7 +10,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
/// <summary>
|
||||
/// The current version of the input file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 1;
|
||||
public const int CurrentVersion = 2;
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
|
@ -7,6 +7,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
public TButton ButtonZl { get; set; }
|
||||
public TButton ButtonSl { get; set; }
|
||||
public TButton ButtonSr { get; set; }
|
||||
public TButton ButtonCapture { get; set; }
|
||||
public TButton DpadUp { get; set; }
|
||||
public TButton DpadDown { get; set; }
|
||||
public TButton DpadLeft { get; set; }
|
||||
|
@ -11,5 +11,6 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
public TButton ButtonB { get; set; }
|
||||
public TButton ButtonY { get; set; }
|
||||
public TButton ButtonA { get; set; }
|
||||
public TButton ButtonHome { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon;
|
||||
using Ryujinx.Horizon.Sdk.OsTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
@ -17,8 +18,8 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
private WindowSystem _windowSystem;
|
||||
|
||||
// Guest event handle to wake up the event loop
|
||||
internal SystemEventType _wakeupEvent;
|
||||
internal MultiWaitHolder _wakeupHolder;
|
||||
private SystemEventType _wakeupEvent;
|
||||
private MultiWaitHolder _wakeupHolder;
|
||||
private KWritableEvent _wakeupEventObj;
|
||||
|
||||
// List of owned process holders
|
||||
@ -195,32 +196,42 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
|
||||
private void ThreadFunc()
|
||||
{
|
||||
HorizonStatic.Register(
|
||||
default,
|
||||
_system.KernelContext.Syscall,
|
||||
null,
|
||||
_thread.ThreadContext,
|
||||
(int)_thread.ThreadContext.GetX(1));
|
||||
|
||||
// lock (_lock)
|
||||
try
|
||||
{
|
||||
Os.CreateSystemEvent(out _wakeupEvent, EventClearMode.ManualClear, true).AbortOnFailure();
|
||||
_wakeupEventObj = _thread.Owner.HandleTable.GetObject<KWritableEvent>(Os.GetWritableHandleOfSystemEvent(ref _wakeupEvent));
|
||||
HorizonStatic.Register(
|
||||
default,
|
||||
_system.KernelContext.Syscall,
|
||||
null,
|
||||
_thread.ThreadContext,
|
||||
(int)_thread.ThreadContext.GetX(1));
|
||||
|
||||
_wakeupHolder = new MultiWaitHolderOfInterProcessEvent(_wakeupEvent.InterProcessEvent);
|
||||
_wakeupHolder.UserData = UserDataTag.WakeupEvent;
|
||||
_multiWait.LinkMultiWaitHolder(_wakeupHolder);
|
||||
}
|
||||
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
var holder = WaitSignaled();
|
||||
if (holder == null)
|
||||
lock (_lock)
|
||||
{
|
||||
break;
|
||||
Os.CreateSystemEvent(out _wakeupEvent, EventClearMode.ManualClear, true).AbortOnFailure();
|
||||
_wakeupEventObj = _thread.Owner.HandleTable.GetObject<KWritableEvent>(
|
||||
Os.GetWritableHandleOfSystemEvent(ref _wakeupEvent));
|
||||
|
||||
_wakeupHolder = new MultiWaitHolderOfInterProcessEvent(_wakeupEvent.InterProcessEvent)
|
||||
{
|
||||
UserData = UserDataTag.WakeupEvent
|
||||
};
|
||||
_multiWait.LinkMultiWaitHolder(_wakeupHolder);
|
||||
}
|
||||
|
||||
Process(holder);
|
||||
while (!_cts.Token.IsCancellationRequested)
|
||||
{
|
||||
var holder = WaitSignaled();
|
||||
if (holder == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Process(holder);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.ServiceAm, $"EventObserver thread encountered an exception: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
internal bool IsInteractable = true;
|
||||
internal bool WindowVisible = true;
|
||||
internal bool ExitLocked = false;
|
||||
internal bool IsApplication { get; }
|
||||
|
||||
internal AppletStateMgr AppletState { get; private set; }
|
||||
public event EventHandler AppletStateChanged;
|
||||
@ -132,10 +133,11 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
public RealApplet(ulong pid, bool isApplication, Horizon system)
|
||||
{
|
||||
_system = system;
|
||||
AppletState = new AppletStateMgr(system, isApplication);
|
||||
AppletState = new AppletStateMgr(system);
|
||||
ProcessHandle = _system.KernelContext.Processes[pid];
|
||||
AppletResourceUserId = ProcessHandle.Pid;
|
||||
AppletId = GetAppletIdFromProgramId(ProcessHandle.TitleId);
|
||||
IsApplication = isApplication;
|
||||
}
|
||||
|
||||
public void RegisterChild(RealApplet applet)
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using Ryujinx.Horizon.Sdk.Applet;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Applets.Types;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
@ -14,21 +15,20 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
private Horizon _system;
|
||||
private readonly object _lock = new();
|
||||
EventObserver _eventObserver = null;
|
||||
private EventObserver _eventObserver = null;
|
||||
|
||||
// Foreground roots
|
||||
RealApplet _homeMenu = null;
|
||||
RealApplet _overlayDisp = null;
|
||||
RealApplet _application = null;
|
||||
// Foreground roots.
|
||||
private RealApplet _homeMenu = null;
|
||||
private RealApplet _overlayDisp = null;
|
||||
// Removed single application field to allow multiple applications.
|
||||
|
||||
// Home menu state
|
||||
bool _homeMenuForegroundLocked = false;
|
||||
RealApplet _foregroundRequestedApplet = null;
|
||||
// Home menu state.
|
||||
private bool _homeMenuForegroundLocked = false;
|
||||
private RealApplet _foregroundRequestedApplet = null;
|
||||
|
||||
|
||||
// aruid -> applet map
|
||||
Dictionary<ulong, RealApplet> _applets = new();
|
||||
List<RealApplet> _rootApplets = new();
|
||||
// aruid -> applet map.
|
||||
private Dictionary<ulong, RealApplet> _applets = new();
|
||||
private List<RealApplet> _rootApplets = new();
|
||||
|
||||
internal ButtonPressTracker ButtonPressTracker { get; }
|
||||
|
||||
@ -60,6 +60,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
return;
|
||||
}
|
||||
|
||||
// If no foreground applet is explicitly requested, choose the last root applet.
|
||||
if (_foregroundRequestedApplet == null && _rootApplets.Count != 0)
|
||||
{
|
||||
_foregroundRequestedApplet = _rootApplets.Last();
|
||||
@ -72,6 +73,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a new process as an applet.
|
||||
/// </summary>
|
||||
internal RealApplet TrackProcess(ulong pid, ulong callerPid, bool isApplication)
|
||||
{
|
||||
lock (_lock)
|
||||
@ -106,6 +110,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the applet in the global tracking data structures.
|
||||
/// </summary>
|
||||
private void TrackApplet(RealApplet applet, bool isApplication)
|
||||
{
|
||||
if (_applets.ContainsKey(applet.AppletResourceUserId))
|
||||
@ -122,30 +129,33 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
_overlayDisp = applet;
|
||||
}
|
||||
else if (isApplication)
|
||||
{
|
||||
_application = applet;
|
||||
}
|
||||
// For application applets, we no longer assign a unique field.
|
||||
// They are simply tracked as root applets (if callerPid == 0) and in the _applets dictionary.
|
||||
|
||||
_applets[applet.AppletResourceUserId] = applet;
|
||||
_eventObserver.TrackAppletProcess(applet);
|
||||
|
||||
if (_applets.Count == 1 || applet.AppletId == RealAppletId.SystemAppletMenu || applet.AppletId == RealAppletId.OverlayApplet)
|
||||
// If this is the first applet being tracked, or if it is one of the special system applets,
|
||||
// perform initial setup.
|
||||
if (_applets.Count == 1 ||
|
||||
applet.AppletId == RealAppletId.SystemAppletMenu ||
|
||||
applet.AppletId == RealAppletId.OverlayApplet)
|
||||
{
|
||||
SetupFirstApplet(applet);
|
||||
_foregroundRequestedApplet = applet;
|
||||
applet.AppletState.SetFocus(false);
|
||||
}
|
||||
|
||||
// _foregroundRequestedApplet = applet;
|
||||
// applet.AppletState.SetFocusState(FocusState.InFocus);
|
||||
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs initial setup for the first tracked applet.
|
||||
/// </summary>
|
||||
private void SetupFirstApplet(RealApplet applet)
|
||||
{
|
||||
if (applet.AppletId == RealAppletId.SystemAppletMenu)
|
||||
{
|
||||
//applet.AppletState.SetFocusHandlingMode(false);
|
||||
applet.AppletState.SetOutOfFocusSuspendingEnabled(false);
|
||||
RequestHomeMenuToGetForeground();
|
||||
}
|
||||
@ -158,7 +168,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
applet.AppletState.SetFocusState(FocusState.InFocus);
|
||||
_foregroundRequestedApplet = applet;
|
||||
RequestApplicationToGetForeground();
|
||||
RequestApplicationToGetForeground(applet.ProcessHandle.Pid);
|
||||
}
|
||||
|
||||
applet.UpdateSuspensionStateLocked(true);
|
||||
@ -166,70 +176,94 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
|
||||
internal RealApplet GetByAruId(ulong aruid)
|
||||
{
|
||||
// lock (_lock)
|
||||
if (_applets.TryGetValue(aruid, out RealApplet applet))
|
||||
{
|
||||
if (_applets.TryGetValue(aruid, out RealApplet applet))
|
||||
{
|
||||
return applet;
|
||||
}
|
||||
|
||||
return null;
|
||||
return applet;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current foreground application.
|
||||
/// If none is explicitly set, the first tracked application is returned.
|
||||
/// </summary>
|
||||
internal RealApplet GetMainApplet()
|
||||
{
|
||||
// lock (_lock)
|
||||
lock (_lock)
|
||||
{
|
||||
if (_application != null)
|
||||
if (_foregroundRequestedApplet != null && _foregroundRequestedApplet.IsApplication)
|
||||
{
|
||||
if (_applets.TryGetValue(_application.AppletResourceUserId, out RealApplet applet))
|
||||
{
|
||||
return applet;
|
||||
}
|
||||
return _foregroundRequestedApplet;
|
||||
}
|
||||
|
||||
return null;
|
||||
return _rootApplets.FirstOrDefault(applet => applet.IsApplication);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RequestHomeMenuToGetForeground()
|
||||
{
|
||||
// lock (_lock)
|
||||
{
|
||||
_foregroundRequestedApplet = _homeMenu;
|
||||
}
|
||||
|
||||
_foregroundRequestedApplet = _homeMenu;
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
|
||||
internal void RequestApplicationToGetForeground()
|
||||
/// <summary>
|
||||
/// Requests that the home menu be focused.
|
||||
/// The PID provided must match the home menu’s PID.
|
||||
/// </summary>
|
||||
internal void RequestHomeMenuToGetForeground(ulong pid)
|
||||
{
|
||||
// lock (_lock)
|
||||
lock (_lock)
|
||||
{
|
||||
_foregroundRequestedApplet = _application;
|
||||
if (_homeMenu != null && _homeMenu.ProcessHandle.Pid == pid)
|
||||
{
|
||||
_foregroundRequestedApplet = _homeMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceAm, $"RequestHomeMenuToGetForeground: Provided pid {pid} does not match the home menu.");
|
||||
}
|
||||
}
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests that an application be focused.
|
||||
/// The PID provided must belong to an application applet.
|
||||
/// </summary>
|
||||
internal void RequestApplicationToGetForeground(ulong pid)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_applets.TryGetValue(pid, out var applet))
|
||||
{
|
||||
_foregroundRequestedApplet = applet;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pid == _homeMenu?.ProcessHandle.Pid)
|
||||
{
|
||||
_foregroundRequestedApplet.AppletState.SetFocusForce(false,true);
|
||||
_foregroundRequestedApplet = _homeMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceAm, $"RequestApplicationToGetForeground: Provided pid {pid} is not an application applet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
|
||||
internal void RequestLockHomeMenuIntoForeground()
|
||||
{
|
||||
// lock (_lock)
|
||||
{
|
||||
_homeMenuForegroundLocked = true;
|
||||
}
|
||||
|
||||
_homeMenuForegroundLocked = true;
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
|
||||
internal void RequestUnlockHomeMenuFromForeground()
|
||||
{
|
||||
// lock (_lock)
|
||||
{
|
||||
_homeMenuForegroundLocked = false;
|
||||
}
|
||||
|
||||
_homeMenuForegroundLocked = false;
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
|
||||
@ -245,48 +279,39 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
|
||||
internal void OnOperationModeChanged()
|
||||
{
|
||||
// lock (_lock)
|
||||
foreach (var (_, applet) in _applets)
|
||||
{
|
||||
foreach (var (aruid, applet) in _applets)
|
||||
lock (applet.Lock)
|
||||
{
|
||||
lock (applet.Lock)
|
||||
{
|
||||
applet.AppletState.OnOperationAndPerformanceModeChanged();
|
||||
}
|
||||
applet.AppletState.OnOperationAndPerformanceModeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnExitRequested()
|
||||
{
|
||||
// lock (_lock)
|
||||
foreach (var (_, applet) in _applets)
|
||||
{
|
||||
foreach (var (aruid, applet) in _applets)
|
||||
lock (applet.Lock)
|
||||
{
|
||||
lock (applet.Lock)
|
||||
{
|
||||
applet.AppletState.OnExitRequested();
|
||||
}
|
||||
applet.AppletState.OnExitRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnSystemButtonPress(SystemButtonType type)
|
||||
{
|
||||
// lock (_lock)
|
||||
switch (type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SystemButtonType.PerformHomeButtonShortPressing:
|
||||
SendButtonAppletMessageLocked(AppletMessage.DetectShortPressingHomeButton);
|
||||
break;
|
||||
case SystemButtonType.PerformHomeButtonLongPressing:
|
||||
SendButtonAppletMessageLocked(AppletMessage.DetectLongPressingHomeButton);
|
||||
break;
|
||||
case SystemButtonType.PerformCaptureButtonShortPressing:
|
||||
SendButtonAppletMessageLocked(AppletMessage.DetectShortPressingCaptureButton);
|
||||
break;
|
||||
}
|
||||
case SystemButtonType.PerformHomeButtonShortPressing:
|
||||
SendButtonAppletMessageLocked(AppletMessage.DetectShortPressingHomeButton);
|
||||
break;
|
||||
case SystemButtonType.PerformHomeButtonLongPressing:
|
||||
SendButtonAppletMessageLocked(AppletMessage.DetectLongPressingHomeButton);
|
||||
break;
|
||||
case SystemButtonType.PerformCaptureButtonShortPressing:
|
||||
SendButtonAppletMessageLocked(AppletMessage.DetectShortPressingCaptureButton);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,6 +322,16 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
lock (_homeMenu.Lock)
|
||||
{
|
||||
_homeMenu.AppletState.PushUnorderedMessage(message);
|
||||
if (message == AppletMessage.DetectShortPressingHomeButton)
|
||||
{
|
||||
foreach (var applet in _applets.Values)
|
||||
{
|
||||
if (applet != _homeMenu)
|
||||
{
|
||||
applet.ProcessHandle.SetActivity(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,9 +344,13 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes terminated applets from tracking.
|
||||
/// </summary>
|
||||
private void PruneTerminatedAppletsLocked()
|
||||
{
|
||||
foreach (var (aruid, applet) in _applets)
|
||||
// We need to iterate over a copy of the dictionary keys because we might remove items.
|
||||
foreach (var (aruid, applet) in _applets.ToList())
|
||||
{
|
||||
lock (applet.Lock)
|
||||
{
|
||||
@ -320,12 +359,14 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the applet has child applets still, terminate them first.
|
||||
if (applet.ChildApplets.Count != 0)
|
||||
{
|
||||
TerminateChildAppletsLocked(applet);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this applet was started by another, remove it from its caller’s child list.
|
||||
if (applet.CallerApplet != null)
|
||||
{
|
||||
applet.CallerApplet.ChildApplets.Remove(applet);
|
||||
@ -343,10 +384,14 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
_foregroundRequestedApplet = null;
|
||||
}
|
||||
|
||||
if (applet == _application)
|
||||
// For application applets, clear the foreground reference if necessary and
|
||||
// notify the home menu that an application has exited.
|
||||
if (applet.IsApplication)
|
||||
{
|
||||
_application = null;
|
||||
_foregroundRequestedApplet = null;
|
||||
if (_foregroundRequestedApplet == applet)
|
||||
{
|
||||
_foregroundRequestedApplet = null;
|
||||
}
|
||||
|
||||
if (_homeMenu != null)
|
||||
{
|
||||
@ -363,9 +408,26 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminates any child applets of the specified parent.
|
||||
/// </summary>
|
||||
private void TerminateChildAppletsLocked(RealApplet parent)
|
||||
{
|
||||
foreach (var child in parent.ChildApplets)
|
||||
{
|
||||
if (child.ProcessHandle.State != ProcessState.Exited)
|
||||
{
|
||||
child.ProcessHandle.Terminate();
|
||||
child.TerminateResult = (ResultCode)Services.Am.ResultCode.LibraryAppletTerminated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the home menu is locked into the foreground, ensure it remains in front.
|
||||
/// </summary>
|
||||
private bool LockHomeMenuIntoForegroundLocked()
|
||||
{
|
||||
// If the home menu is not locked into the foreground, then there's nothing to do.
|
||||
if (_homeMenu == null || !_homeMenuForegroundLocked)
|
||||
{
|
||||
_homeMenuForegroundLocked = false;
|
||||
@ -387,18 +449,9 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TerminateChildAppletsLocked(RealApplet parent)
|
||||
{
|
||||
foreach (var child in parent.ChildApplets)
|
||||
{
|
||||
if (child.ProcessHandle.State != ProcessState.Exited)
|
||||
{
|
||||
child.ProcessHandle.Terminate();
|
||||
child.TerminateResult = (ResultCode)Services.Am.ResultCode.LibraryAppletTerminated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the specified applet and its children.
|
||||
/// </summary>
|
||||
private void UpdateAppletStateLocked(RealApplet applet, bool isForeground)
|
||||
{
|
||||
if (applet == null)
|
||||
@ -427,7 +480,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
return false;
|
||||
});
|
||||
|
||||
// TODO: Update visibility state
|
||||
// TODO: Update visibility state if needed.
|
||||
|
||||
applet.SetInteractibleLocked(isForeground && applet.WindowVisible);
|
||||
|
||||
@ -445,13 +498,81 @@ namespace Ryujinx.HLE.HOS.Applets
|
||||
applet.UpdateSuspensionStateLocked(true);
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"Updating applet state for {applet.AppletId}: visible={applet.WindowVisible}, foreground={isForeground}, obscured={isObscured}, reqFState={applet.AppletState.RequestedFocusState}, ackFState={applet.AppletState.AcknowledgedFocusState}, runnable={applet.AppletState.IsRunnable()}");
|
||||
|
||||
|
||||
// Recurse into child applets
|
||||
Logger.Info?.Print(LogClass.ServiceAm,
|
||||
$"Updating applet state for {applet.AppletId}: visible={applet.WindowVisible}, foreground={isForeground}, obscured={isObscured}, reqFState={applet.AppletState.RequestedFocusState}, ackFState={applet.AppletState.AcknowledgedFocusState}, runnable={applet.AppletState.IsRunnable()}");
|
||||
|
||||
// Recurse into child applets.
|
||||
foreach (var child in applet.ChildApplets)
|
||||
{
|
||||
UpdateAppletStateLocked(child, isForeground);
|
||||
if (child == _foregroundRequestedApplet)
|
||||
{
|
||||
UpdateAppletStateLocked(child, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateAppletStateLocked(child, isForeground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the process identifier of the currently focused applet.
|
||||
/// </summary>
|
||||
public ulong GetFocusedApp()
|
||||
{
|
||||
if (_foregroundRequestedApplet == null)
|
||||
{
|
||||
return _homeMenu == null ? 0 : _homeMenu.ProcessHandle.Pid;
|
||||
}
|
||||
return _foregroundRequestedApplet.ProcessHandle.Pid;
|
||||
}
|
||||
|
||||
internal RealApplet GetFirstApplet()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_applets.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ulong oldestPID = _applets.Keys.Min();
|
||||
return _applets[oldestPID];
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsFocusedApplet(RealApplet applet)
|
||||
{
|
||||
return _foregroundRequestedApplet == applet;
|
||||
}
|
||||
|
||||
internal RealApplet GetApplicationApplet()
|
||||
{
|
||||
RealApplet applet = null;
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var (_, value) in _applets)
|
||||
{
|
||||
if (value.IsApplication)
|
||||
{
|
||||
applet = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return applet;
|
||||
}
|
||||
|
||||
public void RemoveProcess(ulong processHandlePid)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_applets.TryGetValue(processHandlePid, out RealApplet applet))
|
||||
{
|
||||
_applets.Remove(processHandlePid);
|
||||
_rootApplets.Remove(applet);
|
||||
_eventObserver.RequestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,31 +64,15 @@ namespace Ryujinx.HLE.HOS
|
||||
internal PerformanceState PerformanceState { get; private set; }
|
||||
|
||||
internal AppletStateMgr IntialAppletState { get; private set; }
|
||||
|
||||
internal AppletStateMgr AppletState
|
||||
|
||||
internal AppletStateMgr GetAppletState(ulong processId)
|
||||
{
|
||||
get
|
||||
if (WindowSystem?.GetByAruId(processId) != null)
|
||||
{
|
||||
ulong processId = 0;
|
||||
if (Device?.Processes?.ActiveApplication != null)
|
||||
{
|
||||
processId = Device.Processes.ActiveApplication.ProcessId;
|
||||
}
|
||||
if (WindowSystem?.GetByAruId(processId) != null)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Real applet instance found");
|
||||
return WindowSystem.GetByAruId(processId).AppletState;
|
||||
}
|
||||
return WindowSystem.GetByAruId(processId).AppletState;
|
||||
}
|
||||
|
||||
return IntialAppletState;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
IntialAppletState = value;
|
||||
}
|
||||
}
|
||||
return IntialAppletState;
|
||||
}
|
||||
|
||||
internal WindowSystem WindowSystem { get; private set; }
|
||||
@ -122,6 +106,9 @@ namespace Ryujinx.HLE.HOS
|
||||
internal CaptureManager CaptureManager { get; private set; }
|
||||
|
||||
internal KEvent VsyncEvent { get; private set; }
|
||||
|
||||
internal KEvent GeneralChannelEvent { get; private set; }
|
||||
internal Queue<byte[]> GeneralChannelData { get; private set; } = new();
|
||||
|
||||
internal KEvent DisplayResolutionChangeEvent { get; private set; }
|
||||
|
||||
@ -204,16 +191,13 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage);
|
||||
|
||||
AppletState = new AppletStateMgr(this, true);
|
||||
|
||||
WindowSystem = new WindowSystem(this);
|
||||
EventObserver = new EventObserver(this, WindowSystem);
|
||||
|
||||
AppletState.SetFocus(true);
|
||||
|
||||
VsyncEvent = new KEvent(KernelContext);
|
||||
|
||||
DisplayResolutionChangeEvent = new KEvent(KernelContext);
|
||||
GeneralChannelEvent = new KEvent(KernelContext);
|
||||
|
||||
SharedFontManager = new SharedFontManager(device, fontStorage);
|
||||
AccountManager = device.Configuration.AccountManager;
|
||||
@ -362,13 +346,24 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
public void ReturnFocus()
|
||||
{
|
||||
AppletState.SetFocus(true);
|
||||
GetAppletState(WindowSystem.GetFocusedApp()).SetFocus(true);
|
||||
}
|
||||
|
||||
public void SimulateWakeUpMessage()
|
||||
{
|
||||
// AppletState.Messages.Enqueue(AppletMessage.Resume);
|
||||
// AppletState.MessageEvent.ReadableEvent.Signal();
|
||||
PushToGeneralChannel(new byte[] {
|
||||
0x53, 0x41, 0x4D, 0x53, 0x01, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
});
|
||||
}
|
||||
|
||||
public void PushToGeneralChannel(byte[] data)
|
||||
{
|
||||
if (data.Length > 0)
|
||||
{
|
||||
GeneralChannelData.Enqueue(data);
|
||||
GeneralChannelEvent.ReadableEvent.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid)
|
||||
@ -530,5 +525,12 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
IsPaused = pause;
|
||||
}
|
||||
|
||||
public void SetupFirst(ulong ProgramId)
|
||||
{
|
||||
bool isApp = ProgramId > 0x01000000000007FF;
|
||||
RealApplet app = WindowSystem.TrackProcess(ProgramId, 0, isApp);
|
||||
app.AppletState.SetFocusForce(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
|
||||
@ -6,17 +8,24 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
class ILibraryAppletProxy : IpcService
|
||||
{
|
||||
private readonly ulong _pid;
|
||||
private readonly ServiceCtx _context;
|
||||
|
||||
public ILibraryAppletProxy(ulong pid)
|
||||
public ILibraryAppletProxy(ServiceCtx context, ulong pid)
|
||||
{
|
||||
_context = context;
|
||||
_pid = pid;
|
||||
}
|
||||
|
||||
private RealApplet GetApplet()
|
||||
{
|
||||
return _context.Device.System.WindowSystem.GetByAruId(_pid);
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
|
||||
public ResultCode GetCommonStateGetter(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ICommonStateGetter(context));
|
||||
MakeObject(context, new ICommonStateGetter(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -70,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
|
||||
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ILibraryAppletCreator());
|
||||
MakeObject(context, new ILibraryAppletCreator(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -92,12 +101,13 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
[CommandCmif(22)]
|
||||
// GetHomeMenuFunctions() -> object<nn::am::service::IHomeMenuFunctions>
|
||||
public ResultCode GetHomeMenuFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IHomeMenuFunctions(context.Device.System));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@ -106,6 +116,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
public ResultCode GetGlobalStateController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IGlobalStateController(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,105 @@
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.OverlayAppletProxy;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
{
|
||||
class IOverlayAppletProxy : IpcService
|
||||
{
|
||||
private readonly ulong _pid;
|
||||
|
||||
public IOverlayAppletProxy(ulong pid)
|
||||
{
|
||||
_pid = pid;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
|
||||
public ResultCode GetCommonStateGetter(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ICommonStateGetter(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetSelfController() -> object<nn::am::service::ISelfController>
|
||||
public ResultCode GetSelfController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ISelfController(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetWindowController() -> object<nn::am::service::IWindowController>
|
||||
public ResultCode GetWindowController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IWindowController(_pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// GetAudioController() -> object<nn::am::service::IAudioController>
|
||||
public ResultCode GetAudioController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IAudioController());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// GetDisplayController() -> object<nn::am::service::IDisplayController>
|
||||
public ResultCode GetDisplayController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IDisplayController(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
|
||||
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ILibraryAppletCreator(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(20)]
|
||||
// GetOverlayFunctions() -> object<nn::am::service::IOverlayFunctions>
|
||||
public ResultCode GetOverlayFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IOverlayFunctions(context.Device.System));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(21)]
|
||||
// GetAppletCommonFunctions() -> object<nn::am::service::IAppletCommonFunctions>
|
||||
public ResultCode GetAppletCommonFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IAppletCommonFunctions());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(23)]
|
||||
// GetGlobalStateController() -> object<nn::am::service::IGlobalStateController>
|
||||
public ResultCode GetGlobalStateController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IGlobalStateController(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1000)]
|
||||
// GetDebugFunctions() -> object<nn::am::service::IDebugFunctions>
|
||||
public ResultCode GetDebugFunctions(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IDebugFunctions());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
|
||||
public ResultCode GetCommonStateGetter(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ICommonStateGetter(context));
|
||||
MakeObject(context, new ICommonStateGetter(context,_pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
|
||||
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
|
||||
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ILibraryAppletCreator());
|
||||
MakeObject(context, new ILibraryAppletCreator(context,_pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -26,9 +26,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||
private int _interactiveOutDataEventHandle;
|
||||
|
||||
private int _indirectLayerHandle;
|
||||
public ulong _pid = 0;
|
||||
|
||||
public ILibraryAppletAccessor(AppletId appletId, Horizon system)
|
||||
public ILibraryAppletAccessor(AppletId appletId, Horizon system, ulong processId)
|
||||
{
|
||||
_pid = processId;
|
||||
_kernelContext = system.KernelContext;
|
||||
|
||||
_stateChangedEvent = new KEvent(system.KernelContext);
|
||||
@ -226,7 +228,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||
{
|
||||
Horizon horizon = _kernelContext.Device.System;
|
||||
|
||||
_indirectLayerHandle = horizon.AppletState.IndirectLayerHandles.Add(_applet);
|
||||
_indirectLayerHandle = horizon.GetAppletState(_pid).IndirectLayerHandles.Add(_applet);
|
||||
|
||||
context.ResponseData.Write((ulong)_indirectLayerHandle);
|
||||
|
||||
@ -255,7 +257,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
||||
|
||||
Horizon horizon = _kernelContext.Device.System;
|
||||
|
||||
horizon.AppletState.IndirectLayerHandles.Delete(_indirectLayerHandle);
|
||||
horizon.GetAppletState(_pid).IndirectLayerHandles.Delete(_indirectLayerHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,175 @@
|
||||
using LibHac.Util;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.OverlayAppletProxy
|
||||
{
|
||||
// Cmd Name
|
||||
// 0 #BeginToWatchShortHomeButtonMessage
|
||||
// 1 #EndToWatchShortHomeButtonMessage
|
||||
// 2 #GetApplicationIdForLogo
|
||||
// 3 #SetGpuTimeSliceBoost
|
||||
// 4 [2.0.0+] #SetAutoSleepTimeAndDimmingTimeEnabled
|
||||
// 5 [2.0.0+] #TerminateApplicationAndSetReason
|
||||
// 6 [3.0.0+] #SetScreenShotPermissionGlobally
|
||||
// 10 [6.0.0+] #StartShutdownSequenceForOverlay
|
||||
// 11 [6.0.0+] #StartRebootSequenceForOverlay
|
||||
// 20 [8.0.0+] #SetHandlingHomeButtonShortPressedEnabled
|
||||
// 21 [14.0.0+] SetHandlingTouchScreenInputEnabled
|
||||
// 30 [9.0.0+] #SetHealthWarningShowingState
|
||||
// 31 [10.0.0+] #IsHealthWarningRequired
|
||||
// 40 [18.0.0+] GetApplicationNintendoLogo
|
||||
// 41 [18.0.0+] GetApplicationStartupMovie
|
||||
// 50 [19.0.0+] SetGpuTimeSliceBoostForApplication
|
||||
// 60 [19.0.0+]
|
||||
// 90 [7.0.0+] #SetRequiresGpuResourceUse
|
||||
// 101 [5.0.0+] #BeginToObserveHidInputForDevelop
|
||||
class IOverlayFunctions : IpcService
|
||||
{
|
||||
|
||||
public IOverlayFunctions(Horizon system)
|
||||
{
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// BeginToWatchShortHomeButtonMessage()
|
||||
public ResultCode BeginToWatchShortHomeButtonMessage(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// EndToWatchShortHomeButtonMessage()
|
||||
public ResultCode EndToWatchShortHomeButtonMessage(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetApplicationIdForLogo() -> nn::ncm::ApplicationId
|
||||
public ResultCode GetApplicationIdForLogo(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
context.ResponseData.Write(0L);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(3)]
|
||||
// SetGpuTimeSliceBoost(u64)
|
||||
public ResultCode SetGpuTimeSliceBoost(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(4)]
|
||||
// SetAutoSleepTimeAndDimmingTimeEnabled(u8)
|
||||
public ResultCode SetAutoSleepTimeAndDimmingTimeEnabled(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// TerminateApplicationAndSetReason(u32)
|
||||
public ResultCode TerminateApplicationAndSetReason(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(6)]
|
||||
// SetScreenShotPermissionGlobally(u8)
|
||||
public ResultCode SetScreenShotPermissionGlobally(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(10)]
|
||||
// StartShutdownSequenceForOverlay()
|
||||
public ResultCode StartShutdownSequenceForOverlay(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(11)]
|
||||
// StartRebootSequenceForOverlay()
|
||||
public ResultCode StartRebootSequenceForOverlay(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(20)]
|
||||
// SetHandlingHomeButtonShortPressedEnabled(u8)
|
||||
public ResultCode SetHandlingHomeButtonShortPressedEnabled(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(21)]
|
||||
// SetHandlingTouchScreenInputEnabled(u8)
|
||||
public ResultCode SetHandlingTouchScreenInputEnabled(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(30)]
|
||||
// SetHealthWarningShowingState(u8)
|
||||
public ResultCode SetHealthWarningShowingState(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(31)]
|
||||
// IsHealthWarningRequired() -> bool
|
||||
public ResultCode IsHealthWarningRequired(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
context.ResponseData.Write(false);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(90)]
|
||||
// SetRequiresGpuResourceUse(u8)
|
||||
public ResultCode SetRequiresGpuResourceUse(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(101)]
|
||||
// BeginToObserveHidInputForDevelop()
|
||||
public ResultCode BeginToObserveHidInputForDevelop(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,16 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Ns.Types;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
|
||||
{
|
||||
@ -18,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
|
||||
private readonly KEvent _stateChangedEvent;
|
||||
private int _stateChangedEventHandle;
|
||||
public RealApplet applet;
|
||||
|
||||
public IApplicationAccessor(ulong pid, ulong applicationId, string contentPath, Horizon system)
|
||||
{
|
||||
@ -70,19 +76,42 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
context.Device.Processes.LoadNca(_contentPath, out processResult);
|
||||
isApplet = true;
|
||||
}
|
||||
|
||||
var applet = context.Device.System.WindowSystem.TrackProcess(processResult.ProcessId, 0, !isApplet);
|
||||
|
||||
ulong caller = 0;
|
||||
if (context.Device.System.WindowSystem.GetFirstApplet() != null)
|
||||
{
|
||||
caller = context.Device.System.WindowSystem.GetFirstApplet().ProcessHandle.Pid;
|
||||
}
|
||||
applet = context.Device.System.WindowSystem.TrackProcess(processResult.ProcessId, caller, !isApplet);
|
||||
applet.AppletState.SetFocusHandlingMode(true);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(20)]
|
||||
// RequestExit()
|
||||
public ResultCode RequestExit(ServiceCtx context)
|
||||
{
|
||||
applet.ProcessHandle.SetActivity(false);
|
||||
applet.AppletState.OnExitRequested();
|
||||
applet?.ProcessHandle.Terminate();
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
[CommandCmif(101)]
|
||||
// RequestForApplicationToGetForeground()
|
||||
public ResultCode RequestForApplicationToGetForeground(ServiceCtx context)
|
||||
{
|
||||
// _stateChangedEvent.ReadableEvent.Signal();
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
context.Device.System.ReturnFocus();
|
||||
|
||||
applet.AppletState.SetFocusForce(true);
|
||||
if (applet.ProcessHandle.IsPaused)
|
||||
{
|
||||
applet.ProcessHandle.SetActivity(false);
|
||||
}
|
||||
context.Device.System.WindowSystem.RequestApplicationToGetForeground(applet.ProcessHandle.Pid);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@ -95,6 +124,28 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(122)]
|
||||
// GetApplicationControlProperty() -> NACP
|
||||
public ResultCode GetApplicationControlProperty(ServiceCtx context)
|
||||
{
|
||||
ulong titleId = context.Device.System.WindowSystem.GetApplicationApplet().ProcessHandle.TitleId;
|
||||
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
ulong position = context.Request.ReceiveBuff[0].Position;
|
||||
foreach (RyuApplicationData ryuApplicationData in context.Device.Configuration.Titles)
|
||||
{
|
||||
if (ryuApplicationData.AppId.Value != titleId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
nacp = ryuApplicationData.Nacp;
|
||||
nacp.Title[1] = ryuApplicationData.Nacp.Title[0];
|
||||
break;
|
||||
}
|
||||
context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(130)]
|
||||
// SetUsers()
|
||||
public ResultCode SetUsers(ServiceCtx context)
|
||||
@ -103,5 +154,25 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
bool enable = context.RequestData.ReadBoolean();
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(131)]
|
||||
// CheckRightsEnvironmentAvailable() -> bool
|
||||
public ResultCode CheckRightsEnvironmentAvailable(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
context.ResponseData.Write(true);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(132)]
|
||||
// GetNsRightsEnvironmentHandle() -> u32
|
||||
public ResultCode GetNsRightsEnvironmentHandle(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
KEvent eventObj = new KEvent(_kernelContext);
|
||||
context.Process.HandleTable.GenerateHandle(eventObj.ReadableEvent, out int handle);
|
||||
context.ResponseData.Write(handle);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Settings.Types;
|
||||
@ -16,6 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
|
||||
private readonly Apm.ManagerServer _apmManagerServer;
|
||||
private readonly Apm.SystemManagerServer _apmSystemManagerServer;
|
||||
private readonly RealApplet _applet;
|
||||
|
||||
private bool _vrModeEnabled;
|
||||
#pragma warning disable CS0414, IDE0052 // Remove unread private member
|
||||
@ -28,9 +30,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
private readonly KEvent _acquiredSleepLockEvent;
|
||||
private int _acquiredSleepLockEventHandle;
|
||||
|
||||
public ICommonStateGetter(ServiceCtx context)
|
||||
public ICommonStateGetter(ServiceCtx context, ulong pid)
|
||||
{
|
||||
_context = context;
|
||||
_applet = context.Device.System.WindowSystem.GetByAruId(pid);
|
||||
|
||||
_apmManagerServer = new Apm.ManagerServer(context);
|
||||
_apmSystemManagerServer = new Apm.SystemManagerServer(context);
|
||||
@ -42,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// GetEventHandle() -> handle<copy>
|
||||
public ResultCode GetEventHandle(ServiceCtx context)
|
||||
{
|
||||
KEvent messageEvent = context.Device.System.AppletState.MessageEvent;
|
||||
KEvent messageEvent = _applet.AppletState.MessageEvent;
|
||||
|
||||
if (_messageEventHandle == 0)
|
||||
{
|
||||
@ -61,12 +64,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// ReceiveMessage() -> nn::am::AppletMessage
|
||||
public ResultCode ReceiveMessage(ServiceCtx context)
|
||||
{
|
||||
if (!context.Device.System.AppletState.PopMessage(out AppletMessage message))
|
||||
if (!_applet.AppletState.PopMessage(out AppletMessage message))
|
||||
{
|
||||
return ResultCode.NoMessages;
|
||||
}
|
||||
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"pid: {_applet.ProcessHandle.Pid}, msg={message}");
|
||||
context.ResponseData.Write((int)message);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@ -94,7 +99,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// GetBootMode() -> u8
|
||||
public ResultCode GetBootMode(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((byte)0); //Unknown value.
|
||||
context.ResponseData.Write((byte)0); // PmBootMode_Normal
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
@ -105,7 +110,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
// GetCurrentFocusState() -> u8
|
||||
public ResultCode GetCurrentFocusState(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((byte)context.Device.System.AppletState.AcknowledgedFocusState);
|
||||
FocusState focusState;
|
||||
lock (_applet.Lock)
|
||||
{
|
||||
focusState = _applet.AppletState.GetAndClearFocusState();
|
||||
}
|
||||
|
||||
if (context.Device.System.WindowSystem.IsFocusedApplet(_applet))
|
||||
{
|
||||
focusState = FocusState.InFocus;
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.ServiceAm, $"pid: {_applet.ProcessHandle.Pid}, GetCurrentFocusState():{focusState}");
|
||||
context.ResponseData.Write((byte)focusState);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -116,6 +133,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
_acquiredSleepLockEvent.ReadableEvent.Signal();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
@ -132,15 +151,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_acquiredSleepLockEventHandle);
|
||||
// NOTE: This needs to be signaled when sleep lock is acquired so it does not just wait forever.
|
||||
// However, since we don't support sleep lock yet, it's fine to signal immediately.
|
||||
_acquiredSleepLockEvent.ReadableEvent.Signal();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(20)]
|
||||
// PushToGeneralChannel(object<nn::am::service::IStorage>)
|
||||
public ResultCode PushInData(ServiceCtx context)
|
||||
{
|
||||
IStorage data = GetObject<IStorage>(context, 0);
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
[CommandCmif(31)]
|
||||
[CommandCmif(32)]
|
||||
// GetReaderLockAccessorEx(u32) -> object<nn::am::service::ILockAccessor>
|
||||
@ -216,7 +241,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
|
||||
_vrModeEnabled = vrModeEnabled;
|
||||
|
||||
using LblApi lblApi = new();
|
||||
using var lblApi = new LblApi();
|
||||
|
||||
if (vrModeEnabled)
|
||||
{
|
||||
|
@ -8,21 +8,16 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
{
|
||||
class IHomeMenuFunctions : IpcService
|
||||
{
|
||||
private readonly KEvent _channelEvent;
|
||||
private int _channelEventHandle;
|
||||
|
||||
public IHomeMenuFunctions(Horizon system)
|
||||
{
|
||||
// TODO: Signal this Event somewhere in future.
|
||||
_channelEvent = new KEvent(system.KernelContext);
|
||||
}
|
||||
public IHomeMenuFunctions(Horizon system) { }
|
||||
|
||||
[CommandCmif(10)]
|
||||
// RequestToGetForeground()
|
||||
public ResultCode RequestToGetForeground(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceAm);
|
||||
context.Device.System.WindowSystem.RequestApplicationToGetForeground();
|
||||
context.Device.System.WindowSystem.RequestApplicationToGetForeground(context.Process.Pid);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -36,6 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(12)]
|
||||
// UnlockForeground()
|
||||
public ResultCode UnlockForeground(ServiceCtx context)
|
||||
@ -52,7 +48,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
{
|
||||
if (_channelEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_channelEvent.ReadableEvent, out _channelEventHandle) != Result.Success)
|
||||
if (context.Process.HandleTable.GenerateHandle(
|
||||
context.Device.System.GeneralChannelEvent.ReadableEvent,
|
||||
out _channelEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator;
|
||||
using Ryujinx.Horizon.Sdk.Applet;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
|
||||
{
|
||||
class ILibraryAppletCreator : IpcService
|
||||
{
|
||||
public ILibraryAppletCreator() { }
|
||||
private readonly ulong _pid;
|
||||
|
||||
public ILibraryAppletCreator(ServiceCtx context, ulong pid)
|
||||
{
|
||||
_pid = pid;
|
||||
}
|
||||
|
||||
[CommandCmif(0)]
|
||||
// CreateLibraryApplet(u32, u32) -> object<nn::am::service::ILibraryAppletAccessor>
|
||||
@ -16,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
int libraryAppletMode = context.RequestData.ReadInt32();
|
||||
#pragma warning restore IDE0059
|
||||
|
||||
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
|
||||
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
|
||||
{
|
||||
class IRemoteStorageController : IpcService
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[CommandCmif(10)]
|
||||
// AcquireForegroundRights()
|
||||
public ResultCode AcquireForegroundRights(ServiceCtx context)
|
||||
|
@ -12,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
// OpenSystemAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ISystemAppletProxy>
|
||||
public ResultCode OpenSystemAppletProxy(ServiceCtx context)
|
||||
{
|
||||
context.Device.System.WindowSystem.TrackProcess(context.Request.HandleDesc.PId, 0, false);
|
||||
MakeObject(context, new ISystemAppletProxy(context.Request.HandleDesc.PId));
|
||||
|
||||
return ResultCode.Success;
|
||||
@ -22,7 +23,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
// OpenLibraryAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ILibraryAppletProxy>
|
||||
public ResultCode OpenLibraryAppletProxy(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ILibraryAppletProxy(context.Request.HandleDesc.PId));
|
||||
context.Device.System.WindowSystem.TrackProcess(context.Request.HandleDesc.PId, 0, false);
|
||||
MakeObject(context, new ILibraryAppletProxy(context,context.Request.HandleDesc.PId));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(300)]
|
||||
// OpenOverlayAppletProxy(pid, handle<copy>) -> object<nn::am::service::IOverlayAppletProxy>
|
||||
public ResultCode OpenOverlayAppletProxy(ServiceCtx context)
|
||||
{
|
||||
context.Device.System.WindowSystem.TrackProcess(context.Request.HandleDesc.PId, 0, false);
|
||||
MakeObject(context, new IOverlayAppletProxy(context.Request.HandleDesc.PId));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -31,6 +43,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||
// OpenSystemApplicationProxy(u64, pid, handle<copy>) -> object<nn::am::service::IApplicationProxy>
|
||||
public ResultCode OpenSystemApplicationProxy(ServiceCtx context)
|
||||
{
|
||||
context.Device.System.WindowSystem.TrackProcess(context.Request.HandleDesc.PId, 0, false);
|
||||
MakeObject(context, new IApplicationProxy(context.Request.HandleDesc.PId));
|
||||
|
||||
return ResultCode.Success;
|
||||
|
@ -1,3 +1,4 @@
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy;
|
||||
|
||||
@ -16,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService
|
||||
// GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter>
|
||||
public ResultCode GetCommonStateGetter(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ICommonStateGetter(context));
|
||||
MakeObject(context, new ICommonStateGetter(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
@ -61,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService
|
||||
// GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator>
|
||||
public ResultCode GetLibraryAppletCreator(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new ILibraryAppletCreator());
|
||||
MakeObject(context, new ILibraryAppletCreator(context, _pid));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
|
||||
internal ref SharedMemory SharedMemory => ref _storage.GetRef<SharedMemory>(0);
|
||||
|
||||
internal const int SharedMemEntryCount = 17;
|
||||
internal const int SharedMemEntryCount = 16;
|
||||
|
||||
public DebugPadDevice DebugPad;
|
||||
public TouchDevice Touchscreen;
|
||||
|
@ -33,7 +33,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
SrLeft = 1 << 25,
|
||||
SlRight = 1 << 26,
|
||||
SrRight = 1 << 27,
|
||||
|
||||
Capture = 1 << 28,
|
||||
Home = 1 << 29,
|
||||
|
||||
// Generic Catch-all
|
||||
Up = DpadUp | LStickUp | RStickUp,
|
||||
Down = DpadDown | LStickDown | RStickDown,
|
||||
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
||||
{
|
||||
struct RingLifo<T> where T : unmanaged, ISampledDataStruct
|
||||
{
|
||||
private const ulong MaxEntries = 17;
|
||||
private const ulong MaxEntries = 16;
|
||||
|
||||
#pragma warning disable IDE0051, CS0169 // Remove unused private member
|
||||
private readonly ulong _unused;
|
||||
|
@ -35,10 +35,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
||||
RightSL = 1 << 26,
|
||||
RightSR = 1 << 27,
|
||||
Palma = 1 << 28,
|
||||
|
||||
// FIXME: Probably a button on Lark.
|
||||
Unknown29 = 1 << 29,
|
||||
|
||||
Verification = 1 << 29,
|
||||
HandheldLeftB = 1 << 30,
|
||||
LeftC = 1UL << 31,
|
||||
UpC = 1UL << 32,
|
||||
RightC = 1UL << 33,
|
||||
DownC = 1UL << 34,
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
|
||||
TouchScreen = RingLifo<TouchScreenState>.Create(),
|
||||
Mouse = RingLifo<MouseState>.Create(),
|
||||
Keyboard = RingLifo<KeyboardState>.Create(),
|
||||
HomeButton = RingLifo<ButtonState>.Create(),
|
||||
SleepButton = RingLifo<ButtonState>.Create(),
|
||||
CaptureButton = RingLifo<ButtonState>.Create(),
|
||||
};
|
||||
|
||||
for (int i = 0; i < result.Npads.Length; i++)
|
||||
|
@ -1,8 +1,35 @@
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Npns
|
||||
{
|
||||
[Service("npns:u")]
|
||||
class INpnsUser : IpcService
|
||||
{
|
||||
public INpnsUser(ServiceCtx context) { }
|
||||
public KEvent receiveEvent;
|
||||
public int receiveEventHandle = 0;
|
||||
|
||||
public INpnsUser(ServiceCtx context)
|
||||
{
|
||||
receiveEvent = new KEvent(context.Device.System.KernelContext);
|
||||
}
|
||||
|
||||
[CommandCmif(5)]
|
||||
// GetReceiveEvent() -> handle(copy)
|
||||
public ResultCode GetReceiveEvent(ServiceCtx context)
|
||||
{
|
||||
if (receiveEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(receiveEvent.ReadableEvent, out receiveEventHandle) !=
|
||||
Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles");
|
||||
}
|
||||
}
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(receiveEventHandle);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,14 +26,18 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
||||
private int _gameCardUpdateDetectionEventHandle;
|
||||
|
||||
private KEvent _gameCardMountFailureEvent;
|
||||
private int _gameCardMountFailureEventHandle;
|
||||
private int _gameCardMountFailureEventHandle;
|
||||
|
||||
private KEvent _gameCardWakeEvent;
|
||||
private int _gameCardWakeEventHandle;
|
||||
|
||||
public IApplicationManagerInterface(ServiceCtx context)
|
||||
{
|
||||
_applicationRecordUpdateSystemEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_sdCardMountStatusChangedEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_gameCardUpdateDetectionEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_gameCardMountFailureEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_gameCardMountFailureEvent = new KEvent(context.Device.System.KernelContext);
|
||||
_gameCardWakeEvent = new KEvent(context.Device.System.KernelContext);
|
||||
}
|
||||
|
||||
|
||||
@ -262,6 +266,33 @@ namespace Ryujinx.HLE.HOS.Services.Ns
|
||||
Logger.Stub?.PrintStub(LogClass.Service);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(511)]
|
||||
// GetGameCardWakenReadyEvent() -> handle<copy>
|
||||
public ResultCode GetGameCardWakenReadyEvent(ServiceCtx context)
|
||||
{
|
||||
if (_gameCardWakeEventHandle == 0)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_gameCardWakeEvent.ReadableEvent, out _gameCardWakeEventHandle) != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gameCardWakeEventHandle);
|
||||
_gameCardWakeEvent.ReadableEvent.Signal();
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(512)]
|
||||
// IsGameCardApplicationRunning() -> bool
|
||||
public ResultCode IsGameCardApplicationRunning(ServiceCtx context)
|
||||
{
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceNs);
|
||||
context.ResponseData.Write(true);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1701)]
|
||||
// GetApplicationView(buffer<unknown, 5>) -> buffer<unknown, 6>
|
||||
|
@ -2,6 +2,18 @@
|
||||
{
|
||||
class IDynamicRightsInterface : IpcService
|
||||
{
|
||||
[CommandCmif(5)]
|
||||
// VerifyActivatedRightsOwners(u64)
|
||||
public ResultCode VerifyActivatedRightsOwners(ServiceCtx context) => ResultCode.Success;
|
||||
|
||||
|
||||
[CommandCmif(13)]
|
||||
// GetRunningApplicationStatus() -> nn::ns::RunningApplicationStatus
|
||||
public ResultCode GetRunningApplicationStatus(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(0);
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
using Ryujinx.HLE.HOS.Services.Olsc.OlscServiceForSystemService;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Olsc
|
||||
@ -16,6 +17,15 @@ namespace Ryujinx.HLE.HOS.Services.Olsc
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1)]
|
||||
// GetRemoteStorageController() -> object<nn::olsc::IRemoteStorageController. >
|
||||
public ResultCode GetRemoteStorageController(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IRemoteStorageController());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(2)]
|
||||
// GetDaemonController() -> object<nn::olsc::IDaemonController>
|
||||
public ResultCode GetDaemonController(ServiceCtx context)
|
||||
|
@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
|
||||
#pragma warning restore IDE0059
|
||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||
|
||||
ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId);
|
||||
ulong pid = context.Device.System.GetAppletState(context.Process.Pid).AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId);
|
||||
|
||||
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid);
|
||||
|
||||
|
@ -388,7 +388,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
|
||||
RenderingSurfaceInfo surfaceInfo = new(ColorFormat.A8B8G8R8, (uint)layerWidth, (uint)layerHeight, (uint)pitch, (uint)layerBuffSize);
|
||||
|
||||
// Get the applet associated with the handle.
|
||||
object appletObject = context.Device.System.AppletState.IndirectLayerHandles.GetData((int)layerHandle);
|
||||
object appletObject = context.Device.System.GetAppletState(context.Process.Pid).IndirectLayerHandles.GetData((int)layerHandle);
|
||||
|
||||
if (appletObject == null)
|
||||
{
|
||||
|
@ -1,18 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Applets.Types;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.SystemState
|
||||
{
|
||||
class AppletStateMgr
|
||||
{
|
||||
public ConcurrentQueue<AppletMessage> Messages;
|
||||
#region Public Properties and Fields
|
||||
|
||||
public bool ForcedSuspend { get; private set; }
|
||||
/// <summary>
|
||||
/// Queue used for unordered messages.
|
||||
/// </summary>
|
||||
public ConcurrentQueue<AppletMessage> Messages { get; }
|
||||
|
||||
public bool ForcedSuspend { get; set; }
|
||||
public FocusState AcknowledgedFocusState { get; private set; } = FocusState.Background;
|
||||
public FocusState RequestedFocusState { get; private set; } = FocusState.Background;
|
||||
|
||||
@ -25,33 +30,49 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
public KEvent LaunchableEvent { get; }
|
||||
|
||||
public IdDictionary AppletResourceUserIds { get; }
|
||||
|
||||
public IdDictionary IndirectLayerHandles { get; }
|
||||
|
||||
internal bool IsApplication { get; }
|
||||
/// <summary>
|
||||
/// Indicates that an exit has been requested.
|
||||
/// </summary>
|
||||
public bool HasRequestedExit => _hasRequestedExit;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
// Flags used for pending notifications.
|
||||
private bool _hasRequestedExit = false;
|
||||
private bool _hasAcknowledgedExit = false;
|
||||
private bool _hasResume = false;
|
||||
private bool _hasFocusStateChanged = false;
|
||||
private bool _hasRequestedRequestToPrepareSleep = false;
|
||||
private bool _hasAcknowledgedRequestToPrepareSleep = false;
|
||||
private bool _requestedRequestToDisplayState = false;
|
||||
private bool _acknowledgedRequestToDisplayState = false;
|
||||
private bool _hasOperationModeChanged = false;
|
||||
private bool _hasPerformanceModeChanged = false;
|
||||
private bool _hasSdCardRemoved = false;
|
||||
private bool _hasSleepRequiredByHighTemperature = false;
|
||||
private bool _hasSleepRequiredByLowBattery = false;
|
||||
private bool _hasAutoPowerDown = false;
|
||||
private bool _hasAlbumScreenShotTaken = false;
|
||||
private bool _hasAlbumRecordingSaved = false;
|
||||
|
||||
// Controls whether notifications for particular events are enabled.
|
||||
private bool _focusStateChangedNotificationEnabled = true;
|
||||
private bool _operationModeChangedNotificationEnabled = true;
|
||||
private bool _performanceModeChangedNotificationEnabled = true;
|
||||
private bool _hasRequestedExit = false;
|
||||
private bool _hasAcknowledgedExit = false;
|
||||
private bool _requestedRequestToDisplayState = false;
|
||||
private bool _acknowledgedRequestToDisplayState = false;
|
||||
private bool _hasRequestedRequestToPrepareSleep = false;
|
||||
private bool _hasAcknowledgedRequestToPrepareSleep = false;
|
||||
private bool _hasOperationModeChanged = false;
|
||||
private bool _hasPerformanceModeChanged = false;
|
||||
private bool _hasResume = false;
|
||||
private bool _hasFocusStateChanged = false;
|
||||
private bool _hasAlbumRecordingSaved = false;
|
||||
private bool _hasAlbumScreenShotTaken = false;
|
||||
private bool _hasAutoPowerDown = false;
|
||||
private bool _hasSleepRequiredByLowBattery = false;
|
||||
private bool _hasSleepRequiredByHighTemperature = false;
|
||||
private bool _hasSdCardRemoved = false;
|
||||
|
||||
// Internal event state for message signaling.
|
||||
private bool _eventSignaled = false;
|
||||
|
||||
// Indicates how the applet handles focus and suspension.
|
||||
private FocusHandlingMode _focusHandlingMode = FocusHandlingMode.NoSuspend;
|
||||
|
||||
public bool HasRequestedExit => _hasRequestedExit;
|
||||
#endregion
|
||||
|
||||
#region Properties with Custom Logic
|
||||
|
||||
public bool FocusStateChangedNotificationEnabled
|
||||
{
|
||||
@ -59,7 +80,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
set
|
||||
{
|
||||
_focusStateChangedNotificationEnabled = value;
|
||||
// SignalEventIfNeeded();
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,10 +104,15 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
}
|
||||
}
|
||||
|
||||
public AppletStateMgr(Horizon system, bool isApplication)
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
// Note: The constructor no longer takes an "isApplication" parameter.
|
||||
public AppletStateMgr(Horizon system)
|
||||
{
|
||||
IsApplication = isApplication;
|
||||
Messages = new ConcurrentQueue<AppletMessage>();
|
||||
|
||||
MessageEvent = new KEvent(system.KernelContext);
|
||||
OperationModeChangedEvent = new KEvent(system.KernelContext);
|
||||
LaunchableEvent = new KEvent(system.KernelContext);
|
||||
@ -95,15 +121,18 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
IndirectLayerHandles = new IdDictionary();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SetFocusState(FocusState state)
|
||||
{
|
||||
if (RequestedFocusState != state)
|
||||
{
|
||||
RequestedFocusState = state;
|
||||
_hasFocusStateChanged = true;
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public FocusState GetAndClearFocusState()
|
||||
@ -115,10 +144,13 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
public void PushUnorderedMessage(AppletMessage message)
|
||||
{
|
||||
Messages.Enqueue(message);
|
||||
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to pop the next pending message. If additional messages remain in the queue,
|
||||
/// signals the event so that consumers can continue processing.
|
||||
/// </summary>
|
||||
public bool PopMessage(out AppletMessage message)
|
||||
{
|
||||
message = GetNextMessage();
|
||||
@ -126,6 +158,201 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
return message != AppletMessage.None;
|
||||
}
|
||||
|
||||
public void OnOperationAndPerformanceModeChanged()
|
||||
{
|
||||
if (_operationModeChangedNotificationEnabled)
|
||||
{
|
||||
_hasOperationModeChanged = true;
|
||||
}
|
||||
if (_performanceModeChangedNotificationEnabled)
|
||||
{
|
||||
_hasPerformanceModeChanged = true;
|
||||
}
|
||||
OperationModeChangedEvent.ReadableEvent.Signal();
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void OnExitRequested()
|
||||
{
|
||||
_hasRequestedExit = true;
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void SetFocusHandlingMode(bool suspend)
|
||||
{
|
||||
// Adjust the focus handling mode based on the desired suspend state.
|
||||
_focusHandlingMode = _focusHandlingMode switch
|
||||
{
|
||||
FocusHandlingMode.AlwaysSuspend or FocusHandlingMode.SuspendHomeSleep when !suspend => FocusHandlingMode.NoSuspend,
|
||||
FocusHandlingMode.NoSuspend when suspend => FocusHandlingMode.SuspendHomeSleep,
|
||||
_ => _focusHandlingMode,
|
||||
};
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void RequestResumeNotification()
|
||||
{
|
||||
// Note: There is a known bug in AM whereby concurrent resume notifications
|
||||
// may cause the first notification to be lost.
|
||||
if (ResumeNotificationEnabled)
|
||||
{
|
||||
_hasResume = true;
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOutOfFocusSuspendingEnabled(bool enabled)
|
||||
{
|
||||
_focusHandlingMode = _focusHandlingMode switch
|
||||
{
|
||||
FocusHandlingMode.AlwaysSuspend when !enabled => FocusHandlingMode.SuspendHomeSleep,
|
||||
FocusHandlingMode.SuspendHomeSleep or FocusHandlingMode.NoSuspend when enabled => FocusHandlingMode.AlwaysSuspend,
|
||||
_ => _focusHandlingMode,
|
||||
};
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void RemoveForceResumeIfPossible()
|
||||
{
|
||||
if (SuspendMode != SuspendMode.ForceResume)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the activity is already resumed, we can remove the forced state.
|
||||
if (ActivityState == ActivityState.ForegroundVisible ||
|
||||
ActivityState == ActivityState.ForegroundObscured)
|
||||
{
|
||||
SuspendMode = SuspendMode.NoOverride;
|
||||
return;
|
||||
}
|
||||
|
||||
// Without a separate application flag, simply remove forced resume.
|
||||
SuspendMode = SuspendMode.NoOverride;
|
||||
}
|
||||
|
||||
public bool IsRunnable()
|
||||
{
|
||||
if (ForcedSuspend)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (SuspendMode)
|
||||
{
|
||||
case SuspendMode.ForceResume:
|
||||
return _hasRequestedExit; // During forced resume, only exit requests make it runnable.
|
||||
case SuspendMode.ForceSuspend:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_hasRequestedExit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ActivityState == ActivityState.ForegroundVisible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ActivityState == ActivityState.ForegroundObscured)
|
||||
{
|
||||
return _focusHandlingMode switch
|
||||
{
|
||||
FocusHandlingMode.AlwaysSuspend => false,
|
||||
FocusHandlingMode.SuspendHomeSleep => true,
|
||||
FocusHandlingMode.NoSuspend => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
// When not in the foreground, run only if suspension is disabled.
|
||||
return _focusHandlingMode == FocusHandlingMode.NoSuspend;
|
||||
}
|
||||
|
||||
public FocusState GetFocusStateWhileForegroundObscured() =>
|
||||
_focusHandlingMode switch
|
||||
{
|
||||
FocusHandlingMode.AlwaysSuspend => FocusState.InFocus,
|
||||
FocusHandlingMode.SuspendHomeSleep => FocusState.OutOfFocus,
|
||||
FocusHandlingMode.NoSuspend => FocusState.OutOfFocus,
|
||||
_ => throw new IndexOutOfRangeException()
|
||||
};
|
||||
|
||||
public FocusState GetFocusStateWhileBackground(bool isObscured) =>
|
||||
_focusHandlingMode switch
|
||||
{
|
||||
FocusHandlingMode.AlwaysSuspend => FocusState.InFocus,
|
||||
FocusHandlingMode.SuspendHomeSleep => isObscured ? FocusState.OutOfFocus : FocusState.InFocus,
|
||||
// Without an application flag, default to Background.
|
||||
FocusHandlingMode.NoSuspend => FocusState.Background,
|
||||
_ => throw new IndexOutOfRangeException(),
|
||||
};
|
||||
|
||||
public bool UpdateRequestedFocusState()
|
||||
{
|
||||
FocusState newState;
|
||||
|
||||
if (SuspendMode == SuspendMode.NoOverride)
|
||||
{
|
||||
newState = ActivityState switch
|
||||
{
|
||||
ActivityState.ForegroundVisible => FocusState.InFocus,
|
||||
ActivityState.ForegroundObscured => GetFocusStateWhileForegroundObscured(),
|
||||
ActivityState.BackgroundVisible => GetFocusStateWhileBackground(false),
|
||||
ActivityState.BackgroundObscured => GetFocusStateWhileBackground(true),
|
||||
_ => throw new IndexOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
newState = GetFocusStateWhileBackground(false);
|
||||
}
|
||||
|
||||
if (newState != RequestedFocusState)
|
||||
{
|
||||
RequestedFocusState = newState;
|
||||
_hasFocusStateChanged = true;
|
||||
SignalEventIfNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetFocus(bool isFocused)
|
||||
{
|
||||
SetFocusHandlingMode(false);
|
||||
FocusState focusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus;
|
||||
SetFocusState(focusState);
|
||||
}
|
||||
|
||||
public void SetFocusForce(bool isFocused, bool shouldSuspend = false)
|
||||
{
|
||||
Messages.Clear();
|
||||
SetFocusHandlingMode(shouldSuspend);
|
||||
RequestedFocusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus;
|
||||
Messages.Enqueue(AppletMessage.FocusStateChanged);
|
||||
if (isFocused)
|
||||
{
|
||||
Messages.Enqueue(AppletMessage.ChangeIntoForeground);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Enqueue(AppletMessage.ChangeIntoBackground);
|
||||
}
|
||||
MessageEvent.ReadableEvent.Signal();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks various flags and the message queue in order to return the next pending message.
|
||||
/// Flags are cleared as soon as their corresponding message is returned.
|
||||
/// </summary>
|
||||
private AppletMessage GetNextMessage()
|
||||
{
|
||||
if (_hasResume)
|
||||
@ -140,31 +367,17 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
return AppletMessage.Exit;
|
||||
}
|
||||
|
||||
if (_focusStateChangedNotificationEnabled)
|
||||
// Unify focus state change handling: if the acknowledged focus state does not match the requested one,
|
||||
// update it and return the appropriate foreground/background message.
|
||||
if (_focusStateChangedNotificationEnabled && RequestedFocusState != AcknowledgedFocusState)
|
||||
{
|
||||
if (IsApplication)
|
||||
AcknowledgedFocusState = RequestedFocusState;
|
||||
return RequestedFocusState switch
|
||||
{
|
||||
if (_hasFocusStateChanged)
|
||||
{
|
||||
_hasFocusStateChanged = false;
|
||||
return AppletMessage.FocusStateChanged;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RequestedFocusState != AcknowledgedFocusState)
|
||||
{
|
||||
AcknowledgedFocusState = RequestedFocusState;
|
||||
|
||||
switch (RequestedFocusState)
|
||||
{
|
||||
case FocusState.InFocus:
|
||||
return AppletMessage.ChangeIntoForeground;
|
||||
case FocusState.OutOfFocus:
|
||||
return AppletMessage.ChangeIntoBackground;
|
||||
}
|
||||
}
|
||||
}
|
||||
FocusState.InFocus => AppletMessage.ChangeIntoForeground,
|
||||
FocusState.OutOfFocus => AppletMessage.ChangeIntoBackground,
|
||||
_ => AppletMessage.FocusStateChanged,
|
||||
};
|
||||
}
|
||||
|
||||
if (_hasRequestedRequestToPrepareSleep != _hasAcknowledgedRequestToPrepareSleep)
|
||||
@ -227,353 +440,54 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||
return AppletMessage.AlbumRecordingSaved;
|
||||
}
|
||||
|
||||
if (Messages.TryDequeue(out AppletMessage message))
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
return AppletMessage.None;
|
||||
return Messages.TryDequeue(out var message) ? message : AppletMessage.None;
|
||||
}
|
||||
|
||||
internal void SignalEventIfNeeded()
|
||||
/// <summary>
|
||||
/// Determines whether the internal event should be signaled based on the state flags and message queue.
|
||||
/// </summary>
|
||||
private bool ShouldSignalEvent()
|
||||
{
|
||||
var shouldSignal = ShouldSignalEvent();
|
||||
bool focusStateChanged = _focusStateChangedNotificationEnabled &&
|
||||
(RequestedFocusState != AcknowledgedFocusState);
|
||||
|
||||
return !Messages.IsEmpty ||
|
||||
focusStateChanged ||
|
||||
_hasResume ||
|
||||
(_hasRequestedExit != _hasAcknowledgedExit) ||
|
||||
(_hasRequestedRequestToPrepareSleep != _hasAcknowledgedRequestToPrepareSleep) ||
|
||||
_hasOperationModeChanged ||
|
||||
_hasPerformanceModeChanged ||
|
||||
_hasSdCardRemoved ||
|
||||
_hasSleepRequiredByHighTemperature ||
|
||||
_hasSleepRequiredByLowBattery ||
|
||||
_hasAutoPowerDown ||
|
||||
(_requestedRequestToDisplayState != _acknowledgedRequestToDisplayState) ||
|
||||
_hasAlbumScreenShotTaken ||
|
||||
_hasAlbumRecordingSaved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals (or clears) the MessageEvent depending on whether there is any pending work.
|
||||
/// </summary>
|
||||
public void SignalEventIfNeeded()
|
||||
{
|
||||
bool shouldSignal = ShouldSignalEvent();
|
||||
|
||||
if (_eventSignaled != shouldSignal)
|
||||
{
|
||||
if (_eventSignaled)
|
||||
{
|
||||
MessageEvent.ReadableEvent.Clear();
|
||||
_eventSignaled = false;
|
||||
}
|
||||
else
|
||||
if (shouldSignal)
|
||||
{
|
||||
MessageEvent.ReadableEvent.Signal();
|
||||
_eventSignaled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldSignalEvent()
|
||||
{
|
||||
bool focusStateChanged = false;
|
||||
if (_focusStateChangedNotificationEnabled)
|
||||
{
|
||||
if (IsApplication)
|
||||
{
|
||||
if (_hasFocusStateChanged)
|
||||
{
|
||||
focusStateChanged = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RequestedFocusState != AcknowledgedFocusState)
|
||||
{
|
||||
focusStateChanged = true;
|
||||
}
|
||||
MessageEvent.ReadableEvent.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
return !Messages.IsEmpty
|
||||
|| focusStateChanged
|
||||
|| _hasResume
|
||||
|| _hasRequestedExit != _hasAcknowledgedExit
|
||||
|| _hasRequestedRequestToPrepareSleep != _hasAcknowledgedRequestToPrepareSleep
|
||||
|| _hasOperationModeChanged
|
||||
|| _hasPerformanceModeChanged
|
||||
|| _hasSdCardRemoved
|
||||
|| _hasSleepRequiredByHighTemperature
|
||||
|| _hasSleepRequiredByLowBattery
|
||||
|| _hasAutoPowerDown
|
||||
|| _requestedRequestToDisplayState != _acknowledgedRequestToDisplayState
|
||||
|| _hasAlbumScreenShotTaken
|
||||
|| _hasAlbumRecordingSaved;
|
||||
}
|
||||
|
||||
public void OnOperationAndPerformanceModeChanged()
|
||||
{
|
||||
if (_operationModeChangedNotificationEnabled)
|
||||
{
|
||||
_hasOperationModeChanged = true;
|
||||
}
|
||||
|
||||
if (_performanceModeChangedNotificationEnabled)
|
||||
{
|
||||
_hasPerformanceModeChanged = true;
|
||||
}
|
||||
|
||||
OperationModeChangedEvent.ReadableEvent.Signal();
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void OnExitRequested()
|
||||
{
|
||||
_hasRequestedExit = true;
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void SetFocusHandlingMode(bool suspend)
|
||||
{
|
||||
switch (_focusHandlingMode)
|
||||
{
|
||||
case FocusHandlingMode.AlwaysSuspend:
|
||||
case FocusHandlingMode.SuspendHomeSleep:
|
||||
if (!suspend)
|
||||
{
|
||||
// Disallow suspension
|
||||
_focusHandlingMode = FocusHandlingMode.NoSuspend;
|
||||
}
|
||||
break;
|
||||
case FocusHandlingMode.NoSuspend:
|
||||
if (suspend)
|
||||
{
|
||||
// Allow suspension temporarily.
|
||||
_focusHandlingMode = FocusHandlingMode.SuspendHomeSleep;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void RequestResumeNotification()
|
||||
{
|
||||
// NOTE: this appears to be a bug in am.
|
||||
// If an applet makes a concurrent request to receive resume notifications
|
||||
// while it is being suspended, the first resume notification will be lost.
|
||||
// This is not the case with other notification types.
|
||||
if (ResumeNotificationEnabled)
|
||||
{
|
||||
_hasResume = true;
|
||||
_eventSignaled = shouldSignal;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOutOfFocusSuspendingEnabled(bool enabled)
|
||||
{
|
||||
switch (_focusHandlingMode)
|
||||
{
|
||||
case FocusHandlingMode.AlwaysSuspend:
|
||||
if (!enabled)
|
||||
{
|
||||
// Allow suspension temporarily.
|
||||
_focusHandlingMode = FocusHandlingMode.SuspendHomeSleep;
|
||||
}
|
||||
break;
|
||||
case FocusHandlingMode.SuspendHomeSleep:
|
||||
case FocusHandlingMode.NoSuspend:
|
||||
if (enabled)
|
||||
{
|
||||
// Allow suspension
|
||||
_focusHandlingMode = FocusHandlingMode.AlwaysSuspend;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SignalEventIfNeeded();
|
||||
}
|
||||
|
||||
public void RemoveForceResumeIfPossible()
|
||||
{
|
||||
// If resume is not forced, we have nothing to do.
|
||||
if (SuspendMode != SuspendMode.ForceResume)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check activity state.
|
||||
// If we are already resumed, we can remove the forced state.
|
||||
switch (ActivityState)
|
||||
{
|
||||
case ActivityState.ForegroundVisible:
|
||||
case ActivityState.ForegroundObscured:
|
||||
SuspendMode = SuspendMode.NoOverride;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check focus handling mode.
|
||||
switch (_focusHandlingMode)
|
||||
{
|
||||
case FocusHandlingMode.AlwaysSuspend:
|
||||
case FocusHandlingMode.SuspendHomeSleep:
|
||||
// If the applet allows suspension, we can remove the forced state.
|
||||
SuspendMode = SuspendMode.NoOverride;
|
||||
break;
|
||||
case FocusHandlingMode.NoSuspend:
|
||||
// If the applet is not an application, we can remove the forced state.
|
||||
// Only applications can be forced to resume.
|
||||
if (!IsApplication)
|
||||
{
|
||||
SuspendMode = SuspendMode.NoOverride;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunnable()
|
||||
{
|
||||
// If suspend is forced, return that.
|
||||
if (ForcedSuspend)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check suspend mode override.
|
||||
switch (SuspendMode)
|
||||
{
|
||||
case SuspendMode.NoOverride:
|
||||
// Continue processing.
|
||||
break;
|
||||
|
||||
case SuspendMode.ForceResume:
|
||||
// The applet is runnable during forced resumption when its exit is requested.
|
||||
return _hasRequestedExit;
|
||||
|
||||
case SuspendMode.ForceSuspend:
|
||||
// The applet is never runnable during forced suspension.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always run if exit is requested.
|
||||
if (_hasRequestedExit)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ActivityState == ActivityState.ForegroundVisible)
|
||||
{
|
||||
// The applet is runnable now.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ActivityState == ActivityState.ForegroundObscured)
|
||||
{
|
||||
switch (_focusHandlingMode)
|
||||
{
|
||||
case FocusHandlingMode.AlwaysSuspend:
|
||||
// The applet is not runnable while running the applet.
|
||||
return false;
|
||||
|
||||
case FocusHandlingMode.SuspendHomeSleep:
|
||||
// The applet is runnable while running the applet.
|
||||
return true;
|
||||
|
||||
case FocusHandlingMode.NoSuspend:
|
||||
// The applet is always runnable.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The activity is a suspended one.
|
||||
// The applet should be suspended unless it has disabled suspension.
|
||||
return _focusHandlingMode == FocusHandlingMode.NoSuspend;
|
||||
}
|
||||
|
||||
public FocusState GetFocusStateWhileForegroundObscured()
|
||||
{
|
||||
switch (_focusHandlingMode)
|
||||
{
|
||||
case FocusHandlingMode.AlwaysSuspend:
|
||||
// The applet never learns it has lost focus.
|
||||
return FocusState.InFocus;
|
||||
|
||||
case FocusHandlingMode.SuspendHomeSleep:
|
||||
// The applet learns it has lost focus when launching a child applet.
|
||||
return FocusState.OutOfFocus;
|
||||
|
||||
case FocusHandlingMode.NoSuspend:
|
||||
// The applet always learns it has lost focus.
|
||||
return FocusState.OutOfFocus;
|
||||
|
||||
default:
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public FocusState GetFocusStateWhileBackground(bool isObscured)
|
||||
{
|
||||
switch (_focusHandlingMode)
|
||||
{
|
||||
case FocusHandlingMode.AlwaysSuspend:
|
||||
// The applet never learns it has lost focus.
|
||||
return FocusState.InFocus;
|
||||
|
||||
case FocusHandlingMode.SuspendHomeSleep:
|
||||
// The applet learns it has lost focus when launching a child applet.
|
||||
return isObscured ? FocusState.OutOfFocus : FocusState.InFocus;
|
||||
|
||||
case FocusHandlingMode.NoSuspend:
|
||||
// The applet always learns it has lost focus.
|
||||
return IsApplication ? FocusState.Background : FocusState.OutOfFocus;
|
||||
|
||||
default:
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateRequestedFocusState()
|
||||
{
|
||||
FocusState newState;
|
||||
|
||||
if (SuspendMode == SuspendMode.NoOverride)
|
||||
{
|
||||
// With no forced suspend or resume, we take the focus state designated
|
||||
// by the combination of the activity flag and the focus handling mode.
|
||||
switch (ActivityState)
|
||||
{
|
||||
case ActivityState.ForegroundVisible:
|
||||
newState = FocusState.InFocus;
|
||||
break;
|
||||
|
||||
case ActivityState.ForegroundObscured:
|
||||
newState = GetFocusStateWhileForegroundObscured();
|
||||
break;
|
||||
|
||||
case ActivityState.BackgroundVisible:
|
||||
newState = GetFocusStateWhileBackground(false);
|
||||
break;
|
||||
|
||||
case ActivityState.BackgroundObscured:
|
||||
newState = GetFocusStateWhileBackground(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// With forced suspend or resume, the applet is guaranteed to be background.
|
||||
newState = GetFocusStateWhileBackground(false);
|
||||
}
|
||||
|
||||
if (newState != RequestedFocusState)
|
||||
{
|
||||
// Mark the focus state as ready for update.
|
||||
RequestedFocusState = newState;
|
||||
_hasFocusStateChanged = true;
|
||||
|
||||
// We changed the focus state.
|
||||
return true;
|
||||
}
|
||||
|
||||
// We didn't change the focus state.
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetFocus(bool isFocused)
|
||||
{
|
||||
AcknowledgedFocusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus;
|
||||
|
||||
Messages.Enqueue(AppletMessage.FocusStateChanged);
|
||||
|
||||
if (isFocused)
|
||||
{
|
||||
Messages.Enqueue(AppletMessage.ChangeIntoForeground);
|
||||
}
|
||||
|
||||
MessageEvent.ReadableEvent.Signal();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Audio.Backends.CompatLayer;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Apm;
|
||||
@ -158,5 +162,23 @@ namespace Ryujinx.HLE
|
||||
Shared = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateWindowSystemInput()
|
||||
{
|
||||
System.WindowSystem.ButtonPressTracker.Update();
|
||||
}
|
||||
|
||||
public bool LoadSystemProgramId(ulong programId)
|
||||
{
|
||||
string contentPath = System.ContentManager.GetInstalledContentPath(programId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||
string filePath = VirtualFileSystem.SwitchPathToSystemPath(contentPath);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
throw new InvalidSystemResourceException("Specified title ID is not installed on the system.");
|
||||
}
|
||||
|
||||
return Processes.LoadNca(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
65
src/Ryujinx.Horizon/Ovln/Ipc/Receiver.cs
Normal file
65
src/Ryujinx.Horizon/Ovln/Ipc/Receiver.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using LibHac.Util;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.OsTypes;
|
||||
using Ryujinx.Horizon.Sdk.Ovln;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Horizon.Ovln.Ipc
|
||||
{
|
||||
partial class Receiver : IReceiver
|
||||
{
|
||||
private int _handle;
|
||||
private SystemEventType _recvEvent;
|
||||
private int _disposalState;
|
||||
|
||||
[CmifCommand(0)]
|
||||
public Result AddSource(SourceName name)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.ServiceOvln, $"SourceName: {name.Unknown.ToHexString()}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(1)]
|
||||
public Result RemoveSource(SourceName name)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.ServiceOvln, $"SourceName: {name.Unknown.ToHexString()}");
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(2)]
|
||||
public Result GetReceiveEventHandle([CopyHandle] out int handle)
|
||||
{
|
||||
if (_handle == 0)
|
||||
{
|
||||
Os.CreateSystemEvent(out _recvEvent, EventClearMode.ManualClear, true).AbortOnFailure();
|
||||
|
||||
_handle = Os.GetReadableHandleOfSystemEvent(ref _recvEvent);
|
||||
|
||||
Os.SignalSystemEvent(ref _recvEvent);
|
||||
}
|
||||
|
||||
handle = _handle;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(4)]
|
||||
public Result Receive()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_handle != 0 && Interlocked.Exchange(ref _disposalState, 1) == 0)
|
||||
{
|
||||
Os.DestroySystemEvent(ref _recvEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,21 @@
|
||||
using LibHac;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Horizon.Sdk.Ovln;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
|
||||
namespace Ryujinx.Horizon.Ovln.Ipc
|
||||
{
|
||||
partial class ReceiverService : IReceiverService
|
||||
{
|
||||
[CmifCommand(0)]
|
||||
// OpenReceiver() -> object<nn::ovln::IReceiver>
|
||||
public Result OpenReceiver(out IReceiver service)
|
||||
{
|
||||
service = new Receiver();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceOvln);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Ovln;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
|
||||
@ -6,5 +7,15 @@ namespace Ryujinx.Horizon.Ovln.Ipc
|
||||
{
|
||||
partial class Sender : ISender
|
||||
{
|
||||
[CmifCommand(0)]
|
||||
// OpenSender() -> object<nn::ovln::ISender>
|
||||
public Result OpenSender(out ISender service)
|
||||
{
|
||||
service = new Sender();
|
||||
|
||||
Logger.Stub?.PrintStub(LogClass.ServiceOvln);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
src/Ryujinx.Horizon/Sdk/Ovln/IReceiver.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Ovln/IReceiver.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ovln
|
||||
{
|
||||
interface IReceiver : IServiceObject
|
||||
{
|
||||
Result AddSource(SourceName name);
|
||||
Result RemoveSource(SourceName name);
|
||||
Result GetReceiveEventHandle(out int handle);
|
||||
}
|
||||
}
|
10
src/Ryujinx.Horizon/Sdk/Ovln/SourceName.cs
Normal file
10
src/Ryujinx.Horizon/Sdk/Ovln/SourceName.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Ovln
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct SourceName {
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)]
|
||||
public byte[] Unknown;
|
||||
}
|
||||
}
|
@ -258,6 +258,7 @@ namespace Ryujinx.Input.SDL2
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Capture, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonCapture));
|
||||
|
||||
// Finally right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
|
||||
@ -270,6 +271,7 @@ namespace Ryujinx.Input.SDL2
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Home, (GamepadButtonInputId)_configuration.RightJoycon.ButtonHome));
|
||||
|
||||
SetTriggerThreshold(_configuration.TriggerThreshold);
|
||||
}
|
||||
|
@ -371,6 +371,7 @@ namespace Ryujinx.Input.SDL2
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Capture, (Key)_configuration.LeftJoycon.ButtonCapture));
|
||||
|
||||
// Finally configure right joycon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
@ -383,6 +384,7 @@ namespace Ryujinx.Input.SDL2
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Home, (Key)_configuration.RightJoycon.ButtonHome));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,9 @@ namespace Ryujinx.Input
|
||||
|
||||
Guide,
|
||||
Misc1,
|
||||
|
||||
Home = Guide,
|
||||
Capture = Misc1,
|
||||
|
||||
// Xbox Elite paddle
|
||||
Paddle1,
|
||||
|
@ -49,7 +49,9 @@ namespace Ryujinx.Input.HLE
|
||||
new(GamepadButtonInputId.SingleLeftTrigger0, ControllerKeys.SlLeft),
|
||||
new(GamepadButtonInputId.SingleRightTrigger0, ControllerKeys.SrLeft),
|
||||
new(GamepadButtonInputId.SingleLeftTrigger1, ControllerKeys.SlRight),
|
||||
new(GamepadButtonInputId.SingleRightTrigger1, ControllerKeys.SrRight)
|
||||
new(GamepadButtonInputId.SingleRightTrigger1, ControllerKeys.SrRight),
|
||||
new(GamepadButtonInputId.Capture, ControllerKeys.Capture),
|
||||
new(GamepadButtonInputId.Home, ControllerKeys.Home)
|
||||
];
|
||||
|
||||
private class HLEKeyboardMappingEntry
|
||||
|
@ -209,6 +209,9 @@ namespace Ryujinx.Input.HLE
|
||||
List<SixAxisInput> hleMotionStates = new(NpadDevices.MaxControllers);
|
||||
|
||||
KeyboardInput? hleKeyboardInput = null;
|
||||
|
||||
bool homeDown = false;
|
||||
bool captureDown = false;
|
||||
|
||||
foreach (InputConfig inputConfig in _inputConfig)
|
||||
{
|
||||
@ -238,6 +241,11 @@ namespace Ryujinx.Input.HLE
|
||||
SixAxisInput altMotionState = isJoyconPair ? controller.GetHLEMotionState(true) : default;
|
||||
|
||||
motionState = (controller.GetHLEMotionState(), altMotionState);
|
||||
|
||||
homeDown |= inputState.Buttons.HasFlag(ControllerKeys.Home);
|
||||
captureDown |= inputState.Buttons.HasFlag(ControllerKeys.Capture);
|
||||
|
||||
inputState.Buttons &= ~(ControllerKeys.Home | ControllerKeys.Capture);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -266,6 +274,9 @@ namespace Ryujinx.Input.HLE
|
||||
|
||||
_device.Hid.Npads.Update(hleInputStates);
|
||||
_device.Hid.Npads.UpdateSixAxis(hleMotionStates);
|
||||
|
||||
_device.Hid.HomeButton.Update(homeDown);
|
||||
_device.Hid.CaptureButton.Update(captureDown);
|
||||
|
||||
if (hleKeyboardInput.HasValue)
|
||||
{
|
||||
@ -315,6 +326,7 @@ namespace Ryujinx.Input.HLE
|
||||
}
|
||||
|
||||
_device.TamperMachine.UpdateInput(hleInputStates);
|
||||
_device.UpdateWindowSystemInput();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -967,6 +967,7 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Multiplayer.LdnServer,
|
||||
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value,
|
||||
ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null));
|
||||
//Device.LoadSystemProgramId(0x010000000000100C);
|
||||
}
|
||||
|
||||
private static IHardwareDeviceDriver InitializeAudio()
|
||||
|
@ -7122,6 +7122,56 @@
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsButtonHome",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Home",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsButtonCapture",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"el_GR": "",
|
||||
"en_US": "Capture",
|
||||
"es_ES": "",
|
||||
"fr_FR": "",
|
||||
"he_IL": "",
|
||||
"it_IT": "",
|
||||
"ja_JP": "",
|
||||
"ko_KR": "",
|
||||
"no_NO": "",
|
||||
"pl_PL": "",
|
||||
"pt_BR": "",
|
||||
"ru_RU": "",
|
||||
"sv_SE": "",
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"ID": "ControllerSettingsDPad",
|
||||
"Translations": {
|
||||
|
@ -118,6 +118,7 @@ namespace Ryujinx.Headless
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonCapture = Key.Unbound,
|
||||
},
|
||||
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
@ -140,6 +141,7 @@ namespace Ryujinx.Headless
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonHome = Key.Unbound,
|
||||
},
|
||||
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
@ -178,6 +180,7 @@ namespace Ryujinx.Headless
|
||||
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||
ButtonCapture = ConfigGamepadInputId.Capture,
|
||||
},
|
||||
|
||||
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
@ -200,6 +203,7 @@ namespace Ryujinx.Headless
|
||||
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||
ButtonHome = ConfigGamepadInputId.Home,
|
||||
},
|
||||
|
||||
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
|
@ -128,6 +128,7 @@ namespace Ryujinx.Ava.Input
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Capture, (Key)_configuration.LeftJoycon.ButtonCapture));
|
||||
|
||||
// Right JoyCon
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
|
||||
@ -140,6 +141,7 @@ namespace Ryujinx.Ava.Input
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
|
||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Home, (Key)_configuration.RightJoycon.ButtonHome));
|
||||
#pragma warning restore IDE0055
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,9 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
[ObservableProperty] private GamepadInputId _dpadRight;
|
||||
|
||||
[ObservableProperty] private GamepadInputId _buttonMinus;
|
||||
[ObservableProperty] private GamepadInputId _buttonPlus;
|
||||
[ObservableProperty] private GamepadInputId _buttonPlus;
|
||||
[ObservableProperty] private GamepadInputId _buttonCapture;
|
||||
[ObservableProperty] private GamepadInputId _buttonHome;
|
||||
|
||||
[ObservableProperty] private GamepadInputId _buttonA;
|
||||
[ObservableProperty] private GamepadInputId _buttonB;
|
||||
@ -140,6 +142,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
LeftButtonSl = controllerInput.LeftJoycon.ButtonSl;
|
||||
LeftButtonSr = controllerInput.LeftJoycon.ButtonSr;
|
||||
ButtonZl = controllerInput.LeftJoycon.ButtonZl;
|
||||
ButtonCapture = controllerInput.LeftJoycon.ButtonCapture;
|
||||
|
||||
ButtonA = controllerInput.RightJoycon.ButtonA;
|
||||
ButtonB = controllerInput.RightJoycon.ButtonB;
|
||||
@ -150,6 +153,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
RightButtonSl = controllerInput.RightJoycon.ButtonSl;
|
||||
RightButtonSr = controllerInput.RightJoycon.ButtonSr;
|
||||
ButtonZr = controllerInput.RightJoycon.ButtonZr;
|
||||
ButtonHome = controllerInput.RightJoycon.ButtonHome;
|
||||
|
||||
DeadzoneLeft = controllerInput.DeadzoneLeft;
|
||||
DeadzoneRight = controllerInput.DeadzoneRight;
|
||||
@ -215,6 +219,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonSl = LeftButtonSl,
|
||||
ButtonSr = LeftButtonSr,
|
||||
ButtonZl = ButtonZl,
|
||||
ButtonCapture = ButtonCapture,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
|
||||
{
|
||||
@ -227,6 +232,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonSr = RightButtonSr,
|
||||
ButtonR = ButtonR,
|
||||
ButtonZr = ButtonZr,
|
||||
ButtonHome = ButtonHome,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
|
||||
{
|
||||
|
@ -30,6 +30,8 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
|
||||
[ObservableProperty] private Key _buttonMinus;
|
||||
[ObservableProperty] private Key _buttonPlus;
|
||||
[ObservableProperty] private Key _buttonCapture;
|
||||
[ObservableProperty] private Key _buttonHome;
|
||||
|
||||
[ObservableProperty] private Key _buttonA;
|
||||
[ObservableProperty] private Key _buttonB;
|
||||
@ -82,6 +84,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
LeftButtonSl = keyboardConfig.LeftJoycon.ButtonSl;
|
||||
LeftButtonSr = keyboardConfig.LeftJoycon.ButtonSr;
|
||||
ButtonZl = keyboardConfig.LeftJoycon.ButtonZl;
|
||||
ButtonCapture = keyboardConfig.LeftJoycon.ButtonCapture;
|
||||
|
||||
ButtonA = keyboardConfig.RightJoycon.ButtonA;
|
||||
ButtonB = keyboardConfig.RightJoycon.ButtonB;
|
||||
@ -92,6 +95,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
RightButtonSl = keyboardConfig.RightJoycon.ButtonSl;
|
||||
RightButtonSr = keyboardConfig.RightJoycon.ButtonSr;
|
||||
ButtonZr = keyboardConfig.RightJoycon.ButtonZr;
|
||||
ButtonHome = keyboardConfig.RightJoycon.ButtonHome;
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,6 +118,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonZl = ButtonZl,
|
||||
ButtonSl = LeftButtonSl,
|
||||
ButtonSr = LeftButtonSr,
|
||||
ButtonCapture = ButtonCapture,
|
||||
},
|
||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||
{
|
||||
@ -126,6 +131,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
ButtonSr = RightButtonSr,
|
||||
ButtonR = ButtonR,
|
||||
ButtonZr = ButtonZr,
|
||||
ButtonHome = ButtonHome,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
|
@ -561,6 +561,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonCapture = Key.Unbound,
|
||||
},
|
||||
LeftJoyconStick =
|
||||
new JoyconConfigKeyboardStick<Key>
|
||||
@ -582,6 +583,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonHome = Key.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
@ -621,6 +623,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||
ButtonCapture = ConfigGamepadInputId.Capture,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
{
|
||||
@ -640,6 +643,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||
ButtonHome = ConfigGamepadInputId.Home,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||
{
|
||||
|
@ -1568,6 +1568,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AppHost.Device.System.SetupFirst(AppHost.Device.Processes.ActiveApplication.ProcessId);
|
||||
|
||||
CanUpdate = false;
|
||||
|
||||
|
@ -309,6 +309,21 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Margin="10"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Width="50"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale ControllerSettingsButtonCapture}"
|
||||
TextAlignment="Center" />
|
||||
<ToggleButton Name="ButtonCapture">
|
||||
<TextBlock
|
||||
Text="{Binding Config.ButtonCapture, Converter={x:Static helpers:KeyValueConverter.Instance}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<!-- Triggers & Side Buttons -->
|
||||
<StackPanel
|
||||
@ -783,6 +798,21 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Margin="10"
|
||||
Orientation="Horizontal">
|
||||
<ToggleButton Name="ButtonHome">
|
||||
<TextBlock
|
||||
Text="{Binding Config.ButtonHome, Converter={x:Static helpers:KeyValueConverter.Instance}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
<TextBlock
|
||||
Width="40"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale ControllerSettingsButtonHome}"
|
||||
TextAlignment="Center" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
@ -184,6 +184,12 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
case "RightJoystick":
|
||||
viewModel.Config.RightJoystick = buttonValue.AsHidType<StickInputId>();
|
||||
break;
|
||||
case "ButtonCapture":
|
||||
viewModel.Config.ButtonCapture = buttonValue.AsHidType<GamepadInputId>();
|
||||
break;
|
||||
case "ButtonHome":
|
||||
viewModel.Config.ButtonHome = buttonValue.AsHidType<GamepadInputId>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -302,6 +302,21 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Margin="10"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Width="50"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale ControllerSettingsButtonCapture}"
|
||||
TextAlignment="Center" />
|
||||
<ToggleButton Name="ButtonCapture">
|
||||
<TextBlock
|
||||
Text="{Binding Config.ButtonCapture, Converter={x:Static helpers:KeyValueConverter.Instance}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<!-- Triggers & Side Buttons -->
|
||||
<StackPanel
|
||||
@ -666,6 +681,21 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Margin="10"
|
||||
Orientation="Horizontal">
|
||||
<ToggleButton Name="ButtonHome">
|
||||
<TextBlock
|
||||
Text="{Binding Config.ButtonHome, Converter={x:Static helpers:KeyValueConverter.Instance}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
<TextBlock
|
||||
Width="40"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{ext:Locale ControllerSettingsButtonHome}"
|
||||
TextAlignment="Center" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
break;
|
||||
case "ButtonMinus":
|
||||
viewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
break;
|
||||
case "LeftStickButton":
|
||||
viewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
@ -161,6 +161,12 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
case "RightStickLeft":
|
||||
viewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonCapture":
|
||||
viewModel.Config.ButtonCapture = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ButtonHome":
|
||||
viewModel.Config.ButtonHome = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -223,6 +223,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonCapture = Key.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
@ -243,6 +244,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonHome = Key.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
|
@ -275,6 +275,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
ButtonZl = Key.Q,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonCapture = Key.Unbound,
|
||||
},
|
||||
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
@ -295,6 +296,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.Unbound,
|
||||
ButtonSr = Key.Unbound,
|
||||
ButtonHome = Key.Unbound,
|
||||
},
|
||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user