Add Motion controls (#1363)
* Add motion controls Apply suggestions from code review Co-authored-by: Ac_K <Acoustik666@gmail.com> * cleanup * add reference orientation and derive relative orientation from it * cleanup * remove unused variable and strange file * Review_2. * change GetInput to TryGetInput * Review_3. Co-authored-by: Ac_K <Acoustik666@gmail.com> Co-authored-by: LDj3SNuD <dvitiello@gmail.com>
This commit is contained in:
parent
a6f8a0b01e
commit
26319d5ab3
24 changed files with 1780 additions and 63 deletions
|
@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 14;
|
public const int CurrentVersion = 15;
|
||||||
|
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -483,12 +483,10 @@ namespace Ryujinx.Configuration
|
||||||
Ui.EnableCustomTheme.Value = false;
|
Ui.EnableCustomTheme.Value = false;
|
||||||
Ui.CustomThemePath.Value = "";
|
Ui.CustomThemePath.Value = "";
|
||||||
Hid.EnableKeyboard.Value = false;
|
Hid.EnableKeyboard.Value = false;
|
||||||
|
|
||||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||||
{
|
{
|
||||||
ToggleVsync = Key.Tab
|
ToggleVsync = Key.Tab
|
||||||
};
|
};
|
||||||
|
|
||||||
Hid.InputConfig.Value = new List<InputConfig>
|
Hid.InputConfig.Value = new List<InputConfig>
|
||||||
{
|
{
|
||||||
new KeyboardConfig
|
new KeyboardConfig
|
||||||
|
@ -529,7 +527,15 @@ namespace Ryujinx.Configuration
|
||||||
ButtonZr = Key.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = Key.PageUp,
|
ButtonSl = Key.PageUp,
|
||||||
ButtonSr = Key.PageDown
|
ButtonSr = Key.PageDown
|
||||||
}
|
},
|
||||||
|
EnableMotion = false,
|
||||||
|
MirrorInput = false,
|
||||||
|
Slot = 0,
|
||||||
|
AltSlot = 0,
|
||||||
|
Sensitivity = 100,
|
||||||
|
GyroDeadzone = 1,
|
||||||
|
DsuServerHost = "127.0.0.1",
|
||||||
|
DsuServerPort = 26760
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -628,7 +634,15 @@ namespace Ryujinx.Configuration
|
||||||
ButtonZr = Key.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = Key.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = Key.Unbound
|
ButtonSr = Key.Unbound
|
||||||
}
|
},
|
||||||
|
EnableMotion = false,
|
||||||
|
MirrorInput = false,
|
||||||
|
Slot = 0,
|
||||||
|
AltSlot = 0,
|
||||||
|
Sensitivity = 100,
|
||||||
|
GyroDeadzone = 1,
|
||||||
|
DsuServerHost = "127.0.0.1",
|
||||||
|
DsuServerPort = 26760
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,5 +16,45 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
/// Player's Index for the controller
|
/// Player's Index for the controller
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PlayerIndex PlayerIndex { get; set; }
|
public PlayerIndex PlayerIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Motion Controller Slot
|
||||||
|
/// </summary>
|
||||||
|
public int Slot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Motion Controller Alternative Slot, for RightJoyCon in Pair mode
|
||||||
|
/// </summary>
|
||||||
|
public int AltSlot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirror motion input in Pair mode
|
||||||
|
/// </summary>
|
||||||
|
public bool MirrorInput { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Host address of the DSU Server
|
||||||
|
/// </summary>
|
||||||
|
public string DsuServerHost { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Port of the DSU Server
|
||||||
|
/// </summary>
|
||||||
|
public int DsuServerPort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gyro Sensitivity
|
||||||
|
/// </summary>
|
||||||
|
public int Sensitivity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gyro Deadzone
|
||||||
|
/// </summary>
|
||||||
|
public double GyroDeadzone { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable Motion Controls
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableMotion { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
{
|
{
|
||||||
|
@ -317,6 +317,89 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry;
|
mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static SixAxixLayoutsIndex ControllerTypeToSixAxisLayout(ControllerType controllerType)
|
||||||
|
=> controllerType switch
|
||||||
|
{
|
||||||
|
ControllerType.ProController => SixAxixLayoutsIndex.ProController,
|
||||||
|
ControllerType.Handheld => SixAxixLayoutsIndex.Handheld,
|
||||||
|
ControllerType.JoyconPair => SixAxixLayoutsIndex.JoyDualLeft,
|
||||||
|
ControllerType.JoyconLeft => SixAxixLayoutsIndex.JoyLeft,
|
||||||
|
ControllerType.JoyconRight => SixAxixLayoutsIndex.JoyRight,
|
||||||
|
ControllerType.Pokeball => SixAxixLayoutsIndex.Pokeball,
|
||||||
|
_ => SixAxixLayoutsIndex.SystemExternal
|
||||||
|
};
|
||||||
|
|
||||||
|
public void UpdateSixAxis(IList<SixAxisInput> states)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < states.Count; ++i)
|
||||||
|
{
|
||||||
|
if (SetSixAxisState(states[i]))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
|
||||||
|
SetSixAxisState(states[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
|
||||||
|
{
|
||||||
|
if (state.PlayerId == PlayerIndex.Unknown)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId];
|
||||||
|
|
||||||
|
if (currentNpad.Header.Type == ControllerType.None)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HidVector accel = new HidVector()
|
||||||
|
{
|
||||||
|
X = state.Accelerometer.X,
|
||||||
|
Y = state.Accelerometer.Y,
|
||||||
|
Z = state.Accelerometer.Z
|
||||||
|
};
|
||||||
|
|
||||||
|
HidVector gyro = new HidVector()
|
||||||
|
{
|
||||||
|
X = state.Gyroscope.X,
|
||||||
|
Y = state.Gyroscope.Y,
|
||||||
|
Z = state.Gyroscope.Z
|
||||||
|
};
|
||||||
|
|
||||||
|
HidVector rotation = new HidVector()
|
||||||
|
{
|
||||||
|
X = state.Rotation.X,
|
||||||
|
Y = state.Rotation.Y,
|
||||||
|
Z = state.Rotation.Z
|
||||||
|
};
|
||||||
|
|
||||||
|
ref NpadSixAxis currentLayout = ref currentNpad.Sixaxis[(int)ControllerTypeToSixAxisLayout(currentNpad.Header.Type) + (isRightPair ? 1 : 0)];
|
||||||
|
ref SixAxisState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
|
||||||
|
|
||||||
|
int previousEntryIndex = (int)(currentLayout.Header.LatestEntry == 0 ?
|
||||||
|
currentLayout.Header.MaxEntryIndex : currentLayout.Header.LatestEntry - 1);
|
||||||
|
|
||||||
|
ref SixAxisState previousEntry = ref currentLayout.Entries[previousEntryIndex];
|
||||||
|
|
||||||
|
currentEntry.Accelerometer = accel;
|
||||||
|
currentEntry.Gyroscope = gyro;
|
||||||
|
currentEntry.Rotations = rotation;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 9; i++)
|
||||||
|
{
|
||||||
|
currentEntry.Orientation[i] = state.Orientation[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentNpad.Header.Type == ControllerType.JoyconPair && !isRightPair;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateAllEntries()
|
private void UpdateAllEntries()
|
||||||
{
|
{
|
||||||
ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
|
ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
|
||||||
|
@ -359,6 +442,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref Array6<NpadSixAxis> sixaxis = ref controllers[i].Sixaxis;
|
||||||
|
for (int l = 0; l < sixaxis.Length; ++l)
|
||||||
|
{
|
||||||
|
ref NpadSixAxis currentLayout = ref sixaxis[l];
|
||||||
|
int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
|
||||||
|
|
||||||
|
ref SixAxisState currentEntry = ref currentLayout.Entries[currentIndex];
|
||||||
|
SixAxisState previousEntry = currentLayout.Entries[previousIndex];
|
||||||
|
|
||||||
|
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
|
||||||
|
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
|
||||||
|
|
||||||
|
currentEntry._unknown2 = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
|
{
|
||||||
|
public struct SixAxisInput
|
||||||
|
{
|
||||||
|
public PlayerIndex PlayerId;
|
||||||
|
public Vector3 Accelerometer;
|
||||||
|
public Vector3 Gyroscope;
|
||||||
|
public Vector3 Rotation;
|
||||||
|
public float[] Orientation;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
|
{
|
||||||
|
enum SixAxixLayoutsIndex : int
|
||||||
|
{
|
||||||
|
ProController = 0,
|
||||||
|
Handheld = 1,
|
||||||
|
JoyDualLeft = 2,
|
||||||
|
JoyDualRight = 3,
|
||||||
|
JoyLeft = 4,
|
||||||
|
JoyRight = 5,
|
||||||
|
Pokeball = 6,
|
||||||
|
SystemExternal = 7
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,8 +7,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||||
public ulong SampleTimestamp2;
|
public ulong SampleTimestamp2;
|
||||||
public HidVector Accelerometer;
|
public HidVector Accelerometer;
|
||||||
public HidVector Gyroscope;
|
public HidVector Gyroscope;
|
||||||
HidVector unknownSensor;
|
public HidVector Rotations;
|
||||||
public fixed float Orientation[9];
|
public fixed float Orientation[9];
|
||||||
ulong _unknown2;
|
public ulong _unknown2;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": 14,
|
"version": 15,
|
||||||
"res_scale": 1,
|
"res_scale": 1,
|
||||||
"res_scale_custom": 1,
|
"res_scale_custom": 1,
|
||||||
"max_anisotropy": -1,
|
"max_anisotropy": -1,
|
||||||
|
@ -86,7 +86,14 @@
|
||||||
"button_zr": "O",
|
"button_zr": "O",
|
||||||
"button_sl": "Unbound",
|
"button_sl": "Unbound",
|
||||||
"button_sr": "Unbound"
|
"button_sr": "Unbound"
|
||||||
}
|
},
|
||||||
|
"slot": 0,
|
||||||
|
"alt_slot": 0,
|
||||||
|
"mirror_input": false,
|
||||||
|
"dsu_server_host": "127.0.0.1",
|
||||||
|
"dsu_server_port": 26760,
|
||||||
|
"sensitivity": 100,
|
||||||
|
"enable_motion": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controller_config": []
|
"controller_config": []
|
||||||
|
|
393
Ryujinx/Motion/Client.cs
Normal file
393
Ryujinx/Motion/Client.cs
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
using Force.Crc32;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
public class Client : IDisposable
|
||||||
|
{
|
||||||
|
public const uint Magic = 0x43555344; // DSUC
|
||||||
|
public const ushort Version = 1001;
|
||||||
|
|
||||||
|
private bool _active;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, IPEndPoint> _hosts;
|
||||||
|
private readonly Dictionary<int, Dictionary<int, MotionInput>> _motionData;
|
||||||
|
private readonly Dictionary<int, UdpClient> _clients;
|
||||||
|
|
||||||
|
private bool[] _clientErrorStatus = new bool[Enum.GetValues(typeof(PlayerIndex)).Length];
|
||||||
|
|
||||||
|
public Client()
|
||||||
|
{
|
||||||
|
_hosts = new Dictionary<int, IPEndPoint>();
|
||||||
|
_motionData = new Dictionary<int, Dictionary<int, MotionInput>>();
|
||||||
|
_clients = new Dictionary<int, UdpClient>();
|
||||||
|
|
||||||
|
CloseClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseClients()
|
||||||
|
{
|
||||||
|
_active = false;
|
||||||
|
|
||||||
|
lock (_clients)
|
||||||
|
{
|
||||||
|
foreach (var client in _clients)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.Value?.Dispose();
|
||||||
|
}
|
||||||
|
#pragma warning disable CS0168
|
||||||
|
catch (SocketException ex)
|
||||||
|
#pragma warning restore CS0168
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to dispose motion client. Error code {ex.ErrorCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_hosts.Clear();
|
||||||
|
_clients.Clear();
|
||||||
|
_motionData.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterClient(int player, string host, int port)
|
||||||
|
{
|
||||||
|
if (_clients.ContainsKey(player))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_clients)
|
||||||
|
{
|
||||||
|
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(host), port);
|
||||||
|
|
||||||
|
UdpClient client = new UdpClient(host, port);
|
||||||
|
|
||||||
|
_clients.Add(player, client);
|
||||||
|
_hosts.Add(player, endPoint);
|
||||||
|
|
||||||
|
_active = true;
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
ReceiveLoop(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FormatException fex)
|
||||||
|
{
|
||||||
|
if (!_clientErrorStatus[player])
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error {fex.Message}");
|
||||||
|
|
||||||
|
_clientErrorStatus[player] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
if (!_clientErrorStatus[player])
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error code {ex.ErrorCode}");
|
||||||
|
|
||||||
|
_clientErrorStatus[player] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetData(int player, int slot, out MotionInput input)
|
||||||
|
{
|
||||||
|
lock (_motionData)
|
||||||
|
{
|
||||||
|
if (_motionData.ContainsKey(player))
|
||||||
|
{
|
||||||
|
input = _motionData[player][slot];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input = null;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Send(byte[] data, int clientId)
|
||||||
|
{
|
||||||
|
if (_clients.TryGetValue(clientId, out UdpClient _client))
|
||||||
|
{
|
||||||
|
if (_client != null && _client.Client != null && _client.Client.Connected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_client?.Send(data, data.Length);
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
if (!_clientErrorStatus[clientId])
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to send data request to motion source at {_client.Client.RemoteEndPoint}. Error code {ex.ErrorCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_clientErrorStatus[clientId] = true;
|
||||||
|
|
||||||
|
_clients.Remove(clientId);
|
||||||
|
|
||||||
|
_hosts.Remove(clientId);
|
||||||
|
|
||||||
|
_client?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] Receive(int clientId)
|
||||||
|
{
|
||||||
|
if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint))
|
||||||
|
{
|
||||||
|
if (_clients.TryGetValue(clientId, out UdpClient _client))
|
||||||
|
{
|
||||||
|
if (_client != null && _client.Client != null)
|
||||||
|
{
|
||||||
|
if (_client.Client.Connected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = _client?.Receive(ref endPoint);
|
||||||
|
|
||||||
|
if (result.Length > 0)
|
||||||
|
{
|
||||||
|
_clientErrorStatus[clientId] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
if (!_clientErrorStatus[clientId])
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to receive data from motion source at {endPoint}. Error code {ex.ErrorCode}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_clientErrorStatus[clientId] = true;
|
||||||
|
|
||||||
|
_clients.Remove(clientId);
|
||||||
|
|
||||||
|
_hosts.Remove(clientId);
|
||||||
|
|
||||||
|
_client?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveLoop(int clientId)
|
||||||
|
{
|
||||||
|
while (_active)
|
||||||
|
{
|
||||||
|
byte[] data = Receive(clientId);
|
||||||
|
|
||||||
|
if (data.Length == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS4014
|
||||||
|
HandleResponse(data, clientId);
|
||||||
|
#pragma warning restore CS4014
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS1998
|
||||||
|
public async Task HandleResponse(byte[] data, int clientId)
|
||||||
|
#pragma warning restore CS1998
|
||||||
|
{
|
||||||
|
MessageType type = (MessageType)BitConverter.ToUInt32(data.AsSpan().Slice(16, 4));
|
||||||
|
|
||||||
|
data = data.AsSpan().Slice(16).ToArray();
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (BinaryReader reader = new BinaryReader(mem))
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MessageType.Protocol:
|
||||||
|
break;
|
||||||
|
case MessageType.Info:
|
||||||
|
ControllerInfoResponse contollerInfo = reader.ReadStruct<ControllerInfoResponse>();
|
||||||
|
break;
|
||||||
|
case MessageType.Data:
|
||||||
|
ControllerDataResponse inputData = reader.ReadStruct<ControllerDataResponse>();
|
||||||
|
|
||||||
|
Vector3 accelerometer = new Vector3()
|
||||||
|
{
|
||||||
|
X = -inputData.AccelerometerX,
|
||||||
|
Y = inputData.AccelerometerZ,
|
||||||
|
Z = -inputData.AccelerometerY
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3 gyroscrope = new Vector3()
|
||||||
|
{
|
||||||
|
X = inputData.GyroscopePitch,
|
||||||
|
Y = inputData.GyroscopeRoll,
|
||||||
|
Z = -inputData.GyroscopeYaw
|
||||||
|
};
|
||||||
|
|
||||||
|
ulong timestamp = inputData.MotionTimestamp;
|
||||||
|
|
||||||
|
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == (PlayerIndex)clientId);
|
||||||
|
|
||||||
|
lock (_motionData)
|
||||||
|
{
|
||||||
|
int slot = inputData.Shared.Slot;
|
||||||
|
|
||||||
|
if (_motionData.ContainsKey(clientId))
|
||||||
|
{
|
||||||
|
if (_motionData[clientId].ContainsKey(slot))
|
||||||
|
{
|
||||||
|
var previousData = _motionData[clientId][slot];
|
||||||
|
|
||||||
|
previousData.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MotionInput input = new MotionInput();
|
||||||
|
input.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
|
||||||
|
_motionData[clientId].Add(slot, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MotionInput input = new MotionInput();
|
||||||
|
input.Update(accelerometer, gyroscrope, timestamp, config.Sensitivity, (float)config.GyroDeadzone);
|
||||||
|
_motionData.Add(clientId, new Dictionary<int, MotionInput>() { { slot, input } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestInfo(int clientId, int slot)
|
||||||
|
{
|
||||||
|
if (!_active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header header = GenerateHeader(clientId);
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (BinaryWriter writer = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
writer.WriteStruct(header);
|
||||||
|
|
||||||
|
ControllerInfoRequest request = new ControllerInfoRequest()
|
||||||
|
{
|
||||||
|
Type = MessageType.Info,
|
||||||
|
PortsCount = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
request.PortIndices[0] = (byte)slot;
|
||||||
|
|
||||||
|
writer.WriteStruct(request);
|
||||||
|
|
||||||
|
header.Length = (ushort)(mem.Length - 16);
|
||||||
|
|
||||||
|
writer.Seek(6, SeekOrigin.Begin);
|
||||||
|
writer.Write(header.Length);
|
||||||
|
|
||||||
|
header.Crc32 = Crc32Algorithm.Compute(mem.ToArray());
|
||||||
|
|
||||||
|
writer.Seek(8, SeekOrigin.Begin);
|
||||||
|
writer.Write(header.Crc32);
|
||||||
|
|
||||||
|
byte[] data = mem.ToArray();
|
||||||
|
|
||||||
|
Send(data, clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void RequestData(int clientId, int slot)
|
||||||
|
{
|
||||||
|
if (!_active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header header = GenerateHeader(clientId);
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (BinaryWriter writer = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
writer.WriteStruct(header);
|
||||||
|
|
||||||
|
ControllerDataRequest request = new ControllerDataRequest()
|
||||||
|
{
|
||||||
|
Type = MessageType.Data,
|
||||||
|
Slot = (byte)slot,
|
||||||
|
SubscriberType = SubscriberType.Slot
|
||||||
|
};
|
||||||
|
|
||||||
|
writer.WriteStruct(request);
|
||||||
|
|
||||||
|
header.Length = (ushort)(mem.Length - 16);
|
||||||
|
|
||||||
|
writer.Seek(6, SeekOrigin.Begin);
|
||||||
|
writer.Write(header.Length);
|
||||||
|
|
||||||
|
header.Crc32 = Crc32Algorithm.Compute(mem.ToArray());
|
||||||
|
|
||||||
|
writer.Seek(8, SeekOrigin.Begin);
|
||||||
|
writer.Write(header.Crc32);
|
||||||
|
|
||||||
|
byte[] data = mem.ToArray();
|
||||||
|
|
||||||
|
Send(data, clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Header GenerateHeader(int clientId)
|
||||||
|
{
|
||||||
|
Header header = new Header()
|
||||||
|
{
|
||||||
|
ID = (uint)clientId,
|
||||||
|
MagicString = Magic,
|
||||||
|
Version = Version,
|
||||||
|
Length = 0,
|
||||||
|
Crc32 = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_active = false;
|
||||||
|
|
||||||
|
CloseClients();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
Ryujinx/Motion/MotionDevice.cs
Normal file
83
Ryujinx/Motion/MotionDevice.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
public class MotionDevice
|
||||||
|
{
|
||||||
|
public Vector3 Gyroscope { get; private set; }
|
||||||
|
public Vector3 Accelerometer { get; private set; }
|
||||||
|
public Vector3 Rotation { get; private set; }
|
||||||
|
public float[] Orientation { get; private set; }
|
||||||
|
|
||||||
|
private Client _motionSource;
|
||||||
|
|
||||||
|
public MotionDevice(Client motionSource)
|
||||||
|
{
|
||||||
|
_motionSource = motionSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterController(PlayerIndex player)
|
||||||
|
{
|
||||||
|
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == player);
|
||||||
|
|
||||||
|
if (config != null && config.EnableMotion)
|
||||||
|
{
|
||||||
|
string host = config.DsuServerHost;
|
||||||
|
int port = config.DsuServerPort;
|
||||||
|
|
||||||
|
_motionSource.RegisterClient((int)player, host, port);
|
||||||
|
_motionSource.RequestData((int)player, config.Slot);
|
||||||
|
|
||||||
|
if (config.ControllerType == ControllerType.JoyconPair && !config.MirrorInput)
|
||||||
|
{
|
||||||
|
_motionSource.RequestData((int)player, config.AltSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Poll(PlayerIndex player, int slot)
|
||||||
|
{
|
||||||
|
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == player);
|
||||||
|
|
||||||
|
Orientation = new float[9];
|
||||||
|
|
||||||
|
if (!config.EnableMotion || !_motionSource.TryGetData((int)player, slot, out MotionInput input))
|
||||||
|
{
|
||||||
|
Accelerometer = new Vector3();
|
||||||
|
Gyroscope = new Vector3();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gyroscope = Truncate(input.Gyroscrope * 0.0027f, 3);
|
||||||
|
Accelerometer = Truncate(input.Accelerometer, 3);
|
||||||
|
Rotation = Truncate(input.Rotation * 0.0027f, 3);
|
||||||
|
|
||||||
|
Matrix4x4 orientation = input.GetOrientation();
|
||||||
|
|
||||||
|
Orientation[0] = Math.Clamp(orientation.M11, -1f, 1f);
|
||||||
|
Orientation[1] = Math.Clamp(orientation.M12, -1f, 1f);
|
||||||
|
Orientation[2] = Math.Clamp(orientation.M13, -1f, 1f);
|
||||||
|
Orientation[3] = Math.Clamp(orientation.M21, -1f, 1f);
|
||||||
|
Orientation[4] = Math.Clamp(orientation.M22, -1f, 1f);
|
||||||
|
Orientation[5] = Math.Clamp(orientation.M23, -1f, 1f);
|
||||||
|
Orientation[6] = Math.Clamp(orientation.M31, -1f, 1f);
|
||||||
|
Orientation[7] = Math.Clamp(orientation.M32, -1f, 1f);
|
||||||
|
Orientation[8] = Math.Clamp(orientation.M33, -1f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 Truncate(Vector3 value, int decimals)
|
||||||
|
{
|
||||||
|
float power = MathF.Pow(10, decimals);
|
||||||
|
|
||||||
|
value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power;
|
||||||
|
value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power;
|
||||||
|
value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
Ryujinx/Motion/MotionInput.cs
Normal file
85
Ryujinx/Motion/MotionInput.cs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
public class MotionInput
|
||||||
|
{
|
||||||
|
public ulong TimeStamp { get; set; }
|
||||||
|
public Vector3 Accelerometer { get; set; }
|
||||||
|
public Vector3 Gyroscrope { get; set; }
|
||||||
|
public Vector3 Rotation { get; set; }
|
||||||
|
|
||||||
|
private readonly MotionSensorFilter _filter;
|
||||||
|
private int _calibrationFrame = 0;
|
||||||
|
|
||||||
|
public MotionInput()
|
||||||
|
{
|
||||||
|
TimeStamp = 0;
|
||||||
|
Accelerometer = new Vector3();
|
||||||
|
Gyroscrope = new Vector3();
|
||||||
|
Rotation = new Vector3();
|
||||||
|
|
||||||
|
// TODO: RE the correct filter.
|
||||||
|
_filter = new MotionSensorFilter(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Vector3 accel, Vector3 gyro, ulong timestamp, int sensitivity, float deadzone)
|
||||||
|
{
|
||||||
|
if (TimeStamp != 0)
|
||||||
|
{
|
||||||
|
if (gyro.Length() <= 1f && accel.Length() >= 0.8f && accel.Z >= 0.8f)
|
||||||
|
{
|
||||||
|
_calibrationFrame++;
|
||||||
|
|
||||||
|
if (_calibrationFrame >= 90)
|
||||||
|
{
|
||||||
|
gyro = Vector3.Zero;
|
||||||
|
|
||||||
|
Rotation = Vector3.Zero;
|
||||||
|
|
||||||
|
_filter.Reset();
|
||||||
|
|
||||||
|
_calibrationFrame = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_calibrationFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Accelerometer = -accel;
|
||||||
|
|
||||||
|
if (gyro.Length() < deadzone)
|
||||||
|
{
|
||||||
|
gyro = Vector3.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
gyro *= (sensitivity / 100f);
|
||||||
|
|
||||||
|
Gyroscrope = gyro;
|
||||||
|
|
||||||
|
float deltaTime = MathF.Abs((long)(timestamp - TimeStamp) / 1000000f);
|
||||||
|
|
||||||
|
Vector3 deltaGyro = gyro * deltaTime;
|
||||||
|
|
||||||
|
Rotation += deltaGyro;
|
||||||
|
|
||||||
|
_filter.SamplePeriod = deltaTime;
|
||||||
|
_filter.Update(accel, DegreeToRad(gyro));
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeStamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix4x4 GetOrientation()
|
||||||
|
{
|
||||||
|
return Matrix4x4.CreateFromQuaternion(_filter.Quaternion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 DegreeToRad(Vector3 degree)
|
||||||
|
{
|
||||||
|
return degree * (MathF.PI / 180);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
166
Ryujinx/Motion/MotionSensorFilter.cs
Normal file
166
Ryujinx/Motion/MotionSensorFilter.cs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
// MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm.
|
||||||
|
// See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/
|
||||||
|
// Based on: https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
|
||||||
|
public class MotionSensorFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sample rate coefficient.
|
||||||
|
/// </summary>
|
||||||
|
public const float SampleRateCoefficient = 0.45f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the sample period.
|
||||||
|
/// </summary>
|
||||||
|
public float SamplePeriod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the algorithm proportional gain.
|
||||||
|
/// </summary>
|
||||||
|
public float Kp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the algorithm integral gain.
|
||||||
|
/// </summary>
|
||||||
|
public float Ki { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Quaternion output.
|
||||||
|
/// </summary>
|
||||||
|
public Quaternion Quaternion { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integral error.
|
||||||
|
/// </summary>
|
||||||
|
private Vector3 _intergralError;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="samplePeriod">
|
||||||
|
/// Sample period.
|
||||||
|
/// </param>
|
||||||
|
public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="samplePeriod">
|
||||||
|
/// Sample period.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="kp">
|
||||||
|
/// Algorithm proportional gain.
|
||||||
|
/// </param>
|
||||||
|
public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MotionSensorFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="samplePeriod">
|
||||||
|
/// Sample period.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="kp">
|
||||||
|
/// Algorithm proportional gain.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="ki">
|
||||||
|
/// Algorithm integral gain.
|
||||||
|
/// </param>
|
||||||
|
public MotionSensorFilter(float samplePeriod, float kp, float ki)
|
||||||
|
{
|
||||||
|
SamplePeriod = samplePeriod;
|
||||||
|
Kp = kp;
|
||||||
|
Ki = ki;
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
_intergralError = new Vector3();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Algorithm IMU update method. Requires only gyroscope and accelerometer data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accel">
|
||||||
|
/// Accelerometer measurement in any calibrated units.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="gyro">
|
||||||
|
/// Gyroscope measurement in radians.
|
||||||
|
/// </param>
|
||||||
|
public void Update(Vector3 accel, Vector3 gyro)
|
||||||
|
{
|
||||||
|
// Normalise accelerometer measurement.
|
||||||
|
float norm = 1f / accel.Length();
|
||||||
|
|
||||||
|
if (!float.IsFinite(norm))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accel *= norm;
|
||||||
|
|
||||||
|
float q2 = Quaternion.X;
|
||||||
|
float q3 = Quaternion.Y;
|
||||||
|
float q4 = Quaternion.Z;
|
||||||
|
float q1 = Quaternion.W;
|
||||||
|
|
||||||
|
// Estimated direction of gravity.
|
||||||
|
Vector3 gravity = new Vector3()
|
||||||
|
{
|
||||||
|
X = 2f * (q2 * q4 - q1 * q3),
|
||||||
|
Y = 2f * (q1 * q2 + q3 * q4),
|
||||||
|
Z = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error is cross product between estimated direction and measured direction of gravity.
|
||||||
|
Vector3 error = new Vector3()
|
||||||
|
{
|
||||||
|
X = accel.Y * gravity.Z - accel.Z * gravity.Y,
|
||||||
|
Y = accel.Z * gravity.X - accel.X * gravity.Z,
|
||||||
|
Z = accel.X * gravity.Y - accel.Y * gravity.X
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Ki > 0f)
|
||||||
|
{
|
||||||
|
_intergralError += error; // Accumulate integral error.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_intergralError = Vector3.Zero; // Prevent integral wind up.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply feedback terms.
|
||||||
|
gyro += (Kp * error) + (Ki * _intergralError);
|
||||||
|
|
||||||
|
// Integrate rate of change of quaternion.
|
||||||
|
Vector3 delta = new Vector3(q2, q3, q4);
|
||||||
|
|
||||||
|
q1 += (-q2 * gyro.X - q3 * gyro.Y - q4 * gyro.Z) * (SampleRateCoefficient * SamplePeriod);
|
||||||
|
q2 += (q1 * gyro.X + delta.Y * gyro.Z - delta.Z * gyro.Y) * (SampleRateCoefficient * SamplePeriod);
|
||||||
|
q3 += (q1 * gyro.Y - delta.X * gyro.Z + delta.Z * gyro.X) * (SampleRateCoefficient * SamplePeriod);
|
||||||
|
q4 += (q1 * gyro.Z + delta.X * gyro.Y - delta.Y * gyro.X) * (SampleRateCoefficient * SamplePeriod);
|
||||||
|
|
||||||
|
// Normalise quaternion.
|
||||||
|
Quaternion quaternion = new Quaternion(q2, q3, q4, q1);
|
||||||
|
|
||||||
|
norm = 1f / quaternion.Length();
|
||||||
|
|
||||||
|
if (!float.IsFinite(norm))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quaternion = quaternion * norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Quaternion = Quaternion.Identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Ryujinx/Motion/Protocol/ControllerData.cs
Normal file
50
Ryujinx/Motion/Protocol/ControllerData.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct ControllerDataRequest
|
||||||
|
{
|
||||||
|
public MessageType Type;
|
||||||
|
public SubscriberType SubscriberType;
|
||||||
|
public byte Slot;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
public byte[] MacAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ControllerDataResponse
|
||||||
|
{
|
||||||
|
public SharedResponse Shared;
|
||||||
|
public byte Connected;
|
||||||
|
public uint PacketID;
|
||||||
|
public byte ExtraButtons;
|
||||||
|
public byte MainButtons;
|
||||||
|
public ushort PSExtraInput;
|
||||||
|
public ushort LeftStickXY;
|
||||||
|
public ushort RightStickXY;
|
||||||
|
public uint DPadAnalog;
|
||||||
|
public ulong MainButtonsAnalog;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
public byte[] Touch1;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
public byte[] Touch2;
|
||||||
|
public ulong MotionTimestamp;
|
||||||
|
public float AccelerometerX;
|
||||||
|
public float AccelerometerY;
|
||||||
|
public float AccelerometerZ;
|
||||||
|
public float GyroscopePitch;
|
||||||
|
public float GyroscopeYaw;
|
||||||
|
public float GyroscopeRoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SubscriberType : byte
|
||||||
|
{
|
||||||
|
All = 0,
|
||||||
|
Slot,
|
||||||
|
Mac
|
||||||
|
}
|
||||||
|
}
|
21
Ryujinx/Motion/Protocol/ControllerInfo.cs
Normal file
21
Ryujinx/Motion/Protocol/ControllerInfo.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ControllerInfoResponse
|
||||||
|
{
|
||||||
|
public SharedResponse Shared;
|
||||||
|
private byte _zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct ControllerInfoRequest
|
||||||
|
{
|
||||||
|
public MessageType Type;
|
||||||
|
public int PortsCount;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
||||||
|
public byte[] PortIndices;
|
||||||
|
}
|
||||||
|
}
|
14
Ryujinx/Motion/Protocol/Header.cs
Normal file
14
Ryujinx/Motion/Protocol/Header.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct Header
|
||||||
|
{
|
||||||
|
public uint MagicString;
|
||||||
|
public ushort Version;
|
||||||
|
public ushort Length;
|
||||||
|
public uint Crc32;
|
||||||
|
public uint ID;
|
||||||
|
}
|
||||||
|
}
|
9
Ryujinx/Motion/Protocol/MessageType.cs
Normal file
9
Ryujinx/Motion/Protocol/MessageType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
public enum MessageType : uint
|
||||||
|
{
|
||||||
|
Protocol = 0x100000,
|
||||||
|
Info,
|
||||||
|
Data
|
||||||
|
}
|
||||||
|
}
|
51
Ryujinx/Motion/Protocol/SharedResponse.cs
Normal file
51
Ryujinx/Motion/Protocol/SharedResponse.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Motion
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct SharedResponse
|
||||||
|
{
|
||||||
|
public MessageType Type;
|
||||||
|
public byte Slot;
|
||||||
|
public SlotState State;
|
||||||
|
public DeviceModelType ModelType;
|
||||||
|
public ConnectionType ConnectionType;
|
||||||
|
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||||
|
public byte[] MacAddress;
|
||||||
|
public BatteryStatus BatteryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SlotState : byte
|
||||||
|
{
|
||||||
|
Disconnected = 0,
|
||||||
|
Reserved,
|
||||||
|
Connected
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DeviceModelType : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
PartialGyro,
|
||||||
|
FullGyro
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ConnectionType : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
USB,
|
||||||
|
Bluetooth
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BatteryStatus : byte
|
||||||
|
{
|
||||||
|
NA = 0,
|
||||||
|
Dying,
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High,
|
||||||
|
Full,
|
||||||
|
Charging,
|
||||||
|
Charged
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Crc32.NET" Version="1.2.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
||||||
<PackageReference Include="GLWidget" Version="1.0.2" />
|
<PackageReference Include="GLWidget" Version="1.0.2" />
|
||||||
<PackageReference Include="GtkSharp" Version="3.22.25.56" />
|
<PackageReference Include="GtkSharp" Version="3.22.25.56" />
|
||||||
|
|
|
@ -28,10 +28,19 @@ namespace Ryujinx.Ui
|
||||||
[GUI] Adjustment _controllerDeadzoneLeft;
|
[GUI] Adjustment _controllerDeadzoneLeft;
|
||||||
[GUI] Adjustment _controllerDeadzoneRight;
|
[GUI] Adjustment _controllerDeadzoneRight;
|
||||||
[GUI] Adjustment _controllerTriggerThreshold;
|
[GUI] Adjustment _controllerTriggerThreshold;
|
||||||
|
[GUI] Adjustment _slotNumber;
|
||||||
|
[GUI] Adjustment _altSlotNumber;
|
||||||
|
[GUI] Adjustment _sensitivity;
|
||||||
|
[GUI] Adjustment _gyroDeadzone;
|
||||||
|
[GUI] CheckButton _enableMotion;
|
||||||
|
[GUI] CheckButton _mirrorInput;
|
||||||
|
[GUI] Entry _dsuServerHost;
|
||||||
|
[GUI] Entry _dsuServerPort;
|
||||||
[GUI] ComboBoxText _inputDevice;
|
[GUI] ComboBoxText _inputDevice;
|
||||||
[GUI] ComboBoxText _profile;
|
[GUI] ComboBoxText _profile;
|
||||||
[GUI] ToggleButton _refreshInputDevicesButton;
|
[GUI] ToggleButton _refreshInputDevicesButton;
|
||||||
[GUI] Box _settingsBox;
|
[GUI] Box _settingsBox;
|
||||||
|
[GUI] Box _altBox;
|
||||||
[GUI] Grid _leftStickKeyboard;
|
[GUI] Grid _leftStickKeyboard;
|
||||||
[GUI] Grid _leftStickController;
|
[GUI] Grid _leftStickController;
|
||||||
[GUI] Box _deadZoneLeftBox;
|
[GUI] Box _deadZoneLeftBox;
|
||||||
|
@ -225,6 +234,7 @@ namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
_leftSideTriggerBox.Hide();
|
_leftSideTriggerBox.Hide();
|
||||||
_rightSideTriggerBox.Hide();
|
_rightSideTriggerBox.Hide();
|
||||||
|
_altBox.Hide();
|
||||||
|
|
||||||
switch (_controllerType.ActiveId)
|
switch (_controllerType.ActiveId)
|
||||||
{
|
{
|
||||||
|
@ -234,6 +244,9 @@ namespace Ryujinx.Ui
|
||||||
case "JoyconRight":
|
case "JoyconRight":
|
||||||
_rightSideTriggerBox.Show();
|
_rightSideTriggerBox.Show();
|
||||||
break;
|
break;
|
||||||
|
case "JoyconPair":
|
||||||
|
_altBox.Show();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_controllerType.ActiveId)
|
switch (_controllerType.ActiveId)
|
||||||
|
@ -290,6 +303,14 @@ namespace Ryujinx.Ui
|
||||||
_controllerDeadzoneLeft.Value = 0;
|
_controllerDeadzoneLeft.Value = 0;
|
||||||
_controllerDeadzoneRight.Value = 0;
|
_controllerDeadzoneRight.Value = 0;
|
||||||
_controllerTriggerThreshold.Value = 0;
|
_controllerTriggerThreshold.Value = 0;
|
||||||
|
_mirrorInput.Active = false;
|
||||||
|
_enableMotion.Active = false;
|
||||||
|
_slotNumber.Value = 0;
|
||||||
|
_altSlotNumber.Value = 0;
|
||||||
|
_sensitivity.Value = 100;
|
||||||
|
_gyroDeadzone.Value = 1;
|
||||||
|
_dsuServerHost.Buffer.Text = "";
|
||||||
|
_dsuServerPort.Buffer.Text = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetValues(InputConfig config)
|
private void SetValues(InputConfig config)
|
||||||
|
@ -332,6 +353,14 @@ namespace Ryujinx.Ui
|
||||||
_zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString();
|
_zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString();
|
||||||
_rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString();
|
_rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString();
|
||||||
_rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString();
|
_rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString();
|
||||||
|
_slotNumber.Value = keyboardConfig.Slot;
|
||||||
|
_altSlotNumber.Value = keyboardConfig.AltSlot;
|
||||||
|
_sensitivity.Value = keyboardConfig.Sensitivity;
|
||||||
|
_gyroDeadzone.Value = keyboardConfig.GyroDeadzone;
|
||||||
|
_enableMotion.Active = keyboardConfig.EnableMotion;
|
||||||
|
_mirrorInput.Active = keyboardConfig.MirrorInput;
|
||||||
|
_dsuServerHost.Buffer.Text = keyboardConfig.DsuServerHost;
|
||||||
|
_dsuServerPort.Buffer.Text = keyboardConfig.DsuServerPort.ToString();
|
||||||
break;
|
break;
|
||||||
case ControllerConfig controllerConfig:
|
case ControllerConfig controllerConfig:
|
||||||
if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString()))
|
if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString()))
|
||||||
|
@ -372,6 +401,14 @@ namespace Ryujinx.Ui
|
||||||
_controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft;
|
_controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft;
|
||||||
_controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight;
|
_controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight;
|
||||||
_controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold;
|
_controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold;
|
||||||
|
_slotNumber.Value = controllerConfig.Slot;
|
||||||
|
_altSlotNumber.Value = controllerConfig.AltSlot;
|
||||||
|
_sensitivity.Value = controllerConfig.Sensitivity;
|
||||||
|
_gyroDeadzone.Value = controllerConfig.GyroDeadzone;
|
||||||
|
_enableMotion.Active = controllerConfig.EnableMotion;
|
||||||
|
_mirrorInput.Active = controllerConfig.MirrorInput;
|
||||||
|
_dsuServerHost.Buffer.Text = controllerConfig.DsuServerHost;
|
||||||
|
_dsuServerPort.Buffer.Text = controllerConfig.DsuServerPort.ToString();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -448,7 +485,15 @@ namespace Ryujinx.Ui
|
||||||
ButtonZr = rButtonZr,
|
ButtonZr = rButtonZr,
|
||||||
ButtonSl = rButtonSl,
|
ButtonSl = rButtonSl,
|
||||||
ButtonSr = rButtonSr
|
ButtonSr = rButtonSr
|
||||||
}
|
},
|
||||||
|
EnableMotion = _enableMotion.Active,
|
||||||
|
MirrorInput = _mirrorInput.Active,
|
||||||
|
Slot = (int)_slotNumber.Value,
|
||||||
|
AltSlot = (int)_slotNumber.Value,
|
||||||
|
Sensitivity = (int)_sensitivity.Value,
|
||||||
|
GyroDeadzone = _gyroDeadzone.Value,
|
||||||
|
DsuServerHost = _dsuServerHost.Buffer.Text,
|
||||||
|
DsuServerPort = int.Parse(_dsuServerPort.Buffer.Text)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,7 +566,15 @@ namespace Ryujinx.Ui
|
||||||
ButtonZr = rButtonZr,
|
ButtonZr = rButtonZr,
|
||||||
ButtonSl = rButtonSl,
|
ButtonSl = rButtonSl,
|
||||||
ButtonSr = rButtonSr
|
ButtonSr = rButtonSr
|
||||||
}
|
},
|
||||||
|
EnableMotion = _enableMotion.Active,
|
||||||
|
MirrorInput = _mirrorInput.Active,
|
||||||
|
Slot = (int)_slotNumber.Value,
|
||||||
|
AltSlot = (int)_slotNumber.Value,
|
||||||
|
Sensitivity = (int)_sensitivity.Value,
|
||||||
|
GyroDeadzone = _gyroDeadzone.Value,
|
||||||
|
DsuServerHost = _dsuServerHost.Buffer.Text,
|
||||||
|
DsuServerPort = int.Parse(_dsuServerPort.Buffer.Text)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,7 +832,15 @@ namespace Ryujinx.Ui
|
||||||
ButtonZr = Key.O,
|
ButtonZr = Key.O,
|
||||||
ButtonSl = Key.Unbound,
|
ButtonSl = Key.Unbound,
|
||||||
ButtonSr = Key.Unbound
|
ButtonSr = Key.Unbound
|
||||||
}
|
},
|
||||||
|
EnableMotion = false,
|
||||||
|
MirrorInput = false,
|
||||||
|
Slot = 0,
|
||||||
|
AltSlot = 0,
|
||||||
|
Sensitivity = 100,
|
||||||
|
GyroDeadzone = 1,
|
||||||
|
DsuServerHost = "127.0.0.1",
|
||||||
|
DsuServerPort = 26760
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (_inputDevice.ActiveId.StartsWith("controller"))
|
else if (_inputDevice.ActiveId.StartsWith("controller"))
|
||||||
|
@ -824,7 +885,15 @@ namespace Ryujinx.Ui
|
||||||
ButtonSr = ControllerInputId.Unbound,
|
ButtonSr = ControllerInputId.Unbound,
|
||||||
InvertStickX = false,
|
InvertStickX = false,
|
||||||
InvertStickY = false
|
InvertStickY = false
|
||||||
}
|
},
|
||||||
|
EnableMotion = false,
|
||||||
|
MirrorInput = false,
|
||||||
|
Slot = 0,
|
||||||
|
AltSlot = 0,
|
||||||
|
Sensitivity = 100,
|
||||||
|
GyroDeadzone = 1,
|
||||||
|
DsuServerHost = "127.0.0.1",
|
||||||
|
DsuServerPort = 26760
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,47 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.22.1 -->
|
<!-- Generated with glade 3.36.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.20"/>
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkAdjustment" id="_altSlotNumber">
|
||||||
|
<property name="upper">4</property>
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">4</property>
|
||||||
|
</object>
|
||||||
<object class="GtkAdjustment" id="_controllerDeadzoneLeft">
|
<object class="GtkAdjustment" id="_controllerDeadzoneLeft">
|
||||||
<property name="upper">1</property>
|
<property name="upper">1</property>
|
||||||
<property name="value">0.050000000000000003</property>
|
<property name="value">0.05</property>
|
||||||
<property name="step_increment">0.01</property>
|
<property name="step_increment">0.01</property>
|
||||||
<property name="page_increment">0.10000000000000001</property>
|
<property name="page_increment">0.1</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAdjustment" id="_controllerDeadzoneRight">
|
<object class="GtkAdjustment" id="_controllerDeadzoneRight">
|
||||||
<property name="upper">1</property>
|
<property name="upper">1</property>
|
||||||
<property name="value">0.050000000000000003</property>
|
<property name="value">0.05</property>
|
||||||
<property name="step_increment">0.01</property>
|
<property name="step_increment">0.01</property>
|
||||||
<property name="page_increment">0.10000000000000001</property>
|
<property name="page_increment">0.1</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkAdjustment" id="_controllerTriggerThreshold">
|
<object class="GtkAdjustment" id="_controllerTriggerThreshold">
|
||||||
<property name="upper">1</property>
|
<property name="upper">1</property>
|
||||||
<property name="value">0.5</property>
|
<property name="value">0.5</property>
|
||||||
<property name="step_increment">0.01</property>
|
<property name="step_increment">0.01</property>
|
||||||
<property name="page_increment">0.10000000000000001</property>
|
<property name="page_increment">0.1</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="_gyroDeadzone">
|
||||||
|
<property name="upper">100</property>
|
||||||
|
<property name="value">0.01</property>
|
||||||
|
<property name="step_increment">0.01</property>
|
||||||
|
<property name="page_increment">0.1</property>
|
||||||
|
<property name="page_size">0.1</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="_sensitivity">
|
||||||
|
<property name="upper">1000</property>
|
||||||
|
<property name="value">100</property>
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">4</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="_slotNumber">
|
||||||
|
<property name="upper">4</property>
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">4</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkWindow" id="_controllerWin">
|
<object class="GtkWindow" id="_controllerWin">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
|
@ -27,9 +50,6 @@
|
||||||
<property name="window_position">center</property>
|
<property name="window_position">center</property>
|
||||||
<property name="default_width">1100</property>
|
<property name="default_width">1100</property>
|
||||||
<property name="default_height">600</property>
|
<property name="default_height">600</property>
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -1616,14 +1636,303 @@
|
||||||
<property name="position">3</property>
|
<property name="position">3</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="MotionBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">10</property>
|
||||||
|
<property name="margin_right">10</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_top">5</property>
|
||||||
|
<property name="margin_bottom">5</property>
|
||||||
|
<property name="label" translatable="yes">Motion</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="_enableMotion">
|
||||||
|
<property name="label" translatable="yes">Enable Motion Controls</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">10</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_right">17</property>
|
||||||
|
<property name="label" translatable="yes">Controller Slot</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinButton" id="_slot">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="margin_left">10</property>
|
||||||
|
<property name="adjustment">_slotNumber</property>
|
||||||
|
<property name="climb_rate">1</property>
|
||||||
|
<property name="snap_to_ticks">True</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">10</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_right">5</property>
|
||||||
|
<property name="label" translatable="yes">Gyro Sensitivity %</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinButton">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="text" translatable="yes">0</property>
|
||||||
|
<property name="adjustment">_sensitivity</property>
|
||||||
|
<property name="climb_rate">1</property>
|
||||||
|
<property name="snap_to_ticks">True</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="_altBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="_mirrorInput">
|
||||||
|
<property name="label" translatable="yes">Mirror Input</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">10</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Right JoyCon Slot</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinButton" id="_slotRight">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="text" translatable="yes">0</property>
|
||||||
|
<property name="climb_rate">1</property>
|
||||||
|
<property name="snap_to_ticks">True</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">30</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Server Host</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="_dsuServerHost">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">30</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Server Port</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="_dsuServerPort">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="padding">5</property>
|
||||||
|
<property name="position">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="label" translatable="yes">Gyro Deadzone</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">7</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScale">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="adjustment">_gyroDeadzone</property>
|
||||||
|
<property name="round_digits">2</property>
|
||||||
|
<property name="digits">2</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
|
<property name="position">8</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
<property name="position">4</property>
|
<property name="position">4</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
@ -1721,5 +2030,8 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child type="titlebar">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
|
@ -13,6 +13,7 @@ using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Ryujinx.Motion;
|
||||||
|
|
||||||
namespace Ryujinx.Ui
|
namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
|
@ -48,6 +49,8 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
private HotkeyButtons _prevHotkeyButtons;
|
private HotkeyButtons _prevHotkeyButtons;
|
||||||
|
|
||||||
|
private Client _dsuClient;
|
||||||
|
|
||||||
private GraphicsDebugLevel _glLogLevel;
|
private GraphicsDebugLevel _glLogLevel;
|
||||||
|
|
||||||
public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel)
|
public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel)
|
||||||
|
@ -79,6 +82,8 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
this.Shown += Renderer_Shown;
|
this.Shown += Renderer_Shown;
|
||||||
|
|
||||||
|
_dsuClient = new Client();
|
||||||
|
|
||||||
_glLogLevel = glLogLevel;
|
_glLogLevel = glLogLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +95,7 @@ namespace Ryujinx.Ui
|
||||||
private void GLRenderer_ShuttingDown(object sender, EventArgs args)
|
private void GLRenderer_ShuttingDown(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
_device.DisposeGpu();
|
_device.DisposeGpu();
|
||||||
|
_dsuClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
|
private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
|
||||||
|
@ -104,6 +110,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
private void GLRenderer_Destroyed(object sender, EventArgs e)
|
private void GLRenderer_Destroyed(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
_dsuClient?.Dispose();
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +294,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
|
_dsuClient?.Dispose();
|
||||||
if (IsStopped)
|
if (IsStopped)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -406,6 +414,9 @@ namespace Ryujinx.Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GamepadInput> gamepadInputs = new List<GamepadInput>(NpadDevices.MaxControllers);
|
List<GamepadInput> gamepadInputs = new List<GamepadInput>(NpadDevices.MaxControllers);
|
||||||
|
List<SixAxisInput> motionInputs = new List<SixAxisInput>(NpadDevices.MaxControllers);
|
||||||
|
|
||||||
|
MotionDevice motionDevice = new MotionDevice(_dsuClient);
|
||||||
|
|
||||||
foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
|
foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
|
||||||
{
|
{
|
||||||
|
@ -419,6 +430,11 @@ namespace Ryujinx.Ui
|
||||||
int rightJoystickDx = 0;
|
int rightJoystickDx = 0;
|
||||||
int rightJoystickDy = 0;
|
int rightJoystickDy = 0;
|
||||||
|
|
||||||
|
if (inputConfig.EnableMotion)
|
||||||
|
{
|
||||||
|
motionDevice.RegisterController(inputConfig.PlayerIndex);
|
||||||
|
}
|
||||||
|
|
||||||
if (inputConfig is KeyboardConfig keyboardConfig)
|
if (inputConfig is KeyboardConfig keyboardConfig)
|
||||||
{
|
{
|
||||||
if (IsFocused)
|
if (IsFocused)
|
||||||
|
@ -488,6 +504,19 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
|
currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
|
||||||
|
|
||||||
|
motionDevice.Poll(inputConfig.PlayerIndex, inputConfig.Slot);
|
||||||
|
|
||||||
|
SixAxisInput sixAxisInput = new SixAxisInput()
|
||||||
|
{
|
||||||
|
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||||
|
Accelerometer = motionDevice.Accelerometer,
|
||||||
|
Gyroscope = motionDevice.Gyroscope,
|
||||||
|
Rotation = motionDevice.Rotation,
|
||||||
|
Orientation = motionDevice.Orientation
|
||||||
|
};
|
||||||
|
|
||||||
|
motionInputs.Add(sixAxisInput);
|
||||||
|
|
||||||
gamepadInputs.Add(new GamepadInput
|
gamepadInputs.Add(new GamepadInput
|
||||||
{
|
{
|
||||||
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||||
|
@ -495,9 +524,29 @@ namespace Ryujinx.Ui
|
||||||
LStick = leftJoystick,
|
LStick = leftJoystick,
|
||||||
RStick = rightJoystick
|
RStick = rightJoystick
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (inputConfig.ControllerType == Common.Configuration.Hid.ControllerType.JoyconPair)
|
||||||
|
{
|
||||||
|
if (!inputConfig.MirrorInput)
|
||||||
|
{
|
||||||
|
motionDevice.Poll(inputConfig.PlayerIndex, inputConfig.AltSlot);
|
||||||
|
|
||||||
|
sixAxisInput = new SixAxisInput()
|
||||||
|
{
|
||||||
|
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||||
|
Accelerometer = motionDevice.Accelerometer,
|
||||||
|
Gyroscope = motionDevice.Gyroscope,
|
||||||
|
Rotation = motionDevice.Rotation,
|
||||||
|
Orientation = motionDevice.Orientation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
motionInputs.Add(sixAxisInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.Hid.Npads.Update(gamepadInputs);
|
_device.Hid.Npads.Update(gamepadInputs);
|
||||||
|
_device.Hid.Npads.UpdateSixAxis(motionInputs);
|
||||||
|
|
||||||
if(IsFocused)
|
if(IsFocused)
|
||||||
{
|
{
|
||||||
|
|
|
@ -82,6 +82,7 @@ namespace Ryujinx.Ui
|
||||||
[GUI] ToggleButton _configureController7;
|
[GUI] ToggleButton _configureController7;
|
||||||
[GUI] ToggleButton _configureController8;
|
[GUI] ToggleButton _configureController8;
|
||||||
[GUI] ToggleButton _configureControllerH;
|
[GUI] ToggleButton _configureControllerH;
|
||||||
|
|
||||||
#pragma warning restore CS0649, IDE0044
|
#pragma warning restore CS0649, IDE0044
|
||||||
|
|
||||||
public SettingsWindow(VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(new Builder("Ryujinx.Ui.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
|
public SettingsWindow(VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(new Builder("Ryujinx.Ui.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
|
||||||
|
|
|
@ -7,11 +7,6 @@
|
||||||
<property name="step_increment">1</property>
|
<property name="step_increment">1</property>
|
||||||
<property name="page_increment">10</property>
|
<property name="page_increment">10</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkEntryCompletion" id="_systemTimeZoneCompletion">
|
|
||||||
<property name="inline-completion">True</property>
|
|
||||||
<property name="inline-selection">True</property>
|
|
||||||
<property name="minimum-key-length">0</property>
|
|
||||||
</object>
|
|
||||||
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
|
<object class="GtkAdjustment" id="_systemTimeDaySpinAdjustment">
|
||||||
<property name="lower">1</property>
|
<property name="lower">1</property>
|
||||||
<property name="upper">31</property>
|
<property name="upper">31</property>
|
||||||
|
@ -40,6 +35,11 @@
|
||||||
<property name="step_increment">1</property>
|
<property name="step_increment">1</property>
|
||||||
<property name="page_increment">10</property>
|
<property name="page_increment">10</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkEntryCompletion" id="_systemTimeZoneCompletion">
|
||||||
|
<property name="minimum_key_length">0</property>
|
||||||
|
<property name="inline_completion">True</property>
|
||||||
|
<property name="inline_selection">True</property>
|
||||||
|
</object>
|
||||||
<object class="GtkWindow" id="_settingsWin">
|
<object class="GtkWindow" id="_settingsWin">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="title" translatable="yes">Ryujinx - Settings</property>
|
<property name="title" translatable="yes">Ryujinx - Settings</property>
|
||||||
|
@ -1062,6 +1062,17 @@
|
||||||
<property name="position">2</property>
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
|
@ -1737,8 +1748,8 @@
|
||||||
<property name="tooltip_text" translatable="yes">Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.</property>
|
<property name="tooltip_text" translatable="yes">Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
<property name="caps_lock_warning">False</property>
|
<property name="caps_lock_warning">False</property>
|
||||||
<property name="placeholder-text">1.0</property>
|
<property name="placeholder_text">1.0</property>
|
||||||
<property name="input-purpose">GTK_INPUT_PURPOSE_NUMBER</property>
|
<property name="input_purpose">number</property>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
|
|
|
@ -460,6 +460,110 @@
|
||||||
"default": "O"
|
"default": "O"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"enable_motion": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/enable_motion",
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Enable Motion Controls",
|
||||||
|
"description": "Enables Motion Controls",
|
||||||
|
"default": false,
|
||||||
|
"examples": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sensitivity": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/sensitivity",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Sensitivity",
|
||||||
|
"description": "Gyro sensitivity",
|
||||||
|
"default": 100,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1000,
|
||||||
|
"examples": [
|
||||||
|
90,
|
||||||
|
100,
|
||||||
|
150
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gyro_deadzone": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/gyro_deadzone",
|
||||||
|
"type": "number",
|
||||||
|
"title": "Gyro Deadzone",
|
||||||
|
"description": "Controller Left Analog Stick Deadzone",
|
||||||
|
"default": 1,
|
||||||
|
"minimum": 0.00,
|
||||||
|
"maximum": 100.00,
|
||||||
|
"examples": [
|
||||||
|
0.01
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"slot": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/slot",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Slot",
|
||||||
|
"description": "DSU motion client slot for main controller",
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 4,
|
||||||
|
"examples": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"alt_slot": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/alt_slot",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Alternate Slot",
|
||||||
|
"description": "DSU motion client slot for secondary controller, eg Right Joycon in Paired mode",
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 4,
|
||||||
|
"examples": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mirror_input": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/mirror_input",
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Mirror Motion Input",
|
||||||
|
"description": "Mirrors main motion input in Paired mode",
|
||||||
|
"default": true,
|
||||||
|
"examples": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dsu_server_port": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/dsu_server_port",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "DSU Server Port",
|
||||||
|
"description": "DSU motion server port",
|
||||||
|
"default": 26760,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 36654,
|
||||||
|
"examples": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dsu_server_host": {
|
||||||
|
"$id": "#/definitions/keyboard_config/properties/dsu_server_host",
|
||||||
|
"type": "string",
|
||||||
|
"title": "DSU Server Host Address",
|
||||||
|
"description": "DSU motion server host address",
|
||||||
|
"default": "127.0.0.1",
|
||||||
|
"examples": [
|
||||||
|
"127.0.0.1",
|
||||||
|
"example.host.com"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -695,6 +799,110 @@
|
||||||
"default": "Button9"
|
"default": "Button9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"enable_motion": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/enable_motion",
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Enable Motion Controls",
|
||||||
|
"description": "Enables Motion Controls",
|
||||||
|
"default": false,
|
||||||
|
"examples": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sensitivity": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/sensitivity",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Sensitivity",
|
||||||
|
"description": "Gyro sensitivity",
|
||||||
|
"default": 100,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1000,
|
||||||
|
"examples": [
|
||||||
|
90,
|
||||||
|
100,
|
||||||
|
150
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gyro_deadzone": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/gyro_deadzone",
|
||||||
|
"type": "number",
|
||||||
|
"title": "Gyro Deadzone",
|
||||||
|
"description": "Controller Left Analog Stick Deadzone",
|
||||||
|
"default": 1,
|
||||||
|
"minimum": 0.00,
|
||||||
|
"maximum": 100.00,
|
||||||
|
"examples": [
|
||||||
|
0.01
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"slot": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/slot",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Slot",
|
||||||
|
"description": "DSU motion client slot for main controller",
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 4,
|
||||||
|
"examples": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"alt_slot": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/alt_slot",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Alternate Slot",
|
||||||
|
"description": "DSU motion client slot for secondary controller, eg Right Joycon in Paired mode",
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 4,
|
||||||
|
"examples": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mirror_input": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/mirror_input",
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Mirror Motion Input",
|
||||||
|
"description": "Mirrors main motion input in Paired mode",
|
||||||
|
"default": true,
|
||||||
|
"examples": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dsu_server_port": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/dsu_server_port",
|
||||||
|
"type": "integer",
|
||||||
|
"title": "DSU Server Port",
|
||||||
|
"description": "DSU motion server port",
|
||||||
|
"default": 26760,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 36654,
|
||||||
|
"examples": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dsu_server_host": {
|
||||||
|
"$id": "#/definitions/controller_config/properties/dsu_server_host",
|
||||||
|
"type": "string",
|
||||||
|
"title": "DSU Server Host Address",
|
||||||
|
"description": "DSU motion server host address",
|
||||||
|
"default": "127.0.0.1",
|
||||||
|
"examples": [
|
||||||
|
"127.0.0.1",
|
||||||
|
"example.host.com"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1241,7 +1449,15 @@
|
||||||
"button_zr": "O",
|
"button_zr": "O",
|
||||||
"button_sl": "Unbound",
|
"button_sl": "Unbound",
|
||||||
"button_sr": "Unbound"
|
"button_sr": "Unbound"
|
||||||
}
|
},
|
||||||
|
"slot": 0,
|
||||||
|
"alt_slot": 0,
|
||||||
|
"mirror_input": false,
|
||||||
|
"dsu_server_host": "127.0.0.1",
|
||||||
|
"dsu_server_port": 26760,
|
||||||
|
"sensitivity": 100,
|
||||||
|
"gyro_deadzone": 1,
|
||||||
|
"enable_motion": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
Reference in a new issue