mirror of
https://github.com/ayufan/steam-deck-tools.git
synced 2025-12-06 07:12:01 +01:00
305 lines
8.6 KiB
C#
305 lines
8.6 KiB
C#
using System.Diagnostics;
|
|
using Nefarius.ViGEm.Client;
|
|
using Nefarius.ViGEm.Client.Exceptions;
|
|
using Nefarius.ViGEm.Client.Targets;
|
|
using Nefarius.ViGEm.Client.Targets.DualShock4;
|
|
using Nefarius.ViGEm.Client.Targets.Xbox360;
|
|
using static CommonHelpers.Log;
|
|
|
|
namespace SteamController.Devices
|
|
{
|
|
public partial class DS4Controller : IDisposable
|
|
{
|
|
public readonly TimeSpan FeedbackTimeout = TimeSpan.FromMilliseconds(1000);
|
|
public const ushort VendorID = 0x054C;
|
|
public const ushort ProductID = 0x05C4;
|
|
|
|
private const int REPORT_SIZE = 63;
|
|
private const int TIMESTAMP_HZ = 800;
|
|
private const int TIMESTAMP_INCREMENT = 188;
|
|
|
|
private ViGEmClient? client;
|
|
private IDualShock4Controller? device;
|
|
private bool isConnected;
|
|
private byte[] reportBytes = new byte[REPORT_SIZE];
|
|
private bool submitReport;
|
|
private int submittedReports = 0;
|
|
private Stopwatch stopwatch = new Stopwatch();
|
|
|
|
public DS4Controller()
|
|
{
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
using (client) { }
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
lock (this) { Fail(); }
|
|
}
|
|
|
|
internal bool Tick()
|
|
{
|
|
if (this.device is not null)
|
|
return true;
|
|
|
|
try
|
|
{
|
|
var client = new ViGEmClient();
|
|
var device = client.CreateDualShock4Controller();
|
|
device.AutoSubmitReport = false;
|
|
device.FeedbackReceived += DS4_FeedbackReceived;
|
|
this.device = device;
|
|
this.client = client;
|
|
return true;
|
|
}
|
|
catch (VigemBusNotFoundException)
|
|
{
|
|
// ViGem is not installed
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void Fail()
|
|
{
|
|
var client = this.client;
|
|
|
|
// unset current device
|
|
this.isConnected = false;
|
|
this.client = null;
|
|
this.device = null;
|
|
this.stopwatch.Stop();
|
|
|
|
try { using (client) { } }
|
|
catch (Exception) { }
|
|
}
|
|
|
|
private void PrepareReport()
|
|
{
|
|
var oldReportBytes = reportBytes;
|
|
|
|
reportBytes = new byte[63];
|
|
submitReport = false;
|
|
|
|
Counter.Set(reportBytes, (byte)((submittedReports << 2) & 0xFF));
|
|
BatteryLevel.Set(reportBytes, 255);
|
|
Timestamp.Set(reportBytes, (ushort)(stopwatch.ElapsedMilliseconds * TIMESTAMP_HZ * TIMESTAMP_INCREMENT / 1000));
|
|
|
|
DPadReleased.Set(reportBytes);
|
|
LeftThumbX.SetScaled(reportBytes, 0);
|
|
LeftThumbY.SetScaled(reportBytes, 0);
|
|
RightThumbX.SetScaled(reportBytes, 0);
|
|
RightThumbY.SetScaled(reportBytes, 0);
|
|
GyroX.Set(reportBytes, 0);
|
|
GyroY.Set(reportBytes, 0);
|
|
GyroZ.Set(reportBytes, 0);
|
|
AccelX.Set(reportBytes, 0);
|
|
AccelY.Set(reportBytes, 0);
|
|
AccelZ.Set(reportBytes, 0);
|
|
|
|
// Copy trackpad packets
|
|
reportBytes[32] = (byte)((reportBytes[32] + 1) & 0x3);
|
|
Array.Copy(oldReportBytes, 33, reportBytes, 33, 9);
|
|
Array.Copy(oldReportBytes, 33, reportBytes, 42, 9 * 2);
|
|
}
|
|
|
|
internal void BeforeUpdate()
|
|
{
|
|
device?.ResetReport();
|
|
|
|
if (!isConnected)
|
|
{
|
|
FeedbackLargeMotor = null;
|
|
FeedbackSmallMotor = null;
|
|
FeedbackReceived = null;
|
|
LightbarColor = null;
|
|
}
|
|
|
|
PrepareReport();
|
|
Connected = false;
|
|
}
|
|
|
|
private void SetConnected(bool wantsConnected)
|
|
{
|
|
if (wantsConnected == isConnected)
|
|
return;
|
|
|
|
if (wantsConnected)
|
|
{
|
|
try
|
|
{
|
|
device?.Connect();
|
|
stopwatch.Restart();
|
|
TraceLine("Connected DS4 Controller.");
|
|
}
|
|
catch (System.ComponentModel.Win32Exception e)
|
|
{
|
|
// This is expected exception (as sometimes device will fail to connect)
|
|
// ERROR_SUCCESS, which likely means COM did not succeed
|
|
if (e.NativeErrorCode == 0)
|
|
DebugException("DS4", "ConnectExpected", e);
|
|
else
|
|
TraceException("DS4", "ConnectExpected", e);
|
|
Fail();
|
|
return;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
TraceException("DS4", "Connect", e);
|
|
Fail();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
device?.Disconnect();
|
|
stopwatch.Stop();
|
|
TraceLine("Disconnected DS4 Controller.");
|
|
}
|
|
catch (VigemTargetNotPluggedInException)
|
|
{
|
|
// everything fine
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
TraceException("DS4", "Disconnect", e);
|
|
Fail();
|
|
return;
|
|
}
|
|
}
|
|
|
|
isConnected = wantsConnected;
|
|
}
|
|
|
|
internal void Update()
|
|
{
|
|
if (device is not null && Connected != isConnected)
|
|
{
|
|
lock (this)
|
|
{
|
|
SetConnected(Connected);
|
|
}
|
|
}
|
|
|
|
if (isConnected && submitReport)
|
|
{
|
|
try
|
|
{
|
|
device?.SubmitRawReport(reportBytes);
|
|
submittedReports++;
|
|
}
|
|
catch (VigemInvalidTargetException)
|
|
{
|
|
// Device was lost
|
|
lock (this) { Fail(); }
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
TraceException("DS4", "SubmitReport", e);
|
|
}
|
|
}
|
|
|
|
if (FeedbackReceived is not null && FeedbackReceived.Value.Add(FeedbackTimeout) < DateTime.Now)
|
|
{
|
|
FeedbackLargeMotor = null;
|
|
FeedbackSmallMotor = null;
|
|
FeedbackReceived = null;
|
|
}
|
|
}
|
|
|
|
public bool Valid
|
|
{
|
|
get { return device is not null; }
|
|
}
|
|
|
|
public bool Connected { get; set; }
|
|
public byte? FeedbackLargeMotor { get; private set; }
|
|
public byte? FeedbackSmallMotor { get; private set; }
|
|
public LightbarColor? LightbarColor { get; private set; }
|
|
public DateTime? FeedbackReceived { get; private set; }
|
|
|
|
public bool this[DualShock4Button button]
|
|
{
|
|
set
|
|
{
|
|
if (value)
|
|
button.Set(reportBytes, value);
|
|
submitReport = true;
|
|
}
|
|
}
|
|
|
|
public bool this[DualShock4DPadDirection button]
|
|
{
|
|
set
|
|
{
|
|
if (value)
|
|
button.Set(reportBytes);
|
|
submitReport = true;
|
|
}
|
|
}
|
|
|
|
public short this[DualShock4Axis axis]
|
|
{
|
|
set
|
|
{
|
|
axis.SetScaled(reportBytes, value);
|
|
submitReport = true;
|
|
}
|
|
}
|
|
|
|
public short this[DualShock4Slider slider]
|
|
{
|
|
set
|
|
{
|
|
slider.Set(reportBytes, value);
|
|
submitReport = true;
|
|
}
|
|
}
|
|
|
|
public short this[DualShock4Sensor sensor]
|
|
{
|
|
set
|
|
{
|
|
sensor.Set(reportBytes, value);
|
|
submitReport = true;
|
|
}
|
|
}
|
|
|
|
public Point? this[DualShock4Finger finger]
|
|
{
|
|
set
|
|
{
|
|
finger.Set(reportBytes, value);
|
|
submitReport = true;
|
|
}
|
|
}
|
|
|
|
public void Overwrite(DualShock4Button button, bool value, int minPressedTime = 0)
|
|
{
|
|
button.Set(reportBytes, value);
|
|
submitReport = true;
|
|
}
|
|
|
|
public void ResetFeedback()
|
|
{
|
|
FeedbackReceived = null;
|
|
}
|
|
|
|
private void DS4_FeedbackReceived(object sender, DualShock4FeedbackReceivedEventArgs e)
|
|
{
|
|
FeedbackLargeMotor = e.LargeMotor;
|
|
FeedbackSmallMotor = e.SmallMotor;
|
|
LightbarColor = e.LightbarColor;
|
|
FeedbackReceived = DateTime.Now;
|
|
}
|
|
}
|
|
}
|