private void performDs4Input()
{
firstActive = DateTime.UtcNow;
NativeMethods.HidD_SetNumInputBuffers(hDevice.safeReadHandle.DangerousGetHandle(), 2);
//List<long> latencyList = new List<long>(51); // Set capacity at max + 1 to avoid any list resizing
Queue <long> latencyQueue = new Queue <long>(51); // Set capacity at max + 1 to avoid any resizing
int tempLatencyCount = 0;
long oldtime = 0;
string currerror = string.Empty;
long curtime = 0;
Stopwatch sw = new Stopwatch();
sw.Start();
timeoutEvent = false;
int maxBatteryValue = 0;
int tempBattery = 0;
while (!exitInputThread)
{
oldCharging = charging;
currerror = string.Empty;
curtime = sw.ElapsedMilliseconds;
this.lastTimeElapsed = curtime - oldtime;
//latencyList.Add(this.lastTimeElapsed);
latencyQueue.Enqueue(this.lastTimeElapsed);
tempLatencyCount++;
oldtime = curtime;
if (tempLatencyCount > 50)
{
//latencyList.RemoveAt(0);
latencyQueue.Dequeue();
tempLatencyCount--;
}
//Latency = latencyList.Average();
//latencyList.Average();
Latency = latencyQueue.Average();
if (conType == ConnectionType.BT)
{
//HidDevice.ReadStatus res = hDevice.ReadFile(btInputReport);
//HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(btInputReport, READ_STREAM_TIMEOUT);
HidDevice.ReadStatus res = hDevice.ReadWithFileStream(btInputReport);
timeoutEvent = false;
//HidDevice.ReadStatus res = hDevice.ReadFileOverlapped(btInputReport, READ_STREAM_TIMEOUT);
if (res == HidDevice.ReadStatus.Success)
{
Array.Copy(btInputReport, 2, inputReport, 0, inputReport.Length);
}
else
{
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);
//Log.LogToGui(Mac.ToString() + " disconnected due to read failure: " + winError, true);
}
sendOutputReport(true); // Kick Windows into noticing the disconnection.
StopOutputUpdate();
isDisconnecting = true;
uiContext.Send(new SendOrPostCallback(delegate(object state4)
{
Removal?.Invoke(this, EventArgs.Empty);
}), null);
//System.Threading.Tasks.Task.Factory.StartNew(() => { Removal?.Invoke(this, EventArgs.Empty); });
//Removal?.Invoke(this, EventArgs.Empty);
timeoutExecuted = true;
return;
}
}
else
{
//HidDevice.ReadStatus res = hDevice.ReadFile(inputReport);
//Array.Clear(inputReport, 0, inputReport.Length);
//HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(inputReport, READ_STREAM_TIMEOUT);
HidDevice.ReadStatus res = hDevice.ReadWithFileStream(inputReport);
//HidDevice.ReadStatus res = hDevice.ReadFileOverlapped(inputReport, READ_STREAM_TIMEOUT);
if (res != HidDevice.ReadStatus.Success)
{
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);
//Log.LogToGui(Mac.ToString() + " disconnected due to read failure: " + winError, true);
}
StopOutputUpdate();
isDisconnecting = true;
uiContext.Send(new SendOrPostCallback(delegate(object state4)
{
Removal?.Invoke(this, EventArgs.Empty);
}), null);
//System.Threading.Tasks.Task.Factory.StartNew(() => { Removal?.Invoke(this, EventArgs.Empty); });
//Removal?.Invoke(this, EventArgs.Empty);
timeoutExecuted = true;
return;
}
else
{
//Array.Copy(inputReport2, 0, inputReport, 0, inputReport.Length);
}
}
if (conType == ConnectionType.BT && btInputReport[0] != 0x11)
{
//Received incorrect report, skip it
continue;
}
DateTime utcNow = DateTime.UtcNow; // timestamp with UTC in case system time zone changes
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];
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;
// First 4 bits denote dpad state. Clock representation
// with 8 meaning centered and 0 meaning DpadUp.
byte dpad_state = (byte)(inputReport[5] & 0x0F);
switch (dpad_state)
{
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;
case 8:
default: cState.DpadUp = false; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = false; break;
}
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;
cState.PS = (inputReport[7] & (1 << 0)) != 0;
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);
sixAxis.handleSixaxis(gyro, accel, cState);
try
{
charging = (inputReport[30] & 0x10) != 0;
maxBatteryValue = charging ? BATTERY_MAX_USB : BATTERY_MAX;
tempBattery = (inputReport[30] & 0x0f) * 100 / maxBatteryValue;
battery = Math.Min((byte)tempBattery, (byte)100);
cState.Battery = (byte)battery;
//System.Diagnostics.Debug.WriteLine("CURRENT BATTERY: " + (inputReport[30] & 0x0f) + " | " + tempBattery + " | " + battery);
if (inputReport[30] != priorInputReport30)
{
priorInputReport30 = inputReport[30];
//Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> power subsystem octet: 0x" + inputReport[30].ToString("x02"));
}
}
catch { currerror = "Index out of bounds: battery"; }
// XXX DS4State mapping needs fixup, turn touches into an array[4] of structs. And include the touchpad details there instead.
try
{
// 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)
{
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;
// Even when idling there is still a touch packet indicating no touch 1 or 2
touchpad.handleTouchpad(inputReport, cState, touchOffset);
}
}
catch { currerror = "Index out of bounds: touchpad"; }
/* Debug output of incoming HID data:
* if (cState.L2 == 0xff && cState.R2 == 0xff)
* {
* 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();
* } */
if (conType == ConnectionType.SONYWA)
{
bool controllerSynced = inputReport[31] == 0;
if (controllerSynced != synced)
{
synced = controllerSynced;
SyncChange?.Invoke(this, EventArgs.Empty);
}
}
bool ds4Idle = cState.FrameCounter == pState.FrameCounter;
if (!ds4Idle)
{
isRemoved = false;
}
if (conType == ConnectionType.USB)
{
lastActive = utcNow;
}
else
{
bool shouldDisconnect = false;
int idleTime = idleTimeout;
if (!isRemoved && idleTime > 0)
{
bool idleInput = isDS4Idle();
if (idleInput)
{
DateTime timeout = lastActive + TimeSpan.FromSeconds(idleTime);
if (!charging)
{
shouldDisconnect = utcNow >= timeout;
}
}
else
{
lastActive = utcNow;
}
}
else
{
lastActive = utcNow;
}
if (shouldDisconnect)
{
Log.LogToGui(Mac.ToString() + " disconnecting due to idle disconnect", false);
if (conType == ConnectionType.BT)
{
if (DisconnectBT(true))
{
timeoutExecuted = true;
return; // all done
}
}
else if (conType == ConnectionType.SONYWA)
{
DisconnectDongle();
}
}
}
if (oldCharging != charging && conType == ConnectionType.BT)
{
if (Global.getQuickCharge() && charging)
{
DisconnectBT(true);
timeoutExecuted = true;
return;
}
}
if (Report != null)
{
Report(this, EventArgs.Empty);
}
bool syncWriteReport = true;
if (conType == ConnectionType.BT)
{
syncWriteReport = false;
}
sendOutputReport(syncWriteReport);
if (!string.IsNullOrEmpty(currerror))
{
error = currerror;
}
else if (!string.IsNullOrEmpty(error))
{
error = string.Empty;
}
cState.CopyTo(pState);
lock (eventQueueLock)
{
Action tempAct = null;
for (int actInd = 0, actLen = eventQueue.Count; actInd < actLen; actInd++)
//foreach (Action tempAct in eventQueue)
{
tempAct = eventQueue.Dequeue();
tempAct.Invoke();
}
//eventQueue.Clear();
}
}
timeoutExecuted = true;
}