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
@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
|
||||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 14;
|
||||
public const int CurrentVersion = 15;
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
|
@ -483,12 +483,10 @@ namespace Ryujinx.Configuration
|
||||
Ui.EnableCustomTheme.Value = false;
|
||||
Ui.CustomThemePath.Value = "";
|
||||
Hid.EnableKeyboard.Value = false;
|
||||
|
||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||
{
|
||||
ToggleVsync = Key.Tab
|
||||
};
|
||||
|
||||
Hid.InputConfig.Value = new List<InputConfig>
|
||||
{
|
||||
new KeyboardConfig
|
||||
@ -529,7 +527,15 @@ namespace Ryujinx.Configuration
|
||||
ButtonZr = Key.O,
|
||||
ButtonSl = Key.PageUp,
|
||||
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,
|
||||
ButtonSl = 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
|
||||
/// </summary>
|
||||
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.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
{
|
||||
@ -317,6 +317,89 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
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()
|
||||
{
|
||||
ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
|
||||
@ -359,6 +442,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
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 HidVector Accelerometer;
|
||||
public HidVector Gyroscope;
|
||||
HidVector unknownSensor;
|
||||
public HidVector Rotations;
|
||||
public fixed float Orientation[9];
|
||||
ulong _unknown2;
|
||||
public ulong _unknown2;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": 14,
|
||||
"version": 15,
|
||||
"res_scale": 1,
|
||||
"res_scale_custom": 1,
|
||||
"max_anisotropy": -1,
|
||||
@ -86,7 +86,14 @@
|
||||
"button_zr": "O",
|
||||
"button_sl": "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": []
|
||||
|
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>
|
||||
<PackageReference Include="Crc32.NET" Version="1.2.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
||||
<PackageReference Include="GLWidget" Version="1.0.2" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.56" />
|
||||
|
@ -28,10 +28,19 @@ namespace Ryujinx.Ui
|
||||
[GUI] Adjustment _controllerDeadzoneLeft;
|
||||
[GUI] Adjustment _controllerDeadzoneRight;
|
||||
[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 _profile;
|
||||
[GUI] ToggleButton _refreshInputDevicesButton;
|
||||
[GUI] Box _settingsBox;
|
||||
[GUI] Box _altBox;
|
||||
[GUI] Grid _leftStickKeyboard;
|
||||
[GUI] Grid _leftStickController;
|
||||
[GUI] Box _deadZoneLeftBox;
|
||||
@ -225,6 +234,7 @@ namespace Ryujinx.Ui
|
||||
{
|
||||
_leftSideTriggerBox.Hide();
|
||||
_rightSideTriggerBox.Hide();
|
||||
_altBox.Hide();
|
||||
|
||||
switch (_controllerType.ActiveId)
|
||||
{
|
||||
@ -234,6 +244,9 @@ namespace Ryujinx.Ui
|
||||
case "JoyconRight":
|
||||
_rightSideTriggerBox.Show();
|
||||
break;
|
||||
case "JoyconPair":
|
||||
_altBox.Show();
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_controllerType.ActiveId)
|
||||
@ -290,6 +303,14 @@ namespace Ryujinx.Ui
|
||||
_controllerDeadzoneLeft.Value = 0;
|
||||
_controllerDeadzoneRight.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)
|
||||
@ -332,6 +353,14 @@ namespace Ryujinx.Ui
|
||||
_zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString();
|
||||
_rSl.Label = keyboardConfig.RightJoycon.ButtonSl.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;
|
||||
case ControllerConfig controllerConfig:
|
||||
if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString()))
|
||||
@ -372,6 +401,14 @@ namespace Ryujinx.Ui
|
||||
_controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft;
|
||||
_controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@ -448,7 +485,15 @@ namespace Ryujinx.Ui
|
||||
ButtonZr = rButtonZr,
|
||||
ButtonSl = rButtonSl,
|
||||
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,
|
||||
ButtonSl = rButtonSl,
|
||||
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,
|
||||
ButtonSl = 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"))
|
||||
@ -824,7 +885,15 @@ namespace Ryujinx.Ui
|
||||
ButtonSr = ControllerInputId.Unbound,
|
||||
InvertStickX = 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"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<!-- Generated with glade 3.36.0 -->
|
||||
<interface>
|
||||
<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">
|
||||
<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="page_increment">0.10000000000000001</property>
|
||||
<property name="page_increment">0.1</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="_controllerDeadzoneRight">
|
||||
<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="page_increment">0.10000000000000001</property>
|
||||
<property name="page_increment">0.1</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="_controllerTriggerThreshold">
|
||||
<property name="upper">1</property>
|
||||
<property name="value">0.5</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 class="GtkWindow" id="_controllerWin">
|
||||
<property name="can_focus">False</property>
|
||||
@ -27,9 +50,6 @@
|
||||
<property name="window_position">center</property>
|
||||
<property name="default_width">1100</property>
|
||||
<property name="default_height">600</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
@ -1616,14 +1636,303 @@
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</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>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</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>
|
||||
<packing>
|
||||
<property name="expand">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>
|
||||
</packing>
|
||||
</child>
|
||||
@ -1721,5 +2030,8 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
@ -13,6 +13,7 @@ using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Ryujinx.Motion;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
@ -48,6 +49,8 @@ namespace Ryujinx.Ui
|
||||
|
||||
private HotkeyButtons _prevHotkeyButtons;
|
||||
|
||||
private Client _dsuClient;
|
||||
|
||||
private GraphicsDebugLevel _glLogLevel;
|
||||
|
||||
public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel)
|
||||
@ -79,6 +82,8 @@ namespace Ryujinx.Ui
|
||||
|
||||
this.Shown += Renderer_Shown;
|
||||
|
||||
_dsuClient = new Client();
|
||||
|
||||
_glLogLevel = glLogLevel;
|
||||
}
|
||||
|
||||
@ -90,6 +95,7 @@ namespace Ryujinx.Ui
|
||||
private void GLRenderer_ShuttingDown(object sender, EventArgs args)
|
||||
{
|
||||
_device.DisposeGpu();
|
||||
_dsuClient?.Dispose();
|
||||
}
|
||||
|
||||
private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
|
||||
@ -104,6 +110,7 @@ namespace Ryujinx.Ui
|
||||
|
||||
private void GLRenderer_Destroyed(object sender, EventArgs e)
|
||||
{
|
||||
_dsuClient?.Dispose();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
@ -287,6 +294,7 @@ namespace Ryujinx.Ui
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
_dsuClient?.Dispose();
|
||||
if (IsStopped)
|
||||
{
|
||||
return;
|
||||
@ -406,6 +414,9 @@ namespace Ryujinx.Ui
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -419,6 +430,11 @@ namespace Ryujinx.Ui
|
||||
int rightJoystickDx = 0;
|
||||
int rightJoystickDy = 0;
|
||||
|
||||
if (inputConfig.EnableMotion)
|
||||
{
|
||||
motionDevice.RegisterController(inputConfig.PlayerIndex);
|
||||
}
|
||||
|
||||
if (inputConfig is KeyboardConfig keyboardConfig)
|
||||
{
|
||||
if (IsFocused)
|
||||
@ -488,6 +504,19 @@ namespace Ryujinx.Ui
|
||||
|
||||
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
|
||||
{
|
||||
PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
|
||||
@ -495,9 +524,29 @@ namespace Ryujinx.Ui
|
||||
LStick = leftJoystick,
|
||||
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.UpdateSixAxis(motionInputs);
|
||||
|
||||
if(IsFocused)
|
||||
{
|
||||
|
@ -82,6 +82,7 @@ namespace Ryujinx.Ui
|
||||
[GUI] ToggleButton _configureController7;
|
||||
[GUI] ToggleButton _configureController8;
|
||||
[GUI] ToggleButton _configureControllerH;
|
||||
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
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="page_increment">10</property>
|
||||
</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">
|
||||
<property name="lower">1</property>
|
||||
<property name="upper">31</property>
|
||||
@ -40,6 +35,11 @@
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</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">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Ryujinx - Settings</property>
|
||||
@ -1062,6 +1062,17 @@
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</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>
|
||||
<packing>
|
||||
<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="valign">center</property>
|
||||
<property name="caps_lock_warning">False</property>
|
||||
<property name="placeholder-text">1.0</property>
|
||||
<property name="input-purpose">GTK_INPUT_PURPOSE_NUMBER</property>
|
||||
<property name="placeholder_text">1.0</property>
|
||||
<property name="input_purpose">number</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
|
@ -460,6 +460,110 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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_sl": "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
|
||||
}
|
||||
]
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user