2014-03-28 02:50:40 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
|
|
|
|
using System.Linq;
|
2015-11-28 06:47:26 +01:00
|
|
|
|
using System.Drawing;
|
2016-11-22 02:06:38 +01:00
|
|
|
|
using DS4Windows.DS4Library;
|
2015-11-28 06:47:26 +01:00
|
|
|
|
|
2015-02-08 22:51:52 +01:00
|
|
|
|
namespace DS4Windows
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
|
|
|
|
public struct DS4Color
|
|
|
|
|
{
|
|
|
|
|
public byte red;
|
|
|
|
|
public byte green;
|
|
|
|
|
public byte blue;
|
2017-05-12 16:48:58 +02:00
|
|
|
|
public DS4Color(Color c)
|
2015-02-08 22:51:52 +01:00
|
|
|
|
{
|
|
|
|
|
red = c.R;
|
|
|
|
|
green = c.G;
|
|
|
|
|
blue = c.B;
|
|
|
|
|
}
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
2015-02-08 22:51:52 +01:00
|
|
|
|
public DS4Color(byte r, byte g, byte b)
|
|
|
|
|
{
|
|
|
|
|
red = r;
|
|
|
|
|
green = g;
|
|
|
|
|
blue = b;
|
|
|
|
|
}
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
2014-09-02 01:23:02 +02:00
|
|
|
|
public override bool Equals(object obj)
|
|
|
|
|
{
|
|
|
|
|
if (obj is DS4Color)
|
|
|
|
|
{
|
|
|
|
|
DS4Color dsc = ((DS4Color)obj);
|
|
|
|
|
return (this.red == dsc.red && this.green == dsc.green && this.blue == dsc.blue);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
2015-11-28 06:47:26 +01:00
|
|
|
|
public Color ToColor => Color.FromArgb(red, green, blue);
|
|
|
|
|
public Color ToColorA
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
byte alphacolor = Math.Max(red, Math.Max(green, blue));
|
|
|
|
|
Color reg = Color.FromArgb(red, green, blue);
|
|
|
|
|
Color full = HuetoRGB(reg.GetHue(), reg.GetBrightness(), reg);
|
|
|
|
|
return Color.FromArgb((alphacolor > 205 ? 255 : (alphacolor + 50)), full);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Color HuetoRGB(float hue, float light, Color rgb)
|
|
|
|
|
{
|
|
|
|
|
float L = (float)Math.Max(.5, light);
|
|
|
|
|
float C = (1 - Math.Abs(2 * L - 1));
|
|
|
|
|
float X = (C * (1 - Math.Abs((hue / 60) % 2 - 1)));
|
|
|
|
|
float m = L - C / 2;
|
|
|
|
|
float R = 0, G = 0, B = 0;
|
|
|
|
|
if (light == 1) return Color.White;
|
|
|
|
|
else if (rgb.R == rgb.G && rgb.G == rgb.B) return Color.White;
|
|
|
|
|
else if (0 <= hue && hue < 60) { R = C; G = X; }
|
|
|
|
|
else if (60 <= hue && hue < 120) { R = X; G = C; }
|
|
|
|
|
else if (120 <= hue && hue < 180) { G = C; B = X; }
|
|
|
|
|
else if (180 <= hue && hue < 240) { G = X; B = C; }
|
|
|
|
|
else if (240 <= hue && hue < 300) { R = X; B = C; }
|
|
|
|
|
else if (300 <= hue && hue < 360) { R = C; B = X; }
|
|
|
|
|
return Color.FromArgb((int)((R + m) * 255), (int)((G + m) * 255), (int)((B + m) * 255));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool TryParse(string value, ref DS4Color ds4color)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string[] ss = value.Split(',');
|
|
|
|
|
return byte.TryParse(ss[0], out ds4color.red) &&byte.TryParse(ss[1], out ds4color.green) && byte.TryParse(ss[2], out ds4color.blue);
|
|
|
|
|
}
|
|
|
|
|
catch { return false; }
|
|
|
|
|
}
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
public override string ToString() => $"Red: {red} Green: {green} Blue: {blue}";
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-07 17:59:15 +02:00
|
|
|
|
public enum ConnectionType : byte { BT, SONYWA, USB }; // Prioritize Bluetooth when both BT and USB are connected.
|
2014-03-29 06:29:08 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The haptics engine uses a stack of these states representing the light bar and rumble motor settings.
|
|
|
|
|
* It (will) handle composing them and the details of output report management.
|
|
|
|
|
*/
|
|
|
|
|
public struct DS4HapticState
|
|
|
|
|
{
|
|
|
|
|
public DS4Color LightBarColor;
|
|
|
|
|
public bool LightBarExplicitlyOff;
|
|
|
|
|
public byte LightBarFlashDurationOn, LightBarFlashDurationOff;
|
|
|
|
|
public byte RumbleMotorStrengthLeftHeavySlow, RumbleMotorStrengthRightLightFast;
|
|
|
|
|
public bool RumbleMotorsExplicitlyOff;
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public bool IsLightBarSet()
|
|
|
|
|
{
|
|
|
|
|
return LightBarExplicitlyOff || LightBarColor.red != 0 || LightBarColor.green != 0 || LightBarColor.blue != 0;
|
|
|
|
|
}
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public bool IsRumbleSet()
|
|
|
|
|
{
|
|
|
|
|
return RumbleMotorsExplicitlyOff || RumbleMotorStrengthLeftHeavySlow != 0 || RumbleMotorStrengthRightLightFast != 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-29 10:19:45 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public class DS4Device
|
|
|
|
|
{
|
|
|
|
|
private const int BT_OUTPUT_REPORT_LENGTH = 78;
|
|
|
|
|
private const int BT_INPUT_REPORT_LENGTH = 547;
|
2017-03-22 10:41:04 +01:00
|
|
|
|
// Use large value for worst case scenario
|
2017-04-22 16:00:12 +02:00
|
|
|
|
private const int READ_STREAM_TIMEOUT = 100;
|
2017-03-23 09:12:50 +01:00
|
|
|
|
// Isolated BT report can have latency as high as 15 ms
|
|
|
|
|
// due to hardware.
|
2017-04-22 16:00:12 +02:00
|
|
|
|
private const int WARN_INTERVAL_BT = 20;
|
|
|
|
|
private const int WARN_INTERVAL_USB = 10;
|
|
|
|
|
// Maximum values for battery level when no USB cable is connected
|
|
|
|
|
// and when a USB cable is connected
|
|
|
|
|
private const int BATTERY_MAX = 8;
|
|
|
|
|
private const int BATTERY_MAX_USB = 11;
|
2017-05-25 11:51:28 +02:00
|
|
|
|
public const string blankSerial = "00:00:00:00:00:00";
|
2014-03-28 02:50:40 +01:00
|
|
|
|
private HidDevice hDevice;
|
|
|
|
|
private string Mac;
|
|
|
|
|
private DS4State cState = new DS4State();
|
|
|
|
|
private DS4State pState = new DS4State();
|
|
|
|
|
private ConnectionType conType;
|
|
|
|
|
private byte[] accel = new byte[6];
|
|
|
|
|
private byte[] gyro = new byte[6];
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private byte[] inputReport;
|
2017-05-19 02:51:01 +02:00
|
|
|
|
//private byte[] inputReport2;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
private byte[] btInputReport = null;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private byte[] outputReportBuffer, outputReport;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
private readonly DS4Touchpad touchpad = null;
|
2015-11-28 06:47:26 +01:00
|
|
|
|
private readonly DS4SixAxis sixAxis = null;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
private byte rightLightFastRumble;
|
|
|
|
|
private byte leftHeavySlowRumble;
|
|
|
|
|
private DS4Color ligtBarColor;
|
|
|
|
|
private byte ledFlashOn, ledFlashOff;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private Thread ds4Input, ds4Output;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
private int battery;
|
2017-05-11 17:13:51 +02:00
|
|
|
|
private DS4Audio audio = null;
|
|
|
|
|
private DS4Audio micAudio = null;
|
2014-05-22 21:13:38 +02:00
|
|
|
|
public DateTime lastActive = DateTime.UtcNow;
|
2015-02-08 22:51:52 +01:00
|
|
|
|
public DateTime firstActive = DateTime.UtcNow;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private bool charging;
|
2017-03-23 07:39:31 +01:00
|
|
|
|
private bool outputRumble = false;
|
2017-03-23 09:12:50 +01:00
|
|
|
|
private int warnInterval = WARN_INTERVAL_USB;
|
2017-04-07 17:59:15 +02:00
|
|
|
|
public int getWarnInterval()
|
|
|
|
|
{
|
|
|
|
|
return warnInterval;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-24 16:06:01 +01:00
|
|
|
|
private bool exitOutputThread = false;
|
2017-05-01 12:40:37 +02:00
|
|
|
|
private bool exitInputThread = false;
|
2017-03-24 16:06:01 +01:00
|
|
|
|
private object exitLocker = new object();
|
2017-05-25 11:51:28 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public event EventHandler<EventArgs> Report = null;
|
|
|
|
|
public event EventHandler<EventArgs> Removal = null;
|
2017-05-25 11:51:28 +02:00
|
|
|
|
public event EventHandler<EventArgs> SyncChange = null;
|
|
|
|
|
public event EventHandler<EventArgs> SerialChange = null;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
public HidDevice HidDevice => hDevice;
|
|
|
|
|
public bool IsExclusive => HidDevice.IsExclusive;
|
2017-04-25 11:24:14 +02:00
|
|
|
|
public bool isExclusive()
|
|
|
|
|
{
|
|
|
|
|
return HidDevice.IsExclusive;
|
|
|
|
|
}
|
2017-05-11 17:13:51 +02:00
|
|
|
|
|
2017-04-09 01:13:56 +02:00
|
|
|
|
private bool isDisconnecting = false;
|
|
|
|
|
public bool IsDisconnecting
|
|
|
|
|
{
|
|
|
|
|
get { return isDisconnecting; }
|
|
|
|
|
private set
|
|
|
|
|
{
|
|
|
|
|
this.isDisconnecting = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-11 17:13:51 +02:00
|
|
|
|
|
2017-04-21 05:09:08 +02:00
|
|
|
|
public bool isDisconnectingStatus()
|
|
|
|
|
{
|
|
|
|
|
return this.isDisconnecting;
|
|
|
|
|
}
|
2017-04-09 01:13:56 +02:00
|
|
|
|
|
|
|
|
|
private bool isRemoving = false;
|
|
|
|
|
public bool IsRemoving
|
|
|
|
|
{
|
|
|
|
|
get { return isRemoving; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
this.isRemoving = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool isRemoved = false;
|
|
|
|
|
public bool IsRemoved
|
|
|
|
|
{
|
|
|
|
|
get { return isRemoved; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
this.isRemoved = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-30 02:44:10 +02:00
|
|
|
|
public object removeLocker = new object();
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
public string MacAddress => Mac;
|
2017-04-21 05:09:08 +02:00
|
|
|
|
public string getMacAddress()
|
|
|
|
|
{
|
|
|
|
|
return this.Mac;
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
public ConnectionType ConnectionType => conType;
|
2017-04-16 08:22:04 +02:00
|
|
|
|
public ConnectionType getConnectionType()
|
|
|
|
|
{
|
|
|
|
|
return this.conType;
|
|
|
|
|
}
|
2017-04-06 22:05:16 +02:00
|
|
|
|
|
|
|
|
|
// behavior only active when > 0
|
|
|
|
|
private int idleTimeout = 0;
|
2017-04-26 23:51:15 +02:00
|
|
|
|
public int IdleTimeout
|
|
|
|
|
{
|
2017-04-06 22:05:16 +02:00
|
|
|
|
get { return idleTimeout; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
idleTimeout = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
2017-04-26 23:51:15 +02:00
|
|
|
|
public int getIdleTimeout()
|
|
|
|
|
{
|
|
|
|
|
return idleTimeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setIdleTimeout(int value)
|
|
|
|
|
{
|
|
|
|
|
if (idleTimeout != value)
|
|
|
|
|
{
|
|
|
|
|
idleTimeout = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
public int Battery => battery;
|
2017-03-24 03:32:33 +01:00
|
|
|
|
public int getBattery()
|
|
|
|
|
{
|
|
|
|
|
return battery;
|
|
|
|
|
}
|
|
|
|
|
|
Version 1.4.266
Flash Lightbar when at high latency now has the option to choose what
you decide is high latency
Show Notifications now has the option to only show warnings, such as
when a controller cannot be grabbed exclusively
Speaking of bad news for Windows 10 users: Hide DS4 has now been
disabled, until i can figure out why this is, it will be disabled, this
means some games that rely on this may not work properly or at all,
sorry about that
As for good news for Windows 10, did you know you can press Windows + G
to open a game bar which can record games. For Windows 10 users, there's
a new special action: Xbox Game DVR. Pick a trigger (only one button)
and tapping/holding/or double tapping does various things, such as
start/stop recording, save an ongoing recording, take a screenshot (via
the xbox app's option or your own hotkey ie form steam), or just open
the gamebar
Much of the code has been updated with c# 6.0
Added manifest so DS4Windows can notice Windows 10 and high DPIs, also
reorganized files
2015-07-31 05:34:22 +02:00
|
|
|
|
public bool Charging => charging;
|
2017-03-24 03:32:33 +01:00
|
|
|
|
public bool isCharging()
|
|
|
|
|
{
|
|
|
|
|
return charging;
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
2017-04-16 08:22:04 +02:00
|
|
|
|
private long lastTimeElapsed = 0;
|
|
|
|
|
public long getLastTimeElapsed()
|
|
|
|
|
{
|
|
|
|
|
return lastTimeElapsed;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public byte RightLightFastRumble
|
|
|
|
|
{
|
|
|
|
|
get { return rightLightFastRumble; }
|
|
|
|
|
set
|
|
|
|
|
{
|
2017-04-28 20:57:33 +02:00
|
|
|
|
if (rightLightFastRumble != value)
|
|
|
|
|
rightLightFastRumble = value;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte LeftHeavySlowRumble
|
|
|
|
|
{
|
|
|
|
|
get { return leftHeavySlowRumble; }
|
|
|
|
|
set
|
|
|
|
|
{
|
2017-04-28 20:57:33 +02:00
|
|
|
|
if (leftHeavySlowRumble != value)
|
|
|
|
|
leftHeavySlowRumble = value;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-24 03:32:33 +01:00
|
|
|
|
public byte getLeftHeavySlowRumble()
|
|
|
|
|
{
|
|
|
|
|
return leftHeavySlowRumble;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public DS4Color LightBarColor
|
|
|
|
|
{
|
|
|
|
|
get { return ligtBarColor; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (ligtBarColor.red != value.red || ligtBarColor.green != value.green || ligtBarColor.blue != value.blue)
|
|
|
|
|
{
|
|
|
|
|
ligtBarColor = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte LightBarOnDuration
|
|
|
|
|
{
|
|
|
|
|
get { return ledFlashOn; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (ledFlashOn != value)
|
|
|
|
|
{
|
|
|
|
|
ledFlashOn = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-24 03:32:33 +01:00
|
|
|
|
|
|
|
|
|
public byte getLightBarOnDuration()
|
|
|
|
|
{
|
|
|
|
|
return ledFlashOn;
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
|
|
|
|
public byte LightBarOffDuration
|
|
|
|
|
{
|
|
|
|
|
get { return ledFlashOff; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (ledFlashOff != value)
|
|
|
|
|
{
|
|
|
|
|
ledFlashOff = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-24 03:32:33 +01:00
|
|
|
|
|
|
|
|
|
public byte getLightBarOffDuration()
|
|
|
|
|
{
|
|
|
|
|
return ledFlashOff;
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
2017-05-17 08:02:12 +02:00
|
|
|
|
// Specify the poll rate interval used for the DS4 hardware when
|
|
|
|
|
// connected via Bluetooth
|
|
|
|
|
private int btPollRate = 0;
|
|
|
|
|
public int BTPollRate
|
|
|
|
|
{
|
|
|
|
|
get { return btPollRate; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (btPollRate != value && value >= 0 && value <= 16)
|
|
|
|
|
{
|
|
|
|
|
btPollRate = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getBTPollRate()
|
|
|
|
|
{
|
|
|
|
|
return btPollRate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setBTPollRate(int value)
|
|
|
|
|
{
|
|
|
|
|
if (btPollRate != value && value >= 0 && value <= 16)
|
|
|
|
|
{
|
|
|
|
|
btPollRate = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public DS4Touchpad Touchpad { get { return touchpad; } }
|
2015-11-28 06:47:26 +01:00
|
|
|
|
public DS4SixAxis SixAxis { get { return sixAxis; } }
|
2014-03-28 02:50:40 +01:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public static ConnectionType HidConnectionType(HidDevice hidDevice)
|
|
|
|
|
{
|
2017-04-06 01:51:20 +02:00
|
|
|
|
ConnectionType result = ConnectionType.USB;
|
|
|
|
|
if (hidDevice.Capabilities.InputReportByteLength == 64)
|
|
|
|
|
{
|
|
|
|
|
if (hidDevice.Capabilities.NumberFeatureDataIndices == 22)
|
|
|
|
|
{
|
|
|
|
|
result = ConnectionType.SONYWA;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
result = ConnectionType.BT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-09 16:24:46 +02:00
|
|
|
|
private SynchronizationContext uiContext = null;
|
2017-05-14 00:01:43 +02:00
|
|
|
|
private Queue<Action> eventQueue = new Queue<Action>();
|
|
|
|
|
private object eventQueueLock = new object();
|
2017-05-09 12:11:50 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
public DS4Device(HidDevice hidDevice)
|
|
|
|
|
{
|
|
|
|
|
hDevice = hidDevice;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
conType = HidConnectionType(hDevice);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
Mac = hDevice.readSerial();
|
2017-04-06 03:37:38 +02:00
|
|
|
|
if (conType == ConnectionType.USB || conType == ConnectionType.SONYWA)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
inputReport = new byte[64];
|
2017-05-19 02:51:01 +02:00
|
|
|
|
//inputReport2 = new byte[64];
|
2014-03-28 02:50:40 +01:00
|
|
|
|
outputReport = new byte[hDevice.Capabilities.OutputReportByteLength];
|
2014-03-29 06:29:08 +01:00
|
|
|
|
outputReportBuffer = new byte[hDevice.Capabilities.OutputReportByteLength];
|
2017-04-06 03:37:38 +02:00
|
|
|
|
if (conType == ConnectionType.USB)
|
|
|
|
|
{
|
|
|
|
|
warnInterval = WARN_INTERVAL_USB;
|
2017-05-25 11:51:28 +02:00
|
|
|
|
synced = true;
|
2017-04-06 03:37:38 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
warnInterval = WARN_INTERVAL_BT;
|
2017-05-11 17:13:51 +02:00
|
|
|
|
audio = new DS4Audio();
|
|
|
|
|
micAudio = new DS4Audio(DS4Library.CoreAudio.DataFlow.Render);
|
2017-05-25 11:51:28 +02:00
|
|
|
|
synced = isValidSerial();
|
2017-04-06 03:37:38 +02:00
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
btInputReport = new byte[BT_INPUT_REPORT_LENGTH];
|
2014-03-29 06:29:08 +01:00
|
|
|
|
inputReport = new byte[btInputReport.Length - 2];
|
2014-03-28 02:50:40 +01:00
|
|
|
|
outputReport = new byte[BT_OUTPUT_REPORT_LENGTH];
|
2014-03-29 06:29:08 +01:00
|
|
|
|
outputReportBuffer = new byte[BT_OUTPUT_REPORT_LENGTH];
|
2017-03-23 09:12:50 +01:00
|
|
|
|
warnInterval = WARN_INTERVAL_BT;
|
2017-05-25 11:51:28 +02:00
|
|
|
|
synced = isValidSerial();
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2017-04-22 09:26:44 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
touchpad = new DS4Touchpad();
|
2015-11-28 06:47:26 +01:00
|
|
|
|
sixAxis = new DS4SixAxis();
|
2017-05-09 16:24:46 +02:00
|
|
|
|
uiContext = SynchronizationContext.Current;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StartUpdate()
|
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (ds4Input == null)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-05-04 11:02:38 +02:00
|
|
|
|
if (!hDevice.IsFileStreamOpen())
|
|
|
|
|
{
|
|
|
|
|
hDevice.OpenFileStream(inputReport.Length);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-09 12:11:50 +02:00
|
|
|
|
//Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> start");
|
2014-04-27 21:32:09 +02:00
|
|
|
|
sendOutputReport(true); // initialize the output report
|
2017-05-09 16:24:46 +02:00
|
|
|
|
|
2017-03-22 08:52:54 +01:00
|
|
|
|
if (conType == ConnectionType.BT)
|
|
|
|
|
{
|
|
|
|
|
// Only use the output thread for Bluetooth connections.
|
|
|
|
|
// USB will utilize overlapped IO instead.
|
2017-05-09 16:24:46 +02:00
|
|
|
|
ds4Output = new Thread(performDs4Output);
|
|
|
|
|
ds4Output.Priority = ThreadPriority.AboveNormal;
|
|
|
|
|
ds4Output.Name = "DS4 Output thread: " + Mac;
|
|
|
|
|
ds4Output.IsBackground = true;
|
2017-03-22 08:52:54 +01:00
|
|
|
|
ds4Output.Start();
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
ds4Input = new Thread(performDs4Input);
|
2017-03-23 03:35:18 +01:00
|
|
|
|
ds4Input.Priority = ThreadPriority.AboveNormal;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
ds4Input.Name = "DS4 Input thread: " + Mac;
|
2017-03-24 16:06:01 +01:00
|
|
|
|
ds4Input.IsBackground = true;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
ds4Input.Start();
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
Console.WriteLine("Thread already running for DS4: " + Mac);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StopUpdate()
|
|
|
|
|
{
|
2017-04-23 04:46:50 +02:00
|
|
|
|
if (ds4Input != null &&
|
|
|
|
|
ds4Input.IsAlive && !ds4Input.ThreadState.HasFlag(System.Threading.ThreadState.Stopped) &&
|
2017-03-24 16:06:01 +01:00
|
|
|
|
!ds4Input.ThreadState.HasFlag(System.Threading.ThreadState.AbortRequested))
|
2014-03-29 06:29:08 +01:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2017-05-01 12:40:37 +02:00
|
|
|
|
exitInputThread = true;
|
|
|
|
|
//ds4Input.Abort();
|
2014-03-29 06:29:08 +01:00
|
|
|
|
ds4Input.Join();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(e.Message);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-22 16:49:01 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
StopOutputUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StopOutputUpdate()
|
|
|
|
|
{
|
2017-03-24 16:06:01 +01:00
|
|
|
|
lock (exitLocker)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-04-23 04:46:50 +02:00
|
|
|
|
if (ds4Output != null &&
|
|
|
|
|
ds4Output.IsAlive && !ds4Output.ThreadState.HasFlag(System.Threading.ThreadState.Stopped) &&
|
2017-03-24 16:06:01 +01:00
|
|
|
|
!ds4Output.ThreadState.HasFlag(System.Threading.ThreadState.AbortRequested))
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-03-24 16:06:01 +01:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
exitOutputThread = true;
|
2017-03-25 09:29:25 +01:00
|
|
|
|
/*lock (outputReport)
|
2017-03-24 16:06:01 +01:00
|
|
|
|
{
|
|
|
|
|
Monitor.PulseAll(outputReport);
|
|
|
|
|
}
|
2017-03-25 09:29:25 +01:00
|
|
|
|
*/
|
2017-03-24 16:06:01 +01:00
|
|
|
|
|
2017-03-25 09:29:25 +01:00
|
|
|
|
ds4Output.Interrupt();
|
2017-03-24 16:06:01 +01:00
|
|
|
|
ds4Output.Join();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(e.Message);
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private bool writeOutput()
|
|
|
|
|
{
|
|
|
|
|
if (conType == ConnectionType.BT)
|
|
|
|
|
{
|
|
|
|
|
return hDevice.WriteOutputReportViaControl(outputReport);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-05-14 00:01:43 +02:00
|
|
|
|
return hDevice.WriteOutputReportViaInterrupt(outputReport, READ_STREAM_TIMEOUT);
|
|
|
|
|
//return hDevice.WriteAsyncOutputReportViaInterrupt(outputReport);
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void performDs4Output()
|
|
|
|
|
{
|
|
|
|
|
lock (outputReport)
|
|
|
|
|
{
|
2017-03-25 09:29:25 +01:00
|
|
|
|
try
|
2014-03-29 06:29:08 +01:00
|
|
|
|
{
|
2017-03-25 09:29:25 +01:00
|
|
|
|
int lastError = 0;
|
|
|
|
|
while (!exitOutputThread)
|
2017-03-23 07:39:31 +01:00
|
|
|
|
{
|
2017-03-25 09:29:25 +01:00
|
|
|
|
bool result = false;
|
|
|
|
|
if (outputRumble)
|
2017-03-23 07:39:31 +01:00
|
|
|
|
{
|
2017-03-25 09:29:25 +01:00
|
|
|
|
result = writeOutput();
|
|
|
|
|
|
|
|
|
|
if (!result)
|
2017-03-23 07:39:31 +01:00
|
|
|
|
{
|
2017-03-25 09:29:25 +01:00
|
|
|
|
int thisError = Marshal.GetLastWin32Error();
|
|
|
|
|
if (lastError != thisError)
|
|
|
|
|
{
|
2017-05-12 16:48:58 +02:00
|
|
|
|
Console.WriteLine(Mac.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> encountered write failure: " + thisError);
|
2017-05-19 02:51:01 +02:00
|
|
|
|
//Log.LogToGui(Mac.ToString() + " encountered write failure: " + thisError, true);
|
2017-03-25 09:29:25 +01:00
|
|
|
|
lastError = thisError;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
outputRumble = false;
|
2017-03-23 07:39:31 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-25 09:29:25 +01:00
|
|
|
|
|
|
|
|
|
if (!outputRumble)
|
2017-03-23 07:39:31 +01:00
|
|
|
|
{
|
2017-03-25 09:29:25 +01:00
|
|
|
|
lastError = 0;
|
|
|
|
|
Monitor.Wait(outputReport);
|
|
|
|
|
/*if (testRumble.IsRumbleSet()) // repeat test rumbles periodically; rumble has auto-shut-off in the DS4 firmware
|
|
|
|
|
Monitor.Wait(outputReport, 10000); // DS4 firmware stops it after 5 seconds, so let the motors rest for that long, too.
|
|
|
|
|
else
|
|
|
|
|
Monitor.Wait(outputReport);
|
|
|
|
|
*/
|
2017-03-23 07:39:31 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-25 09:29:25 +01:00
|
|
|
|
}
|
|
|
|
|
catch (ThreadInterruptedException)
|
|
|
|
|
{
|
2017-03-23 07:39:31 +01:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Is the device alive and receiving valid sensor input reports? */
|
|
|
|
|
public bool IsAlive()
|
|
|
|
|
{
|
|
|
|
|
return priorInputReport30 != 0xff;
|
|
|
|
|
}
|
2017-04-22 09:26:44 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private byte priorInputReport30 = 0xff;
|
2017-05-25 11:51:28 +02:00
|
|
|
|
|
|
|
|
|
private bool synced = false;
|
|
|
|
|
public bool Synced
|
|
|
|
|
{
|
|
|
|
|
get { return synced; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (synced != value)
|
|
|
|
|
{
|
|
|
|
|
synced = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool isSynced()
|
|
|
|
|
{
|
|
|
|
|
return synced;
|
|
|
|
|
}
|
|
|
|
|
|
Shift modifier: Hold an action to use another set of controls, if nothing is set to the shifted control, in falls back to the default action
View input of controls in profiles, see exactly when a deadzone is passed and check the input delay for controllers (special thanks to jhebbel), click the on sixaxis panel
Click the Empty text on in the lightbar box to copy the lightbar color from full to empty.
While opened, option to keep the window size after closing the profile's settings
Old profiles are automatically upgraded if it's missing new settings, such as how colors are now saved, sixaxis deadzones, and shift controls
Other UI changes for profile settings, flipped touchpad and other settings boxes
Others:
Fix for when clicking the semicolon in the select an action screen
Fix assigning Sixaxis action to a key
minor UI changes and bug fixes, such as auto resize of the log listview
DS4Updater: Also now works for the new numbering system, can read the version number right from the exe instead of in profiles.xml, UI additions to better notify users of errors, Bug fixes for non-portable users
2014-07-07 21:22:42 +02:00
|
|
|
|
public double Latency = 0;
|
2014-12-17 19:29:22 +01:00
|
|
|
|
public string error;
|
2017-04-29 10:19:45 +02:00
|
|
|
|
public bool firstReport = false;
|
2017-05-17 10:59:09 +02:00
|
|
|
|
public bool oldCharging = false;
|
2017-04-22 09:26:44 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private void performDs4Input()
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2015-02-08 22:51:52 +01:00
|
|
|
|
firstActive = DateTime.UtcNow;
|
2017-03-10 01:07:17 +01:00
|
|
|
|
NativeMethods.HidD_SetNumInputBuffers(hDevice.safeReadHandle.DangerousGetHandle(), 2);
|
2017-05-19 02:51:01 +02:00
|
|
|
|
List<long> latencyList = new List<long>(51); // Set capacity at max + 1 to avoid any list resizing
|
|
|
|
|
int tempLatencyCount = 0;
|
Shift modifier: Hold an action to use another set of controls, if nothing is set to the shifted control, in falls back to the default action
View input of controls in profiles, see exactly when a deadzone is passed and check the input delay for controllers (special thanks to jhebbel), click the on sixaxis panel
Click the Empty text on in the lightbar box to copy the lightbar color from full to empty.
While opened, option to keep the window size after closing the profile's settings
Old profiles are automatically upgraded if it's missing new settings, such as how colors are now saved, sixaxis deadzones, and shift controls
Other UI changes for profile settings, flipped touchpad and other settings boxes
Others:
Fix for when clicking the semicolon in the select an action screen
Fix assigning Sixaxis action to a key
minor UI changes and bug fixes, such as auto resize of the log listview
DS4Updater: Also now works for the new numbering system, can read the version number right from the exe instead of in profiles.xml, UI additions to better notify users of errors, Bug fixes for non-portable users
2014-07-07 21:22:42 +02:00
|
|
|
|
long oldtime = 0;
|
|
|
|
|
Stopwatch sw = new Stopwatch();
|
|
|
|
|
sw.Start();
|
2017-05-19 02:51:01 +02:00
|
|
|
|
|
2017-05-01 12:40:37 +02:00
|
|
|
|
while (!exitInputThread)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-05-17 10:59:09 +02:00
|
|
|
|
oldCharging = charging;
|
2014-12-17 19:29:22 +01:00
|
|
|
|
string currerror = string.Empty;
|
2017-04-16 08:22:04 +02:00
|
|
|
|
long curtime = sw.ElapsedMilliseconds;
|
|
|
|
|
this.lastTimeElapsed = curtime - oldtime;
|
2017-05-19 02:51:01 +02:00
|
|
|
|
latencyList.Add(this.lastTimeElapsed);
|
|
|
|
|
tempLatencyCount++;
|
2017-04-16 08:22:04 +02:00
|
|
|
|
oldtime = curtime;
|
Shift modifier: Hold an action to use another set of controls, if nothing is set to the shifted control, in falls back to the default action
View input of controls in profiles, see exactly when a deadzone is passed and check the input delay for controllers (special thanks to jhebbel), click the on sixaxis panel
Click the Empty text on in the lightbar box to copy the lightbar color from full to empty.
While opened, option to keep the window size after closing the profile's settings
Old profiles are automatically upgraded if it's missing new settings, such as how colors are now saved, sixaxis deadzones, and shift controls
Other UI changes for profile settings, flipped touchpad and other settings boxes
Others:
Fix for when clicking the semicolon in the select an action screen
Fix assigning Sixaxis action to a key
minor UI changes and bug fixes, such as auto resize of the log listview
DS4Updater: Also now works for the new numbering system, can read the version number right from the exe instead of in profiles.xml, UI additions to better notify users of errors, Bug fixes for non-portable users
2014-07-07 21:22:42 +02:00
|
|
|
|
|
2017-05-19 02:51:01 +02:00
|
|
|
|
if (tempLatencyCount > 50)
|
|
|
|
|
{
|
|
|
|
|
latencyList.RemoveAt(0);
|
|
|
|
|
tempLatencyCount--;
|
|
|
|
|
}
|
Shift modifier: Hold an action to use another set of controls, if nothing is set to the shifted control, in falls back to the default action
View input of controls in profiles, see exactly when a deadzone is passed and check the input delay for controllers (special thanks to jhebbel), click the on sixaxis panel
Click the Empty text on in the lightbar box to copy the lightbar color from full to empty.
While opened, option to keep the window size after closing the profile's settings
Old profiles are automatically upgraded if it's missing new settings, such as how colors are now saved, sixaxis deadzones, and shift controls
Other UI changes for profile settings, flipped touchpad and other settings boxes
Others:
Fix for when clicking the semicolon in the select an action screen
Fix assigning Sixaxis action to a key
minor UI changes and bug fixes, such as auto resize of the log listview
DS4Updater: Also now works for the new numbering system, can read the version number right from the exe instead of in profiles.xml, UI additions to better notify users of errors, Bug fixes for non-portable users
2014-07-07 21:22:42 +02:00
|
|
|
|
|
2017-05-19 02:51:01 +02:00
|
|
|
|
Latency = latencyList.Average();
|
Shift modifier: Hold an action to use another set of controls, if nothing is set to the shifted control, in falls back to the default action
View input of controls in profiles, see exactly when a deadzone is passed and check the input delay for controllers (special thanks to jhebbel), click the on sixaxis panel
Click the Empty text on in the lightbar box to copy the lightbar color from full to empty.
While opened, option to keep the window size after closing the profile's settings
Old profiles are automatically upgraded if it's missing new settings, such as how colors are now saved, sixaxis deadzones, and shift controls
Other UI changes for profile settings, flipped touchpad and other settings boxes
Others:
Fix for when clicking the semicolon in the select an action screen
Fix assigning Sixaxis action to a key
minor UI changes and bug fixes, such as auto resize of the log listview
DS4Updater: Also now works for the new numbering system, can read the version number right from the exe instead of in profiles.xml, UI additions to better notify users of errors, Bug fixes for non-portable users
2014-07-07 21:22:42 +02:00
|
|
|
|
|
2017-04-06 01:51:20 +02:00
|
|
|
|
if (conType == ConnectionType.BT)
|
2014-04-27 21:32:09 +02:00
|
|
|
|
{
|
2017-03-22 08:52:54 +01:00
|
|
|
|
//HidDevice.ReadStatus res = hDevice.ReadFile(btInputReport);
|
2017-05-14 00:01:43 +02:00
|
|
|
|
//HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(btInputReport, READ_STREAM_TIMEOUT);
|
|
|
|
|
HidDevice.ReadStatus res = hDevice.ReadWithFileStream(btInputReport);
|
|
|
|
|
//HidDevice.ReadStatus res = hDevice.ReadFileOverlapped(btInputReport, READ_STREAM_TIMEOUT);
|
2014-04-27 21:32:09 +02:00
|
|
|
|
if (res == HidDevice.ReadStatus.Success)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
Array.Copy(btInputReport, 2, inputReport, 0, inputReport.Length);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2017-05-12 16:48:58 +02:00
|
|
|
|
if (res == HidDevice.ReadStatus.WaitTimedOut)
|
|
|
|
|
{
|
|
|
|
|
Log.LogToGui(Mac.ToString() + " disconnected due to timeout", true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int winError = Marshal.GetLastWin32Error();
|
|
|
|
|
Console.WriteLine(Mac.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> disconnect due to read failure: " + winError);
|
2017-05-19 02:51:01 +02:00
|
|
|
|
//Log.LogToGui(Mac.ToString() + " disconnected due to read failure: " + winError, true);
|
2017-05-12 16:48:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
sendOutputReport(true); // Kick Windows into noticing the disconnection.
|
|
|
|
|
StopOutputUpdate();
|
2017-04-09 01:13:56 +02:00
|
|
|
|
isDisconnecting = true;
|
2017-05-09 16:24:46 +02:00
|
|
|
|
uiContext.Send(new SendOrPostCallback(delegate (object state4)
|
2017-05-09 12:11:50 +02:00
|
|
|
|
{
|
|
|
|
|
Removal?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}), null);
|
|
|
|
|
|
2014-04-27 21:32:09 +02:00
|
|
|
|
return;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2014-04-27 21:32:09 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-03-22 08:52:54 +01:00
|
|
|
|
//HidDevice.ReadStatus res = hDevice.ReadFile(inputReport);
|
2017-04-06 17:30:41 +02:00
|
|
|
|
//Array.Clear(inputReport, 0, inputReport.Length);
|
2017-05-14 00:01:43 +02:00
|
|
|
|
//HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(inputReport, READ_STREAM_TIMEOUT);
|
|
|
|
|
HidDevice.ReadStatus res = hDevice.ReadWithFileStream(inputReport);
|
|
|
|
|
//HidDevice.ReadStatus res = hDevice.ReadFileOverlapped(inputReport, READ_STREAM_TIMEOUT);
|
2014-04-27 21:32:09 +02:00
|
|
|
|
if (res != HidDevice.ReadStatus.Success)
|
|
|
|
|
{
|
2017-05-12 16:48:58 +02:00
|
|
|
|
if (res == HidDevice.ReadStatus.WaitTimedOut)
|
|
|
|
|
{
|
|
|
|
|
Log.LogToGui(Mac.ToString() + " disconnected due to timeout", true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int winError = Marshal.GetLastWin32Error();
|
|
|
|
|
Console.WriteLine(Mac.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> disconnect due to read failure: " + winError);
|
2017-05-19 02:51:01 +02:00
|
|
|
|
//Log.LogToGui(Mac.ToString() + " disconnected due to read failure: " + winError, true);
|
2017-05-12 16:48:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-27 21:32:09 +02:00
|
|
|
|
StopOutputUpdate();
|
2017-04-09 01:13:56 +02:00
|
|
|
|
isDisconnecting = true;
|
2017-05-09 16:24:46 +02:00
|
|
|
|
uiContext.Send(new SendOrPostCallback(delegate (object state4)
|
2017-05-09 12:11:50 +02:00
|
|
|
|
{
|
|
|
|
|
Removal?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}), null);
|
|
|
|
|
|
2014-04-27 21:32:09 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2017-03-22 08:52:54 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
2017-03-28 10:48:02 +02:00
|
|
|
|
//Array.Copy(inputReport2, 0, inputReport, 0, inputReport.Length);
|
2017-03-22 08:52:54 +01:00
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
|
|
|
|
if (conType == ConnectionType.BT && btInputReport[0] != 0x11)
|
2014-03-29 06:29:08 +01:00
|
|
|
|
{
|
|
|
|
|
//Received incorrect report, skip it
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
2017-05-19 02:51:01 +02:00
|
|
|
|
DateTime utcNow = DateTime.UtcNow; // timestamp with UTC in case system time zone changes
|
2014-03-29 06:29:08 +01:00
|
|
|
|
resetHapticState();
|
|
|
|
|
cState.ReportTimeStamp = utcNow;
|
|
|
|
|
cState.LX = inputReport[1];
|
|
|
|
|
cState.LY = inputReport[2];
|
|
|
|
|
cState.RX = inputReport[3];
|
|
|
|
|
cState.RY = inputReport[4];
|
|
|
|
|
cState.L2 = inputReport[8];
|
|
|
|
|
cState.R2 = inputReport[9];
|
|
|
|
|
|
2017-04-28 20:57:33 +02:00
|
|
|
|
cState.Triangle = (inputReport[5] & (1 << 7)) != 0;
|
|
|
|
|
cState.Circle = (inputReport[5] & (1 << 6)) != 0;
|
|
|
|
|
cState.Cross = (inputReport[5] & (1 << 5)) != 0;
|
|
|
|
|
cState.Square = (inputReport[5] & (1 << 4)) != 0;
|
2017-02-18 09:58:44 +01:00
|
|
|
|
|
|
|
|
|
// First 4 bits denote dpad state. Clock representation
|
|
|
|
|
// with 8 meaning centered and 0 meaning DpadUp.
|
|
|
|
|
byte dpad_state = (byte)(inputReport[5] & 0x0F);
|
2014-03-29 06:29:08 +01:00
|
|
|
|
|
|
|
|
|
switch (dpad_state)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
case 0: cState.DpadUp = true; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = false; break;
|
|
|
|
|
case 1: cState.DpadUp = true; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = true; break;
|
|
|
|
|
case 2: cState.DpadUp = false; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = true; break;
|
|
|
|
|
case 3: cState.DpadUp = false; cState.DpadDown = true; cState.DpadLeft = false; cState.DpadRight = true; break;
|
|
|
|
|
case 4: cState.DpadUp = false; cState.DpadDown = true; cState.DpadLeft = false; cState.DpadRight = false; break;
|
|
|
|
|
case 5: cState.DpadUp = false; cState.DpadDown = true; cState.DpadLeft = true; cState.DpadRight = false; break;
|
|
|
|
|
case 6: cState.DpadUp = false; cState.DpadDown = false; cState.DpadLeft = true; cState.DpadRight = false; break;
|
|
|
|
|
case 7: cState.DpadUp = true; cState.DpadDown = false; cState.DpadLeft = true; cState.DpadRight = false; break;
|
2017-04-28 20:57:33 +02:00
|
|
|
|
case 8:
|
|
|
|
|
default: cState.DpadUp = false; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = false; break;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 20:57:33 +02:00
|
|
|
|
cState.R3 = (inputReport[6] & (1 << 7)) != 0;
|
|
|
|
|
cState.L3 = (inputReport[6] & (1 << 6)) != 0;
|
|
|
|
|
cState.Options = (inputReport[6] & (1 << 5)) != 0;
|
|
|
|
|
cState.Share = (inputReport[6] & (1 << 4)) != 0;
|
|
|
|
|
cState.R1 = (inputReport[6] & (1 << 1)) != 0;
|
|
|
|
|
cState.L1 = (inputReport[6] & (1 << 0)) != 0;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
|
2017-04-28 20:57:33 +02:00
|
|
|
|
cState.PS = (inputReport[7] & (1 << 0)) != 0;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
cState.TouchButton = (inputReport[7] & (1 << 2 - 1)) != 0;
|
|
|
|
|
cState.FrameCounter = (byte)(inputReport[7] >> 2);
|
|
|
|
|
|
|
|
|
|
// Store Gyro and Accel values
|
|
|
|
|
Array.Copy(inputReport, 14, accel, 0, 6);
|
|
|
|
|
Array.Copy(inputReport, 20, gyro, 0, 6);
|
2015-11-28 06:47:26 +01:00
|
|
|
|
sixAxis.handleSixaxis(gyro, accel, cState);
|
2014-12-17 19:29:22 +01:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2017-04-06 20:58:47 +02:00
|
|
|
|
charging = (inputReport[30] & 0x10) != 0;
|
2017-04-22 16:00:12 +02:00
|
|
|
|
int maxBatteryValue = charging ? BATTERY_MAX_USB : BATTERY_MAX;
|
|
|
|
|
int tempBattery = (inputReport[30] & 0x0f) * 100 / maxBatteryValue;
|
|
|
|
|
battery = Math.Min((byte)tempBattery, (byte)100);
|
2017-04-06 20:58:47 +02:00
|
|
|
|
cState.Battery = (byte)battery;
|
2014-12-17 19:29:22 +01:00
|
|
|
|
if (inputReport[30] != priorInputReport30)
|
|
|
|
|
{
|
|
|
|
|
priorInputReport30 = inputReport[30];
|
2017-04-22 16:00:12 +02:00
|
|
|
|
//Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> power subsystem octet: 0x" + inputReport[30].ToString("x02"));
|
2014-12-17 19:29:22 +01:00
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2015-02-08 22:51:52 +01:00
|
|
|
|
catch { currerror = "Index out of bounds: battery"; }
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
// XXX DS4State mapping needs fixup, turn touches into an array[4] of structs. And include the touchpad details there instead.
|
2014-12-17 19:29:22 +01:00
|
|
|
|
try
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-05-02 15:29:46 +02:00
|
|
|
|
// Only care if one touch packet is detected. Other touch packets
|
|
|
|
|
// don't seem to contain relevant data. ds4drv does not use them either.
|
|
|
|
|
for (int touches = Math.Max((int)(inputReport[-1 + DS4Touchpad.TOUCHPAD_DATA_OFFSET - 1]), 1), touchOffset = 0; touches > 0; touches--, touchOffset += 9)
|
|
|
|
|
//for (int touches = inputReport[-1 + DS4Touchpad.TOUCHPAD_DATA_OFFSET - 1], touchOffset = 0; touches > 0; touches--, touchOffset += 9)
|
2014-12-17 19:29:22 +01:00
|
|
|
|
{
|
|
|
|
|
cState.TouchPacketCounter = inputReport[-1 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset];
|
|
|
|
|
cState.Touch1 = (inputReport[0 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] >> 7) != 0 ? false : true; // >= 1 touch detected
|
|
|
|
|
cState.Touch1Identifier = (byte)(inputReport[0 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7f);
|
|
|
|
|
cState.Touch2 = (inputReport[4 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] >> 7) != 0 ? false : true; // 2 touches detected
|
|
|
|
|
cState.Touch2Identifier = (byte)(inputReport[4 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7f);
|
|
|
|
|
cState.TouchLeft = (inputReport[1 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] + ((inputReport[2 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0xF) * 255) >= 1920 * 2 / 5) ? false : true;
|
|
|
|
|
cState.TouchRight = (inputReport[1 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] + ((inputReport[2 + DS4Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0xF) * 255) < 1920 * 2 / 5) ? false : true;
|
2014-04-27 21:32:09 +02:00
|
|
|
|
// Even when idling there is still a touch packet indicating no touch 1 or 2
|
2014-12-17 19:29:22 +01:00
|
|
|
|
touchpad.handleTouchpad(inputReport, cState, touchOffset);
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2015-02-08 22:51:52 +01:00
|
|
|
|
catch { currerror = "Index out of bounds: touchpad"; }
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
/* Debug output of incoming HID data:
|
|
|
|
|
if (cState.L2 == 0xff && cState.R2 == 0xff)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
Console.Write(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + ">");
|
|
|
|
|
for (int i = 0; i < inputReport.Length; i++)
|
|
|
|
|
Console.Write(" " + inputReport[i].ToString("x2"));
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
} */
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
2017-05-25 11:51:28 +02:00
|
|
|
|
if (conType == ConnectionType.SONYWA)
|
|
|
|
|
{
|
|
|
|
|
bool noneSynced = inputReport[31] == 0;
|
|
|
|
|
if (noneSynced != synced)
|
|
|
|
|
{
|
|
|
|
|
SyncChange?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
synced = noneSynced;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-06 20:58:47 +02:00
|
|
|
|
bool ds4Idle = cState.FrameCounter == pState.FrameCounter;
|
2017-04-09 01:13:56 +02:00
|
|
|
|
if (!ds4Idle)
|
|
|
|
|
{
|
|
|
|
|
isRemoved = false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-07 05:24:16 +02:00
|
|
|
|
if (conType == ConnectionType.USB)
|
2017-04-06 22:05:16 +02:00
|
|
|
|
{
|
2014-05-28 04:49:58 +02:00
|
|
|
|
lastActive = utcNow;
|
2017-04-06 22:05:16 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-04-27 21:32:09 +02:00
|
|
|
|
bool shouldDisconnect = false;
|
2017-04-06 22:05:16 +02:00
|
|
|
|
int idleTime = idleTimeout;
|
2017-04-09 01:13:56 +02:00
|
|
|
|
if (!isRemoved && idleTime > 0)
|
2014-04-27 21:32:09 +02:00
|
|
|
|
{
|
2017-04-06 20:58:47 +02:00
|
|
|
|
bool idleInput = isDS4Idle();
|
|
|
|
|
if (idleInput)
|
2014-04-27 21:32:09 +02:00
|
|
|
|
{
|
2017-04-06 20:58:47 +02:00
|
|
|
|
DateTime timeout = lastActive + TimeSpan.FromSeconds(idleTime);
|
|
|
|
|
if (!charging)
|
2014-05-28 04:49:58 +02:00
|
|
|
|
shouldDisconnect = utcNow >= timeout;
|
2014-04-27 21:32:09 +02:00
|
|
|
|
}
|
2017-04-06 22:05:16 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
lastActive = utcNow;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
lastActive = utcNow;
|
2014-04-27 21:32:09 +02:00
|
|
|
|
}
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
2017-04-07 05:24:16 +02:00
|
|
|
|
if (shouldDisconnect)
|
|
|
|
|
{
|
2017-05-12 16:48:58 +02:00
|
|
|
|
Log.LogToGui(Mac.ToString() + " disconnecting due to idle disconnect", false);
|
|
|
|
|
|
2017-04-07 05:24:16 +02:00
|
|
|
|
if (conType == ConnectionType.BT)
|
|
|
|
|
{
|
2017-05-01 11:29:19 +02:00
|
|
|
|
if (DisconnectBT(true))
|
2017-04-07 05:24:16 +02:00
|
|
|
|
return; // all done
|
|
|
|
|
}
|
|
|
|
|
else if (conType == ConnectionType.SONYWA)
|
|
|
|
|
{
|
2017-04-09 01:13:56 +02:00
|
|
|
|
DisconnectDongle();
|
2017-04-07 05:24:16 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2017-04-06 20:58:47 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
if (Report != null)
|
|
|
|
|
Report(this, EventArgs.Empty);
|
2017-04-16 08:22:04 +02:00
|
|
|
|
|
2017-03-22 08:52:54 +01:00
|
|
|
|
bool syncWriteReport = true;
|
|
|
|
|
if (conType == ConnectionType.BT)
|
|
|
|
|
{
|
|
|
|
|
syncWriteReport = false;
|
|
|
|
|
}
|
|
|
|
|
sendOutputReport(syncWriteReport);
|
2017-04-16 08:22:04 +02:00
|
|
|
|
|
2014-12-17 19:29:22 +01:00
|
|
|
|
if (!string.IsNullOrEmpty(currerror))
|
2017-04-06 20:58:47 +02:00
|
|
|
|
error = currerror;
|
2017-05-19 02:51:01 +02:00
|
|
|
|
else if (!string.IsNullOrEmpty(error))
|
|
|
|
|
error = string.Empty;
|
2017-04-16 08:22:04 +02:00
|
|
|
|
|
2014-04-27 21:32:09 +02:00
|
|
|
|
cState.CopyTo(pState);
|
2017-05-14 00:01:43 +02:00
|
|
|
|
|
2017-05-14 04:31:39 +02:00
|
|
|
|
lock (eventQueueLock)
|
2017-05-14 00:01:43 +02:00
|
|
|
|
{
|
|
|
|
|
Action tempAct = null;
|
|
|
|
|
for (int actInd = 0, actLen = eventQueue.Count; actInd < actLen; actInd++)
|
2017-05-17 08:02:12 +02:00
|
|
|
|
//foreach (Action tempAct in eventQueue)
|
2017-05-14 00:01:43 +02:00
|
|
|
|
{
|
|
|
|
|
tempAct = eventQueue.Dequeue();
|
|
|
|
|
tempAct.Invoke();
|
|
|
|
|
}
|
2017-05-17 08:02:12 +02:00
|
|
|
|
|
|
|
|
|
//eventQueue.Clear();
|
2017-05-14 00:01:43 +02:00
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-18 22:23:41 +01:00
|
|
|
|
public void FlushHID()
|
|
|
|
|
{
|
|
|
|
|
hDevice.flush_Queue();
|
|
|
|
|
}
|
2017-04-12 04:26:08 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private void sendOutputReport(bool synchronous)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
setTestRumble();
|
|
|
|
|
setHapticState();
|
2017-04-22 09:26:44 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (conType == ConnectionType.BT)
|
|
|
|
|
{
|
|
|
|
|
outputReportBuffer[0] = 0x11;
|
2017-05-17 08:02:12 +02:00
|
|
|
|
//outputReportBuffer[1] = 0x80;
|
|
|
|
|
//outputReportBuffer[1] = 0x84;
|
2017-05-19 02:51:01 +02:00
|
|
|
|
outputReportBuffer[1] = (byte)(0x80 | btPollRate); // input report rate
|
2014-03-29 06:29:08 +01:00
|
|
|
|
outputReportBuffer[3] = 0xff;
|
2017-05-19 02:51:01 +02:00
|
|
|
|
outputReportBuffer[6] = rightLightFastRumble; // fast motor
|
|
|
|
|
outputReportBuffer[7] = leftHeavySlowRumble; // slow motor
|
|
|
|
|
outputReportBuffer[8] = LightBarColor.red; // red
|
|
|
|
|
outputReportBuffer[9] = LightBarColor.green; // green
|
|
|
|
|
outputReportBuffer[10] = LightBarColor.blue; // blue
|
|
|
|
|
outputReportBuffer[11] = ledFlashOn; // flash on duration
|
|
|
|
|
outputReportBuffer[12] = ledFlashOff; // flash off duration
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
outputReportBuffer[0] = 0x05;
|
|
|
|
|
outputReportBuffer[1] = 0xff;
|
2017-05-19 02:51:01 +02:00
|
|
|
|
outputReportBuffer[4] = rightLightFastRumble; // fast motor
|
|
|
|
|
outputReportBuffer[5] = leftHeavySlowRumble; // slow motor
|
|
|
|
|
outputReportBuffer[6] = LightBarColor.red; // red
|
|
|
|
|
outputReportBuffer[7] = LightBarColor.green; // green
|
|
|
|
|
outputReportBuffer[8] = LightBarColor.blue; // blue
|
|
|
|
|
outputReportBuffer[9] = ledFlashOn; // flash on duration
|
|
|
|
|
outputReportBuffer[10] = ledFlashOff; // flash off duration
|
2017-05-09 12:11:50 +02:00
|
|
|
|
if (conType == ConnectionType.SONYWA)
|
|
|
|
|
{
|
|
|
|
|
// Headphone volume levels
|
|
|
|
|
outputReportBuffer[19] = outputReportBuffer[20] = Convert.ToByte(audio.getVolume());
|
|
|
|
|
// Microphone volume level
|
|
|
|
|
outputReportBuffer[21] = Convert.ToByte(micAudio.getVolume());
|
|
|
|
|
}
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
2017-04-12 04:26:08 +02:00
|
|
|
|
|
2017-03-30 02:44:10 +02:00
|
|
|
|
bool quitOutputThread = false;
|
2017-04-12 04:26:08 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
lock (outputReport)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (synchronous)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-03-23 07:39:31 +01:00
|
|
|
|
outputRumble = false;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
outputReportBuffer.CopyTo(outputReport, 0);
|
|
|
|
|
try
|
|
|
|
|
{
|
2017-03-24 16:06:01 +01:00
|
|
|
|
if (!writeOutput())
|
2014-11-20 20:03:18 +01:00
|
|
|
|
{
|
2017-05-12 16:48:58 +02:00
|
|
|
|
int winError = Marshal.GetLastWin32Error();
|
|
|
|
|
Console.WriteLine(Mac.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> encountered synchronous write failure: " + winError);
|
2017-05-19 02:51:01 +02:00
|
|
|
|
//Log.LogToGui(Mac.ToString() + " encountered synchronous write failure: " + winError, true);
|
2017-03-30 02:44:10 +02:00
|
|
|
|
quitOutputThread = true;
|
2014-11-20 20:03:18 +01:00
|
|
|
|
}
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// If it's dead already, don't worry about it.
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
bool output = false;
|
2017-04-12 04:26:08 +02:00
|
|
|
|
for (int i = 0, arlen = outputReport.Length; !output && i < arlen; i++)
|
2014-03-29 06:29:08 +01:00
|
|
|
|
output = outputReport[i] != outputReportBuffer[i];
|
2017-04-29 10:19:45 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (output)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-03-23 07:39:31 +01:00
|
|
|
|
outputRumble = true;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
outputReportBuffer.CopyTo(outputReport, 0);
|
|
|
|
|
Monitor.Pulse(outputReport);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-03-30 02:44:10 +02:00
|
|
|
|
|
|
|
|
|
if (quitOutputThread)
|
|
|
|
|
{
|
|
|
|
|
StopOutputUpdate();
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-01 11:29:19 +02:00
|
|
|
|
public bool DisconnectBT(bool callRemoval = false)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
|
|
|
|
if (Mac != null)
|
|
|
|
|
{
|
2014-04-27 21:32:09 +02:00
|
|
|
|
Console.WriteLine("Trying to disconnect BT device " + Mac);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
IntPtr btHandle = IntPtr.Zero;
|
|
|
|
|
int IOCTL_BTH_DISCONNECT_DEVICE = 0x41000c;
|
|
|
|
|
|
|
|
|
|
byte[] btAddr = new byte[8];
|
|
|
|
|
string[] sbytes = Mac.Split(':');
|
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
|
|
|
{
|
2017-04-28 20:57:33 +02:00
|
|
|
|
// parse hex byte in reverse order
|
2014-03-28 02:50:40 +01:00
|
|
|
|
btAddr[5 - i] = Convert.ToByte(sbytes[i], 16);
|
|
|
|
|
}
|
2017-04-27 03:39:33 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
long lbtAddr = BitConverter.ToInt64(btAddr, 0);
|
|
|
|
|
|
|
|
|
|
bool success = false;
|
2017-05-01 11:29:19 +02:00
|
|
|
|
// Wait for output report to be written
|
|
|
|
|
lock (outputReport)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-05-01 11:29:19 +02:00
|
|
|
|
NativeMethods.BLUETOOTH_FIND_RADIO_PARAMS p = new NativeMethods.BLUETOOTH_FIND_RADIO_PARAMS();
|
|
|
|
|
p.dwSize = Marshal.SizeOf(typeof(NativeMethods.BLUETOOTH_FIND_RADIO_PARAMS));
|
|
|
|
|
IntPtr searchHandle = NativeMethods.BluetoothFindFirstRadio(ref p, ref btHandle);
|
|
|
|
|
int bytesReturned = 0;
|
|
|
|
|
|
|
|
|
|
while (!success && btHandle != IntPtr.Zero)
|
2017-04-27 03:39:33 +02:00
|
|
|
|
{
|
2017-05-01 11:29:19 +02:00
|
|
|
|
success = NativeMethods.DeviceIoControl(btHandle, IOCTL_BTH_DISCONNECT_DEVICE, ref lbtAddr, 8, IntPtr.Zero, 0, ref bytesReturned, IntPtr.Zero);
|
|
|
|
|
NativeMethods.CloseHandle(btHandle);
|
|
|
|
|
if (!success)
|
|
|
|
|
{
|
|
|
|
|
if (!NativeMethods.BluetoothFindNextRadio(searchHandle, ref btHandle))
|
|
|
|
|
btHandle = IntPtr.Zero;
|
|
|
|
|
}
|
2017-04-27 03:39:33 +02:00
|
|
|
|
}
|
2017-05-01 11:29:19 +02:00
|
|
|
|
|
|
|
|
|
NativeMethods.BluetoothFindRadioClose(searchHandle);
|
|
|
|
|
Console.WriteLine("Disconnect successful: " + success);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2017-04-27 03:39:33 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
success = true; // XXX return value indicates failure, but it still works?
|
2017-04-27 03:39:33 +02:00
|
|
|
|
if (success)
|
2014-03-29 06:29:08 +01:00
|
|
|
|
{
|
|
|
|
|
IsDisconnecting = true;
|
2017-04-27 03:39:33 +02:00
|
|
|
|
StopOutputUpdate();
|
2017-04-22 16:49:01 +02:00
|
|
|
|
|
2017-05-01 11:29:19 +02:00
|
|
|
|
if (callRemoval)
|
|
|
|
|
{
|
2017-05-09 16:24:46 +02:00
|
|
|
|
uiContext.Send(new SendOrPostCallback(delegate (object state)
|
2017-05-09 12:11:50 +02:00
|
|
|
|
{
|
|
|
|
|
Removal?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}), null);
|
2017-05-01 11:29:19 +02:00
|
|
|
|
}
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
2017-04-27 03:39:33 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
return success;
|
|
|
|
|
}
|
2017-04-27 03:39:33 +02:00
|
|
|
|
|
2014-03-28 02:50:40 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-09 01:13:56 +02:00
|
|
|
|
public bool DisconnectDongle(bool remove = false)
|
2017-04-06 03:37:38 +02:00
|
|
|
|
{
|
|
|
|
|
bool result = false;
|
|
|
|
|
byte[] disconnectReport = new byte[65];
|
|
|
|
|
disconnectReport[0] = 0xe2;
|
|
|
|
|
disconnectReport[1] = 0x02;
|
2017-04-09 01:13:56 +02:00
|
|
|
|
Array.Clear(disconnectReport, 2, 63);
|
2017-04-06 17:30:41 +02:00
|
|
|
|
|
2017-05-01 11:50:02 +02:00
|
|
|
|
lock (outputReport)
|
|
|
|
|
{
|
|
|
|
|
result = hDevice.WriteFeatureReport(disconnectReport);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-06 17:30:41 +02:00
|
|
|
|
if (result && remove)
|
2017-04-06 03:37:38 +02:00
|
|
|
|
{
|
2017-04-09 01:13:56 +02:00
|
|
|
|
isDisconnecting = true;
|
2017-04-06 10:19:12 +02:00
|
|
|
|
StopOutputUpdate();
|
2017-04-27 03:39:33 +02:00
|
|
|
|
|
2017-05-09 16:24:46 +02:00
|
|
|
|
uiContext.Send(new SendOrPostCallback(delegate (object state4)
|
2017-05-09 12:11:50 +02:00
|
|
|
|
{
|
|
|
|
|
Removal?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}), null);
|
2017-04-06 03:37:38 +02:00
|
|
|
|
}
|
2017-04-09 01:13:56 +02:00
|
|
|
|
else if (result && !remove)
|
|
|
|
|
{
|
|
|
|
|
isRemoved = true;
|
|
|
|
|
}
|
2017-04-06 03:37:38 +02:00
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private DS4HapticState testRumble = new DS4HapticState();
|
2017-04-22 06:22:36 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public void setRumble(byte rightLightFastMotor, byte leftHeavySlowMotor)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
testRumble.RumbleMotorStrengthRightLightFast = rightLightFastMotor;
|
|
|
|
|
testRumble.RumbleMotorStrengthLeftHeavySlow = leftHeavySlowMotor;
|
|
|
|
|
testRumble.RumbleMotorsExplicitlyOff = rightLightFastMotor == 0 && leftHeavySlowMotor == 0;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
private void setTestRumble()
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (testRumble.IsRumbleSet())
|
|
|
|
|
{
|
|
|
|
|
pushHapticState(testRumble);
|
|
|
|
|
if (testRumble.RumbleMotorsExplicitlyOff)
|
|
|
|
|
testRumble.RumbleMotorsExplicitlyOff = false;
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public DS4State getCurrentState()
|
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
return cState.Clone();
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public DS4State getPreviousState()
|
|
|
|
|
{
|
2014-03-29 06:29:08 +01:00
|
|
|
|
return pState.Clone();
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void getExposedState(DS4StateExposed expState, DS4State state)
|
|
|
|
|
{
|
2014-04-27 21:32:09 +02:00
|
|
|
|
cState.CopyTo(state);
|
2017-03-28 10:48:02 +02:00
|
|
|
|
expState.setAccel(accel);
|
|
|
|
|
expState.setGyro(gyro);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void getCurrentState(DS4State state)
|
|
|
|
|
{
|
2014-04-27 21:32:09 +02:00
|
|
|
|
cState.CopyTo(state);
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void getPreviousState(DS4State state)
|
|
|
|
|
{
|
2014-04-27 21:32:09 +02:00
|
|
|
|
pState.CopyTo(state);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-28 06:47:26 +01:00
|
|
|
|
private bool isDS4Idle()
|
2014-04-27 21:32:09 +02:00
|
|
|
|
{
|
|
|
|
|
if (cState.Square || cState.Cross || cState.Circle || cState.Triangle)
|
|
|
|
|
return false;
|
|
|
|
|
if (cState.DpadUp || cState.DpadLeft || cState.DpadDown || cState.DpadRight)
|
|
|
|
|
return false;
|
|
|
|
|
if (cState.L3 || cState.R3 || cState.L1 || cState.R1 || cState.Share || cState.Options)
|
|
|
|
|
return false;
|
|
|
|
|
if (cState.L2 != 0 || cState.R2 != 0)
|
|
|
|
|
return false;
|
|
|
|
|
// TODO calibrate to get an accurate jitter and center-play range and centered position
|
|
|
|
|
const int slop = 64;
|
|
|
|
|
if (cState.LX <= 127 - slop || cState.LX >= 128 + slop || cState.LY <= 127 - slop || cState.LY >= 128 + slop)
|
|
|
|
|
return false;
|
|
|
|
|
if (cState.RX <= 127 - slop || cState.RX >= 128 + slop || cState.RY <= 127 - slop || cState.RY >= 128 + slop)
|
|
|
|
|
return false;
|
|
|
|
|
if (cState.Touch1 || cState.Touch2 || cState.TouchButton)
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
2014-03-29 06:29:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private DS4HapticState[] hapticState = new DS4HapticState[1];
|
|
|
|
|
private int hapticStackIndex = 0;
|
|
|
|
|
private void resetHapticState()
|
|
|
|
|
{
|
|
|
|
|
hapticStackIndex = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use the "most recently set" haptic state for each of light bar/motor.
|
|
|
|
|
private void setHapticState()
|
|
|
|
|
{
|
|
|
|
|
DS4Color lightBarColor = LightBarColor;
|
|
|
|
|
byte lightBarFlashDurationOn = LightBarOnDuration, lightBarFlashDurationOff = LightBarOffDuration;
|
|
|
|
|
byte rumbleMotorStrengthLeftHeavySlow = LeftHeavySlowRumble, rumbleMotorStrengthRightLightFast = rightLightFastRumble;
|
2017-03-27 17:19:10 +02:00
|
|
|
|
int hapticLen = hapticState.Length;
|
|
|
|
|
for (int i=0; i < hapticLen; i++)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-03-27 17:19:10 +02:00
|
|
|
|
DS4HapticState haptic = hapticState[i];
|
|
|
|
|
if (i == hapticStackIndex)
|
2014-03-29 06:29:08 +01:00
|
|
|
|
break; // rest haven't been used this time
|
2017-04-28 20:57:33 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (haptic.IsLightBarSet())
|
|
|
|
|
{
|
|
|
|
|
lightBarColor = haptic.LightBarColor;
|
|
|
|
|
lightBarFlashDurationOn = haptic.LightBarFlashDurationOn;
|
|
|
|
|
lightBarFlashDurationOff = haptic.LightBarFlashDurationOff;
|
|
|
|
|
}
|
2017-04-28 20:57:33 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
if (haptic.IsRumbleSet())
|
|
|
|
|
{
|
|
|
|
|
rumbleMotorStrengthLeftHeavySlow = haptic.RumbleMotorStrengthLeftHeavySlow;
|
|
|
|
|
rumbleMotorStrengthRightLightFast = haptic.RumbleMotorStrengthRightLightFast;
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
2017-04-28 20:57:33 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
LightBarColor = lightBarColor;
|
|
|
|
|
LightBarOnDuration = lightBarFlashDurationOn;
|
|
|
|
|
LightBarOffDuration = lightBarFlashDurationOff;
|
|
|
|
|
LeftHeavySlowRumble = rumbleMotorStrengthLeftHeavySlow;
|
|
|
|
|
RightLightFastRumble = rumbleMotorStrengthRightLightFast;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
public void pushHapticState(DS4HapticState hs)
|
2014-03-28 02:50:40 +01:00
|
|
|
|
{
|
2017-04-16 08:22:04 +02:00
|
|
|
|
int hapsLen = hapticState.Length;
|
|
|
|
|
if (hapticStackIndex == hapsLen)
|
2014-03-29 06:29:08 +01:00
|
|
|
|
{
|
2017-04-16 08:22:04 +02:00
|
|
|
|
DS4HapticState[] newHaptics = new DS4HapticState[hapsLen + 1];
|
|
|
|
|
Array.Copy(hapticState, newHaptics, hapsLen);
|
2014-03-29 06:29:08 +01:00
|
|
|
|
hapticState = newHaptics;
|
|
|
|
|
}
|
2017-04-28 20:57:33 +02:00
|
|
|
|
|
2014-03-29 06:29:08 +01:00
|
|
|
|
hapticState[hapticStackIndex++] = hs;
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override
|
|
|
|
|
public String ToString()
|
|
|
|
|
{
|
|
|
|
|
return Mac;
|
|
|
|
|
}
|
2017-05-01 11:29:19 +02:00
|
|
|
|
|
2017-05-01 12:40:37 +02:00
|
|
|
|
public void runRemoval()
|
|
|
|
|
{
|
|
|
|
|
Removal?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-01 11:29:19 +02:00
|
|
|
|
public void removeReportHandlers()
|
|
|
|
|
{
|
|
|
|
|
this.Report = null;
|
|
|
|
|
}
|
2017-05-14 00:01:43 +02:00
|
|
|
|
|
|
|
|
|
public void queueEvent(Action act)
|
|
|
|
|
{
|
|
|
|
|
lock (eventQueueLock)
|
|
|
|
|
{
|
|
|
|
|
eventQueue.Enqueue(act);
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-25 11:51:28 +02:00
|
|
|
|
|
|
|
|
|
public void updateSerial()
|
|
|
|
|
{
|
|
|
|
|
hDevice.resetSerial();
|
|
|
|
|
string tempMac = hDevice.readSerial();
|
|
|
|
|
if (tempMac != Mac)
|
|
|
|
|
{
|
|
|
|
|
Mac = tempMac;
|
|
|
|
|
SerialChange?.Invoke(this, EventArgs.Empty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool isValidSerial()
|
|
|
|
|
{
|
|
|
|
|
return !Mac.Equals(blankSerial);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool isValidSerial(string test)
|
|
|
|
|
{
|
|
|
|
|
return !test.Equals(blankSerial);
|
|
|
|
|
}
|
2014-03-28 02:50:40 +01:00
|
|
|
|
}
|
|
|
|
|
}
|