Compare commits
No commits in common. "52e0c23d0610cef8391446b2e4218033f37f1fe8" and "4ebc318da5ec7b15cc614f307551d622075b716c" have entirely different histories.
52e0c23d06
...
4ebc318da5
57 changed files with 281 additions and 1915 deletions
|
|
@ -3,13 +3,13 @@
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="11.3.12" />
|
<PackageVersion Include="Avalonia" Version="11.3.6" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
|
||||||
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
|
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
|
||||||
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
|
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
|
||||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
|
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||||
<PackageVersion Include="DynamicData" Version="9.4.1" />
|
<PackageVersion Include="DynamicData" Version="9.4.1" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" />
|
<PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" />
|
||||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||||
|
|
@ -30,7 +30,6 @@
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="Melanchall.DryWetMIDI" Version="8.0.1" />
|
|
||||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
|
@ -42,7 +41,7 @@
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.126" />
|
||||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
||||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
||||||
<PackageVersion Include="Gommon" Version="2.8.0.1" />
|
<PackageVersion Include="Gommon" Version="2.8.0.1" />
|
||||||
|
|
|
||||||
14
Ryujinx.sln
14
Ryujinx.sln
|
|
@ -59,8 +59,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "src\Ryujin
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL3", "src\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj", "{D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL3", "src\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj", "{D728444C-3D1F-4A0E-B4C9-5C9375D47EA3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.Midi", "src\Ryujinx.Input.Midi\Ryujinx.Input.Midi.csproj", "{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
|
||||||
|
|
@ -394,18 +392,6 @@ Global
|
||||||
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x64.Build.0 = Release|Any CPU
|
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.ActiveCfg = Release|Any CPU
|
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.Build.0 = Release|Any CPU
|
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{2E49F4FB-4ABA-49A5-9292-8C969F22F44E}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
|
|
||||||
|
|
@ -575,31 +575,6 @@
|
||||||
"zh_TW": "停止模擬"
|
"zh_TW": "停止模擬"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ID": "MenuBarOptionsRestartEmulation",
|
|
||||||
"Translations": {
|
|
||||||
"ar_SA": "",
|
|
||||||
"de_DE": "",
|
|
||||||
"el_GR": "",
|
|
||||||
"en_US": "Restart Emulation",
|
|
||||||
"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": "MenuBarOptionsSettings",
|
"ID": "MenuBarOptionsSettings",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
|
@ -11325,31 +11300,6 @@
|
||||||
"zh_TW": "刪除"
|
"zh_TW": "刪除"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ID": "UserProfilesSave",
|
|
||||||
"Translations": {
|
|
||||||
"ar_SA": "",
|
|
||||||
"de_DE": "Speichern",
|
|
||||||
"el_GR": "",
|
|
||||||
"en_US": "Save",
|
|
||||||
"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": "UserProfilesClose",
|
"ID": "UserProfilesClose",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
|
@ -24901,4 +24851,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
Invalid,
|
Invalid,
|
||||||
WindowKeyboard,
|
WindowKeyboard,
|
||||||
Midi,
|
|
||||||
GamepadSDL2, //backcompat
|
GamepadSDL2, //backcompat
|
||||||
GamepadSDL3,
|
GamepadSDL3,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
|
|
@ -8,7 +7,6 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
[JsonSerializable(typeof(InputConfig))]
|
[JsonSerializable(typeof(InputConfig))]
|
||||||
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
|
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
|
||||||
[JsonSerializable(typeof(StandardMidiInputConfig))]
|
|
||||||
[JsonSerializable(typeof(StandardControllerInputConfig))]
|
[JsonSerializable(typeof(StandardControllerInputConfig))]
|
||||||
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
|
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -59,7 +58,6 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
return backendType switch
|
return backendType switch
|
||||||
{
|
{
|
||||||
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig),
|
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig),
|
||||||
InputBackendType.Midi => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardMidiInputConfig),
|
|
||||||
InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
|
InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
|
||||||
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
||||||
};
|
};
|
||||||
|
|
@ -72,9 +70,6 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
case InputBackendType.WindowKeyboard:
|
case InputBackendType.WindowKeyboard:
|
||||||
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig);
|
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig);
|
||||||
break;
|
break;
|
||||||
case InputBackendType.Midi:
|
|
||||||
JsonSerializer.Serialize(writer, value as StandardMidiInputConfig, _serializerContext.StandardMidiInputConfig);
|
|
||||||
break;
|
|
||||||
case InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3:
|
case InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3:
|
||||||
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig);
|
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid.Midi
|
|
||||||
{
|
|
||||||
public struct MidiBinding : IEquatable<MidiBinding>
|
|
||||||
{
|
|
||||||
public MidiBindingKind Kind { get; set; }
|
|
||||||
public byte Number { get; set; }
|
|
||||||
public byte Channel { get; set; }
|
|
||||||
public byte Threshold { get; set; }
|
|
||||||
|
|
||||||
public readonly bool IsUnbound => Kind == MidiBindingKind.Unbound;
|
|
||||||
|
|
||||||
public readonly bool Equals(MidiBinding other)
|
|
||||||
{
|
|
||||||
return Kind == other.Kind &&
|
|
||||||
Number == other.Number &&
|
|
||||||
Channel == other.Channel &&
|
|
||||||
Threshold == other.Threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly bool Equals(object obj)
|
|
||||||
{
|
|
||||||
return obj is MidiBinding other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine((byte)Kind, Number, Channel, Threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(MidiBinding left, MidiBinding right)
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(MidiBinding left, MidiBinding right)
|
|
||||||
{
|
|
||||||
return !left.Equals(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid.Midi
|
|
||||||
{
|
|
||||||
[JsonConverter(typeof(JsonStringEnumConverter<MidiBindingKind>))]
|
|
||||||
public enum MidiBindingKind : byte
|
|
||||||
{
|
|
||||||
Unbound,
|
|
||||||
Note,
|
|
||||||
ControlChange,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid.Midi
|
|
||||||
{
|
|
||||||
public class StandardMidiInputConfig : GenericKeyboardInputConfig<MidiBinding> { }
|
|
||||||
}
|
|
||||||
|
|
@ -22,11 +22,10 @@ namespace Ryujinx.Common.Utilities
|
||||||
}
|
}
|
||||||
|
|
||||||
// "dumpable" attribute of the calling process
|
// "dumpable" attribute of the calling process
|
||||||
private const int PR_GET_DUMPABLE = 3;
|
|
||||||
private const int PR_SET_DUMPABLE = 4;
|
private const int PR_SET_DUMPABLE = 4;
|
||||||
|
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
[DllImport("libc", SetLastError = true)]
|
||||||
private static partial int prctl(int option, int arg2);
|
private static extern int prctl(int option, int arg2);
|
||||||
|
|
||||||
public static void SetCoreDumpable(bool dumpable)
|
public static void SetCoreDumpable(bool dumpable)
|
||||||
{
|
{
|
||||||
|
|
@ -37,13 +36,5 @@ namespace Ryujinx.Common.Utilities
|
||||||
Debug.Assert(result == 0);
|
Debug.Assert(result == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the below line to display dumpable status in the console:
|
|
||||||
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
|
|
||||||
public static bool IsCoreDumpable()
|
|
||||||
{
|
|
||||||
int result = prctl(PR_GET_DUMPABLE, 0);
|
|
||||||
return result == 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -488,8 +488,6 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
if (keyPaths.Length is 0)
|
if (keyPaths.Length is 0)
|
||||||
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
|
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
|
||||||
|
|
||||||
List<string> failedFiles = new();
|
|
||||||
|
|
||||||
foreach (string filePath in keyPaths)
|
foreach (string filePath in keyPaths)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -499,18 +497,15 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, e.Message);
|
Logger.Error?.Print(LogClass.Application, e.Message);
|
||||||
failedFiles.Add(Path.GetFileName(filePath));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
|
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
|
||||||
|
|
||||||
File.Copy(filePath, destPath, true);
|
if (File.Exists(destPath))
|
||||||
}
|
File.Delete(destPath);
|
||||||
|
|
||||||
if (failedFiles.Count > 0)
|
File.Copy(filePath, destPath, true);
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -523,6 +518,8 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
FileInfo info = new(keysSource);
|
FileInfo info = new(keysSource);
|
||||||
|
|
||||||
|
using FileStream file = File.OpenRead(keysSource);
|
||||||
|
|
||||||
if (info.Extension is not ".keys")
|
if (info.Extension is not ".keys")
|
||||||
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
|
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
|
||||||
|
|
||||||
|
|
@ -537,6 +534,10 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
|
|
||||||
string dest = Path.Combine(installDirectory, info.Name);
|
string dest = Path.Combine(installDirectory, info.Name);
|
||||||
|
|
||||||
|
if (File.Exists(dest))
|
||||||
|
File.Delete(dest);
|
||||||
|
|
||||||
|
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
|
||||||
File.Copy(keysSource, dest, true);
|
File.Copy(keysSource, dest, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1058,7 +1059,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AreKeysAlreadyPresent(string pathToCheck)
|
public static bool AreKeysAlredyPresent(string pathToCheck)
|
||||||
{
|
{
|
||||||
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
|
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
|
||||||
foreach (string file in fileNames)
|
foreach (string file in fileNames)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
_activeCount = 0;
|
_activeCount = 0;
|
||||||
|
|
||||||
JoyHold = NpadJoyHoldType.Vertical;
|
JoyHold = NpadJoyHoldType.Vertical;
|
||||||
SixAxisActive = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
|
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
|
||||||
|
|
@ -581,29 +580,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
|
|
||||||
return needUpdateRight;
|
return needUpdateRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool isAtRest(int playerNumber)
|
|
||||||
{
|
|
||||||
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
|
|
||||||
|
|
||||||
if (currentNpad.StyleSet == NpadStyleTag.None)
|
|
||||||
{
|
|
||||||
return true; // it will always be at rest because it cannot move.
|
|
||||||
}
|
|
||||||
|
|
||||||
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
|
|
||||||
|
|
||||||
float acceleration = Math.Abs(storage.Acceleration.X)
|
|
||||||
+ Math.Abs(storage.Acceleration.Y)
|
|
||||||
+ Math.Abs(storage.Acceleration.Z);
|
|
||||||
|
|
||||||
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
|
|
||||||
+ Math.Abs(storage.AngularVelocity.Y)
|
|
||||||
+ Math.Abs(storage.AngularVelocity.Z);
|
|
||||||
|
|
||||||
// TODO: check against config deadzone and add sensitivity setting
|
|
||||||
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
|
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -602,33 +602,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
}
|
}
|
||||||
|
|
||||||
[CommandCmif(82)]
|
[CommandCmif(82)]
|
||||||
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
|
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
|
||||||
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
|
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
|
||||||
{
|
{
|
||||||
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
int sixAxisSensorHandle = context.RequestData.ReadInt32();
|
||||||
|
|
||||||
// 4 byte struct w/ 4-byte alignment
|
|
||||||
|
|
||||||
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
|
|
||||||
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
|
|
||||||
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
|
|
||||||
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
|
|
||||||
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
|
|
||||||
|
|
||||||
// 32bit sign extension padding -> if = 0, + offset, else - offset
|
|
||||||
|
|
||||||
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
|
|
||||||
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
|
|
||||||
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
|
|
||||||
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
|
|
||||||
|
|
||||||
context.RequestData.BaseStream.Position += 4; // Padding
|
context.RequestData.BaseStream.Position += 4; // Padding
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
long appletResourceUserId = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
|
bool isAtRest = true;
|
||||||
// We currently do not support stopping or starting SixAxisTracking.
|
|
||||||
|
context.ResponseData.Write(isAtRest);
|
||||||
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -643,7 +629,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
|
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
|
||||||
public ISslService(ServiceCtx context) { }
|
public ISslService(ServiceCtx context) { }
|
||||||
|
|
||||||
[CommandCmif(0)]
|
[CommandCmif(0)]
|
||||||
// CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext>
|
// CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext>
|
||||||
public ResultCode CreateContext(ServiceCtx context)
|
public ResultCode CreateContext(ServiceCtx context)
|
||||||
{
|
{
|
||||||
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
|
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
|
||||||
|
|
@ -126,18 +126,14 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
|
||||||
}
|
}
|
||||||
|
|
||||||
[CommandCmif(100)]
|
[CommandCmif(100)]
|
||||||
// CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContextForSystem>
|
// CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64)
|
||||||
public ResultCode CreateContextForSystem(ServiceCtx context)
|
public ResultCode CreateContextForSystem(ServiceCtx context)
|
||||||
{
|
{
|
||||||
|
ulong pid = context.RequestData.ReadUInt64();
|
||||||
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
|
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
|
||||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
|
||||||
ulong pidPlaceholder = context.RequestData.ReadUInt64();
|
ulong pidPlaceholder = context.RequestData.ReadUInt64();
|
||||||
#pragma warning restore IDE0059
|
|
||||||
|
|
||||||
// Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class.
|
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder });
|
||||||
MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,5 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||||
SimplifiedChinese,
|
SimplifiedChinese,
|
||||||
TraditionalChinese,
|
TraditionalChinese,
|
||||||
BrazilianPortuguese,
|
BrazilianPortuguese,
|
||||||
Polish,
|
|
||||||
Thai,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||||
"es-419",
|
"es-419",
|
||||||
"zh-Hans",
|
"zh-Hans",
|
||||||
"zh-Hant",
|
"zh-Hant",
|
||||||
"pt-BR",
|
"pt-BR"
|
||||||
"pl",
|
|
||||||
"th"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
internal long DesiredKeyboardLayout { get; private set; }
|
internal long DesiredKeyboardLayout { get; private set; }
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,5 @@ namespace Ryujinx.HLE.HOS.SystemState
|
||||||
TraditionalChinese,
|
TraditionalChinese,
|
||||||
SimplifiedChinese,
|
SimplifiedChinese,
|
||||||
BrazilianPortuguese,
|
BrazilianPortuguese,
|
||||||
Polish,
|
|
||||||
Thai,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,12 @@
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Sdk.Ns
|
namespace Ryujinx.Horizon.Sdk.Ns
|
||||||
{
|
{
|
||||||
public struct ApplicationControlProperty
|
public struct ApplicationControlProperty
|
||||||
{
|
{
|
||||||
/// <summary>
|
public Array16<ApplicationTitle> Title;
|
||||||
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
|
|
||||||
/// </summary>
|
|
||||||
public Array16<ApplicationTitle> TitleBlock;
|
|
||||||
public Array37<byte> Isbn;
|
public Array37<byte> Isbn;
|
||||||
public StartupUserAccountValue StartupUserAccount;
|
public StartupUserAccountValue StartupUserAccount;
|
||||||
public UserAccountSwitchLockValue UserAccountSwitchLock;
|
public UserAccountSwitchLockValue UserAccountSwitchLock;
|
||||||
|
|
@ -65,10 +58,7 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||||
public RepairFlagValue RepairFlag;
|
public RepairFlagValue RepairFlag;
|
||||||
public byte ProgramIndex;
|
public byte ProgramIndex;
|
||||||
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
|
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
|
||||||
public byte ApplicationErrorCodePrefix;
|
public Array4<byte> Reserved3214;
|
||||||
public TitleCompressionValue TitleCompression;
|
|
||||||
public byte AcdIndex;
|
|
||||||
public byte ApparentPlatform;
|
|
||||||
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
|
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
|
||||||
public ApplicationJitConfiguration JitConfiguration;
|
public ApplicationJitConfiguration JitConfiguration;
|
||||||
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
|
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
|
||||||
|
|
@ -84,47 +74,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||||
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
|
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
|
||||||
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
|
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
|
||||||
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
|
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
|
||||||
|
|
||||||
private const int TitleCount = 32;
|
|
||||||
private const int TitleEntrySize = 0x300;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
|
|
||||||
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
|
|
||||||
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
|
|
||||||
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
|
|
||||||
/// </summary>
|
|
||||||
public readonly ApplicationTitle[] Title
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var titles = new ApplicationTitle[TitleCount];
|
|
||||||
|
|
||||||
if (TitleCompression != TitleCompressionValue.Enable)
|
|
||||||
{
|
|
||||||
TitleBlock.AsSpan().CopyTo(titles);
|
|
||||||
|
|
||||||
return titles;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
|
|
||||||
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
|
|
||||||
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
|
|
||||||
|
|
||||||
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
|
|
||||||
|
|
||||||
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
|
|
||||||
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
|
|
||||||
{
|
|
||||||
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
|
|
||||||
|
|
||||||
return titles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ApplicationTitle
|
public struct ApplicationTitle
|
||||||
{
|
{
|
||||||
|
|
@ -181,8 +130,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||||
TraditionalChinese = 13,
|
TraditionalChinese = 13,
|
||||||
SimplifiedChinese = 14,
|
SimplifiedChinese = 14,
|
||||||
BrazilianPortuguese = 15,
|
BrazilianPortuguese = 15,
|
||||||
Polish = 16,
|
|
||||||
Thai = 17,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Organization
|
public enum Organization
|
||||||
|
|
@ -355,11 +302,5 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
||||||
Deny = 0,
|
Deny = 0,
|
||||||
Allow = 1,
|
Allow = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TitleCompressionValue : byte
|
|
||||||
{
|
|
||||||
Disable = 0,
|
|
||||||
Enable = 1,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Midi.Assigner
|
|
||||||
{
|
|
||||||
public sealed class MidiBindingAssigner
|
|
||||||
{
|
|
||||||
private readonly IMidiGamepad _gamepad;
|
|
||||||
private MidiCapturedInput? _capturedInput;
|
|
||||||
|
|
||||||
public MidiBindingAssigner(IMidiGamepad gamepad)
|
|
||||||
{
|
|
||||||
_gamepad = gamepad;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
_gamepad?.ResetCapturedInputs();
|
|
||||||
_capturedInput = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReadInput()
|
|
||||||
{
|
|
||||||
if (_gamepad != null && _gamepad.TryDequeueCapturedInput(out MidiCapturedInput input))
|
|
||||||
{
|
|
||||||
_capturedInput = input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAnyButtonPressed()
|
|
||||||
{
|
|
||||||
return _capturedInput.HasValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShouldCancel()
|
|
||||||
{
|
|
||||||
return _gamepad == null || !_gamepad.IsConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MidiBinding? GetPressedBinding(bool captureAnyChannel, byte threshold)
|
|
||||||
{
|
|
||||||
if (!_capturedInput.HasValue)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MidiCapturedInput input = _capturedInput.Value;
|
|
||||||
_capturedInput = null;
|
|
||||||
|
|
||||||
return new MidiBinding
|
|
||||||
{
|
|
||||||
Kind = input.Kind,
|
|
||||||
Number = input.Number,
|
|
||||||
Channel = captureAnyChannel ? (byte)0 : input.Channel,
|
|
||||||
Threshold = threshold,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.Input.Midi
|
|
||||||
{
|
|
||||||
public interface IMidiGamepad : IGamepad
|
|
||||||
{
|
|
||||||
bool TryDequeueCapturedInput(out MidiCapturedInput input);
|
|
||||||
void ResetCapturedInputs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Midi
|
|
||||||
{
|
|
||||||
public readonly struct MidiCapturedInput
|
|
||||||
{
|
|
||||||
public MidiBindingKind Kind { get; }
|
|
||||||
public byte Number { get; }
|
|
||||||
public byte Channel { get; }
|
|
||||||
public byte Value { get; }
|
|
||||||
|
|
||||||
public MidiCapturedInput(MidiBindingKind kind, byte number, byte channel, byte value)
|
|
||||||
{
|
|
||||||
Kind = kind;
|
|
||||||
Number = number;
|
|
||||||
Channel = channel;
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
using Melanchall.DryWetMidi.Core;
|
|
||||||
using Melanchall.DryWetMidi.Multimedia;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Midi
|
|
||||||
{
|
|
||||||
internal sealed class MidiDeviceConnection : IDisposable
|
|
||||||
{
|
|
||||||
private readonly InputDevice _device;
|
|
||||||
|
|
||||||
public MidiDeviceState State { get; } = new();
|
|
||||||
|
|
||||||
public bool IsConnected { get; private set; }
|
|
||||||
|
|
||||||
public MidiDeviceConnection(InputDevice device)
|
|
||||||
{
|
|
||||||
_device = device;
|
|
||||||
_device.EventReceived += HandleEventReceived;
|
|
||||||
_device.StartEventsListening();
|
|
||||||
IsConnected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleEventReceived(object sender, MidiEventReceivedEventArgs e)
|
|
||||||
{
|
|
||||||
switch (e.Event)
|
|
||||||
{
|
|
||||||
case NoteOnEvent noteOn:
|
|
||||||
{
|
|
||||||
byte channel = (byte)noteOn.Channel;
|
|
||||||
byte noteNumber = (byte)noteOn.NoteNumber;
|
|
||||||
byte velocity = (byte)noteOn.Velocity;
|
|
||||||
|
|
||||||
State.SetNoteValue(channel, noteNumber, velocity);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NoteOffEvent noteOff:
|
|
||||||
State.SetNoteValue((byte)noteOff.Channel, (byte)noteOff.NoteNumber, 0);
|
|
||||||
break;
|
|
||||||
case ControlChangeEvent controlChange:
|
|
||||||
State.SetControlValue((byte)controlChange.Channel, (byte)controlChange.ControlNumber, (byte)controlChange.ControlValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!IsConnected)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsConnected = false;
|
|
||||||
_device.EventReceived -= HandleEventReceived;
|
|
||||||
_device.StopEventsListening();
|
|
||||||
_device.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Midi
|
|
||||||
{
|
|
||||||
internal class MidiDeviceState
|
|
||||||
{
|
|
||||||
private readonly byte[,] _noteVelocities = new byte[16, 128];
|
|
||||||
private readonly byte[,] _controlValues = new byte[16, 128];
|
|
||||||
private readonly Queue<MidiCapturedInput> _capturedInputs = [];
|
|
||||||
private readonly Lock _lock = new();
|
|
||||||
|
|
||||||
public void SetNoteValue(byte channel, byte note, byte velocity)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_noteVelocities[channel, note] = velocity;
|
|
||||||
|
|
||||||
if (velocity > 0)
|
|
||||||
{
|
|
||||||
_capturedInputs.Enqueue(new MidiCapturedInput(MidiBindingKind.Note, note, (byte)(channel + 1), velocity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetControlValue(byte channel, byte control, byte value)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_controlValues[channel, control] = value;
|
|
||||||
_capturedInputs.Enqueue(new MidiCapturedInput(MidiBindingKind.ControlChange, control, (byte)(channel + 1), value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsBindingActive(MidiBinding binding)
|
|
||||||
{
|
|
||||||
if (binding.IsUnbound)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
int threshold = binding.Threshold == 0 ? 1 : binding.Threshold;
|
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < 16; channelIndex++)
|
|
||||||
{
|
|
||||||
if (binding.Channel != 0 && binding.Channel != channelIndex + 1)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int currentValue = binding.Kind switch
|
|
||||||
{
|
|
||||||
MidiBindingKind.Note => _noteVelocities[channelIndex, binding.Number],
|
|
||||||
MidiBindingKind.ControlChange => _controlValues[channelIndex, binding.Number],
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (currentValue >= threshold)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeueCapturedInput(out MidiCapturedInput input)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_capturedInputs.Count > 0)
|
|
||||||
{
|
|
||||||
input = _capturedInputs.Dequeue();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetCapturedInputs()
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_capturedInputs.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Midi
|
|
||||||
{
|
|
||||||
public sealed class MidiGamepad : IMidiGamepad
|
|
||||||
{
|
|
||||||
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, MidiBinding From)
|
|
||||||
{
|
|
||||||
public bool IsValid => To is not GamepadButtonInputId.Unbound && !From.IsUnbound;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly MidiDeviceConnection _connection;
|
|
||||||
private readonly List<ButtonMappingEntry> _buttonsUserMapping = [];
|
|
||||||
private readonly Lock _userMappingLock = new();
|
|
||||||
|
|
||||||
private StandardMidiInputConfig _configuration;
|
|
||||||
|
|
||||||
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
|
|
||||||
public string Id { get; }
|
|
||||||
public string Name { get; }
|
|
||||||
public bool IsConnected => _connection.IsConnected;
|
|
||||||
|
|
||||||
internal MidiGamepad(string id, string name, MidiDeviceConnection connection)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
Name = name;
|
|
||||||
_connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPressed(GamepadButtonInputId inputId)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public (float, float) GetStick(StickInputId inputId)
|
|
||||||
{
|
|
||||||
return (0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3 GetMotionData(MotionInputId inputId)
|
|
||||||
{
|
|
||||||
return Vector3.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTriggerThreshold(float triggerThreshold)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetConfiguration(InputConfig configuration)
|
|
||||||
{
|
|
||||||
lock (_userMappingLock)
|
|
||||||
{
|
|
||||||
_configuration = (StandardMidiInputConfig)configuration;
|
|
||||||
_buttonsUserMapping.Clear();
|
|
||||||
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, _configuration.LeftJoyconStick.StickButton));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, _configuration.LeftJoycon.DpadUp));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, _configuration.LeftJoycon.DpadDown));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, _configuration.LeftJoycon.DpadLeft));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, _configuration.LeftJoycon.DpadRight));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, _configuration.LeftJoycon.ButtonMinus));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, _configuration.LeftJoycon.ButtonL));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, _configuration.LeftJoycon.ButtonZl));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, _configuration.LeftJoycon.ButtonSr));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, _configuration.LeftJoycon.ButtonSl));
|
|
||||||
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, _configuration.RightJoyconStick.StickButton));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, _configuration.RightJoycon.ButtonA));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, _configuration.RightJoycon.ButtonB));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, _configuration.RightJoycon.ButtonX));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, _configuration.RightJoycon.ButtonY));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, _configuration.RightJoycon.ButtonPlus));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, _configuration.RightJoycon.ButtonR));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, _configuration.RightJoycon.ButtonZr));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, _configuration.RightJoycon.ButtonSr));
|
|
||||||
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, _configuration.RightJoycon.ButtonSl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetLed(uint packedRgb)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
|
||||||
{
|
|
||||||
GamepadStateSnapshot result = default;
|
|
||||||
|
|
||||||
lock (_userMappingLock)
|
|
||||||
{
|
|
||||||
if (_configuration == null)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
|
||||||
{
|
|
||||||
if (!entry.IsValid || result.IsPressed(entry.To))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.SetPressed(entry.To, _connection.State.IsBindingActive(entry.From));
|
|
||||||
}
|
|
||||||
|
|
||||||
(short leftStickX, short leftStickY) = GetStickValues(_configuration.LeftJoyconStick);
|
|
||||||
(short rightStickX, short rightStickY) = GetStickValues(_configuration.RightJoyconStick);
|
|
||||||
|
|
||||||
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
|
|
||||||
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GamepadStateSnapshot GetStateSnapshot()
|
|
||||||
{
|
|
||||||
return GetMappedStateSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDequeueCapturedInput(out MidiCapturedInput input)
|
|
||||||
{
|
|
||||||
return _connection.State.TryDequeueCapturedInput(out input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetCapturedInputs()
|
|
||||||
{
|
|
||||||
_connection.State.ResetCapturedInputs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_connection.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float ConvertRawStickValue(short value)
|
|
||||||
{
|
|
||||||
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
|
|
||||||
|
|
||||||
return value * ConvertRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (short, short) GetStickValues(Ryujinx.Common.Configuration.Hid.Keyboard.JoyconConfigKeyboardStick<MidiBinding> stickConfig)
|
|
||||||
{
|
|
||||||
short stickX = 0;
|
|
||||||
short stickY = 0;
|
|
||||||
|
|
||||||
if (_connection.State.IsBindingActive(stickConfig.StickUp))
|
|
||||||
{
|
|
||||||
stickY += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_connection.State.IsBindingActive(stickConfig.StickDown))
|
|
||||||
{
|
|
||||||
stickY -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_connection.State.IsBindingActive(stickConfig.StickRight))
|
|
||||||
{
|
|
||||||
stickX += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_connection.State.IsBindingActive(stickConfig.StickLeft))
|
|
||||||
{
|
|
||||||
stickX -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2 stick = new(stickX, stickY);
|
|
||||||
if (stick != Vector2.Zero)
|
|
||||||
{
|
|
||||||
stick = Vector2.Normalize(stick);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
using Melanchall.DryWetMidi.Multimedia;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.Midi
|
|
||||||
{
|
|
||||||
public sealed class MidiGamepadDriver : IGamepadDriver
|
|
||||||
{
|
|
||||||
public string DriverName => "MIDI";
|
|
||||||
|
|
||||||
public ReadOnlySpan<string> GamepadsIds => EnumerateDeviceIds().ToArray();
|
|
||||||
|
|
||||||
public event Action<string> OnGamepadConnected
|
|
||||||
{
|
|
||||||
add { }
|
|
||||||
remove { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<string> OnGamepadDisconnected
|
|
||||||
{
|
|
||||||
add { }
|
|
||||||
remove { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSupported => OperatingSystem.IsWindows() || OperatingSystem.IsMacOS();
|
|
||||||
|
|
||||||
public string GetDeviceName(string id)
|
|
||||||
{
|
|
||||||
return TryGetDeviceInfo(id, out _, out string name) ? name : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IGamepad GetGamepad(string id)
|
|
||||||
{
|
|
||||||
if (!TryGetDeviceInfo(id, out int index, out string name))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDevice device = InputDevice.GetByIndex(index);
|
|
||||||
|
|
||||||
if (device == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MidiGamepad(id, name, new MidiDeviceConnection(device));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IGamepad> GetGamepads()
|
|
||||||
{
|
|
||||||
foreach (string id in EnumerateDeviceIds())
|
|
||||||
{
|
|
||||||
IGamepad gamepad = GetGamepad(id);
|
|
||||||
|
|
||||||
if (gamepad != null)
|
|
||||||
{
|
|
||||||
yield return gamepad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> EnumerateDeviceIds()
|
|
||||||
{
|
|
||||||
if (!IsSupported)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 0; index < InputDevice.GetDevicesCount(); index++)
|
|
||||||
{
|
|
||||||
yield return index.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetDeviceInfo(string id, out int index, out string name)
|
|
||||||
{
|
|
||||||
index = -1;
|
|
||||||
name = null;
|
|
||||||
|
|
||||||
if (!IsSupported || !int.TryParse(id, out index))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < 0 || index >= InputDevice.GetDevicesCount())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
using InputDevice device = InputDevice.GetByIndex(index);
|
|
||||||
name = device?.Name;
|
|
||||||
|
|
||||||
return device != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Melanchall.DryWetMIDI" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,32 +1,14 @@
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.Input.HLE
|
namespace Ryujinx.Input.HLE
|
||||||
{
|
{
|
||||||
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver midiDriver = null)
|
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver)
|
||||||
: IDisposable
|
: IDisposable
|
||||||
{
|
{
|
||||||
private readonly Dictionary<InputBackendType, IGamepadDriver> _drivers = new()
|
|
||||||
{
|
|
||||||
{ InputBackendType.WindowKeyboard, keyboardDriver },
|
|
||||||
{ InputBackendType.Midi, midiDriver },
|
|
||||||
{ InputBackendType.GamepadSDL2, gamepadDriver },
|
|
||||||
{ InputBackendType.GamepadSDL3, gamepadDriver },
|
|
||||||
};
|
|
||||||
|
|
||||||
public IGamepadDriver KeyboardDriver { get; } = keyboardDriver;
|
public IGamepadDriver KeyboardDriver { get; } = keyboardDriver;
|
||||||
public IGamepadDriver GamepadDriver { get; } = gamepadDriver;
|
public IGamepadDriver GamepadDriver { get; } = gamepadDriver;
|
||||||
public IGamepadDriver MidiDriver { get; } = midiDriver;
|
|
||||||
public IGamepadDriver MouseDriver { get; private set; }
|
public IGamepadDriver MouseDriver { get; private set; }
|
||||||
|
|
||||||
public IGamepadDriver GetDriver(InputBackendType backend)
|
|
||||||
{
|
|
||||||
_drivers.TryGetValue(backend, out IGamepadDriver driver);
|
|
||||||
|
|
||||||
return driver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetMouseDriver(IGamepadDriver mouseDriver)
|
public void SetMouseDriver(IGamepadDriver mouseDriver)
|
||||||
{
|
{
|
||||||
MouseDriver?.Dispose();
|
MouseDriver?.Dispose();
|
||||||
|
|
@ -36,7 +18,7 @@ namespace Ryujinx.Input.HLE
|
||||||
|
|
||||||
public NpadManager CreateNpadManager()
|
public NpadManager CreateNpadManager()
|
||||||
{
|
{
|
||||||
return new NpadManager(this, MouseDriver);
|
return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TouchScreenManager CreateTouchScreenManager()
|
public TouchScreenManager CreateTouchScreenManager()
|
||||||
|
|
@ -55,7 +37,6 @@ namespace Ryujinx.Input.HLE
|
||||||
{
|
{
|
||||||
KeyboardDriver?.Dispose();
|
KeyboardDriver?.Dispose();
|
||||||
GamepadDriver?.Dispose();
|
GamepadDriver?.Dispose();
|
||||||
MidiDriver?.Dispose();
|
|
||||||
MouseDriver?.Dispose();
|
MouseDriver?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
|
|
@ -28,7 +30,6 @@ namespace Ryujinx.Input.HLE
|
||||||
|
|
||||||
private readonly NpadController[] _controllers;
|
private readonly NpadController[] _controllers;
|
||||||
|
|
||||||
private readonly InputManager _inputManager;
|
|
||||||
private readonly IGamepadDriver _keyboardDriver;
|
private readonly IGamepadDriver _keyboardDriver;
|
||||||
private readonly IGamepadDriver _gamepadDriver;
|
private readonly IGamepadDriver _gamepadDriver;
|
||||||
private readonly IGamepadDriver _mouseDriver;
|
private readonly IGamepadDriver _mouseDriver;
|
||||||
|
|
@ -42,14 +43,13 @@ namespace Ryujinx.Input.HLE
|
||||||
private readonly List<GamepadInput> _hleInputStates = [];
|
private readonly List<GamepadInput> _hleInputStates = [];
|
||||||
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
|
private readonly List<SixAxisInput> _hleMotionStates = new(NpadDevices.MaxControllers);
|
||||||
|
|
||||||
public NpadManager(InputManager inputManager, IGamepadDriver mouseDriver)
|
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
|
||||||
{
|
{
|
||||||
_controllers = new NpadController[MaxControllers];
|
_controllers = new NpadController[MaxControllers];
|
||||||
_cemuHookClient = new CemuHookClient(this);
|
_cemuHookClient = new CemuHookClient(this);
|
||||||
|
|
||||||
_inputManager = inputManager;
|
_keyboardDriver = keyboardDriver;
|
||||||
_keyboardDriver = inputManager.KeyboardDriver;
|
_gamepadDriver = gamepadDriver;
|
||||||
_gamepadDriver = inputManager.GamepadDriver;
|
|
||||||
_mouseDriver = mouseDriver;
|
_mouseDriver = mouseDriver;
|
||||||
_inputConfig = [];
|
_inputConfig = [];
|
||||||
|
|
||||||
|
|
@ -102,7 +102,16 @@ namespace Ryujinx.Input.HLE
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool DriverConfigurationUpdate(ref NpadController controller, InputConfig config)
|
private bool DriverConfigurationUpdate(ref NpadController controller, InputConfig config)
|
||||||
{
|
{
|
||||||
IGamepadDriver targetDriver = _inputManager.GetDriver(config.Backend);
|
IGamepadDriver targetDriver = _gamepadDriver;
|
||||||
|
|
||||||
|
if (config is StandardControllerInputConfig)
|
||||||
|
{
|
||||||
|
targetDriver = _gamepadDriver;
|
||||||
|
}
|
||||||
|
else if (config is StandardKeyboardInputConfig)
|
||||||
|
{
|
||||||
|
targetDriver = _keyboardDriver;
|
||||||
|
}
|
||||||
|
|
||||||
Debug.Assert(targetDriver != null, "Unknown input configuration!");
|
Debug.Assert(targetDriver != null, "Unknown input configuration!");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
using NUnit.Framework;
|
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.Input;
|
|
||||||
using Ryujinx.Input.HLE;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.Tests.HLE
|
|
||||||
{
|
|
||||||
public class MidiInputConfigTests
|
|
||||||
{
|
|
||||||
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void MidiInputConfigRoundTripsThroughInputConfigConverter()
|
|
||||||
{
|
|
||||||
StandardMidiInputConfig config = new()
|
|
||||||
{
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
Backend = InputBackendType.Midi,
|
|
||||||
Id = "0",
|
|
||||||
Name = "Test MIDI Device",
|
|
||||||
ControllerType = ControllerType.ProController,
|
|
||||||
PlayerIndex = PlayerIndex.Player1,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
DpadUp = new MidiBinding { Kind = MidiBindingKind.Note, Number = 60, Channel = 1, Threshold = 10 },
|
|
||||||
DpadDown = new MidiBinding { Kind = MidiBindingKind.ControlChange, Number = 1, Channel = 0, Threshold = 64 },
|
|
||||||
},
|
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = new MidiBinding { Kind = MidiBindingKind.Note, Number = 61, Channel = 0, Threshold = 1 },
|
|
||||||
StickButton = new MidiBinding { Kind = MidiBindingKind.ControlChange, Number = 64, Channel = 2, Threshold = 127 },
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
ButtonA = new MidiBinding { Kind = MidiBindingKind.Note, Number = 72, Channel = 3, Threshold = 20 },
|
|
||||||
},
|
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickLeft = new MidiBinding { Kind = MidiBindingKind.ControlChange, Number = 10, Channel = 0, Threshold = 90 },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
string json = JsonHelper.Serialize<InputConfig>(config, SerializerContext.InputConfig);
|
|
||||||
InputConfig roundTripped = JsonHelper.Deserialize<InputConfig>(json, SerializerContext.InputConfig);
|
|
||||||
|
|
||||||
Assert.That(roundTripped, Is.TypeOf<StandardMidiInputConfig>());
|
|
||||||
|
|
||||||
StandardMidiInputConfig midiConfig = (StandardMidiInputConfig)roundTripped;
|
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
|
||||||
{
|
|
||||||
Assert.That(midiConfig.Backend, Is.EqualTo(InputBackendType.Midi));
|
|
||||||
Assert.That(midiConfig.LeftJoycon.DpadUp, Is.EqualTo(config.LeftJoycon.DpadUp));
|
|
||||||
Assert.That(midiConfig.LeftJoycon.DpadDown, Is.EqualTo(config.LeftJoycon.DpadDown));
|
|
||||||
Assert.That(midiConfig.LeftJoyconStick.StickUp, Is.EqualTo(config.LeftJoyconStick.StickUp));
|
|
||||||
Assert.That(midiConfig.LeftJoyconStick.StickButton, Is.EqualTo(config.LeftJoyconStick.StickButton));
|
|
||||||
Assert.That(midiConfig.RightJoycon.ButtonA, Is.EqualTo(config.RightJoycon.ButtonA));
|
|
||||||
Assert.That(midiConfig.RightJoyconStick.StickLeft, Is.EqualTo(config.RightJoyconStick.StickLeft));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void InputManagerReturnsMidiDriverForMidiBackend()
|
|
||||||
{
|
|
||||||
using StubGamepadDriver keyboard = new("Keyboard");
|
|
||||||
using StubGamepadDriver gamepad = new("Gamepad");
|
|
||||||
using StubGamepadDriver midi = new("MIDI");
|
|
||||||
using InputManager inputManager = new(keyboard, gamepad, midi);
|
|
||||||
|
|
||||||
Assert.That(inputManager.GetDriver(InputBackendType.WindowKeyboard), Is.SameAs(keyboard));
|
|
||||||
Assert.That(inputManager.GetDriver(InputBackendType.GamepadSDL3), Is.SameAs(gamepad));
|
|
||||||
Assert.That(inputManager.GetDriver(InputBackendType.Midi), Is.SameAs(midi));
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class StubGamepadDriver(string driverName) : IGamepadDriver
|
|
||||||
{
|
|
||||||
public string DriverName { get; } = driverName;
|
|
||||||
public ReadOnlySpan<string> GamepadsIds => [];
|
|
||||||
|
|
||||||
public event Action<string> OnGamepadConnected
|
|
||||||
{
|
|
||||||
add { }
|
|
||||||
remove { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<string> OnGamepadDisconnected
|
|
||||||
{
|
|
||||||
add { }
|
|
||||||
remove { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public IGamepad GetGamepad(string id)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IGamepad> GetGamepads()
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
|
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
|
||||||
<ProjectReference Include="..\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj" />
|
<ProjectReference Include="..\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj" />
|
<ProjectReference Include="..\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Cpu;
|
using Ryujinx.Cpu;
|
||||||
|
|
@ -75,24 +74,18 @@ namespace Ryujinx.Headless
|
||||||
|
|
||||||
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
|
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
|
||||||
|
|
||||||
InputBackendType inputBackend = InputBackendType.WindowKeyboard;
|
bool isKeyboard = true;
|
||||||
|
|
||||||
if (gamepad == null)
|
if (gamepad == null)
|
||||||
{
|
{
|
||||||
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
|
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
|
||||||
inputBackend = InputBackendType.GamepadSDL3;
|
isKeyboard = false;
|
||||||
|
|
||||||
if (gamepad == null)
|
if (gamepad == null)
|
||||||
{
|
{
|
||||||
gamepad = _inputManager.MidiDriver?.GetGamepad(inputId);
|
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
|
||||||
inputBackend = InputBackendType.Midi;
|
|
||||||
|
|
||||||
if (gamepad == null)
|
return null;
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +97,7 @@ namespace Ryujinx.Headless
|
||||||
|
|
||||||
if (inputProfileName == null || inputProfileName.Equals("default"))
|
if (inputProfileName == null || inputProfileName.Equals("default"))
|
||||||
{
|
{
|
||||||
if (inputBackend == InputBackendType.WindowKeyboard)
|
if (isKeyboard)
|
||||||
{
|
{
|
||||||
config = new StandardKeyboardInputConfig
|
config = new StandardKeyboardInputConfig
|
||||||
{
|
{
|
||||||
|
|
@ -157,58 +150,6 @@ namespace Ryujinx.Headless
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (inputBackend == InputBackendType.Midi)
|
|
||||||
{
|
|
||||||
static MidiBinding UnboundBinding() => new() { Kind = MidiBindingKind.Unbound, Threshold = 1 };
|
|
||||||
|
|
||||||
config = new StandardMidiInputConfig
|
|
||||||
{
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
Backend = InputBackendType.Midi,
|
|
||||||
Id = null,
|
|
||||||
ControllerType = ControllerType.JoyconPair,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
DpadUp = UnboundBinding(),
|
|
||||||
DpadDown = UnboundBinding(),
|
|
||||||
DpadLeft = UnboundBinding(),
|
|
||||||
DpadRight = UnboundBinding(),
|
|
||||||
ButtonMinus = UnboundBinding(),
|
|
||||||
ButtonL = UnboundBinding(),
|
|
||||||
ButtonZl = UnboundBinding(),
|
|
||||||
ButtonSl = UnboundBinding(),
|
|
||||||
ButtonSr = UnboundBinding(),
|
|
||||||
},
|
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = UnboundBinding(),
|
|
||||||
StickDown = UnboundBinding(),
|
|
||||||
StickLeft = UnboundBinding(),
|
|
||||||
StickRight = UnboundBinding(),
|
|
||||||
StickButton = UnboundBinding(),
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
ButtonA = UnboundBinding(),
|
|
||||||
ButtonB = UnboundBinding(),
|
|
||||||
ButtonX = UnboundBinding(),
|
|
||||||
ButtonY = UnboundBinding(),
|
|
||||||
ButtonPlus = UnboundBinding(),
|
|
||||||
ButtonR = UnboundBinding(),
|
|
||||||
ButtonZr = UnboundBinding(),
|
|
||||||
ButtonSl = UnboundBinding(),
|
|
||||||
ButtonSr = UnboundBinding(),
|
|
||||||
},
|
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = UnboundBinding(),
|
|
||||||
StickDown = UnboundBinding(),
|
|
||||||
StickLeft = UnboundBinding(),
|
|
||||||
StickRight = UnboundBinding(),
|
|
||||||
StickButton = UnboundBinding(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool isNintendoStyle = gamepadName.Contains("Nintendo");
|
bool isNintendoStyle = gamepadName.Contains("Nintendo");
|
||||||
|
|
@ -288,14 +229,10 @@ namespace Ryujinx.Headless
|
||||||
{
|
{
|
||||||
string profileBasePath;
|
string profileBasePath;
|
||||||
|
|
||||||
if (inputBackend == InputBackendType.WindowKeyboard)
|
if (isKeyboard)
|
||||||
{
|
{
|
||||||
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
|
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
|
||||||
}
|
}
|
||||||
else if (inputBackend == InputBackendType.Midi)
|
|
||||||
{
|
|
||||||
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "midi");
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
|
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
|
||||||
|
|
@ -325,12 +262,7 @@ namespace Ryujinx.Headless
|
||||||
config.Id = inputId;
|
config.Id = inputId;
|
||||||
config.PlayerIndex = index;
|
config.PlayerIndex = index;
|
||||||
|
|
||||||
string inputTypeName = inputBackend switch
|
string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
|
||||||
{
|
|
||||||
InputBackendType.WindowKeyboard => "Keyboard",
|
|
||||||
InputBackendType.Midi => "MIDI",
|
|
||||||
_ => "Gamepad",
|
|
||||||
};
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
|
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using Ryujinx.Input.Midi;
|
|
||||||
using Ryujinx.Input.SDL3;
|
using Ryujinx.Input.SDL3;
|
||||||
using Ryujinx.SDL3.Common;
|
using Ryujinx.SDL3.Common;
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -182,7 +181,7 @@ namespace Ryujinx.Headless
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
|
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
|
|
||||||
_inputManager = new InputManager(new SDL3KeyboardDriver(), new SDL3GamepadDriver(), new MidiGamepadDriver());
|
_inputManager = new InputManager(new SDL3KeyboardDriver(), new SDL3GamepadDriver());
|
||||||
|
|
||||||
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
|
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
|
||||||
|
|
||||||
|
|
@ -217,18 +216,6 @@ namespace Ryujinx.Headless
|
||||||
gamepad.Dispose();
|
gamepad.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_inputManager.MidiDriver != null)
|
|
||||||
{
|
|
||||||
foreach (string id in _inputManager.MidiDriver.GamepadsIds)
|
|
||||||
{
|
|
||||||
IGamepad gamepad = _inputManager.MidiDriver.GetGamepad(id);
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
|
|
||||||
|
|
||||||
gamepad.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ namespace Ryujinx.Ava
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
public static bool UseHardwareAcceleration { get; private set; }
|
public static bool UseHardwareAcceleration { get; private set; }
|
||||||
public static string BackendThreadingArg { get; private set; }
|
public static string BackendThreadingArg { get; private set; }
|
||||||
public static bool CoreDumpArg { get; private set; }
|
|
||||||
|
|
||||||
private const uint MbIconwarning = 0x30;
|
private const uint MbIconwarning = 0x30;
|
||||||
|
|
||||||
|
|
@ -82,8 +81,6 @@ namespace Ryujinx.Ava
|
||||||
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
|
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
|
||||||
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
|
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
|
||||||
|
|
||||||
CoreDumpArg = coreDumpArg;
|
|
||||||
|
|
||||||
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
|
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
|
||||||
// This is undesirable and causes very odd behavior during development (the process stops responding,
|
// This is undesirable and causes very odd behavior during development (the process stops responding,
|
||||||
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
|
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<PackageReference Include="Svg.Controls.Avalonia" />
|
<PackageReference Include="Svg.Controls.Avalonia" />
|
||||||
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
<PackageReference Include="Svg.Controls.Skia.Avalonia" />
|
||||||
<PackageReference Include="DynamicData" />
|
<PackageReference Include="DynamicData" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" />
|
<PackageReference Include="FluentAvaloniaUI.NoAnim" />
|
||||||
<PackageReference Include="CommandLineParser" />
|
<PackageReference Include="CommandLineParser" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||||
<PackageReference Include="DiscordRichPresence" />
|
<PackageReference Include="DiscordRichPresence" />
|
||||||
|
|
@ -76,7 +76,6 @@
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input.Midi\Ryujinx.Input.Midi.csproj" />
|
|
||||||
<ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.Apple\Ryujinx.Audio.Backends.Apple.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.Apple\Ryujinx.Audio.Backends.Apple.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(data.Name))
|
if (string.IsNullOrWhiteSpace(data.Name))
|
||||||
{
|
{
|
||||||
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||||
{
|
{
|
||||||
if (!controlTitle.NameString.IsEmpty())
|
if (!controlTitle.NameString.IsEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -1417,7 +1417,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(data.Developer))
|
if (string.IsNullOrWhiteSpace(data.Developer))
|
||||||
{
|
{
|
||||||
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||||
{
|
{
|
||||||
if (!controlTitle.PublisherString.IsEmpty())
|
if (!controlTitle.PublisherString.IsEmpty())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Systems.Configuration.System
|
||||||
SimplifiedChinese,
|
SimplifiedChinese,
|
||||||
TraditionalChinese,
|
TraditionalChinese,
|
||||||
BrazilianPortuguese,
|
BrazilianPortuguese,
|
||||||
Polish,
|
|
||||||
Thai,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LanguageEnumHelper
|
public static class LanguageEnumHelper
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using Avalonia.Data.Converters;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|
@ -174,15 +173,6 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||||
keyString = stickInputId.ToString();
|
keyString = stickInputId.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
case MidiBinding midiBinding:
|
|
||||||
keyString = midiBinding.Kind switch
|
|
||||||
{
|
|
||||||
MidiBindingKind.Unbound => LocaleManager.Instance[LocaleKeys.KeyUnbound],
|
|
||||||
MidiBindingKind.Note => $"Note {midiBinding.Number} Ch {(midiBinding.Channel == 0 ? "Any" : midiBinding.Channel)} T{midiBinding.Threshold}",
|
|
||||||
MidiBindingKind.ControlChange => $"CC {midiBinding.Number} Ch {(midiBinding.Channel == 0 ? "Any" : midiBinding.Channel)} T{midiBinding.Threshold}",
|
|
||||||
_ => midiBinding.ToString(),
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Midi,
|
|
||||||
Controller,
|
Controller,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models.Input
|
|
||||||
{
|
|
||||||
public partial class MidiInputConfig : BaseModel
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public ControllerType ControllerType { get; set; }
|
|
||||||
public PlayerIndex PlayerIndex { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftStickUp { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftStickDown { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftStickLeft { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftStickRight { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftStickButton { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightStickUp { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightStickDown { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightStickLeft { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightStickRight { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightStickButton { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding DpadUp { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding DpadDown { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding DpadLeft { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding DpadRight { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonMinus { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonPlus { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonA { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonB { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonX { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonY { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonL { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonR { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonZl { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding ButtonZr { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftButtonSl { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding LeftButtonSr { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightButtonSl { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial MidiBinding RightButtonSr { get; set; }
|
|
||||||
|
|
||||||
public MidiInputConfig(InputConfig config)
|
|
||||||
{
|
|
||||||
if (config == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Id = config.Id;
|
|
||||||
Name = config.Name;
|
|
||||||
ControllerType = config.ControllerType;
|
|
||||||
PlayerIndex = config.PlayerIndex;
|
|
||||||
|
|
||||||
if (config is not StandardMidiInputConfig midiConfig)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LeftStickUp = midiConfig.LeftJoyconStick.StickUp;
|
|
||||||
LeftStickDown = midiConfig.LeftJoyconStick.StickDown;
|
|
||||||
LeftStickLeft = midiConfig.LeftJoyconStick.StickLeft;
|
|
||||||
LeftStickRight = midiConfig.LeftJoyconStick.StickRight;
|
|
||||||
LeftStickButton = midiConfig.LeftJoyconStick.StickButton;
|
|
||||||
|
|
||||||
RightStickUp = midiConfig.RightJoyconStick.StickUp;
|
|
||||||
RightStickDown = midiConfig.RightJoyconStick.StickDown;
|
|
||||||
RightStickLeft = midiConfig.RightJoyconStick.StickLeft;
|
|
||||||
RightStickRight = midiConfig.RightJoyconStick.StickRight;
|
|
||||||
RightStickButton = midiConfig.RightJoyconStick.StickButton;
|
|
||||||
|
|
||||||
DpadUp = midiConfig.LeftJoycon.DpadUp;
|
|
||||||
DpadDown = midiConfig.LeftJoycon.DpadDown;
|
|
||||||
DpadLeft = midiConfig.LeftJoycon.DpadLeft;
|
|
||||||
DpadRight = midiConfig.LeftJoycon.DpadRight;
|
|
||||||
ButtonL = midiConfig.LeftJoycon.ButtonL;
|
|
||||||
ButtonMinus = midiConfig.LeftJoycon.ButtonMinus;
|
|
||||||
LeftButtonSl = midiConfig.LeftJoycon.ButtonSl;
|
|
||||||
LeftButtonSr = midiConfig.LeftJoycon.ButtonSr;
|
|
||||||
ButtonZl = midiConfig.LeftJoycon.ButtonZl;
|
|
||||||
|
|
||||||
ButtonA = midiConfig.RightJoycon.ButtonA;
|
|
||||||
ButtonB = midiConfig.RightJoycon.ButtonB;
|
|
||||||
ButtonX = midiConfig.RightJoycon.ButtonX;
|
|
||||||
ButtonY = midiConfig.RightJoycon.ButtonY;
|
|
||||||
ButtonR = midiConfig.RightJoycon.ButtonR;
|
|
||||||
ButtonPlus = midiConfig.RightJoycon.ButtonPlus;
|
|
||||||
RightButtonSl = midiConfig.RightJoycon.ButtonSl;
|
|
||||||
RightButtonSr = midiConfig.RightJoycon.ButtonSr;
|
|
||||||
ButtonZr = midiConfig.RightJoycon.ButtonZr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InputConfig GetConfig()
|
|
||||||
{
|
|
||||||
return new StandardMidiInputConfig
|
|
||||||
{
|
|
||||||
Id = Id,
|
|
||||||
Name = Name,
|
|
||||||
Backend = InputBackendType.Midi,
|
|
||||||
PlayerIndex = PlayerIndex,
|
|
||||||
ControllerType = ControllerType,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
DpadUp = DpadUp,
|
|
||||||
DpadDown = DpadDown,
|
|
||||||
DpadLeft = DpadLeft,
|
|
||||||
DpadRight = DpadRight,
|
|
||||||
ButtonL = ButtonL,
|
|
||||||
ButtonMinus = ButtonMinus,
|
|
||||||
ButtonZl = ButtonZl,
|
|
||||||
ButtonSl = LeftButtonSl,
|
|
||||||
ButtonSr = LeftButtonSr,
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
ButtonA = ButtonA,
|
|
||||||
ButtonB = ButtonB,
|
|
||||||
ButtonX = ButtonX,
|
|
||||||
ButtonY = ButtonY,
|
|
||||||
ButtonPlus = ButtonPlus,
|
|
||||||
ButtonSl = RightButtonSl,
|
|
||||||
ButtonSr = RightButtonSr,
|
|
||||||
ButtonR = ButtonR,
|
|
||||||
ButtonZr = ButtonZr,
|
|
||||||
},
|
|
||||||
LeftJoyconStick = new Ryujinx.Common.Configuration.Hid.Keyboard.JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = LeftStickUp,
|
|
||||||
StickDown = LeftStickDown,
|
|
||||||
StickRight = LeftStickRight,
|
|
||||||
StickLeft = LeftStickLeft,
|
|
||||||
StickButton = LeftStickButton,
|
|
||||||
},
|
|
||||||
RightJoyconStick = new Ryujinx.Common.Configuration.Hid.Keyboard.JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = RightStickUp,
|
|
||||||
StickDown = RightStickDown,
|
|
||||||
StickLeft = RightStickLeft,
|
|
||||||
StickRight = RightStickRight,
|
|
||||||
StickButton = RightStickButton,
|
|
||||||
},
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -61,18 +61,6 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MidiInputConfig _midiConfig;
|
|
||||||
public MidiInputConfig MidiConfig
|
|
||||||
{
|
|
||||||
get => _midiConfig;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_midiConfig = value;
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private (float, float) _uiStickLeft;
|
private (float, float) _uiStickLeft;
|
||||||
public (float, float) UiStickLeft
|
public (float, float) UiStickLeft
|
||||||
{
|
{
|
||||||
|
|
@ -143,13 +131,6 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (config is MidiInputViewModel midiConfig)
|
|
||||||
{
|
|
||||||
MidiConfig = midiConfig.Config;
|
|
||||||
Type = DeviceType.Midi;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type = DeviceType.None;
|
Type = DeviceType.None;
|
||||||
}
|
}
|
||||||
|
|
@ -228,17 +209,6 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||||
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
|
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
case DeviceType.Midi:
|
|
||||||
IGamepad midi = Parent.SelectedGamepad;
|
|
||||||
|
|
||||||
if (midi != null)
|
|
||||||
{
|
|
||||||
GamepadStateSnapshot snapshot = midi.GetMappedStateSnapshot();
|
|
||||||
leftBuffer = snapshot.GetStick(StickInputId.Left);
|
|
||||||
rightBuffer = snapshot.GetStick(StickInputId.Right);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DeviceType.None:
|
case DeviceType.None:
|
||||||
|
|
@ -282,7 +252,6 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardConfig = null;
|
KeyboardConfig = null;
|
||||||
MidiConfig = null;
|
|
||||||
GamepadConfig = null;
|
GamepadConfig = null;
|
||||||
Parent = null;
|
Parent = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.Core;
|
|
||||||
using FluentAvalonia.UI.Windowing;
|
using FluentAvalonia.UI.Windowing;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
|
@ -54,9 +53,6 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
Name = FormatTitle();
|
Name = FormatTitle();
|
||||||
|
|
||||||
// Disable menu animations
|
|
||||||
FAUISettings.SetAnimationsEnabledAtAppLevel(false);
|
|
||||||
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,9 @@ using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.Midi;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
@ -42,7 +40,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg";
|
private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg";
|
||||||
private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg";
|
private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg";
|
||||||
private const string KeyboardString = "keyboard";
|
private const string KeyboardString = "keyboard";
|
||||||
private const string MidiString = "midi";
|
|
||||||
private const string ControllerString = "controller";
|
private const string ControllerString = "controller";
|
||||||
private readonly MainWindow _mainWindow;
|
private readonly MainWindow _mainWindow;
|
||||||
|
|
||||||
|
|
@ -98,15 +95,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
|
|
||||||
// XAML Flags
|
// XAML Flags
|
||||||
public bool ShowSettings => _device > 0;
|
public bool ShowSettings => _device > 0;
|
||||||
public DeviceType CurrentDeviceType => _device >= 0 && _device < Devices.Count ? Devices[_device].Type : DeviceType.None;
|
public bool IsController => _device > 1;
|
||||||
public bool IsController => CurrentDeviceType == DeviceType.Controller;
|
public bool IsKeyboard => !IsController;
|
||||||
public bool IsKeyboard => CurrentDeviceType == DeviceType.Keyboard;
|
|
||||||
public bool IsMidi => CurrentDeviceType == DeviceType.Midi;
|
|
||||||
public bool IsRight { get; set; }
|
public bool IsRight { get; set; }
|
||||||
public bool IsLeft { get; set; }
|
public bool IsLeft { get; set; }
|
||||||
public string RevertDeviceId { get; set; }
|
public string RevertDeviceId { get; set; }
|
||||||
public bool HasLed => SelectedGamepad != null && (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0;
|
public bool HasLed => (SelectedGamepad.Features & GamepadFeaturesFlag.Led) != 0;
|
||||||
public bool CanClearLed => SelectedGamepad?.Name.ContainsIgnoreCase("DualSense") == true;
|
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
||||||
|
|
||||||
public event Action NotifyChangesEvent;
|
public event Action NotifyChangesEvent;
|
||||||
|
|
||||||
|
|
@ -354,11 +349,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick);
|
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config is StandardMidiInputConfig midiInputConfig)
|
|
||||||
{
|
|
||||||
ConfigViewModel = new MidiInputViewModel(this, new MidiInputConfig(midiInputConfig), VisualStick);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config is StandardControllerInputConfig controllerInputConfig)
|
if (Config is StandardControllerInputConfig controllerInputConfig)
|
||||||
{
|
{
|
||||||
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
|
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
|
||||||
|
|
@ -417,10 +407,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
{
|
{
|
||||||
type = DeviceType.Keyboard;
|
type = DeviceType.Keyboard;
|
||||||
}
|
}
|
||||||
else if (Config is StandardMidiInputConfig)
|
|
||||||
{
|
|
||||||
type = DeviceType.Midi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config is StandardControllerInputConfig)
|
if (Config is StandardControllerInputConfig)
|
||||||
{
|
{
|
||||||
|
|
@ -466,10 +452,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
|
SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type == DeviceType.Midi)
|
|
||||||
{
|
|
||||||
SelectedGamepad = _mainWindow.InputManager.MidiDriver?.GetGamepad(id);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
|
SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
|
||||||
|
|
@ -627,19 +609,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mainWindow.InputManager.MidiDriver is MidiGamepadDriver midiDriver)
|
|
||||||
{
|
|
||||||
foreach (string id in midiDriver.GamepadsIds)
|
|
||||||
{
|
|
||||||
string name = midiDriver.GetDeviceName(id);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
Devices.Add((DeviceType.Midi, id, GetShortGamepadName(name)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceList.AddRange(Devices.Select(x => x.Name));
|
DeviceList.AddRange(Devices.Select(x => x.Name));
|
||||||
Device = Math.Min(Device, DeviceList.Count);
|
Device = Math.Min(Device, DeviceList.Count);
|
||||||
}
|
}
|
||||||
|
|
@ -654,10 +623,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
{
|
{
|
||||||
path = Path.Combine(path, KeyboardString);
|
path = Path.Combine(path, KeyboardString);
|
||||||
}
|
}
|
||||||
else if (type == DeviceType.Midi)
|
|
||||||
{
|
|
||||||
path = Path.Combine(path, MidiString);
|
|
||||||
}
|
|
||||||
else if (type == DeviceType.Controller)
|
else if (type == DeviceType.Controller)
|
||||||
{
|
{
|
||||||
path = Path.Combine(path, ControllerString);
|
path = Path.Combine(path, ControllerString);
|
||||||
|
|
@ -755,62 +720,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (activeDevice.Type == DeviceType.Midi)
|
|
||||||
{
|
|
||||||
string id = activeDevice.Id;
|
|
||||||
string name = activeDevice.Name;
|
|
||||||
|
|
||||||
static MidiBinding UnboundBinding() => new() { Kind = MidiBindingKind.Unbound, Threshold = 1 };
|
|
||||||
|
|
||||||
config = new StandardMidiInputConfig
|
|
||||||
{
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
Backend = InputBackendType.Midi,
|
|
||||||
Id = id,
|
|
||||||
Name = name,
|
|
||||||
ControllerType = ControllerType.ProController,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
DpadUp = UnboundBinding(),
|
|
||||||
DpadDown = UnboundBinding(),
|
|
||||||
DpadLeft = UnboundBinding(),
|
|
||||||
DpadRight = UnboundBinding(),
|
|
||||||
ButtonMinus = UnboundBinding(),
|
|
||||||
ButtonL = UnboundBinding(),
|
|
||||||
ButtonZl = UnboundBinding(),
|
|
||||||
ButtonSl = UnboundBinding(),
|
|
||||||
ButtonSr = UnboundBinding(),
|
|
||||||
},
|
|
||||||
LeftJoyconStick = new JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = UnboundBinding(),
|
|
||||||
StickDown = UnboundBinding(),
|
|
||||||
StickLeft = UnboundBinding(),
|
|
||||||
StickRight = UnboundBinding(),
|
|
||||||
StickButton = UnboundBinding(),
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<MidiBinding>
|
|
||||||
{
|
|
||||||
ButtonA = UnboundBinding(),
|
|
||||||
ButtonB = UnboundBinding(),
|
|
||||||
ButtonX = UnboundBinding(),
|
|
||||||
ButtonY = UnboundBinding(),
|
|
||||||
ButtonPlus = UnboundBinding(),
|
|
||||||
ButtonR = UnboundBinding(),
|
|
||||||
ButtonZr = UnboundBinding(),
|
|
||||||
ButtonSl = UnboundBinding(),
|
|
||||||
ButtonSr = UnboundBinding(),
|
|
||||||
},
|
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<MidiBinding>
|
|
||||||
{
|
|
||||||
StickUp = UnboundBinding(),
|
|
||||||
StickDown = UnboundBinding(),
|
|
||||||
StickLeft = UnboundBinding(),
|
|
||||||
StickRight = UnboundBinding(),
|
|
||||||
StickButton = UnboundBinding(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (activeDevice.Type == DeviceType.Controller)
|
else if (activeDevice.Type == DeviceType.Controller)
|
||||||
{
|
{
|
||||||
bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
|
bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
|
||||||
|
|
@ -996,10 +905,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
{
|
{
|
||||||
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
|
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
|
||||||
}
|
}
|
||||||
else if (IsMidi)
|
|
||||||
{
|
|
||||||
config = (ConfigViewModel as MidiInputViewModel).Config.GetConfig();
|
|
||||||
}
|
|
||||||
else if (IsController)
|
else if (IsController)
|
||||||
{
|
{
|
||||||
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||||
|
|
@ -1103,18 +1008,15 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
KeyboardInputConfig inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
|
KeyboardInputConfig inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
|
||||||
inputConfig.Id = device.Id;
|
inputConfig.Id = device.Id;
|
||||||
}
|
}
|
||||||
else if (device.Type == DeviceType.Midi)
|
|
||||||
{
|
|
||||||
MidiInputConfig inputConfig = (ConfigViewModel as MidiInputViewModel).Config;
|
|
||||||
inputConfig.Id = device.Id;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GamepadInputConfig inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
|
GamepadInputConfig inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
|
||||||
inputConfig.Id = device.Id.Split(" ")[0];
|
inputConfig.Id = device.Id.Split(" ")[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
InputConfig config = GetCurrentConfigFromViewModel();
|
InputConfig config = !IsController
|
||||||
|
? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
|
||||||
|
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||||
config.ControllerType = Controllers[_controller].Type;
|
config.ControllerType = Controllers[_controller].Type;
|
||||||
config.PlayerIndex = _playerId;
|
config.PlayerIndex = _playerId;
|
||||||
config.Name = device.Name;
|
config.Name = device.Name;
|
||||||
|
|
@ -1153,7 +1055,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
OnPropertyChanged(nameof(IsController));
|
OnPropertyChanged(nameof(IsController));
|
||||||
OnPropertyChanged(nameof(ShowSettings));
|
OnPropertyChanged(nameof(ShowSettings));
|
||||||
OnPropertyChanged(nameof(IsKeyboard));
|
OnPropertyChanged(nameof(IsKeyboard));
|
||||||
OnPropertyChanged(nameof(IsMidi));
|
|
||||||
OnPropertyChanged(nameof(IsRight));
|
OnPropertyChanged(nameof(IsRight));
|
||||||
OnPropertyChanged(nameof(IsLeft));
|
OnPropertyChanged(nameof(IsLeft));
|
||||||
NotifyChangesEvent?.Invoke();
|
NotifyChangesEvent?.Invoke();
|
||||||
|
|
@ -1174,16 +1075,5 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
|
|
||||||
AvaloniaKeyboardDriver.Dispose();
|
AvaloniaKeyboardDriver.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputConfig GetCurrentConfigFromViewModel()
|
|
||||||
{
|
|
||||||
return ConfigViewModel switch
|
|
||||||
{
|
|
||||||
KeyboardInputViewModel keyboardViewModel => keyboardViewModel.Config.GetConfig(),
|
|
||||||
MidiInputViewModel midiViewModel => midiViewModel.Config.GetConfig(),
|
|
||||||
ControllerInputViewModel controllerViewModel => controllerViewModel.Config.GetConfig(),
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
{
|
{
|
||||||
public partial class KeyboardInputViewModel : BaseModel
|
public partial class KeyboardInputViewModel : BaseModel
|
||||||
{
|
{
|
||||||
public bool ShowMidiCaptureOptions => false;
|
|
||||||
|
|
||||||
public bool CaptureAnyChannel
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
} = true;
|
|
||||||
|
|
||||||
public int CaptureThreshold
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
} = 1;
|
|
||||||
|
|
||||||
public KeyboardInputConfig Config
|
public KeyboardInputConfig Config
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using Ryujinx.Ava.UI.Models.Input;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels.Input
|
|
||||||
{
|
|
||||||
public partial class MidiInputViewModel : BaseModel
|
|
||||||
{
|
|
||||||
public bool ShowMidiCaptureOptions => true;
|
|
||||||
|
|
||||||
public MidiInputConfig Config
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
field = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public StickVisualizer Visualizer
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
field = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsLeft
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
field = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
OnPropertyChanged(nameof(HasSides));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsRight
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
field = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
OnPropertyChanged(nameof(HasSides));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasSides => IsLeft ^ IsRight;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial int CaptureThreshold { get; set; } = 1;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial bool CaptureAnyChannel { get; set; } = true;
|
|
||||||
|
|
||||||
public readonly InputViewModel ParentModel;
|
|
||||||
|
|
||||||
public MidiInputViewModel(InputViewModel model, MidiInputConfig config, StickVisualizer visualizer)
|
|
||||||
{
|
|
||||||
ParentModel = model;
|
|
||||||
Visualizer = visualizer;
|
|
||||||
model.NotifyChangesEvent += OnParentModelChanged;
|
|
||||||
OnParentModelChanged();
|
|
||||||
Config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnParentModelChanged()
|
|
||||||
{
|
|
||||||
IsLeft = ParentModel.IsLeft;
|
|
||||||
IsRight = ParentModel.IsRight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -174,7 +174,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private string _screenshotKey = "F8";
|
private string _screenshotKey = "F8";
|
||||||
private float _volume;
|
private float _volume;
|
||||||
private ApplicationData _currentApplicationData;
|
private ApplicationData _currentApplicationData;
|
||||||
private bool _pendingRestart;
|
|
||||||
private readonly AutoResetEvent _rendererWaitEvent;
|
private readonly AutoResetEvent _rendererWaitEvent;
|
||||||
private int _customVSyncInterval;
|
private int _customVSyncInterval;
|
||||||
private int _customVSyncIntervalPercentageProxy;
|
private int _customVSyncIntervalPercentageProxy;
|
||||||
|
|
@ -1063,7 +1062,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
string dialogMessage =
|
string dialogMessage =
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
|
||||||
|
|
||||||
if (ContentManager.AreKeysAlreadyPresent(systemDirectory))
|
if (ContentManager.AreKeysAlredyPresent(systemDirectory))
|
||||||
{
|
{
|
||||||
dialogMessage +=
|
dialogMessage +=
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
|
||||||
|
|
@ -1251,14 +1250,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
await LoadApplication(_currentApplicationData);
|
await LoadApplication(_currentApplicationData);
|
||||||
}
|
}
|
||||||
else if (_pendingRestart)
|
|
||||||
{
|
|
||||||
_pendingRestart = false;
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"Restarting emulation for '{_currentApplicationData.Name}'");
|
|
||||||
|
|
||||||
await LoadApplication(_currentApplicationData);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Otherwise, clear state.
|
// Otherwise, clear state.
|
||||||
|
|
@ -1267,21 +1258,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartEmulation()
|
|
||||||
{
|
|
||||||
if (AppHost is null || _currentApplicationData is null)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, "RestartEmulation called but no application is running.");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"Restart requested for '{_currentApplicationData.Name}'");
|
|
||||||
|
|
||||||
_pendingRestart = true;
|
|
||||||
AppHost.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
|
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
|
||||||
{
|
{
|
||||||
if (ShowMenuAndStatusBar && !ShowLoadProgress)
|
if (ShowMenuAndStatusBar && !ShowLoadProgress)
|
||||||
|
|
|
||||||
|
|
@ -230,9 +230,6 @@
|
||||||
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
|
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
|
||||||
<views:KeyboardInputView />
|
<views:KeyboardInputView />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate DataType="viewModels:MidiInputViewModel">
|
|
||||||
<views:KeyboardInputView />
|
|
||||||
</DataTemplate>
|
|
||||||
</ContentControl.DataTemplates>
|
</ContentControl.DataTemplates>
|
||||||
</ContentControl>
|
</ContentControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
|
||||||
|
|
@ -12,7 +11,8 @@
|
||||||
d:DesignHeight="800"
|
d:DesignHeight="800"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Input.KeyboardInputView"
|
x:Class="Ryujinx.Ava.UI.Views.Input.KeyboardInputView"
|
||||||
x:CompileBindings="False"
|
x:DataType="viewModels:KeyboardInputViewModel"
|
||||||
|
x:CompileBindings="True"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
|
|
@ -29,37 +29,6 @@
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Orientation="Vertical">
|
Orientation="Vertical">
|
||||||
<Border
|
|
||||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
|
||||||
BorderThickness="1"
|
|
||||||
Margin="0,0,0,8"
|
|
||||||
Padding="10"
|
|
||||||
CornerRadius="5"
|
|
||||||
IsVisible="{Binding ShowMidiCaptureOptions}">
|
|
||||||
<StackPanel
|
|
||||||
Orientation="Horizontal"
|
|
||||||
Spacing="10"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<CheckBox IsChecked="{Binding CaptureAnyChannel}">
|
|
||||||
<TextBlock Text="Any Channel" />
|
|
||||||
</CheckBox>
|
|
||||||
<TextBlock
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Text="Threshold" />
|
|
||||||
<controls:SliderScroll
|
|
||||||
Width="130"
|
|
||||||
Maximum="127"
|
|
||||||
TickFrequency="1"
|
|
||||||
IsSnapToTickEnabled="True"
|
|
||||||
SmallChange="1"
|
|
||||||
Minimum="1"
|
|
||||||
Value="{Binding CaptureThreshold, Mode=TwoWay}" />
|
|
||||||
<TextBlock
|
|
||||||
Width="30"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Text="{Binding CaptureThreshold}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
<!-- Button / JoyStick Settings -->
|
<!-- Button / JoyStick Settings -->
|
||||||
<Grid
|
<Grid
|
||||||
Name="SettingButtons"
|
Name="SettingButtons"
|
||||||
|
|
|
||||||
|
|
@ -4,32 +4,21 @@ using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.Threading;
|
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
|
||||||
using Ryujinx.Common.Configuration.Hid.Midi;
|
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.Assigner;
|
using Ryujinx.Input.Assigner;
|
||||||
using Ryujinx.Input.Midi;
|
|
||||||
using Ryujinx.Input.Midi.Assigner;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Button = Ryujinx.Input.Button;
|
using Button = Ryujinx.Input.Button;
|
||||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Views.Input
|
namespace Ryujinx.Ava.UI.Views.Input
|
||||||
{
|
{
|
||||||
public partial class KeyboardInputView : RyujinxControl<BaseModel>
|
public partial class KeyboardInputView : RyujinxControl<KeyboardInputViewModel>
|
||||||
{
|
{
|
||||||
private ButtonKeyAssigner _keyboardAssigner;
|
private ButtonKeyAssigner _currentAssigner;
|
||||||
private ToggleButton _midiToggleButton;
|
|
||||||
private CancellationTokenSource _midiCancellationTokenSource;
|
|
||||||
private bool _shouldUnbindMidi;
|
|
||||||
|
|
||||||
public KeyboardInputView()
|
public KeyboardInputView()
|
||||||
{
|
{
|
||||||
|
|
@ -48,148 +37,157 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||||
{
|
{
|
||||||
base.OnPointerReleased(e);
|
base.OnPointerReleased(e);
|
||||||
|
|
||||||
ToggleButton currentButton = GetCurrentToggleButton();
|
if (_currentAssigner is { ToggledButton.IsPointerOver: false })
|
||||||
|
|
||||||
if (currentButton is { IsPointerOver: false })
|
|
||||||
{
|
{
|
||||||
CancelCurrentAssignment();
|
_currentAssigner.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
|
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not ToggleButton button)
|
if (sender is not ToggleButton button)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (button.IsChecked is not true)
|
if (button.IsChecked is true)
|
||||||
{
|
{
|
||||||
if (GetCurrentToggleButton() == button)
|
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
|
||||||
{
|
{
|
||||||
CancelCurrentAssignment();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
if (_currentAssigner == null)
|
||||||
}
|
{
|
||||||
|
_currentAssigner = new ButtonKeyAssigner(button);
|
||||||
|
|
||||||
if (GetCurrentToggleButton() == button)
|
Focus(NavigationMethod.Pointer);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GetCurrentToggleButton() != null)
|
PointerPressed += MouseClick;
|
||||||
{
|
|
||||||
CancelCurrentAssignment();
|
|
||||||
button.IsChecked = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Focus(NavigationMethod.Pointer);
|
IKeyboard keyboard =
|
||||||
PointerPressed += MouseClick;
|
(IKeyboard)ViewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
|
||||||
|
IButtonAssigner assigner =
|
||||||
|
new KeyboardKeyAssigner((IKeyboard)ViewModel.ParentModel.SelectedGamepad);
|
||||||
|
|
||||||
if (DataContext is MidiInputViewModel midiViewModel)
|
_currentAssigner.ButtonAssigned += (_, be) =>
|
||||||
{
|
{
|
||||||
await StartMidiAssignmentAsync(button, midiViewModel);
|
if (be.ButtonValue.HasValue)
|
||||||
|
{
|
||||||
|
Button buttonValue = be.ButtonValue.Value;
|
||||||
|
ViewModel.ParentModel.IsModified = true;
|
||||||
|
|
||||||
|
switch (button.Name)
|
||||||
|
{
|
||||||
|
case "ButtonZl":
|
||||||
|
ViewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonL":
|
||||||
|
ViewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonMinus":
|
||||||
|
ViewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftStickButton":
|
||||||
|
ViewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftStickUp":
|
||||||
|
ViewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftStickDown":
|
||||||
|
ViewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftStickRight":
|
||||||
|
ViewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftStickLeft":
|
||||||
|
ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "DpadUp":
|
||||||
|
ViewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "DpadDown":
|
||||||
|
ViewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "DpadLeft":
|
||||||
|
ViewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "DpadRight":
|
||||||
|
ViewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftButtonSr":
|
||||||
|
ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "LeftButtonSl":
|
||||||
|
ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightButtonSr":
|
||||||
|
ViewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightButtonSl":
|
||||||
|
ViewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonZr":
|
||||||
|
ViewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonR":
|
||||||
|
ViewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonPlus":
|
||||||
|
ViewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonA":
|
||||||
|
ViewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonB":
|
||||||
|
ViewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonX":
|
||||||
|
ViewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "ButtonY":
|
||||||
|
ViewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightStickButton":
|
||||||
|
ViewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightStickUp":
|
||||||
|
ViewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightStickDown":
|
||||||
|
ViewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightStickRight":
|
||||||
|
ViewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "RightStickLeft":
|
||||||
|
ViewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_currentAssigner.GetInputAndAssign(assigner, keyboard);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_currentAssigner != null)
|
||||||
|
{
|
||||||
|
_currentAssigner.Cancel();
|
||||||
|
_currentAssigner = null;
|
||||||
|
button.IsChecked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StartKeyboardAssignment(button);
|
_currentAssigner?.Cancel();
|
||||||
|
_currentAssigner = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartKeyboardAssignment(ToggleButton button)
|
|
||||||
{
|
|
||||||
_keyboardAssigner = new ButtonKeyAssigner(button);
|
|
||||||
|
|
||||||
IKeyboard keyboard = (IKeyboard)GetCancelKeyboard();
|
|
||||||
IButtonAssigner assigner = new KeyboardKeyAssigner((IKeyboard)GetSelectedGamepad());
|
|
||||||
|
|
||||||
_keyboardAssigner.ButtonAssigned += (_, be) =>
|
|
||||||
{
|
|
||||||
_keyboardAssigner = null;
|
|
||||||
|
|
||||||
if (be.ButtonValue.HasValue)
|
|
||||||
{
|
|
||||||
Button buttonValue = be.ButtonValue.Value;
|
|
||||||
AssignKeyboardBinding(button.Name, buttonValue.AsHidType<Key>());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_keyboardAssigner.GetInputAndAssign(assigner, keyboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartMidiAssignmentAsync(ToggleButton button, MidiInputViewModel viewModel)
|
|
||||||
{
|
|
||||||
if (GetSelectedGamepad() is not IMidiGamepad midiGamepad)
|
|
||||||
{
|
|
||||||
button.IsChecked = false;
|
|
||||||
PointerPressed -= MouseClick;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_midiToggleButton = button;
|
|
||||||
_shouldUnbindMidi = false;
|
|
||||||
_midiCancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
MidiBindingAssigner assigner = new(midiGamepad);
|
|
||||||
IKeyboard keyboard = (IKeyboard)GetCancelKeyboard();
|
|
||||||
|
|
||||||
assigner.Initialize();
|
|
||||||
|
|
||||||
MidiBinding? binding = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
binding = await Task.Run(async () =>
|
|
||||||
{
|
|
||||||
while (!_midiCancellationTokenSource.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await Task.Delay(10, _midiCancellationTokenSource.Token);
|
|
||||||
|
|
||||||
assigner.ReadInput();
|
|
||||||
|
|
||||||
if (assigner.IsAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Ryujinx.Input.Key.Escape)))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_shouldUnbindMidi)
|
|
||||||
{
|
|
||||||
return CreateUnboundMidiBinding();
|
|
||||||
}
|
|
||||||
|
|
||||||
return assigner.GetPressedBinding(viewModel.CaptureAnyChannel, (byte)viewModel.CaptureThreshold);
|
|
||||||
}, _midiCancellationTokenSource.Token);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
PointerPressed -= MouseClick;
|
|
||||||
|
|
||||||
if (_midiToggleButton != null)
|
|
||||||
{
|
|
||||||
_midiToggleButton.IsChecked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_midiToggleButton = null;
|
|
||||||
_midiCancellationTokenSource?.Dispose();
|
|
||||||
_midiCancellationTokenSource = null;
|
|
||||||
|
|
||||||
if (binding.HasValue)
|
|
||||||
{
|
|
||||||
AssignMidiBinding(button.Name, binding.Value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
|
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
|
||||||
|
|
||||||
bool shouldRemoveBinding = e.GetCurrentPoint(this).Properties.IsRightButtonPressed;
|
bool shouldRemoveBinding = e.GetCurrentPoint(this).Properties.IsRightButtonPressed;
|
||||||
|
|
||||||
if (shouldRemoveBinding)
|
if (shouldRemoveBinding)
|
||||||
|
|
@ -197,177 +195,61 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||||
DeleteBind();
|
DeleteBind();
|
||||||
}
|
}
|
||||||
|
|
||||||
CancelCurrentAssignment(shouldUnbind);
|
_currentAssigner?.Cancel(shouldUnbind);
|
||||||
|
|
||||||
PointerPressed -= MouseClick;
|
PointerPressed -= MouseClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeleteBind()
|
private void DeleteBind()
|
||||||
{
|
{
|
||||||
ToggleButton button = GetCurrentToggleButton();
|
|
||||||
|
|
||||||
if (button == null)
|
if (_currentAssigner != null)
|
||||||
{
|
{
|
||||||
return;
|
Dictionary<string, Action> buttonActions = new()
|
||||||
|
{
|
||||||
|
{ "ButtonZl", () => ViewModel.Config.ButtonZl = Key.Unbound },
|
||||||
|
{ "ButtonL", () => ViewModel.Config.ButtonL = Key.Unbound },
|
||||||
|
{ "ButtonMinus", () => ViewModel.Config.ButtonMinus = Key.Unbound },
|
||||||
|
{ "LeftStickButton", () => ViewModel.Config.LeftStickButton = Key.Unbound },
|
||||||
|
{ "LeftStickUp", () => ViewModel.Config.LeftStickUp = Key.Unbound },
|
||||||
|
{ "LeftStickDown", () => ViewModel.Config.LeftStickDown = Key.Unbound },
|
||||||
|
{ "LeftStickRight", () => ViewModel.Config.LeftStickRight = Key.Unbound },
|
||||||
|
{ "LeftStickLeft", () => ViewModel.Config.LeftStickLeft = Key.Unbound },
|
||||||
|
{ "DpadUp", () => ViewModel.Config.DpadUp = Key.Unbound },
|
||||||
|
{ "DpadDown", () => ViewModel.Config.DpadDown = Key.Unbound },
|
||||||
|
{ "DpadLeft", () => ViewModel.Config.DpadLeft = Key.Unbound },
|
||||||
|
{ "DpadRight", () => ViewModel.Config.DpadRight = Key.Unbound },
|
||||||
|
{ "LeftButtonSr", () => ViewModel.Config.LeftButtonSr = Key.Unbound },
|
||||||
|
{ "LeftButtonSl", () => ViewModel.Config.LeftButtonSl = Key.Unbound },
|
||||||
|
{ "RightButtonSr", () => ViewModel.Config.RightButtonSr = Key.Unbound },
|
||||||
|
{ "RightButtonSl", () => ViewModel.Config.RightButtonSl = Key.Unbound },
|
||||||
|
{ "ButtonZr", () => ViewModel.Config.ButtonZr = Key.Unbound },
|
||||||
|
{ "ButtonR", () => ViewModel.Config.ButtonR = Key.Unbound },
|
||||||
|
{ "ButtonPlus", () => ViewModel.Config.ButtonPlus = Key.Unbound },
|
||||||
|
{ "ButtonA", () => ViewModel.Config.ButtonA = Key.Unbound },
|
||||||
|
{ "ButtonB", () => ViewModel.Config.ButtonB = Key.Unbound },
|
||||||
|
{ "ButtonX", () => ViewModel.Config.ButtonX = Key.Unbound },
|
||||||
|
{ "ButtonY", () => ViewModel.Config.ButtonY = Key.Unbound },
|
||||||
|
{ "RightStickButton", () => ViewModel.Config.RightStickButton = Key.Unbound },
|
||||||
|
{ "RightStickUp", () => ViewModel.Config.RightStickUp = Key.Unbound },
|
||||||
|
{ "RightStickDown", () => ViewModel.Config.RightStickDown = Key.Unbound },
|
||||||
|
{ "RightStickRight", () => ViewModel.Config.RightStickRight = Key.Unbound },
|
||||||
|
{ "RightStickLeft", () => ViewModel.Config.RightStickLeft = Key.Unbound }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
ViewModel.ParentModel.IsModified = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DataContext is MidiInputViewModel)
|
|
||||||
{
|
|
||||||
AssignMidiBinding(button.Name, CreateUnboundMidiBinding());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AssignKeyboardBinding(button.Name, Key.Unbound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CancelCurrentAssignment(bool shouldUnbind = false)
|
|
||||||
{
|
|
||||||
if (_keyboardAssigner != null)
|
|
||||||
{
|
|
||||||
_keyboardAssigner.Cancel(shouldUnbind);
|
|
||||||
_keyboardAssigner = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_midiToggleButton != null)
|
|
||||||
{
|
|
||||||
_shouldUnbindMidi = shouldUnbind;
|
|
||||||
_midiCancellationTokenSource?.Cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ToggleButton GetCurrentToggleButton()
|
|
||||||
{
|
|
||||||
return _keyboardAssigner?.ToggledButton ?? _midiToggleButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IGamepad GetSelectedGamepad()
|
|
||||||
{
|
|
||||||
return DataContext switch
|
|
||||||
{
|
|
||||||
KeyboardInputViewModel keyboardViewModel => keyboardViewModel.ParentModel.SelectedGamepad,
|
|
||||||
MidiInputViewModel midiViewModel => midiViewModel.ParentModel.SelectedGamepad,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private IGamepad GetCancelKeyboard()
|
|
||||||
{
|
|
||||||
return DataContext switch
|
|
||||||
{
|
|
||||||
KeyboardInputViewModel keyboardViewModel => keyboardViewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"),
|
|
||||||
MidiInputViewModel midiViewModel => midiViewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"),
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AssignKeyboardBinding(string controlName, Key value)
|
|
||||||
{
|
|
||||||
if (DataContext is not KeyboardInputViewModel viewModel)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.ParentModel.IsModified = true;
|
|
||||||
|
|
||||||
Dictionary<string, Action> buttonActions = new()
|
|
||||||
{
|
|
||||||
{ "ButtonZl", () => viewModel.Config.ButtonZl = value },
|
|
||||||
{ "ButtonL", () => viewModel.Config.ButtonL = value },
|
|
||||||
{ "ButtonMinus", () => viewModel.Config.ButtonMinus = value },
|
|
||||||
{ "LeftStickButton", () => viewModel.Config.LeftStickButton = value },
|
|
||||||
{ "LeftStickUp", () => viewModel.Config.LeftStickUp = value },
|
|
||||||
{ "LeftStickDown", () => viewModel.Config.LeftStickDown = value },
|
|
||||||
{ "LeftStickRight", () => viewModel.Config.LeftStickRight = value },
|
|
||||||
{ "LeftStickLeft", () => viewModel.Config.LeftStickLeft = value },
|
|
||||||
{ "DpadUp", () => viewModel.Config.DpadUp = value },
|
|
||||||
{ "DpadDown", () => viewModel.Config.DpadDown = value },
|
|
||||||
{ "DpadLeft", () => viewModel.Config.DpadLeft = value },
|
|
||||||
{ "DpadRight", () => viewModel.Config.DpadRight = value },
|
|
||||||
{ "LeftButtonSr", () => viewModel.Config.LeftButtonSr = value },
|
|
||||||
{ "LeftButtonSl", () => viewModel.Config.LeftButtonSl = value },
|
|
||||||
{ "RightButtonSr", () => viewModel.Config.RightButtonSr = value },
|
|
||||||
{ "RightButtonSl", () => viewModel.Config.RightButtonSl = value },
|
|
||||||
{ "ButtonZr", () => viewModel.Config.ButtonZr = value },
|
|
||||||
{ "ButtonR", () => viewModel.Config.ButtonR = value },
|
|
||||||
{ "ButtonPlus", () => viewModel.Config.ButtonPlus = value },
|
|
||||||
{ "ButtonA", () => viewModel.Config.ButtonA = value },
|
|
||||||
{ "ButtonB", () => viewModel.Config.ButtonB = value },
|
|
||||||
{ "ButtonX", () => viewModel.Config.ButtonX = value },
|
|
||||||
{ "ButtonY", () => viewModel.Config.ButtonY = value },
|
|
||||||
{ "RightStickButton", () => viewModel.Config.RightStickButton = value },
|
|
||||||
{ "RightStickUp", () => viewModel.Config.RightStickUp = value },
|
|
||||||
{ "RightStickDown", () => viewModel.Config.RightStickDown = value },
|
|
||||||
{ "RightStickRight", () => viewModel.Config.RightStickRight = value },
|
|
||||||
{ "RightStickLeft", () => viewModel.Config.RightStickLeft = value },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (buttonActions.TryGetValue(controlName, out Action action))
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AssignMidiBinding(string controlName, MidiBinding value)
|
|
||||||
{
|
|
||||||
if (DataContext is not MidiInputViewModel viewModel)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.ParentModel.IsModified = true;
|
|
||||||
|
|
||||||
Dictionary<string, Action> buttonActions = new()
|
|
||||||
{
|
|
||||||
{ "ButtonZl", () => viewModel.Config.ButtonZl = value },
|
|
||||||
{ "ButtonL", () => viewModel.Config.ButtonL = value },
|
|
||||||
{ "ButtonMinus", () => viewModel.Config.ButtonMinus = value },
|
|
||||||
{ "LeftStickButton", () => viewModel.Config.LeftStickButton = value },
|
|
||||||
{ "LeftStickUp", () => viewModel.Config.LeftStickUp = value },
|
|
||||||
{ "LeftStickDown", () => viewModel.Config.LeftStickDown = value },
|
|
||||||
{ "LeftStickRight", () => viewModel.Config.LeftStickRight = value },
|
|
||||||
{ "LeftStickLeft", () => viewModel.Config.LeftStickLeft = value },
|
|
||||||
{ "DpadUp", () => viewModel.Config.DpadUp = value },
|
|
||||||
{ "DpadDown", () => viewModel.Config.DpadDown = value },
|
|
||||||
{ "DpadLeft", () => viewModel.Config.DpadLeft = value },
|
|
||||||
{ "DpadRight", () => viewModel.Config.DpadRight = value },
|
|
||||||
{ "LeftButtonSr", () => viewModel.Config.LeftButtonSr = value },
|
|
||||||
{ "LeftButtonSl", () => viewModel.Config.LeftButtonSl = value },
|
|
||||||
{ "RightButtonSr", () => viewModel.Config.RightButtonSr = value },
|
|
||||||
{ "RightButtonSl", () => viewModel.Config.RightButtonSl = value },
|
|
||||||
{ "ButtonZr", () => viewModel.Config.ButtonZr = value },
|
|
||||||
{ "ButtonR", () => viewModel.Config.ButtonR = value },
|
|
||||||
{ "ButtonPlus", () => viewModel.Config.ButtonPlus = value },
|
|
||||||
{ "ButtonA", () => viewModel.Config.ButtonA = value },
|
|
||||||
{ "ButtonB", () => viewModel.Config.ButtonB = value },
|
|
||||||
{ "ButtonX", () => viewModel.Config.ButtonX = value },
|
|
||||||
{ "ButtonY", () => viewModel.Config.ButtonY = value },
|
|
||||||
{ "RightStickButton", () => viewModel.Config.RightStickButton = value },
|
|
||||||
{ "RightStickUp", () => viewModel.Config.RightStickUp = value },
|
|
||||||
{ "RightStickDown", () => viewModel.Config.RightStickDown = value },
|
|
||||||
{ "RightStickRight", () => viewModel.Config.RightStickRight = value },
|
|
||||||
{ "RightStickLeft", () => viewModel.Config.RightStickLeft = value },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (buttonActions.TryGetValue(controlName, out Action action))
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MidiBinding CreateUnboundMidiBinding()
|
|
||||||
{
|
|
||||||
return new MidiBinding
|
|
||||||
{
|
|
||||||
Kind = MidiBindingKind.Unbound,
|
|
||||||
Threshold = 1,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDetachedFromVisualTree(e);
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
_currentAssigner?.Cancel();
|
||||||
CancelCurrentAssignment();
|
_currentAssigner = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,18 @@
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
|
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
|
||||||
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
|
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
|
||||||
<ColorPicker
|
<ui:ColorPickerButton
|
||||||
Margin="5"
|
Margin="5"
|
||||||
|
IsMoreButtonVisible="False"
|
||||||
|
UseColorPalette="False"
|
||||||
|
UseColorTriangle="False"
|
||||||
|
UseColorWheel="False"
|
||||||
|
ShowAcceptDismissButtons="False"
|
||||||
IsAlphaEnabled="False"
|
IsAlphaEnabled="False"
|
||||||
AttachedToVisualTree="ColorPicker_OnAttachedToVisualTree"
|
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
|
||||||
ColorChanged="ColorPicker_OnColorChanged"
|
ColorChanged="ColorPickerButton_OnColorChanged"
|
||||||
Color="{Binding LedColor, Mode=TwoWay}">
|
Color="{Binding LedColor, Mode=TwoWay}">
|
||||||
</ColorPicker>
|
</ui:ColorPickerButton>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
|
|
@ -31,17 +30,19 @@ namespace Ryujinx.UI.Views.Input
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorPicker_OnColorChanged(object sender, ColorChangedEventArgs args)
|
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
|
||||||
{
|
{
|
||||||
|
if (!args.NewColor.HasValue)
|
||||||
|
return;
|
||||||
if (!ViewModel.EnableLedChanging)
|
if (!ViewModel.EnableLedChanging)
|
||||||
return;
|
return;
|
||||||
if (ViewModel.TurnOffLed)
|
if (ViewModel.TurnOffLed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.ToUInt32());
|
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorPicker_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
if (!ViewModel.EnableLedChanging)
|
if (!ViewModel.EnableLedChanging)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -167,12 +167,6 @@
|
||||||
Icon="{ext:Icon fa-solid fa-stop}"
|
Icon="{ext:Icon fa-solid fa-stop}"
|
||||||
InputGesture="Escape"
|
InputGesture="Escape"
|
||||||
IsEnabled="{Binding IsGameRunning}" />
|
IsEnabled="{Binding IsGameRunning}" />
|
||||||
<MenuItem
|
|
||||||
Name="RestartEmulationMenuItem"
|
|
||||||
Header="{ext:Locale MenuBarOptionsRestartEmulation}"
|
|
||||||
Icon="{ext:Icon fa-solid fa-rotate-right}"
|
|
||||||
InputGesture="Ctrl + R"
|
|
||||||
IsEnabled="{Binding IsGameRunning}" />
|
|
||||||
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" />
|
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||||
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
|
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
|
||||||
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
|
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
|
||||||
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
|
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
|
||||||
RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation());
|
|
||||||
CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp);
|
CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp);
|
||||||
InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes);
|
InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes);
|
||||||
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);
|
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
<Button
|
<Button
|
||||||
Name="SaveButton"
|
Name="SaveButton"
|
||||||
Click="SaveButton_Click">
|
Click="SaveButton_Click">
|
||||||
<TextBlock Text="{ext:Locale UserProfilesSave}" />
|
<TextBlock Text="{ext:Locale UserProfilesSetProfileImage}" />
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -78,16 +78,22 @@
|
||||||
Spacing="10"
|
Spacing="10"
|
||||||
Margin="0 24 0 0"
|
Margin="0 24 0 0"
|
||||||
HorizontalAlignment="Right">
|
HorizontalAlignment="Right">
|
||||||
<ColorPicker
|
<ui:ColorPickerButton
|
||||||
|
FlyoutPlacement="Top"
|
||||||
|
IsMoreButtonVisible="False"
|
||||||
|
UseColorPalette="False"
|
||||||
|
UseColorTriangle="False"
|
||||||
|
UseColorWheel="False"
|
||||||
|
ShowAcceptDismissButtons="False"
|
||||||
IsAlphaEnabled="False"
|
IsAlphaEnabled="False"
|
||||||
Color="{Binding BackgroundColor, Mode=TwoWay}"
|
Color="{Binding BackgroundColor, Mode=TwoWay}"
|
||||||
Name="ColorButton">
|
Name="ColorButton">
|
||||||
<ColorPicker.Styles>
|
<ui:ColorPickerButton.Styles>
|
||||||
<Style Selector="Grid#Root > DockPanel > Grid">
|
<Style Selector="Grid#Root > DockPanel > Grid">
|
||||||
<Setter Property="IsVisible" Value="False" />
|
<Setter Property="IsVisible" Value="False" />
|
||||||
</Style>
|
</Style>
|
||||||
</ColorPicker.Styles>
|
</ui:ColorPickerButton.Styles>
|
||||||
</ColorPicker>
|
</ui:ColorPickerButton>
|
||||||
<Button
|
<Button
|
||||||
Content="{ext:Locale AvatarChoose}"
|
Content="{ext:Locale AvatarChoose}"
|
||||||
Height="35"
|
Height="35"
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@
|
||||||
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
|
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
|
||||||
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
|
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
|
||||||
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
|
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
|
||||||
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
|
|
||||||
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
|
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
|
||||||
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
|
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
|
||||||
</Window.KeyBindings>
|
</Window.KeyBindings>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using Ryujinx.Input.Midi;
|
|
||||||
using Ryujinx.Input.SDL3;
|
using Ryujinx.Input.SDL3;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -106,7 +105,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver(), new MidiGamepadDriver());
|
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver());
|
||||||
|
|
||||||
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
|
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
|
||||||
this.ScalingChanged += OnScalingChanged;
|
this.ScalingChanged += OnScalingChanged;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -13,42 +11,29 @@ namespace Ryujinx.Ava.Utilities
|
||||||
extension(IStorageProvider storageProvider)
|
extension(IStorageProvider storageProvider)
|
||||||
{
|
{
|
||||||
public Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
|
public Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
|
||||||
CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false)))
|
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false))
|
||||||
.Then(folders => folders.FindFirst());
|
.Then(folders => folders.FindFirst());
|
||||||
|
|
||||||
public Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
|
public Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
|
||||||
CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false)))
|
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false))
|
||||||
.Then(files => files.FindFirst());
|
.Then(files => files.FindFirst());
|
||||||
|
|
||||||
public Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
|
public Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
|
||||||
CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true)))
|
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true))
|
||||||
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
|
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
|
||||||
|
|
||||||
public Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
|
public Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
|
||||||
CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true)))
|
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true))
|
||||||
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
|
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<T> CoreDumpable<T>(Func<Task<T>> picker)
|
|
||||||
{
|
|
||||||
OsUtils.SetCoreDumpable(true);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await picker();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!Program.CoreDumpArg)
|
|
||||||
OsUtils.SetCoreDumpable(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
|
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
|
||||||
{
|
{
|
||||||
if (openOptions is null)
|
if (openOptions is null)
|
||||||
return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
|
return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
|
||||||
|
|
||||||
openOptions.AllowMultiple = allowMultiple;
|
openOptions.AllowMultiple = allowMultiple;
|
||||||
|
|
||||||
return openOptions;
|
return openOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +43,7 @@ namespace Ryujinx.Ava.Utilities
|
||||||
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
|
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
|
||||||
|
|
||||||
openOptions.AllowMultiple = allowMultiple;
|
openOptions.AllowMultiple = allowMultiple;
|
||||||
|
|
||||||
return openOptions;
|
return openOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue