Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Motion controls mainly for Windows handhelds #610

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Alimer.Bindings.SDL" Version="3.7.1" />
<PackageVersion Include="Avalonia" Version="11.0.13" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.13" />
Expand Down
6 changes: 6 additions & 0 deletions Ryujinx.sln
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input.SDL3", "src\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj", "{3BF24278-547D-42C2-9D43-182B978F54DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
Expand Down Expand Up @@ -259,6 +261,10 @@ Global
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public override MotionConfigController Read(ref Utf8JsonReader reader, Type type
return motionBackendType switch
{
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardMotionConfigController),
MotionInputBackendType.Handheld => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardMotionConfigController),
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, _serializerContext.CemuHookMotionConfigController),
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
};
Expand All @@ -66,6 +67,7 @@ public override void Write(Utf8JsonWriter writer, MotionConfigController value,
switch (value.MotionBackend)
{
case MotionInputBackendType.GamepadDriver:
case MotionInputBackendType.Handheld:
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, _serializerContext.StandardMotionConfigController);
break;
case MotionInputBackendType.CemuHook:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public enum MotionInputBackendType : byte
Invalid,
GamepadDriver,
CemuHook,
Handheld,
}
}
13 changes: 13 additions & 0 deletions src/Ryujinx.Input.SDL3/Ryujinx.Input.SDL3.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<PackageReference Include="Alimer.Bindings.SDL" />
</ItemGroup>

</Project>
85 changes: 85 additions & 0 deletions src/Ryujinx.Input.SDL3/SDL3MotionDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using SDL3;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using static SDL3.SDL3;

namespace Ryujinx.Input.SDL3
{
public unsafe class SDL3MotionDriver : IHandheld, IDisposable
{
private readonly Dictionary<SDL_SensorType, SDL_Sensor> sensors;
private bool _disposed;

public SDL3MotionDriver()
{
int result = SDL_Init(SDL_InitFlags.Sensor);
if (result < 0)
{
throw new InvalidOperationException($"SDL sensor initialization failed: {SDL_GetError()}");
}
sensors = SDL_GetSensors().ToArray().ToDictionary(SDL_GetSensorTypeForID, SDL_OpenSensor);
}

~SDL3MotionDriver()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing && sensors != null)
{
foreach (var sensor in sensors.Values)
{
if (sensor != IntPtr.Zero)
{
SDL_CloseSensor(sensor);
}
}
}

_disposed = true;
}

public Vector3 GetMotionData(MotionInputId inputType)
{
ObjectDisposedException.ThrowIf(_disposed, this);

return inputType switch
{
MotionInputId.Gyroscope => GetSensorVector(SDL_SensorType.Gyro) * 180 / MathF.PI,
MotionInputId.Accelerometer => GetSensorVector(SDL_SensorType.Accel) / SDL_STANDARD_GRAVITY,
_ => Vector3.Zero
};
}

private Vector3 GetSensorVector(SDL_SensorType sensorType)
{
if (!sensors.TryGetValue(sensorType, out SDL_Sensor sensor))
{
return Vector3.Zero;
}

var data = stackalloc float[3];
if (SDL_GetSensorData(sensor, data, 3) < 0)
{
return Vector3.Zero;
}

return new Vector3(data[0], data[1], data[2]);
}
}
}
6 changes: 4 additions & 2 deletions src/Ryujinx.Input/HLE/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

namespace Ryujinx.Input.HLE
{
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver)
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IHandheld handheld)
: IDisposable
{
public IGamepadDriver KeyboardDriver { get; } = keyboardDriver;
public IGamepadDriver GamepadDriver { get; } = gamepadDriver;
public IGamepadDriver MouseDriver { get; private set; }
public IHandheld Handheld { get; } = handheld;

public void SetMouseDriver(IGamepadDriver mouseDriver)
{
Expand All @@ -18,7 +19,7 @@ public void SetMouseDriver(IGamepadDriver mouseDriver)

public NpadManager CreateNpadManager()
{
return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver);
return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver, Handheld);
}

public TouchScreenManager CreateTouchScreenManager()
Expand All @@ -38,6 +39,7 @@ protected virtual void Dispose(bool disposing)
KeyboardDriver?.Dispose();
GamepadDriver?.Dispose();
MouseDriver?.Dispose();
Handheld?.Dispose();
}
}

Expand Down
16 changes: 15 additions & 1 deletion src/Ryujinx.Input/HLE/NpadController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,14 @@ public HLEKeyboardMappingEntry(Key targetKey, byte target)
public string Id { get; private set; }

private readonly CemuHookClient _cemuHookClient;
private readonly IHandheld _handheld;

public NpadController(CemuHookClient cemuHookClient)
public NpadController(CemuHookClient cemuHookClient, IHandheld handheld)
{
State = default;
Id = null;
_cemuHookClient = cemuHookClient;
_handheld = handheld;
}

public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config)
Expand Down Expand Up @@ -287,6 +289,18 @@ public void Update()

if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
{
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.Handheld)
{
Vector3 accelerometer = _handheld.GetMotionData(MotionInputId.Accelerometer);
Vector3 gyroscope = _handheld.GetMotionData(MotionInputId.Gyroscope);

accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);

_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
_rightMotionInput = _leftMotionInput;
}

if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
{
if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))
Expand Down
6 changes: 4 additions & 2 deletions src/Ryujinx.Input/HLE/NpadManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ public class NpadManager : IDisposable
private readonly IGamepadDriver _keyboardDriver;
private readonly IGamepadDriver _gamepadDriver;
private readonly IGamepadDriver _mouseDriver;
private readonly IHandheld _handheld;
private bool _isDisposed;

private List<InputConfig> _inputConfig;
private bool _enableKeyboard;
private bool _enableMouse;
private Switch _device;

public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver, IHandheld handheld)
{
_controllers = new NpadController[MaxControllers];
_cemuHookClient = new CemuHookClient(this);
Expand All @@ -47,6 +48,7 @@ public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver,
_gamepadDriver = gamepadDriver;
_mouseDriver = mouseDriver;
_inputConfig = [];
_handheld = handheld;

_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
Expand Down Expand Up @@ -139,7 +141,7 @@ public void ReloadConfiguration(List<InputConfig> inputConfig, bool enableKeyboa
}
else
{
controller = new(_cemuHookClient);
controller = new(_cemuHookClient, _handheld);
}

bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
Expand Down
10 changes: 10 additions & 0 deletions src/Ryujinx.Input/IHandheld.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Numerics;

namespace Ryujinx.Input
{
public interface IHandheld : IDisposable
{
Vector3 GetMotionData(MotionInputId gyroscope);
}
}
27 changes: 26 additions & 1 deletion src/Ryujinx/Assets/locales.json
Original file line number Diff line number Diff line change
Expand Up @@ -7522,6 +7522,31 @@
"zh_TW": "使用與 CemuHook 相容的體感"
}
},
{
"ID": "ControllerSettingsMotionUseHandheldCompatibleMotion",
"Translations": {
"ar_SA": "استخدام الحركة المتوافقة مع Hendheld",
"de_DE": "Hendheld kompatible Bewegungssteuerung",
"el_GR": "Κίνηση συμβατή με Hendheld",
"en_US": "Use Hendheld compatible motion",
"es_ES": "Usar movimiento compatible con Hendheld",
"fr_FR": "Utiliser un capteur de mouvements Hendheld",
"he_IL": "השתמש בתנועת Hendheld תואמת ",
"it_IT": "Usa sensore compatibile con Hendheld",
"ja_JP": "Hendheld 互換モーションを使用",
"ko_KR": "Hendheld 호환 모션 사용",
"no_NO": "Bruk Hendheld kompatibel bevegelse",
"pl_PL": "Użyj ruchu zgodnego z Hendheld",
"pt_BR": "Usar sensor compatível com Hendheld",
"ru_RU": "Включить совместимость с Hendheld",
"sv_SE": "Använd Hendheld-kompatibel rörelse",
"th_TH": "ใช้การเคลื่อนไหวที่เข้ากันได้กับ Hendheld",
"tr_TR": "Hendheld uyumlu hareket kullan",
"uk_UA": "Використовувати рух, сумісний з Hendheld",
"zh_CN": "使用 Hendheld 兼容的体感协议",
"zh_TW": "使用與 Hendheld 相容的體感"
}
},
{
"ID": "ControllerSettingsMotionControllerSlot",
"Translations": {
Expand Down Expand Up @@ -23173,4 +23198,4 @@
}
}
]
}
}
3 changes: 2 additions & 1 deletion src/Ryujinx/Headless/HeadlessRyujinx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -182,7 +183,7 @@ static void Load(string[] originalArgs, Options option)
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();

_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver(), new SDL3MotionDriver());

GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;

Expand Down
1 change: 1 addition & 0 deletions src/Ryujinx/Ryujinx.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
Expand Down
5 changes: 3 additions & 2 deletions src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Ryujinx.Ava.UI.Models.Input
public class GamepadInputConfig : BaseModel
{
public bool EnableCemuHookMotion { get; set; }
public bool EnableHandheldMotion { get; set; }
public string DsuServerHost { get; set; }
public int DsuServerPort { get; set; }
public int Slot { get; set; }
Expand Down Expand Up @@ -518,7 +519,7 @@ public GamepadInputConfig(InputConfig config)
EnableMotion = controllerInput.Motion.EnableMotion;
GyroDeadzone = controllerInput.Motion.GyroDeadzone;
Sensitivity = controllerInput.Motion.Sensitivity;

EnableHandheldMotion = controllerInput.Motion.MotionBackend == MotionInputBackendType.Handheld;
if (controllerInput.Motion is CemuHookMotionConfigController cemuHook)
{
EnableCemuHookMotion = true;
Expand Down Expand Up @@ -641,7 +642,7 @@ public InputConfig GetConfig()
config.Motion = new StandardMotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.GamepadDriver,
MotionBackend = EnableHandheldMotion ? MotionInputBackendType.Handheld : MotionInputBackendType.GamepadDriver,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity,
};
Expand Down
30 changes: 29 additions & 1 deletion src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@ public partial class MotionInputViewModel : BaseModel

[ObservableProperty] private double _gyroDeadzone;

[ObservableProperty] private bool _enableCemuHookMotion;
private bool _enableCemuHookMotion;
public bool EnableCemuHookMotion
{
get => _enableCemuHookMotion;
set
{
if (value)
{
EnableHandheldMotion = false;
}
_enableCemuHookMotion = value;
OnPropertyChanged();
}
}

private bool _enableHandheldMotion;
public bool EnableHandheldMotion
{
get => _enableHandheldMotion;
set
{
if (value)
{
EnableCemuHookMotion = false;
}
_enableHandheldMotion = value;
OnPropertyChanged();
}
}
}
}
Loading