Compare commits

..

56 Commits

Author SHA1 Message Date
Vladimir Sokolov
fe484e8cb1 Merge branch 'master' into master 2025-01-13 08:31:53 +10:00
GabCoolGuy
f1dee50275 infra: Update to LLVM 17 (#519)
This fixes macos builds not building correctly because of a missing
LLVM 14 package.
2025-01-12 14:57:57 -06:00
shinyoyo
c2ae49eb47 Add some missing Simplified Chinese translations (#515) 2025-01-12 12:33:27 -06:00
WilliamWsyHK
47c71966d0 Add compat of Xenoblade 2 JP edition same as global edition (#517) 2025-01-12 12:31:58 -06:00
Vladimir Sokolov
44de9b378b Merge branch 'master' into master 2025-01-11 22:54:24 +10:00
Vova
2ec032c48b Added description in another file 2025-01-11 22:37:33 +10:00
Vova
f75efbea54 oops, rename to"SpecialExitEmulator" 2025-01-11 22:27:28 +10:00
Vova
11f1922a82 fix specialExitEmulator -> SpecialExitEmulator, Added description to clarify function 2025-01-11 22:20:08 +10:00
Evan Husted
f9e8f4bc29 docs: compat: Ori and the Will of the Wisps is now ingame, not playable 2025-01-11 06:10:58 -06:00
Vladimir Sokolov
de18c4927f Merge branch 'master' into master 2025-01-11 21:26:51 +10:00
Evan Husted
7694c8c046 Do not auto release stable 2025-01-11 04:08:11 -06:00
Evan Husted
0dd789e8a5 misc: chore: remove redundant trimming on CompatibilityEntry.GameName init 2025-01-11 01:26:34 -06:00
Evan Husted
4e0aafd005 docs: compat: Trim redundant/duplicate information to save space 2025-01-11 01:18:10 -06:00
Evan Husted
c5091f499e docs: compat: The House of the Dead: Remake Playable 2025-01-11 00:38:32 -06:00
Evan Husted
41c8fd8194 misc: chore: lol this field was misspelled 2025-01-10 23:23:53 -06:00
Evan Husted
d4a7ee25ea misc: chore: use ObservableProperty on input view models 2025-01-10 23:23:05 -06:00
Evan Husted
3141c560fb misc: chore: remove sender parameter from LdnGameData receieved event 2025-01-10 23:15:55 -06:00
Evan Husted
de341b285b misc: use ObservableProperty on HotkeyConfig fields 2025-01-10 23:15:37 -06:00
Evan Husted
78e7a3085a add back a comment that was removed for no reason 2025-01-10 21:16:31 -06:00
Evan Husted
01e22f1c67 newline brace 2025-01-10 21:13:39 -06:00
Evan Husted
cc95e80ee9 misc: chore: Move converters into a directory in Helpers. Namespace unchanged 2025-01-10 20:24:53 -06:00
Evan Husted
d75ce52bd4 UI: Show play time in one time unit, maxing out at hours. 2025-01-10 20:23:47 -06:00
Vladimir Sokolov
3352d70ea4 Update SettingsViewModel.cs
small fix
2025-01-11 10:43:38 +10:00
Vladimir Sokolov
864cd57b51 Merge branch 'master' into master 2025-01-10 20:47:37 +10:00
Evan Husted
4a4ea557de UI: compat: show last updated date on entry hover 2025-01-10 01:43:34 -06:00
Evan Husted
33f42adb11 Merge remote-tracking branch 'origin/master' 2025-01-09 22:09:01 -06:00
LotP1
918ec1bde3 cores rework (#505)
This PR changes the core count to be defined in the device instead of
being a const value.
This is mostly a change for future features I want to implement and
should not impact any functionality.
The console will now log the range of cores requested from the
application, and for now, if the requested range is not 0 to 2 (the 3
cores used for application emulation), it will give an error message
which tells the user to contact me on discord. I'm doing this because
I'm interested in finding applications/games that don't use 3 cores and
the error will be removed in the future once I've gotten enough data.
2025-01-09 21:43:18 -06:00
Evan Husted
cca429d46a misc: chore: restore not enable 2025-01-09 21:42:54 -06:00
Vladimir Sokolov
06cd53925c Merge branch 'master' into master 2025-01-10 10:14:47 +10:00
Vladimir Sokolov
33fefb1323 Merge branch 'master' into master 2025-01-09 07:55:05 +10:00
Vladimir Sokolov
0c503e10ae Update SettingsViewModel.cs 2025-01-08 23:09:09 +10:00
Vova
1c6390cbfb minor bugs fixed 2025-01-08 23:05:58 +10:00
Vova
c20452be61 Fixed a bug where the emulator would still terminate the game when pressing a hotkey (unnecessary check removed) 2025-01-08 23:00:41 +10:00
Vova
b6667a8352 multiple fixes, variable typo fixes, adherence to a certain style. Fixed initialization of the new function, defaults to 0 2025-01-08 22:45:33 +10:00
Vova
37b4dd2133 Added new option "exit by pressing plus and minus buttons" to the input section. 2025-01-08 21:43:18 +10:00
Vladimir Sokolov
007d3bc045 Merge branch 'Ryubing:master' into master 2025-01-08 18:53:27 +10:00
Vladimir Sokolov
be2aa5ae8f Merge branch 'Ryubing:master' into master 2024-12-30 14:27:54 +10:00
Vladimir Sokolov
7f5978155b Merge branch 'GreemDev:master' into master 2024-12-24 20:59:34 +10:00
Vladimir Sokolov
cd43362042 Merge branch 'GreemDev:master' into master 2024-12-22 19:55:43 +10:00
Vova
39e8283fb8 Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2024-12-15 09:33:41 +10:00
Vladimir Sokolov
3e3de18976 Merge branch 'GreemDev:master' into master 2024-11-30 11:01:14 +10:00
Vladimir Sokolov
a158a93d0a Merge branch 'GreemDev:master' into master 2024-11-25 09:10:46 +10:00
Vladimir Sokolov
ff90b68d09 Merge branch 'GreemDev:master' into master 2024-11-23 21:22:17 +10:00
Vladimir Sokolov
d7bd165861 Merge branch 'GreemDev:master' into master 2024-11-21 09:15:18 +10:00
Vladimir Sokolov
5bfa01b58a Merge branch 'GreemDev:master' into master 2024-11-13 09:45:58 +10:00
Vladimir Sokolov
7c30426f89 Merge branch 'GreemDev:master' into master 2024-11-10 15:42:43 +10:00
Vladimir Sokolov
267aaca87d Merge branch 'GreemDev:master' into master 2024-11-10 14:41:40 +10:00
Vova
b9012e291b code cleaning 2024-11-10 14:41:02 +10:00
Vladimir Sokolov
7da4a917ba Merge branch 'GreemDev:master' into master 2024-11-10 13:13:11 +10:00
Vladimir Sokolov
4bab858793 Merge branch 'GreemDev:master' into master 2024-11-09 20:21:57 +10:00
Vladimir Sokolov
ca7c17186e Merge branch 'GreemDev:master' into master 2024-11-04 20:47:13 +10:00
Vladimir Sokolov
ea061cf60c Merge branch 'GreemDev:master' into master 2024-11-03 18:55:10 +10:00
Vova
d83da7d2fb Fixed the logic of saving the input section. Added a new dialog box when changing parameters 2024-11-02 22:42:57 +10:00
Vladimir Sokolov
25499cbbf6 Merge branch 'GreemDev:master' into master 2024-11-02 22:38:33 +10:00
Vova
8074a4dd87 Fixed a visual bug in "input settings", when switching between players the settings were reset to default 2024-10-31 18:19:05 +10:00
Vova
13d2498405 test 2024-10-31 18:09:55 +10:00
67 changed files with 3947 additions and 3839 deletions

View File

@@ -129,11 +129,11 @@ jobs:
with:
global-json-file: global.json
- name: Setup LLVM 14
- name: Setup LLVM 17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 14
sudo ./llvm.sh 17
- name: Install rcodesign
run: |

View File

@@ -210,11 +210,11 @@ jobs:
with:
global-json-file: global.json
- name: Setup LLVM 15
- name: Setup LLVM 17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
sudo ./llvm.sh 17
- name: Install rcodesign
run: |

View File

@@ -3,16 +3,6 @@ name: Release job
on:
workflow_dispatch:
inputs: {}
push:
branches: [ release ]
paths-ignore:
- '.github/**'
- 'docs/**'
- 'assets/**'
- '*.yml'
- '*.json'
- '*.config'
- '*.md'
concurrency: release
@@ -201,11 +191,11 @@ jobs:
with:
global-json-file: global.json
- name: Setup LLVM 15
- name: Setup LLVM 17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
sudo ./llvm.sh 17
- name: Install rcodesign
run: |

View File

@@ -42,7 +42,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.0.1" />
<PackageVersion Include="Gommon" Version="2.7.0.2" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />

View File

@@ -19,7 +19,7 @@ if platform.system() == "Darwin":
else:
OTOOL = shutil.which("llvm-otool")
if OTOOL is None:
for llvm_ver in [15, 14, 13]:
for llvm_ver in [17, 16, 15, 14, 13]:
otool_path = shutil.which(f"llvm-otool-{llvm_ver}")
if otool_path is not None:
OTOOL = otool_path

View File

@@ -26,7 +26,7 @@ else:
LIPO = shutil.which("llvm-lipo")
if LIPO is None:
for llvm_ver in [15, 14, 13]:
for llvm_ver in [17, 16, 15, 14, 13]:
lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}")
if lipo_path is not None:
LIPO = lipo_path

View File

@@ -67,11 +67,11 @@ python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-14)" ];
if ! [ -x "$(command -v llvm-lipo-17)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-14
LIPO=llvm-lipo-17
fi
else
LIPO=lipo

View File

@@ -62,11 +62,11 @@ python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTP
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-14)" ];
if ! [ -x "$(command -v llvm-lipo-17)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-14
LIPO=llvm-lipo-17
fi
else
LIPO=lipo

File diff suppressed because it is too large Load Diff

View File

@@ -284,7 +284,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
uint[] defaultCapabilities = {
0x030363F7,
(((uint)KScheduler.CpuCoresCount - 1) << 24) + (((uint)KScheduler.CpuCoresCount - 1) << 16) + 0x63F7u,
0x1FFFFFCF,
0x207FFFEF,
0x47E0060F,

View File

@@ -63,6 +63,7 @@ namespace Ryujinx.HLE.HOS.Kernel
TickSource = tickSource;
Device = device;
Memory = memory;
KScheduler.CpuCoresCount = device.CpuCoresCount;
Running = true;

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Kernel
return result;
}
process.DefaultCpuCore = 3;
process.DefaultCpuCore = KScheduler.CpuCoresCount - 1;
context.Processes.TryAdd(process.Pid, process);

View File

@@ -277,7 +277,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return result;
}
result = Capabilities.InitializeForUser(capabilities, MemoryManager);
result = Capabilities.InitializeForUser(capabilities, MemoryManager, IsApplication);
if (result != Result.Success)
{

View File

@@ -35,15 +35,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
DebuggingFlags &= ~3u;
KernelReleaseVersion = KProcess.KernelVersionPacked;
return Parse(capabilities, memoryManager);
return Parse(capabilities, memoryManager, false);
}
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager, bool isApplication)
{
return Parse(capabilities, memoryManager);
return Parse(capabilities, memoryManager, isApplication);
}
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager, bool isApplication)
{
int mask0 = 0;
int mask1 = 0;
@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
if (cap.GetCapabilityType() != CapabilityType.MapRange)
{
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager, isApplication);
if (result != Result.Success)
{
@@ -120,7 +120,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return Result.Success;
}
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager, bool isApplication)
{
CapabilityType code = cap.GetCapabilityType();
@@ -176,6 +176,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore);
AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio);
if (isApplication && lowestCpuCore == 0 && highestCpuCore != 2)
Ryujinx.Common.Logging.Logger.Error?.Print(Ryujinx.Common.Logging.LogClass.Application, $"Application requested cores with index range {lowestCpuCore} to {highestCpuCore}! Report this to @LotP on the Ryujinx/Ryubing discord server (discord.gg/ryujinx)!");
else if (isApplication)
Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $"Application requested cores with index range {lowestCpuCore} to {highestCpuCore}");
break;
}

View File

@@ -2683,7 +2683,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
return KernelResult.InvalidCombination;
}
if ((uint)preferredCore > 3)
if ((uint)preferredCore > KScheduler.CpuCoresCount - 1)
{
if ((preferredCore | 2) != -1)
{

View File

@@ -9,13 +9,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
partial class KScheduler : IDisposable
{
public const int PrioritiesCount = 64;
public const int CpuCoresCount = 4;
public static int CpuCoresCount;
private const int RoundRobinTimeQuantumMs = 10;
private static readonly int[] _preemptionPriorities = { 59, 59, 59, 63 };
private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
private static int[] _srcCoresHighestPrioThreads;
private readonly KernelContext _context;
private readonly int _coreId;
@@ -47,6 +45,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
_coreId = coreId;
_currentThread = null;
if (_srcCoresHighestPrioThreads == null)
{
_srcCoresHighestPrioThreads = new int[CpuCoresCount];
}
}
private static int PreemptionPriorities(int index)
{
return index == CpuCoresCount - 1 ? 63 : 59;
}
public static ulong SelectThreads(KernelContext context)
@@ -437,7 +445,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
for (int core = 0; core < CpuCoresCount; core++)
{
RotateScheduledQueue(context, core, _preemptionPriorities[core]);
RotateScheduledQueue(context, core, PreemptionPriorities(core));
}
context.CriticalSection.Leave();

View File

@@ -24,14 +24,14 @@ namespace Ryujinx.HLE.HOS.Services
// not large enough.
private const int PointerBufferSize = 0x8000;
private readonly static uint[] _defaultCapabilities = {
0x030363F7,
private static uint[] _defaultCapabilities => [
(((uint)KScheduler.CpuCoresCount - 1) << 24) + (((uint)KScheduler.CpuCoresCount - 1) << 16) + 0x63F7u,
0x1FFFFFCF,
0x207FFFEF,
0x47E0060F,
0x0048BFFF,
0x01007FFF,
};
];
// The amount of time Dispose() will wait to Join() the thread executing the ServerLoop()
private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3);

View File

@@ -32,6 +32,8 @@ namespace Ryujinx.HLE
public TamperMachine TamperMachine { get; }
public IHostUIHandler UIHandler { get; }
public int CpuCoresCount = 4; //Switch 1 has 4 cores
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
public bool CustomVSyncIntervalEnabled { get; set; } = false;
public int CustomVSyncInterval { get; set; }

View File

@@ -253,11 +253,23 @@ namespace Ryujinx.Input.SDL2
return IGamepad.GetStateSnapshot(this);
}
private static bool hotButtonMinus = false;
private static bool hotExit = false;
public bool SpecialExit()
{
if (hotButtonMinus)
{
hotButtonMinus = false;
return hotExit;
}
return hotExit = false;
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
GamepadStateSnapshot rawState = GetStateSnapshot();
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (_buttonsUserMapping.Count == 0)
@@ -270,6 +282,28 @@ namespace Ryujinx.Input.SDL2
if (!entry.IsValid)
continue;
if (GamepadButtonInputId.Minus == entry.To)
{
if (rawState.IsPressed(entry.From) && !hotButtonMinus)
{
hotButtonMinus = true;
}
else if (!result.IsPressed(entry.From) && hotButtonMinus)
{
hotButtonMinus = false;
}
}
if (GamepadButtonInputId.Plus == entry.To)
{
if (rawState.IsPressed(entry.To) && hotButtonMinus)
{
hotExit = true;
}
}
// Do not touch state of button already pressed
if (!result.IsPressed(entry.To))
{
@@ -376,5 +410,7 @@ namespace Ryujinx.Input.SDL2
return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1;
}
}
}

View File

@@ -329,6 +329,11 @@ namespace Ryujinx.Input.SDL2
return result;
}
public bool SpecialExit()
{
return false;
}
public GamepadStateSnapshot GetStateSnapshot()
{
throw new NotSupportedException();

View File

@@ -25,6 +25,10 @@ namespace Ryujinx.Input.SDL2
{
_driver = driver;
}
public bool SpecialExit()
{
return false;
}
public Vector2 GetPosition()
{

View File

@@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Collections.Concurrent;
@@ -273,15 +274,21 @@ namespace Ryujinx.Input.HLE
}
}
public void Update()
public bool Update()
{
// _gamepad may be altered by other threads
var gamepad = _gamepad;
if (gamepad != null && GamepadDriver != null)
{
State = gamepad.GetMappedStateSnapshot();
if (gamepad.SpecialExit())
{
return true;
}
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
{
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
@@ -334,6 +341,7 @@ namespace Ryujinx.Input.HLE
State = default;
_leftMotionInput = null;
}
return false;
}
public GamepadInput GetHLEInputState()

View File

@@ -200,8 +200,10 @@ namespace Ryujinx.Input.HLE
ReloadConfiguration(inputConfig, enableKeyboard, enableMouse);
}
public void Update(float aspectRatio = 1)
public bool Update(float aspectRatio = 1)
{
bool specialExit = false;
lock (_lock)
{
List<GamepadInput> hleInputStates = new();
@@ -225,9 +227,10 @@ namespace Ryujinx.Input.HLE
DriverConfigurationUpdate(ref controller, inputConfig);
controller.UpdateUserConfiguration(inputConfig);
controller.Update();
controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex));
specialExit = controller.Update(); //hotkey press check
controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex));
inputState = controller.GetHLEInputState();
inputState.Buttons |= _device.Hid.UpdateStickButtons(inputState.LStick, inputState.RStick);
@@ -315,6 +318,8 @@ namespace Ryujinx.Input.HLE
_device.TamperMachine.UpdateInput(hleInputStates);
}
return specialExit;
}
internal InputConfig GetPlayerInputConfigByIndex(int index)

View File

@@ -79,6 +79,12 @@ namespace Ryujinx.Input
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
GamepadStateSnapshot GetMappedStateSnapshot();
/// <summary>
/// Gets the state if the minus and plus buttons were pressed on the gamepad.
/// </summary>
/// <returns>returns true if the buttons were pressed.</returns>
bool SpecialExit();
/// <summary>
/// Get a snaphost of the state of the gamepad.
/// </summary>

View File

@@ -18,6 +18,7 @@ using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Renderer;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Views.Main;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
@@ -70,6 +71,7 @@ namespace Ryujinx.Ava
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
private const int TargetFps = 60;
private const float VolumeDelta = 0.05f;
static bool SpecialExit = false;
private static readonly Cursor _invisibleCursor = new(StandardCursorType.None);
private readonly nint _invisibleCursorWin;
@@ -96,6 +98,7 @@ namespace Ryujinx.Ava
private bool _isCursorInRenderer = true;
private bool _ignoreCursorState = false;
private enum CursorStates
{
CursorIsHidden,
@@ -503,10 +506,15 @@ namespace Ryujinx.Ava
_viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
MainLoop();
Exit();
}
public bool IsSpecialExit()
{
return SpecialExit;
}
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
{
if (Device != null)
@@ -589,6 +597,7 @@ namespace Ryujinx.Ava
_isStopped = true;
Stop();
}
public void DisposeContext()
@@ -1135,6 +1144,7 @@ namespace Ryujinx.Ava
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld];
string vSyncMode = Device.VSyncMode.ToString();
UpdateShaderCount();
if (GraphicsConfig.ResScale != 1)
@@ -1200,7 +1210,17 @@ namespace Ryujinx.Ava
return false;
}
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()))
{
if (ConfigurationState.Instance.Hid.SpecialExitEmulator.Value == 1)
{
SpecialExit = true; // close App
}
if (ConfigurationState.Instance.Hid.SpecialExitEmulator.Value > 0)
{
_isActive = false; //close game
}
}
if (_viewModel.IsActive)
{
@@ -1335,6 +1355,8 @@ namespace Ryujinx.Ava
Device.Hid.DebugPad.Update();
return true;
}

View File

@@ -147,6 +147,31 @@
"zh_TW": "滑鼠直接存取"
}
},
{
"ID": "SettingsExtraCloseApp",
"Translations": {
"ar_SA": "خروج سريع من التطبيق",
"de_DE": "Schneller Ausstieg aus der Anwendung",
"el_GR": "Γρήγορη έξοδος από την εφαρμογή",
"en_US": "Quick Exit from Application",
"es_ES": "Salida rápida de la aplicación",
"fr_FR": "Sortie rapide de l'application",
"he_IL": "יציאה מהירה מהאפליקציה",
"it_IT": "Uscita rapida dall'applicazione",
"ja_JP": "アプリケーションからの迅速な終了",
"ko_KR": "애플리케이션에서 빠른 종료",
"no_NO": "Rask avslutning av applikasjonen",
"pl_PL": "Szybkie wyjście z aplikacji",
"pt_BR": "Saída rápida do aplicativo",
"ru_RU": "Быстрый выход из приложения",
"sv_SE": "Snabb avslutning från applikationen",
"th_TH": "ออกจากแอปพลิเคชันอย่างรวดเร็ว",
"tr_TR": "Uygulamadan Hızlı Çıkış",
"uk_UA": "Швидкий вихід з програми",
"zh_CN": "快速退出应用程序",
"zh_TW": "快速退出應用程式"
}
},
{
"ID": "SettingsTabSystemMemoryManagerMode",
"Translations": {
@@ -1243,7 +1268,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "问答与指南",
"zh_TW": ""
}
},
@@ -4018,7 +4043,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "与 PC 日期和时间重新同步",
"zh_TW": ""
}
},
@@ -11047,6 +11072,81 @@
"zh_TW": "淺色"
}
},
{
"ID": "SettingsTabInputDisableExitHotKey",
"Translations": {
"ar_SA": "الخروج السريع معطل",
"de_DE": "Schneller Ausstieg deaktiviert",
"el_GR": "Η γρήγορη έξοδος είναι απενεργοποιημένη",
"en_US": "Quick exit disabled",
"es_ES": "Salida rápida desactivada",
"fr_FR": "Sortie rapide désactivée",
"he_IL": "יציאה מהירה מושבתת",
"it_IT": "Uscita rapida disabilitata",
"ja_JP": "クイック終了が無効です",
"ko_KR": "빠른 종료 비활성화됨",
"no_NO": "Rask avslutning er deaktivert",
"pl_PL": "Szybkie wyjście wyłączone",
"pt_BR": "Saída rápida desativada",
"ru_RU": "Быстрый выход выключен",
"sv_SE": "Snabb avslutning inaktiverad",
"th_TH": "ปิดใช้งานออกอย่างรวดเร็ว",
"tr_TR": "Hızlı çıkış devre dışı bırakıldı",
"uk_UA": "Швидкий вихід вимкнено",
"zh_CN": "快速退出已禁用",
"zh_TW": "快速退出已停用"
}
},
{
"ID": "SettingsTabInputHotkeyIsCloseApp",
"Translations": {
"ar_SA": "إغلاق التطبيق بالضغط على الزرين '+' و '-'.",
"de_DE": "App schließen mit den '+' und '-' Tasten.",
"el_GR": "Κλείσιμο της εφαρμογής με τα κουμπιά '+' και '-'.",
"en_US": "Close app by '+' and '-' buttons.",
"es_ES": "Cerrar la aplicación pulsando los botones '+' y '-'.",
"fr_FR": "Fermer l'application avec les boutons '+' et '-'.",
"he_IL": "סגור את האפליקציה בלחיצה על '+' ו-'-'.",
"it_IT": "Chiudi l'app premendo i pulsanti '+' e '-'.",
"ja_JP": "「+」と「-」ボタンを押してアプリを終了します。",
"ko_KR": "'+' 및 '-' 버튼을 눌러 앱을 종료합니다.",
"no_NO": "Lukk appen med '+' og '-' knappene.",
"pl_PL": "Zamknij aplikację przyciskiem '+' i '-'.",
"pt_BR": "Fechar o aplicativo pressionando os botões '+' e '-'.",
"ru_RU": "Закрыть приложение нажатием '+' и '-'.",
"sv_SE": "Stäng appen med '+' och '-' knapparna.",
"th_TH": "ปิดแอปโดยกดปุ่ม '+' และ '-'.",
"tr_TR": "'+' ve '-' düğmelerine basarak uygulamayı kapatın.",
"uk_UA": "Закрити додаток натисканням '+' та '-'.",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabInputHotkeyIsCloseGame",
"Translations": {
"ar_SA": "الخروج من اللعبة بالضغط على الزرين '+' و '-'.",
"de_DE": "Spiel beenden mit den '+' und '-' Tasten.",
"el_GR": "Έξοδος από το παιχνίδι με τα κουμπιά '+' και '-'.",
"en_US": "Exit game by '+' and '-' buttons.",
"es_ES": "Salir del juego pulsando los botones '+' y '-'.",
"fr_FR": "Quitter le jeu avec les boutons '+' et '-'.",
"he_IL": "יציאה מהמשחק בלחיצה על '+' ו-'-'.",
"it_IT": "Esci dal gioco premendo i pulsanti '+' e '-'.",
"ja_JP": "「+」と「-」ボタンを押してゲームを終了します。",
"ko_KR": "'+' 및 '-' 버튼을 눌러 게임을 종료합니다.",
"no_NO": "Avslutt spillet med '+' og '-' knappene.",
"pl_PL": "Wyjście z gry przyciskiem '+' i '-'.",
"pt_BR": "Sair do jogo pressionando os botões '+' e '-'.",
"ru_RU": "Выйти из игры нажатием '+' и '-'.",
"sv_SE": "Avsluta spelet med '+' och '-' knapparna.",
"th_TH": "ออกจากเกมโดยกดปุ่ม '+' และ '-'.",
"tr_TR": "'+' ve '-' düğmelerine basarak oyundan çıkın.",
"uk_UA": "Вийти з гри натисканням '+' та '-'.",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "ControllerSettingsConfigureGeneral",
"Translations": {
@@ -14193,7 +14293,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ryujinx — це емулятор для Nintendo Switch™.\nОтримуйте всі останні новини в нашому Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.",
"zh_CN": "",
"zh_CN": "Ryujinx 是一个 Nintendo Switch™ 模拟器。\n有兴趣做出贡献的开发者可以在我们的 GitHub 或 Discord 上了解更多信息。\n",
"zh_TW": ""
}
},
@@ -15147,6 +15247,31 @@
"zh_TW": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後觸控螢幕功能可能無法使用。\n\n如果不確定請保持關閉狀態。"
}
},
{
"ID": "SpecialExitTooltip",
"Translations": {
"ar_SA": "يقوم بتفعيل مفاتيح الاختصار 'زائد' و 'ناقص'.\nاضغط على زرّي زائد وناقص في نفس الوقت لتنفيذ إحدى العمليات:\n\n1) إغلاق التطبيق باستخدام مفاتيح الاختصار.\n2) الخروج من اللعبة دون إغلاق التطبيق.\n\nيعمل فقط مع أذرع التحكم.",
"de_DE": "Aktiviert die Hotkeys 'Plus' und 'Minus'.\nDrücken Sie gleichzeitig die Plus- und Minus-Tasten, um eine der folgenden Aktionen auszuführen:\n\n1) Schließt die Anwendung durch Drücken der Hotkeys.\n2) Beendet das Spiel, ohne die Anwendung zu schließen.\n\nFunktioniert nur mit Gamepads.",
"el_GR": "Ενεργοποιεί τα πλήκτρα πρόσβασης 'συν' και 'πλην'.\nΠατήστε ταυτόχρονα τα κουμπιά συν και πλην για μία από τις ενέργειες:\n\n1) Κλείνει την εφαρμογή πατώντας τα πλήκτρα πρόσβασης.\n2) Εξέρχεται από το παιχνίδι χωρίς να κλείσει η εφαρμογή.\n\nΛειτουργεί μόνο με gamepads.",
"en_US": "Activates the hot keys 'plus' and 'minus'.\nPress buttons plus and minus at the same time to get one of the actions:\n\n1) Closes the application by pressing the hot keys.\n2) Exits the game without closing the application.\n\nWorks only with gamepads.",
"es_ES": "Activa las teclas rápidas 'más' y 'menos'.\nPresiona los botones más y menos al mismo tiempo para realizar una de las siguientes acciones:\n\n1) Cierra la aplicación presionando las teclas rápidas.\n2) Salir del juego sin cerrar la aplicación.\n\nFunciona solo con mandos.",
"fr_FR": "Active les raccourcis 'plus' et 'moins'.\nAppuyez simultanément sur les boutons plus et moins pour effectuer l'une des actions suivantes :\n\n1) Ferme l'application en appuyant sur les raccourcis.\n2) Quitte le jeu sans fermer l'application.\n\nFonctionne uniquement avec les manettes.",
"he_IL": "מפעיל את המקשים הקצרים 'פלוס' ו-'מינוס'.\nלחץ על הכפתורים פלוס ומינוס בו זמנית כדי לבצע אחת מהפעולות:\n\n1) סוגר את היישום באמצעות המקשים הקצרים.\n2) יוצא מהמשחק מבלי לסגור את היישום.\n\nפועל רק עם בקרי משחק.",
"it_IT": "Attiva i tasti rapidi 'più' e 'meno'.\nPremere i pulsanti più e meno contemporaneamente per eseguire una delle seguenti azioni:\n\n1) Chiude l'applicazione premendo i tasti rapidi.\n2) Esce dal gioco senza chiudere l'applicazione.\n\nFunziona solo con i gamepad.",
"ja_JP": "ホットキー「プラス」と「マイナス」を有効化します。\nプラスとマイナスのボタンを同時に押して、次のいずれかの操作を実行します:\n\n1) ホットキーを押すことでアプリを閉じます。\n2) アプリを閉じずにゲームを終了します。\n\nゲームパッドでのみ動作します。",
"ko_KR": "'플러스' 및 '마이너스' 단축키를 활성화합니다.\n플러스 및 마이너스 버튼을 동시에 눌러 다음 작업 중 하나를 수행합니다:\n\n1) 단축키를 눌러 애플리케이션을 닫습니다.\n2) 애플리케이션을 닫지 않고 게임을 종료합니다.\n\n게임패드에서만 작동합니다.",
"no_NO": "Aktiverer hurtigtastene 'pluss' og 'minus'.\nTrykk på knappene pluss og minus samtidig for å utføre en av følgende handlinger:\n\n1) Lukker applikasjonen ved å trykke på hurtigtastene.\n2) Avslutter spillet uten å lukke applikasjonen.\n\nFungerer kun med spillkontroller.",
"pl_PL": "Aktywuje klawisze skrótu 'plus' i 'minus'.\nNaciśnij jednocześnie przyciski plus i minus, aby wykonać jedną z akcji:\n\n1) Zamknij aplikację, naciskając klawisze skrótu.\n2) Wyjdź z gry bez zamykania aplikacji.\n\nDziała tylko z gamepadami.",
"pt_BR": "Ativa as teclas de atalho 'mais' e 'menos'.\nPressione os botões mais e menos ao mesmo tempo para realizar uma das ações:\n\n1) Fecha o aplicativo pressionando as teclas de atalho.\n2) Sai do jogo sem fechar o aplicativo.\n\nFunciona apenas com gamepads.",
"ru_RU": "Активирует горячие клавиши 'плюс' и 'минус'.\nНажмите одновременно кнопки плюс и минус чтобы получить одно из действий:\n\n1) Закрывает приложение по нажатию горячих кнопок.\n2) Выходит из игры без закрытия приложения.\n\nРаботает только с геймпадами.",
"sv_SE": "Aktiverar snabbtangenterna 'plus' och 'minus'.\nTryck samtidigt på plus- och minusknapparna för att utföra en av följande åtgärder:\n\n1) Stänger applikationen med snabbtangenterna.\n2) Avslutar spelet utan att stänga applikationen.\n\nFungerar bara med spelkontroller.",
"th_TH": "เปิดใช้งานปุ่มลัด '+' และ '-'.\nกดปุ่ม '+' และ '-' พร้อมกันเพื่อทำการอย่างใดอย่างหนึ่ง:\n\n1) ปิดแอปพลิเคชันด้วยการกดปุ่มลัด\n2) ออกจากเกมโดยไม่ปิดแอปพลิเคชัน\n\nใช้งานได้เฉพาะกับจอยเกม",
"tr_TR": "'Artı' ve 'eksi' kısayol tuşlarını etkinleştirir.\nArtı ve eksi tuşlarına aynı anda basarak aşağıdaki işlemlerden birini gerçekleştirin:\n\n1) Kısayol tuşlarına basarak uygulamayı kapatır.\n2) Uygulamayı kapatmadan oyundan çıkar.\n\nYalnızca gamepad'lerle çalışır.",
"uk_UA": "Активує гарячі клавіші '+' та '-'.\nНатисніть одночасно кнопки плюс та мінус для виконання однієї з дій:\n\n1) Закриває додаток за допомогою гарячих клавіш.\n2) Виходить із гри без закриття додатка.\n\nПрацює лише з геймпадами.",
"zh_CN": "激活快捷键“加号”和“减号”。\n同时按下加号和减号按钮以执行以下操作之一\n\n1) 按快捷键关闭应用程序。\n2) 退出游戏而不关闭应用程序。\n\n仅适用于游戏手柄。",
"zh_TW": "啟用快捷鍵「加號」和「減號」。\n同時按下加號和減號按鈕以執行以下其中一項操作\n\n1) 按快捷鍵關閉應用程式。\n2) 離開遊戲而不關閉應用程式。\n\n僅適用於遊戲手柄。"
}
},
{
"ID": "RegionTooltip",
"Translations": {
@@ -17818,7 +17943,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "档案对话框",
"zh_TW": ""
}
},
@@ -19543,7 +19668,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Необрізаних {0} тайтл(ів)...",
"zh_CN": "",
"zh_CN": "正在精简 {0} 个游戏",
"zh_TW": ""
}
},
@@ -19718,7 +19843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Заголовок",
"zh_CN": "",
"zh_CN": "标题",
"zh_TW": ""
}
},
@@ -19743,7 +19868,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Економія місця",
"zh_CN": "",
"zh_CN": "节省空间",
"zh_TW": ""
}
},
@@ -19793,7 +19918,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Зшивання",
"zh_CN": "",
"zh_CN": "取消精简",
"zh_TW": ""
}
},
@@ -22597,6 +22722,31 @@
"zh_TW": ""
}
},
{
"ID": "CompatibilityListLastUpdated",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Last updated: {0}",
"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": "最后更新于: {0}",
"zh_TW": ""
}
},
{
"ID": "CompatibilityListWarning",
"Translations": {
@@ -22618,7 +22768,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "此兼容性列表可能包含过时的条目。\n不要只测试 \"进入游戏\" 状态的游戏。",
"zh_TW": ""
}
},
@@ -22643,7 +22793,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "正在搜索兼容性条目...",
"zh_TW": ""
}
},
@@ -22668,7 +22818,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "打开兼容性列表",
"zh_TW": ""
}
},
@@ -22693,7 +22843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "仅显示拥有的游戏",
"zh_TW": ""
}
},
@@ -22718,7 +22868,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可游玩",
"zh_TW": ""
}
},
@@ -22743,7 +22893,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "进入游戏",
"zh_TW": ""
}
},
@@ -22768,7 +22918,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "菜单",
"zh_TW": ""
}
},
@@ -22793,7 +22943,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "启动",
"zh_TW": ""
}
},
@@ -22818,7 +22968,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "什么都没有",
"zh_TW": ""
}
}

View File

@@ -1,4 +1,4 @@
using DiscordRPC;
using DiscordRPC;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Ava;

View File

@@ -350,11 +350,11 @@ namespace Ryujinx.Headless
{
return options.GraphicsBackend switch
{
GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet),
GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit),
GraphicsBackend.Metal => OperatingSystem.IsMacOS() ?
new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode, options.IgnoreControllerApplet) :
new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit) :
throw new Exception("Attempted to use Metal renderer on non-macOS platform!"),
_ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet)
_ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet, options.SpecialExit)
};
}

View File

@@ -23,8 +23,9 @@ namespace Ryujinx.Headless
AspectRatio aspectRatio,
bool enableMouse,
HideCursorMode hideCursorMode,
bool ignoreControllerApplet)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) { }
bool ignoreControllerApplet,
int specialExitEmulator)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator) { }
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL;

View File

@@ -118,8 +118,9 @@ namespace Ryujinx.Headless
AspectRatio aspectRatio,
bool enableMouse,
HideCursorMode hideCursorMode,
bool ignoreControllerApplet)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet)
bool ignoreControllerApplet,
int specialExitEmulator)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator)
{
_glLogLevel = glLogLevel;
}

View File

@@ -150,7 +150,10 @@ namespace Ryujinx.Headless
if (NeedsOverride(nameof(IgnoreControllerApplet)))
IgnoreControllerApplet = configurationState.IgnoreApplet;
if (NeedsOverride(nameof(SpecialExit)))
SpecialExit = configurationState.Hid.SpecialExitEmulator;
return;
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
@@ -274,6 +277,9 @@ namespace Ryujinx.Headless
[Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")]
public bool EnableMouse { get; set; }
[Option("enable-press-hotkeys-to-exit", Required = false, Default = 0, HelpText = "press the minus and plus buttons to: 0 -disable, 1 - exit app, 2 - exit game.")]
public int SpecialExit { get; set; }
[Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")]
public HideCursorMode HideCursorMode { get; set; }
@@ -414,6 +420,7 @@ namespace Ryujinx.Headless
[Option("ignore-controller-applet", Required = false, Default = false, HelpText = "Enable ignoring the controller applet when your game loses connection to your controller.")]
public bool IgnoreControllerApplet { get; set; }
// Values
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]

View File

@@ -18,8 +18,9 @@ namespace Ryujinx.Headless
AspectRatio aspectRatio,
bool enableMouse,
HideCursorMode hideCursorMode,
bool ignoreControllerApplet)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet)
bool ignoreControllerApplet,
int specialExitEmulator)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet, specialExitEmulator)
{
_glLogLevel = glLogLevel;
}

View File

@@ -88,6 +88,7 @@ namespace Ryujinx.Headless
private readonly AspectRatio _aspectRatio;
private readonly bool _enableMouse;
private readonly int _specialExitEmulator;
private readonly bool _ignoreControllerApplet;
public WindowBase(
@@ -96,7 +97,8 @@ namespace Ryujinx.Headless
AspectRatio aspectRatio,
bool enableMouse,
HideCursorMode hideCursorMode,
bool ignoreControllerApplet)
bool ignoreControllerApplet,
int specialExitEmulator)
{
MouseDriver = new SDL2MouseDriver(hideCursorMode);
_inputManager = inputManager;
@@ -112,6 +114,7 @@ namespace Ryujinx.Headless
_gpuDoneEvent = new ManualResetEvent(false);
_aspectRatio = aspectRatio;
_enableMouse = enableMouse;
_specialExitEmulator = specialExitEmulator;
_ignoreControllerApplet = ignoreControllerApplet;
HostUITheme = new HeadlessHostUiTheme();

View File

@@ -30,6 +30,11 @@ namespace Ryujinx.Ava.Input
public readonly Key From = from;
}
public bool SpecialExit()
{
return false;
}
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{
_buttonsUserMapping = [];

View File

@@ -13,6 +13,11 @@ namespace Ryujinx.Ava.Input
public string Id => "0";
public string Name => "AvaloniaMouse";
public bool SpecialExit()
{
return false;
}
public bool IsConnected => true;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public bool[] Buttons => _driver.PressedButtons;

View File

@@ -133,12 +133,13 @@
Spacing="5">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding TimePlayedString}"
Text="{Binding LastPlayedString}"
TextAlignment="End"
TextWrapping="Wrap" />
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding LastPlayedString}"
Text="{Binding TimePlayedString}"
IsVisible="{Binding HasPlayedPreviously}"
TextAlignment="End"
TextWrapping="Wrap" />
<TextBlock

View File

@@ -1,152 +1,53 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
namespace Ryujinx.Ava.UI.Models.Input
{
public class HotkeyConfig : BaseModel
public partial class HotkeyConfig : BaseModel
{
private Key _toggleVSyncMode;
public Key ToggleVSyncMode
{
get => _toggleVSyncMode;
set
{
_toggleVSyncMode = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _toggleVSyncMode;
private Key _screenshot;
public Key Screenshot
{
get => _screenshot;
set
{
_screenshot = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _screenshot;
private Key _showUI;
public Key ShowUI
{
get => _showUI;
set
{
_showUI = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _showUI;
private Key _pause;
public Key Pause
{
get => _pause;
set
{
_pause = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _pause;
private Key _toggleMute;
public Key ToggleMute
{
get => _toggleMute;
set
{
_toggleMute = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _toggleMute;
private Key _resScaleUp;
public Key ResScaleUp
{
get => _resScaleUp;
set
{
_resScaleUp = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _resScaleUp;
private Key _resScaleDown;
public Key ResScaleDown
{
get => _resScaleDown;
set
{
_resScaleDown = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _resScaleDown;
private Key _volumeUp;
public Key VolumeUp
{
get => _volumeUp;
set
{
_volumeUp = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _volumeUp;
private Key _volumeDown;
public Key VolumeDown
{
get => _volumeDown;
set
{
_volumeDown = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _volumeDown;
private Key _customVSyncIntervalIncrement;
public Key CustomVSyncIntervalIncrement
{
get => _customVSyncIntervalIncrement;
set
{
_customVSyncIntervalIncrement = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _customVSyncIntervalIncrement;
private Key _customVSyncIntervalDecrement;
public Key CustomVSyncIntervalDecrement
{
get => _customVSyncIntervalDecrement;
set
{
_customVSyncIntervalDecrement = value;
OnPropertyChanged();
}
}
[ObservableProperty] private Key _customVSyncIntervalDecrement;
public HotkeyConfig(KeyboardHotkeys config)
{
if (config != null)
{
ToggleVSyncMode = config.ToggleVSyncMode;
Screenshot = config.Screenshot;
ShowUI = config.ShowUI;
Pause = config.Pause;
ToggleMute = config.ToggleMute;
ResScaleUp = config.ResScaleUp;
ResScaleDown = config.ResScaleDown;
VolumeUp = config.VolumeUp;
VolumeDown = config.VolumeDown;
CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement;
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
}
if (config == null)
return;
ToggleVSyncMode = config.ToggleVSyncMode;
Screenshot = config.Screenshot;
ShowUI = config.ShowUI;
Pause = config.Pause;
ToggleMute = config.ToggleMute;
ResScaleUp = config.ResScaleUp;
ResScaleDown = config.ResScaleDown;
VolumeUp = config.VolumeUp;
VolumeDown = config.VolumeDown;
CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement;
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
}
public KeyboardHotkeys GetConfig()
{
var config = new KeyboardHotkeys
public KeyboardHotkeys GetConfig() =>
new()
{
ToggleVSyncMode = ToggleVSyncMode,
Screenshot = Screenshot,
@@ -160,8 +61,5 @@ namespace Ryujinx.Ava.UI.Models.Input
CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
};
return config;
}
}
}

View File

@@ -1,21 +1,13 @@
using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class ControllerInputViewModel : BaseModel
public partial class ControllerInputViewModel : BaseModel
{
private GamepadInputConfig _config;
public GamepadInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
[ObservableProperty] private GamepadInputConfig _config;
private bool _isLeft;
public bool IsLeft
@@ -43,16 +35,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
[ObservableProperty] private SvgImage _image;
public readonly InputViewModel ParentModel;

View File

@@ -1,9 +1,8 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Svg.Skia;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
@@ -32,7 +31,7 @@ using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class InputViewModel : BaseModel, IDisposable
public partial class InputViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg";
@@ -48,8 +47,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _controller;
private string _controllerImage;
private int _device;
private object _configViewModel;
private string _profileName;
[ObservableProperty] private object _configViewModel;
[ObservableProperty] private string _profileName;
private bool _isLoaded;
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -73,17 +72,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
public object ConfigViewModel
{
get => _configViewModel;
set
{
_configViewModel = value;
OnPropertyChanged();
}
}
public PlayerIndex PlayerIdChoose
{
get => _playerIdChoose;
@@ -200,16 +188,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
}
public string ProfileName
{
get => _profileName; set
{
_profileName = value;
OnPropertyChanged();
}
}
public int Device
{
get => _device;

View File

@@ -1,20 +1,12 @@
using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using Ryujinx.Ava.UI.Models.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class KeyboardInputViewModel : BaseModel
public partial class KeyboardInputViewModel : BaseModel
{
private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
[ObservableProperty] private KeyboardInputConfig _config;
private bool _isLeft;
public bool IsLeft
@@ -42,16 +34,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
[ObservableProperty] private SvgImage _image;
public readonly InputViewModel ParentModel;

View File

@@ -1,93 +1,23 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class MotionInputViewModel : BaseModel
public partial class MotionInputViewModel : BaseModel
{
private int _slot;
public int Slot
{
get => _slot;
set
{
_slot = value;
OnPropertyChanged();
}
}
[ObservableProperty] private int _slot;
private int _altSlot;
public int AltSlot
{
get => _altSlot;
set
{
_altSlot = value;
OnPropertyChanged();
}
}
[ObservableProperty] private int _altSlot;
private string _dsuServerHost;
public string DsuServerHost
{
get => _dsuServerHost;
set
{
_dsuServerHost = value;
OnPropertyChanged();
}
}
[ObservableProperty] private string _dsuServerHost;
private int _dsuServerPort;
public int DsuServerPort
{
get => _dsuServerPort;
set
{
_dsuServerPort = value;
OnPropertyChanged();
}
}
[ObservableProperty] private int _dsuServerPort;
private bool _mirrorInput;
public bool MirrorInput
{
get => _mirrorInput;
set
{
_mirrorInput = value;
OnPropertyChanged();
}
}
[ObservableProperty] private bool _mirrorInput;
private int _sensitivity;
public int Sensitivity
{
get => _sensitivity;
set
{
_sensitivity = value;
OnPropertyChanged();
}
}
[ObservableProperty] private int _sensitivity;
private double _gryoDeadzone;
public double GyroDeadzone
{
get => _gryoDeadzone;
set
{
_gryoDeadzone = value;
OnPropertyChanged();
}
}
[ObservableProperty] private double _gyroDeadzone;
private bool _enableCemuHookMotion;
public bool EnableCemuHookMotion
{
get => _enableCemuHookMotion;
set
{
_enableCemuHookMotion = value;
OnPropertyChanged();
}
}
[ObservableProperty] private bool _enableCemuHookMotion;
}
}

View File

@@ -1,27 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class RumbleInputViewModel : BaseModel
public partial class RumbleInputViewModel : BaseModel
{
private float _strongRumble;
public float StrongRumble
{
get => _strongRumble;
set
{
_strongRumble = value;
OnPropertyChanged();
}
}
[ObservableProperty] private float _strongRumble;
private float _weakRumble;
public float WeakRumble
{
get => _weakRumble;
set
{
_weakRumble = value;
OnPropertyChanged();
}
}
[ObservableProperty] private float _weakRumble;
}
}

View File

@@ -743,7 +743,7 @@ namespace Ryujinx.Ava.UI.ViewModels
.Sort(GetComparer())
#pragma warning disable MVVMTK0034
.Bind(out _appsObservableList)
#pragma warning enable MVVMTK0034
#pragma warning restore MVVMTK0034
.AsObservableList();
OnPropertyChanged(nameof(AppsObservableList));
@@ -1049,6 +1049,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void InitializeGame()
{
RendererHostControl.WindowCreated += RendererHost_Created;
AppHost.StatusUpdatedEvent += Update_StatusBar;
@@ -1058,7 +1059,13 @@ namespace Ryujinx.Ava.UI.ViewModels
AppHost?.Start();
if (AppHost?.IsSpecialExit() == true)
{
Window.ForceExit();
}
AppHost?.DisposeContext();
}
private async Task HandleRelaunch()

View File

@@ -128,6 +128,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
public int EnableSpecialExit { get; set; }
public VSyncMode VSyncMode
{
get => _vSyncMode;
@@ -259,6 +260,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public int OpenglDebugLevel { get; set; }
public int MemoryMode { get; set; }
public int BaseStyleIndex { get; set; }
public int GraphicsBackendIndex
{
get => _graphicsBackendIndex;
@@ -511,6 +514,13 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDockedMode = config.System.EnableDockedMode;
EnableKeyboard = config.Hid.EnableKeyboard;
EnableMouse = config.Hid.EnableMouse;
EnableSpecialExit = config.Hid.SpecialExitEmulator.Value switch
{
0 => 0, // "Hotkey 'Exit' is Disabled"
1 => 1, // "Close app. by hotkey"
2 => 2, // "Close game by hotkey"
_ => 0
};
// Keyboard Hotkeys
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
@@ -618,6 +628,13 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableDockedMode.Value = EnableDockedMode;
config.Hid.EnableKeyboard.Value = EnableKeyboard;
config.Hid.EnableMouse.Value = EnableMouse;
config.Hid.SpecialExitEmulator.Value = EnableSpecialExit switch
{
0 => 0, // "Hotkey 'Exit' is Disabled",
1 => 1, // "Close app. by hotkey",
2 => 2, // "Close game by hotkey",
_ => 0
};
// Keyboard Hotkeys
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -58,6 +58,20 @@
<TextBlock
Text="{ext:Locale SettingsTabInputDirectMouseAccess}" />
</CheckBox>
<ComboBox SelectedIndex="{Binding EnableSpecialExit}"
ToolTip.Tip="{ext:Locale SpecialExitTooltip}"
HorizontalContentAlignment="Left"
MinWidth="160">
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabInputDisableExitHotKey}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabInputHotkeyIsCloseApp}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabInputHotkeyIsCloseGame}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</Grid>

View File

@@ -45,6 +45,7 @@ namespace Ryujinx.Ava.UI.Windows
internal readonly AvaHostUIHandler UiHandler;
private bool _isLoading;
private bool _isExitWithoutConfirm = false;
private bool _applicationsLoadedOnce;
private UserChannelPersistence _userChannelPersistence;
@@ -164,7 +165,7 @@ namespace Ryujinx.Ava.UI.Windows
});
}
private void ApplicationLibrary_LdnGameDataReceived(object sender, LdnGameDataReceivedEventArgs e)
private void ApplicationLibrary_LdnGameDataReceived(LdnGameDataReceivedEventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
@@ -408,13 +409,10 @@ namespace Ryujinx.Ava.UI.Windows
{
StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
ApplicationGrid.DataContext = ApplicationList.DataContext = ViewModel;
ApplicationGrid.ApplicationOpened += Application_Opened;
ApplicationGrid.DataContext = ViewModel;
ApplicationList.ApplicationOpened += Application_Opened;
ApplicationList.DataContext = ViewModel;
}
private void SetWindowSizePosition()
@@ -574,11 +572,11 @@ namespace Ryujinx.Ava.UI.Windows
protected override void OnClosing(WindowClosingEventArgs e)
{
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit && !_isExitWithoutConfirm)
{
e.Cancel = true;
ConfirmExit();
ConfirmExit();
return;
}
@@ -619,6 +617,12 @@ namespace Ryujinx.Ava.UI.Windows
base.OnClosing(e);
}
public void ForceExit()
{
_isExitWithoutConfirm = true;
Close();
}
private void ConfirmExit()
{
Dispatcher.UIThread.InvokeAsync(async () =>

View File

@@ -1,4 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
@@ -23,6 +25,11 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public SettingsWindow()

View File

@@ -37,6 +37,8 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public bool HasPlayedPreviously => TimePlayedString != string.Empty;
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
public Language DesiredLanguage { get; set; }
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
public event EventHandler<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
public event Action<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
public readonly IObservableCache<ApplicationData, ulong> Applications;
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
@@ -779,7 +779,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
using HttpClient httpClient = new HttpClient();
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{
LdnData = ldnGameDataArray
});
@@ -787,7 +787,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
catch (Exception ex)
{
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});
@@ -795,7 +795,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
}
else
{
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});

View File

@@ -1,4 +1,5 @@
using Gommon;
using Humanizer;
using nietras.SeparatedValues;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Logging;
@@ -56,7 +57,7 @@ namespace Ryujinx.Ava.Utilities.Compat
? titleIdRow
: default(Optional<string>);
GameName = ColStr(row[indices.GameName]).Trim().Trim('"');
GameName = ColStr(row[indices.GameName]);
Labels = ColStr(row[indices.Labels]).Split(';');
Status = ColStr(row[indices.Status]).ToLower() switch
@@ -83,12 +84,14 @@ namespace Ryujinx.Ava.Utilities.Compat
public LocaleKeys? Status { get; }
public DateTime LastUpdated { get; }
public string LocalizedLastUpdated =>
LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
public string FormattedTitleId => TitleId
.OrElse(new string(' ', 16));
public string FormattedIssueLabels => Labels
.Where(it => !it.StartsWithIgnoreCase("status"))
.Select(FormatLabelName)
.JoinToString(", ");
@@ -97,7 +100,9 @@ namespace Ryujinx.Ava.Utilities.Compat
StringBuilder sb = new("CompatibilityEntry: {");
sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
sb.Append($"{nameof(TitleId)}={TitleId}, ");
sb.Append($"{nameof(Labels)}=\"{Labels}\", ");
sb.Append($"{nameof(Labels)}={
Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
}, ");
sb.Append($"{nameof(Status)}=\"{Status}\", ");
sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
sb.Append('}');

View File

@@ -44,8 +44,11 @@
ItemsSource="{Binding CurrentEntries}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:CompatibilityEntry}">
<Grid Width="750" ColumnDefinitions="Auto,Auto,Auto,*"
Margin="5">
<Grid Width="750"
Margin="5"
ColumnDefinitions="Auto,Auto,Auto,*"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedLastUpdated}">
<TextBlock Grid.Column="0"
Text="{Binding GameName}"
Width="320"

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 59;
public const int CurrentVersion = 60;
/// <summary>
/// Version of the configuration file format
@@ -366,6 +366,12 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public bool EnableMouse { get; set; }
/// <summary>
/// Allows you to choose one of several behaviors when pressing hotkeys:
/// 0 - Do nothing, 1 - Close the emulator application, 2 - Exit the game.
/// </summary>
public int SpecialExitEmulator { get; set; }
/// <summary>
/// Hotkey Keyboard Bindings
/// </summary>

View File

@@ -136,6 +136,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse;
Hid.SpecialExitEmulator.Value = cff.SpecialExitEmulator;
Hid.Hotkeys.Value = cff.Hotkeys;
Hid.InputConfig.Value = cff.InputConfig ?? [];
@@ -414,6 +415,10 @@ namespace Ryujinx.Ava.Utilities.Configuration
// This was accidentally enabled by default when it was PRed. That is not what we want,
// so as a compromise users who want to use it will simply need to re-enable it once after updating.
cff.IgnoreApplet = false;
}),
(60, static cff =>
{
cff.SpecialExitEmulator = 0;
})
);
}

View File

@@ -420,6 +420,13 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public ReactiveObject<bool> EnableMouse { get; private set; }
/// <summary>
/// Allows you to choose one of several behaviors when pressing hotkeys:
/// 0 - Do nothing, 1 - Close the emulator application, 2 - Exit the game.
/// </summary>
public ReactiveObject<int> SpecialExitEmulator { get; private set; }
/// <summary>
/// Hotkey Keyboard Bindings
/// </summary>
@@ -436,6 +443,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
{
EnableKeyboard = new ReactiveObject<bool>();
EnableMouse = new ReactiveObject<bool>();
SpecialExitEmulator = new ReactiveObject<int>();
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
InputConfig = new ReactiveObject<List<InputConfig>>();
}

View File

@@ -128,6 +128,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
ShowConsole = UI.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse,
SpecialExitEmulator = Hid.SpecialExitEmulator,
Hotkeys = Hid.Hotkeys,
KeyboardConfig = [],
ControllerConfig = [],
@@ -241,6 +242,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.WindowStartup.WindowMaximized.Value = false;
Hid.EnableKeyboard.Value = false;
Hid.EnableMouse.Value = false;
Hid.SpecialExitEmulator.Value = 0;
Hid.Hotkeys.Value = new KeyboardHotkeys
{
ToggleVSyncMode = Key.F1,

View File

@@ -1,3 +1,5 @@
using Humanizer;
using Humanizer.Localisation;
using Ryujinx.Ava.Common.Locale;
using System;
using System.Globalization;
@@ -31,7 +33,7 @@ namespace Ryujinx.Ava.Utilities
Gigabytes = 9,
Terabytes = 10,
Petabytes = 11,
Exabytes = 12,
Exabytes = 12
}
private const double SizeBase10 = 1000;
@@ -48,22 +50,24 @@ namespace Ryujinx.Ava.Utilities
public static string FormatTimeSpan(TimeSpan? timeSpan)
{
if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
{
// Game was never played
return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
}
return string.Empty;
if (timeSpan.Value.TotalSeconds < 60)
return timeSpan.Value.Humanize(1,
countEmptyUnits: false,
maxUnit: TimeUnit.Second,
minUnit: TimeUnit.Second);
if (timeSpan.Value.TotalDays < 1)
{
// Game was played for less than a day
return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture);
}
// Game was played for more than a day
TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days));
string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture);
return $"{timeSpan.Value.Days}d, {onlyTimeString}";
if (timeSpan.Value.TotalMinutes < 60)
return timeSpan.Value.Humanize(1,
countEmptyUnits: false,
maxUnit: TimeUnit.Minute,
minUnit: TimeUnit.Minute);
return timeSpan.Value.Humanize(1,
countEmptyUnits: false,
maxUnit: TimeUnit.Hour,
minUnit: TimeUnit.Hour);
}
/// <summary>