diff --git a/DS4Control/ButtonMouse.cs b/DS4Control/ButtonMouse.cs index b2d8ffc..482b3a0 100644 --- a/DS4Control/ButtonMouse.cs +++ b/DS4Control/ButtonMouse.cs @@ -13,10 +13,14 @@ namespace DS4Control private DS4State s = new DS4State(); private bool buttonLock; // Toggled with a two-finger touchpad push, we accept and absorb button input without any fingers on a touchpad, helping with drag-and-drop. private DS4Device dev = null; + private readonly MouseCursor cursor; + private readonly MouseWheel wheel; public ButtonMouse(int deviceID, DS4Device d) { deviceNum = deviceID; dev = d; + cursor = new MouseCursor(deviceNum); + wheel = new MouseWheel(deviceNum); } public override string ToString() @@ -26,56 +30,36 @@ namespace DS4Control public void touchesMoved(object sender, TouchpadEventArgs arg) { - if (arg.touches.Length == 1) - { - double sensitivity = Global.getTouchSensitivity(deviceNum) / 100.0; - int mouseDeltaX = (int)(sensitivity * (arg.touches[0].deltaX)); - int mouseDeltaY = (int)(sensitivity * (arg.touches[0].deltaY)); - InputMethods.MoveCursorBy(mouseDeltaX, mouseDeltaY); - } - else if (arg.touches.Length == 2) - { - Touch lastT0 = arg.touches[0].previousTouch; - Touch lastT1 = arg.touches[1].previousTouch; - Touch T0 = arg.touches[0]; - Touch T1 = arg.touches[1]; - - //mouse wheel 120 == 1 wheel click according to Windows API - int lastMidX = (lastT0.hwX + lastT1.hwX) / 2, lastMidY = (lastT0.hwY + lastT1.hwY) / 2, - currentMidX = (T0.hwX + T1.hwX) / 2, currentMidY = (T0.hwY + T1.hwY) / 2; // XXX Will controller swap touch IDs? - double coefficient = Global.getScrollSensitivity(deviceNum); - // Adjust for touch distance: "standard" distance is 960 pixels, i.e. half the width. Scroll farther if fingers are farther apart, and vice versa, in linear proportion. - double touchXDistance = T1.hwX - T0.hwX, touchYDistance = T1.hwY - T0.hwY, touchDistance = Math.Sqrt(touchXDistance * touchXDistance + touchYDistance * touchYDistance); - coefficient *= touchDistance / 960.0; - InputMethods.MouseWheel((int)(coefficient * (lastMidY - currentMidY)), (int)(coefficient * (currentMidX - lastMidX))); - } + cursor.touchesMoved(arg); + wheel.touchesMoved(arg); + dev.getCurrentState(s); synthesizeMouseButtons(false); } - public void untouched() + public void touchUnchanged(object sender, EventArgs unused) { - if (buttonLock) + dev.getCurrentState(s); + if (buttonLock || s.Touch1 || s.Touch2) synthesizeMouseButtons(false); - else - dev.getCurrentState(s); } public void touchesBegan(object sender, TouchpadEventArgs arg) { + cursor.touchesBegan(arg); + wheel.touchesBegan(arg); + dev.getCurrentState(s); synthesizeMouseButtons(false); } public void touchesEnded(object sender, TouchpadEventArgs arg) { + dev.getCurrentState(s); if (!buttonLock) synthesizeMouseButtons(true); - else - dev.getCurrentState(s); } private void synthesizeMouseButtons(bool justRelease) { - dev.getCurrentState(s); bool previousLeftButton = leftButton, previousMiddleButton = middleButton, previousRightButton = rightButton; if (justRelease) { @@ -113,17 +97,20 @@ namespace DS4Control { if (upperDown) { - mapTouchPad(DS4Controls.TouchUpper, true); + if (!mapTouchPad(DS4Controls.TouchUpper, true)) + InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_MIDDLEUP); upperDown = false; } if (leftDown) { - mapTouchPad(DS4Controls.TouchButton, true); + if (!mapTouchPad(DS4Controls.TouchButton, true)) + InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_LEFTUP); leftDown = false; } if (rightDown) { - mapTouchPad(DS4Controls.TouchMulti, true); + if (!mapTouchPad(DS4Controls.TouchMulti, true)) + InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_RIGHTUP); rightDown = false; } dev.setRumble(0, 0); @@ -134,7 +121,8 @@ namespace DS4Control byte leftRumble, rightRumble; if (arg.touches == null) //No touches, finger on upper portion of touchpad { - mapTouchPad(DS4Controls.TouchUpper, false); + if (!mapTouchPad(DS4Controls.TouchUpper, false)) + InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_MIDDLEDOWN); upperDown = true; leftRumble = rightRumble = 127; } @@ -142,23 +130,23 @@ namespace DS4Control { if (isLeft(arg.touches[0])) { - mapTouchPad(DS4Controls.TouchButton, false); + if (!mapTouchPad(DS4Controls.TouchButton, false)) + InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_LEFTDOWN); leftDown = true; leftRumble = 63; rightRumble = 0; } else if (isRight(arg.touches[0])) { - mapTouchPad(DS4Controls.TouchMulti, false); + if (!mapTouchPad(DS4Controls.TouchMulti, false)) + InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_RIGHTDOWN); rightDown = true; leftRumble = 0; rightRumble = 63; } else { - mapTouchPad(DS4Controls.TouchUpper, false); // ambiguous = same as upper - upperDown = true; - leftRumble = rightRumble = 127; + leftRumble = rightRumble = 0; // Ignore ambiguous pushes. } } else diff --git a/DS4Control/Control.cs b/DS4Control/Control.cs index 22784ec..1508bf1 100644 --- a/DS4Control/Control.cs +++ b/DS4Control/Control.cs @@ -5,7 +5,7 @@ using System.Text; using DS4Library; namespace DS4Control { - public class Control: IDisposable + public class Control { X360Device x360Bus; DS4Device[] DS4Controllers = new DS4Device[4]; @@ -38,6 +38,17 @@ namespace DS4Control } } + private void WarnExclusiveModeFailure(DS4Device device) + { + if (DS4Devices.isExclusiveMode && !device.IsExclusive) + { + String message = "Warning: Could not open DS4 " + device.MacAddress + " exclusively. " + + "You must quit other applications like Steam, Uplay before activating the 'Hide DS4 Controller' option."; + LogDebug(message); + Log.LogToTray(message); + } + } + public bool Start() { if (x360Bus.Open() && x360Bus.Start()) @@ -53,33 +64,29 @@ namespace DS4Control int ind = 0; foreach (DS4Device device in devices) { - LogDebug("Found Controller: " + device.MacAddress); - if (device.MacAddress == "00:00:00:00:00:00") - { - LogDebug("WARNING: If you are seing all zeroes as controller ID"); - LogDebug("You might be running not fully supported BT dongle"); - LogDebug("Only some basic functionality is enabled"); - } + LogDebug("Found Controller: " + device.MacAddress + " (" + device.ConnectionType + ")"); + WarnExclusiveModeFailure(device); DS4Controllers[ind] = device; device.Removal += this.On_DS4Removal; TPadModeSwitcher m_switcher = new TPadModeSwitcher(device, ind); m_switcher.Debug += OnDebug; - m_switcher.setMode(Global.getTouchEnabled(ind) ? 1 : 0); modeSwitcher[ind] = m_switcher; DS4Color color = Global.loadColor(ind); device.LightBarColor = color; x360Bus.Plugin(ind + 1); device.Report += this.On_Report; + m_switcher.setMode(Global.getTouchEnabled(ind) ? 1 : 0); ind++; LogDebug("Controller: " + device.MacAddress + " is ready to use"); Log.LogToTray("Controller: " + device.MacAddress + " is ready to use"); - if (ind >= 4) + if (ind >= 4) // out of Xinput devices! break; } } catch (Exception e) { LogDebug(e.Message); + Log.LogToTray(e.Message); } running = true; @@ -106,19 +113,28 @@ namespace DS4Control LogDebug("Stopping DS4 Controllers"); DS4Devices.stopControllers(); LogDebug("Stopped DS4 Tool"); + Global.ControllerStatusChanged(this); } return true; } + private volatile bool justRemoved; // inhibits HotPlug temporarily when a device is being torn down public bool HotPlug() { - if(running) + if (running) { + if (justRemoved) + { + justRemoved = false; + System.Threading.Thread.Sleep(200); + } DS4Devices.findControllers(); IEnumerable devices = DS4Devices.getDS4Controllers(); foreach (DS4Device device in devices) { + if (device.IsDisconnecting) + continue; if (((Func)delegate { for (Int32 Index = 0; Index < DS4Controllers.Length; Index++) @@ -131,15 +147,19 @@ namespace DS4Control for (Int32 Index = 0; Index < DS4Controllers.Length; Index++) if (DS4Controllers[Index] == null) { + LogDebug("Found Controller: " + device.MacAddress + " (" + device.ConnectionType + ")"); + WarnExclusiveModeFailure(device); DS4Controllers[Index] = device; device.Removal += this.On_DS4Removal; TPadModeSwitcher m_switcher = new TPadModeSwitcher(device, Index); m_switcher.Debug += OnDebug; modeSwitcher[Index] = m_switcher; - m_switcher.setMode(Global.getTouchEnabled(Index) ? 1 : 0); device.LightBarColor = Global.loadColor(Index); device.Report += this.On_Report; x360Bus.Plugin(Index + 1); + m_switcher.setMode(Global.getTouchEnabled(Index) ? 1 : 0); + LogDebug("Controller: " + device.MacAddress + " is ready to use"); + Log.LogToTray("Controller: " + device.MacAddress + " is ready to use"); break; } } @@ -152,7 +172,21 @@ namespace DS4Control if (DS4Controllers[index] != null) { DS4Device d = DS4Controllers[index]; - return d.MacAddress + ", Battery = " + d.Battery + "%," + " Touchpad = " + modeSwitcher[index].ToString() + " (" + d.ConnectionType + ")"; + if (!d.IsAlive()) + return null; // awaiting the first battery charge indication + String battery; + if (d.Charging) + { + if (d.Battery >= 100) + battery = "fully-charged"; + else + battery = "charging at ~" + d.Battery + "%"; + } + else + { + battery = "draining at ~" + d.Battery + "%"; + } + return d.MacAddress + " (" + d.ConnectionType + "), Battery is " + battery + ", Touchpad in " + modeSwitcher[index].ToString(); } else return null; @@ -168,11 +202,13 @@ namespace DS4Control ind = i; if (ind != -1) { + justRemoved = true; x360Bus.Unplug(ind + 1); LogDebug("Controller " + device.MacAddress + " was removed or lost connection"); Log.LogToTray("Controller " + device.MacAddress + " was removed or lost connection"); DS4Controllers[ind] = null; modeSwitcher[ind] = null; + Global.ControllerStatusChanged(this); } } @@ -189,28 +225,24 @@ namespace DS4Control if (ind!=-1) { - DS4State cState; + device.getExposedState(ExposedState[ind], CurrentState[ind]); + DS4State cState = CurrentState[ind]; + device.getPreviousState(PreviousState[ind]); + DS4State pState = PreviousState[ind]; + if (modeSwitcher[ind].getCurrentMode() is ButtonMouse) { - device.getExposedState(ExposedState[ind], CurrentState[ind]); - cState = CurrentState[ind]; ButtonMouse mode = (ButtonMouse)modeSwitcher[ind].getCurrentMode(); - if (!cState.Touch1 && !cState.Touch2 && !cState.TouchButton) - mode.untouched(); - cState = mode.getDS4State(); + // XXX so disgusting, need to virtualize this again + mode.getDS4State().Copy(cState); } else { device.getExposedState(ExposedState[ind], CurrentState[ind]); cState = CurrentState[ind]; } - device.getPreviousState(PreviousState[ind]); - DS4State pState = PreviousState[ind]; - CheckForHotkeys(ind, cState, pState); - - DS4LightBar.updateBatteryStatus(cState.Battery, device, ind); if (Global.getHasCustomKeysorButtons(ind)) { @@ -218,7 +250,14 @@ namespace DS4Control cState = MappedState[ind]; } + // Update the GUI/whatever. + DS4LightBar.updateLightBar(device, ind); + if (pState.Battery != cState.Battery) + Global.ControllerStatusChanged(this); + x360Bus.Parse(cState, processingData[ind].Report, ind); + // We push the translated Xinput state, and simultaneously we + // pull back any possible rumble data coming from Xinput consumers. if (x360Bus.Report(processingData[ind].Report, processingData[ind].Rumble)) { Byte Big = (Byte)(processingData[ind].Rumble[3]); @@ -272,16 +311,5 @@ namespace DS4Control heavyBoosted = 255; DS4Controllers[deviceNum].setRumble((byte)lightBoosted, (byte)heavyBoosted); } - - public DS4Device getDS4Controller(int deviceNum) - { - return DS4Controllers[deviceNum]; - } - - //CA1001 TypesThatOwnDisposableFieldsShouldBeDisposable - public void Dispose() - { - x360Bus.Dispose(); - } } } diff --git a/DS4Control/CursorOnlyMode.cs b/DS4Control/CursorOnlyMode.cs index 0b56b1b..6e6bd50 100644 --- a/DS4Control/CursorOnlyMode.cs +++ b/DS4Control/CursorOnlyMode.cs @@ -9,9 +9,14 @@ namespace DS4Control public class MouseCursorOnly : ITouchpadBehaviour { private int deviceNum; + private readonly MouseCursor cursor; + private readonly MouseWheel wheel; + public MouseCursorOnly(int deviceID) { deviceNum = deviceID; + cursor = new MouseCursor(deviceNum); + wheel = new MouseWheel(deviceNum); } public override string ToString() @@ -21,16 +26,15 @@ namespace DS4Control public void touchesMoved(object sender, TouchpadEventArgs arg) { - if (arg.touches.Length == 1) - { - double sensitivity = Global.getTouchSensitivity(deviceNum) / 100.0; - int mouseDeltaX = (int)(sensitivity * (arg.touches[0].deltaX)); - int mouseDeltaY = (int)(sensitivity * (arg.touches[0].deltaY)); - InputMethods.MoveCursorBy(mouseDeltaX, mouseDeltaY); - } + cursor.touchesMoved(arg); + wheel.touchesMoved(arg); } - public void touchesBegan(object sender, TouchpadEventArgs arg) { } + public void touchesBegan(object sender, TouchpadEventArgs arg) + { + cursor.touchesBegan(arg); + wheel.touchesBegan(arg); + } public void touchesEnded(object sender, TouchpadEventArgs arg) { } @@ -38,6 +42,6 @@ namespace DS4Control public void touchButtonDown(object sender, TouchpadEventArgs arg) { } - public void untouched(object sender, TouchpadEventArgs nullUnused) { } + public void touchUnchanged(object sender, EventArgs unused) { } } } diff --git a/DS4Control/DS4Control.csproj b/DS4Control/DS4Control.csproj index 8964569..d11ad68 100644 --- a/DS4Control/DS4Control.csproj +++ b/DS4Control/DS4Control.csproj @@ -45,6 +45,8 @@ + + diff --git a/DS4Control/DS4LightBar.cs b/DS4Control/DS4LightBar.cs index 756cf2a..61c14ac 100644 --- a/DS4Control/DS4LightBar.cs +++ b/DS4Control/DS4LightBar.cs @@ -9,7 +9,7 @@ namespace DS4Control { private readonly static byte[/* Light On duration */, /* Light Off duration */] BatteryIndicatorDurations = { - { 255, 255 }, // 0 doesn't happen + { 0, 0 }, // 0 is for "charging" OR anything sufficiently-"charged" { 28, 252 }, { 56, 224 }, { 84, 196 }, @@ -18,45 +18,76 @@ namespace DS4Control { 168, 112 }, { 196, 84 }, { 224, 56}, // on 80% of the time at 80, etc. - { 252, 28 }, // on 90% of the time at 90 - { 0, 0 } // no flash at 100 + { 252, 28 } // on 90% of the time at 90 }; + static double[] counters = new double[4]; - public static void updateBatteryStatus(int battery, DS4Device device, int deviceNum) + public static void updateLightBar(DS4Device device, int deviceNum) { + DS4Color color; if (Global.getLedAsBatteryIndicator(deviceNum)) { - byte[] fullColor = { - Global.loadColor(deviceNum).red, - Global.loadColor(deviceNum).green, - Global.loadColor(deviceNum).blue - }; - - // New Setting - DS4Color color = Global.loadLowColor(deviceNum); - byte[] lowColor = { color.red, color.green, color.blue }; - - uint ratio = (uint)battery; - color = Global.getTransitionedColor(lowColor, fullColor, ratio); - device.LightBarColor = color; + if (device.Charging == false || device.Battery >= 100) // when charged, don't show the charging animation + { + DS4Color fullColor = new DS4Color + { + red = Global.loadColor(deviceNum).red, + green = Global.loadColor(deviceNum).green, + blue = Global.loadColor(deviceNum).blue + }; + color = Global.loadLowColor(deviceNum); + DS4Color lowColor = new DS4Color + { + red = color.red, + green = color.green, + blue = color.blue + }; + color = Global.getTransitionedColor(lowColor, fullColor, (uint)device.Battery); + } + else // Display rainbow when charging. + { + counters[deviceNum]++; + double theta = Math.PI * 2.0 * counters[deviceNum] / 1800.0; + const double brightness = Math.PI; // small brightness numbers (far from max 128.0) mean less light steps and slower output reports; also, the lower the brightness the faster you can charge + color = new DS4Color + { + red = (byte)(brightness * Math.Sin(theta) + brightness - 0.5), + green = (byte)(brightness * Math.Sin(theta + (Math.PI * 2.0) / 3.0) + brightness - 0.5), + blue = (byte)(brightness * Math.Sin(theta + 2.0 * (Math.PI * 2.0) / 3.0) + brightness - 0.5) + }; + } } else { - DS4Color color = Global.loadColor(deviceNum); - device.LightBarColor = color; + color = Global.loadColor(deviceNum); } - if (Global.getFlashWhenLowBattery(deviceNum)) + DS4HapticState haptics = new DS4HapticState { - device.LightBarOnDuration = BatteryIndicatorDurations[battery / 10, 0]; - device.LightBarOffDuration = BatteryIndicatorDurations[battery / 10, 1]; + LightBarColor = color + }; + if (haptics.IsLightBarSet()) + { + if (Global.getFlashWhenLowBattery(deviceNum)) + { + int level = device.Battery / 10; + if (level >= 10) + level = 0; // all values of ~0% or >~100% are rendered the same + haptics.LightBarFlashDurationOn = BatteryIndicatorDurations[level, 0]; + haptics.LightBarFlashDurationOff = BatteryIndicatorDurations[level, 1]; + } + else + { + haptics.LightBarFlashDurationOff = haptics.LightBarFlashDurationOn = 0; + } } else { - device.LightBarOffDuration = device.LightBarOnDuration = 0; + haptics.LightBarExplicitlyOff = true; } + device.pushHapticState(haptics); } } diff --git a/DS4Control/DragMouse.cs b/DS4Control/DragMouse.cs index 7463eed..be9f97b 100644 --- a/DS4Control/DragMouse.cs +++ b/DS4Control/DragMouse.cs @@ -8,10 +8,12 @@ using System.Threading; namespace DS4Control { - class DragMouse: Mouse, IDisposable + class DragMouse: Mouse { protected bool leftClick = false; protected Timer timer; + private readonly MouseCursor cursor; + private readonly MouseWheel wheel; public DragMouse(int deviceID):base(deviceID) { @@ -21,7 +23,9 @@ namespace DS4Control leftClick = false; }, null, System.Threading.Timeout.Infinite, - System.Threading.Timeout.Infinite); + System.Threading.Timeout.Infinite); + cursor = new MouseCursor(deviceNum); + wheel = new MouseWheel(deviceNum); } public override string ToString() @@ -29,44 +33,9 @@ namespace DS4Control return "Drag Mode"; } - public override void touchesMoved(object sender, TouchpadEventArgs arg) - { - if (arg.touches.Length == 1) - { - double sensitivity = Global.getTouchSensitivity(deviceNum) / 100.0; - int mouseDeltaX = (int)(sensitivity * (arg.touches[0].deltaX)); - int mouseDeltaY = (int)(sensitivity * (arg.touches[0].deltaY)); - InputMethods.MoveCursorBy(mouseDeltaX, mouseDeltaY); - } - else if (arg.touches.Length == 2 && !leftClick) - { - Touch lastT0 = arg.touches[0].previousTouch; - Touch lastT1 = arg.touches[1].previousTouch; - Touch T0 = arg.touches[0]; - Touch T1 = arg.touches[1]; - - //mouse wheel 120 == 1 wheel click according to Windows API - int lastMidX = (lastT0.hwX + lastT1.hwX) / 2, lastMidY = (lastT0.hwY + lastT1.hwY) / 2, - currentMidX = (T0.hwX + T1.hwX) / 2, currentMidY = (T0.hwY + T1.hwY) / 2; // XXX Will controller swap touch IDs? - double coefficient = Global.getScrollSensitivity(deviceNum); - // Adjust for touch distance: "standard" distance is 960 pixels, i.e. half the width. Scroll farther if fingers are farther apart, and vice versa, in linear proportion. - double touchXDistance = T1.hwX - T0.hwX, touchYDistance = T1.hwY - T0.hwY, touchDistance = Math.Sqrt(touchXDistance * touchXDistance + touchYDistance * touchYDistance); - coefficient *= touchDistance / 960.0; - InputMethods.MouseWheel((int)(coefficient * (lastMidY - currentMidY)), (int)(coefficient * (currentMidX - lastMidX))); - } - else - { - double sensitivity = Global.getTouchSensitivity(deviceNum) / 100.0; - int mouseDeltaX = (int)(sensitivity * (arg.touches[1].deltaX)); - int mouseDeltaY = (int)(sensitivity * (arg.touches[1].deltaY)); - InputMethods.MoveCursorBy(mouseDeltaX, mouseDeltaY); - } - } - public override void touchesBegan(object sender, TouchpadEventArgs arg) { - pastTime = arg.timeStamp; - firstTouch = arg.touches[0]; + base.touchesBegan(sender, arg); if (leftClick) timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } @@ -139,11 +108,5 @@ namespace DS4Control leftClick = true; } } - - //CA1001 TypesThatOwnDisposableFieldsShouldBeDisposable - public void Dispose() - { - timer.Dispose(); - } } } diff --git a/DS4Control/ITouchpadBehaviour.cs b/DS4Control/ITouchpadBehaviour.cs index 1185549..a052d22 100644 --- a/DS4Control/ITouchpadBehaviour.cs +++ b/DS4Control/ITouchpadBehaviour.cs @@ -12,5 +12,6 @@ namespace DS4Control void touchButtonUp(object sender, TouchpadEventArgs arg); void touchButtonDown(object sender, TouchpadEventArgs arg); void touchesEnded(object sender, TouchpadEventArgs arg); + void touchUnchanged(object sender, EventArgs unused); } } diff --git a/DS4Control/Mapping.cs b/DS4Control/Mapping.cs index 1b08c51..ad310da 100644 --- a/DS4Control/Mapping.cs +++ b/DS4Control/Mapping.cs @@ -40,8 +40,6 @@ namespace DS4Control MappedState.LY = 127; MappedState.RX = 127; MappedState.RY = 127; - int MouseDeltaX = 0; - int MouseDeltaY = 0; foreach (KeyValuePair customButton in Global.getCustomButtons()) { @@ -183,34 +181,6 @@ namespace DS4Control else if (PrevOn && !CurOn) InputMethods.MouseEvent(InputMethods.MOUSEEVENTF_MIDDLEUP); break; - case X360Controls.MouseUp: - if (MouseDeltaY == 0) - { - MouseDeltaY = calculateRelativeMouseDelta(customButton.Key, cState, prevState); - MouseDeltaY = -Math.Abs(MouseDeltaY); - } - break; - case X360Controls.MouseDown: - if (MouseDeltaY == 0) - { - MouseDeltaY = calculateRelativeMouseDelta(customButton.Key, cState, prevState); - MouseDeltaY = Math.Abs(MouseDeltaY); - } - break; - case X360Controls.MouseLeft: - if (MouseDeltaX == 0) - { - MouseDeltaX = calculateRelativeMouseDelta(customButton.Key, cState, prevState); - MouseDeltaX = -Math.Abs(MouseDeltaX); - } - break; - case X360Controls.MouseRight: - if (MouseDeltaX == 0) - { - MouseDeltaX = calculateRelativeMouseDelta(customButton.Key, cState, prevState); - MouseDeltaX = Math.Abs(MouseDeltaX); - } - break; case X360Controls.Unbound: resetToDefaultValue(customButton.Key, MappedState); break; @@ -225,54 +195,6 @@ namespace DS4Control MappedState.RX = cState.RX; if (!RY) MappedState.RY = cState.RY; - InputMethods.MoveCursorBy(MouseDeltaX, MouseDeltaY); - } - - private static int calculateRelativeMouseDelta(DS4Controls control, DS4State cState, DS4State pState) - { - int axisVal = -1; - int DEAD_ZONE = 10; - float SPEED_MULTIPLIER = 0.000004f; - bool positive = false; - float deltaTime = cState.ReportTimeStamp.Ticks - pState.ReportTimeStamp.Ticks; - switch (control) - { - case DS4Controls.LXNeg: - axisVal = cState.LX; - break; - case DS4Controls.LXPos: - positive = true; - axisVal = cState.LX; - break; - case DS4Controls.RXNeg: - axisVal = cState.RX; - break; - case DS4Controls.RXPos: - positive = true; - axisVal = cState.RX; - break; - case DS4Controls.LYNeg: - axisVal = cState.LY; - break; - case DS4Controls.LYPos: - positive = true; - axisVal = cState.LY; - break; - case DS4Controls.RYNeg: - axisVal = cState.RY; - break; - case DS4Controls.RYPos: - positive = true; - axisVal = cState.RY; - break; - } - axisVal = axisVal - 127; - int delta = 0; - if ( (!positive && axisVal < -DEAD_ZONE ) || (positive && axisVal > DEAD_ZONE)) - { - delta = (int)(float) (axisVal * SPEED_MULTIPLIER * deltaTime); - } - return delta; } public static bool compare(byte b1, byte b2) diff --git a/DS4Control/Mouse.cs b/DS4Control/Mouse.cs index 574ef16..db9d9a0 100644 --- a/DS4Control/Mouse.cs +++ b/DS4Control/Mouse.cs @@ -11,11 +11,15 @@ namespace DS4Control protected DateTime pastTime; protected Touch firstTouch; protected int deviceNum; + private readonly MouseCursor cursor; + private readonly MouseWheel wheel; protected bool rightClick = false; public Mouse(int deviceID) { deviceNum = deviceID; + cursor = new MouseCursor(deviceNum); + wheel = new MouseWheel(deviceNum); } public override string ToString() @@ -25,39 +29,23 @@ namespace DS4Control public virtual void touchesMoved(object sender, TouchpadEventArgs arg) { - if (arg.touches.Length == 1) - { - double sensitivity = Global.getTouchSensitivity(deviceNum) / 100.0; - int mouseDeltaX = (int)(sensitivity * (arg.touches[0].deltaX)); - int mouseDeltaY = (int)(sensitivity * (arg.touches[0].deltaY)); - InputMethods.MoveCursorBy(mouseDeltaX, mouseDeltaY); - } - else if (arg.touches.Length == 2) - { - Touch lastT0 = arg.touches[0].previousTouch; - Touch lastT1 = arg.touches[1].previousTouch; - Touch T0 = arg.touches[0]; - Touch T1 = arg.touches[1]; - - //mouse wheel 120 == 1 wheel click according to Windows API - int lastMidX = (lastT0.hwX + lastT1.hwX) / 2, lastMidY = (lastT0.hwY + lastT1.hwY) / 2, - currentMidX = (T0.hwX + T1.hwX) / 2, currentMidY = (T0.hwY + T1.hwY) / 2; // XXX Will controller swap touch IDs? - double coefficient = Global.getScrollSensitivity(deviceNum); - // Adjust for touch distance: "standard" distance is 960 pixels, i.e. half the width. Scroll farther if fingers are farther apart, and vice versa, in linear proportion. - double touchXDistance = T1.hwX - T0.hwX, touchYDistance = T1.hwY - T0.hwY, touchDistance = Math.Sqrt(touchXDistance * touchXDistance + touchYDistance * touchYDistance); - coefficient *= touchDistance / 960.0; - InputMethods.MouseWheel((int)(coefficient * (lastMidY - currentMidY)), (int)(coefficient * (currentMidX - lastMidX))); - } + cursor.touchesMoved(arg); + wheel.touchesMoved(arg); + //Log.LogToGui("moved to " + arg.touches[0].hwX + "," + arg.touches[0].hwY); } public virtual void touchesBegan(object sender, TouchpadEventArgs arg) { + cursor.touchesBegan(arg); + wheel.touchesBegan(arg); pastTime = arg.timeStamp; firstTouch = arg.touches[0]; + //Log.LogToGui("began at " + arg.touches[0].hwX + "," + arg.touches[0].hwY); } public virtual void touchesEnded(object sender, TouchpadEventArgs arg) { + //Log.LogToGui("ended at " + arg.touches[0].hwX + "," + arg.touches[0].hwY); if (Global.getTapSensitivity(deviceNum) != 0) { DateTime test = arg.timeStamp; @@ -111,6 +99,8 @@ namespace DS4Control } } + public void touchUnchanged(object sender, EventArgs unused) { } + protected bool mapTouchPad(DS4Controls padControl, bool release = false) { ushort key = Global.getCustomKey(padControl); diff --git a/DS4Control/ScpUtil.cs b/DS4Control/ScpUtil.cs index 4573eb9..21d3d73 100644 --- a/DS4Control/ScpUtil.cs +++ b/DS4Control/ScpUtil.cs @@ -12,8 +12,7 @@ namespace DS4Control public enum DS4KeyType : byte { None = 0, ScanCode = 1, Repeat = 2 }; //Increment by exponents of 2*, starting at 2^0 public enum Ds3PadId : byte { None = 0xFF, One = 0x00, Two = 0x01, Three = 0x02, Four = 0x03, All = 0x04 }; public enum DS4Controls : byte { LXNeg, LXPos, LYNeg, LYPos, RXNeg, RXPos, RYNeg, RYPos, L1, L2, L3, R1, R2, R3, Square, Triangle, Circle, Cross, DpadUp, DpadRight, DpadDown, DpadLeft, PS, TouchButton, TouchUpper, TouchMulti, Share, Options }; - public enum X360Controls : byte { LXNeg, LXPos, LYNeg, LYPos, RXNeg, RXPos, RYNeg, RYPos, LB, LT, LS, RB, RT, RS, X, Y, B, A, DpadUp, DpadRight, DpadDown, DpadLeft, Guide, Back, Start, LeftMouse, RightMouse, MiddleMouse, Unbound, - MouseLeft, MouseRight, MouseDown, MouseUp}; + public enum X360Controls : byte { LXNeg, LXPos, LYNeg, LYPos, RXNeg, RXPos, RYNeg, RYPos, LB, LT, LS, RB, RT, RS, X, Y, B, A, DpadUp, DpadRight, DpadDown, DpadLeft, Guide, Back, Start, LeftMouse, RightMouse, MiddleMouse, Unbound }; public class DebugEventArgs : EventArgs { @@ -82,6 +81,13 @@ namespace DS4Control protected static BackingStore m_Config = new BackingStore(); protected static Int32 m_IdleTimeout = 600000; + public static event EventHandler ControllerStatusChange; // called when a controller is added/removed/battery or touchpad mode changes/etc. + public static void ControllerStatusChanged(object sender) + { + if (ControllerStatusChange != null) + ControllerStatusChange(sender, EventArgs.Empty); + } + public static DS4Color loadColor(int device) { DS4Color color = new DS4Color(); @@ -327,22 +333,12 @@ namespace DS4Control byte bdif = (byte)(bmax - bmin); return (byte)(bmin + (bdif * ratio / 100)); } - public static DS4Color getTransitionedColor(byte[] c1, byte[] c2, uint ratio) - { - DS4Color color = new DS4Color(); - color.red = 255; - color.green = 255; - color.blue = 255; - uint r = ratio % 101; - if (c1.Length != 3 || c2.Length != 3 || ratio < 0) - { - return color; - } - color.red = applyRatio(c1[0], c2[0], ratio); - color.green = applyRatio(c1[1], c2[1], ratio); - color.blue = applyRatio(c1[2], c2[2], ratio); - - return color; + public static DS4Color getTransitionedColor(DS4Color c1, DS4Color c2, uint ratio) + {; + c1.red = applyRatio(c1.red, c2.red, ratio); + c1.green = applyRatio(c1.green, c2.green, ratio); + c1.blue = applyRatio(c1.blue, c2.blue, ratio); + return c1; } } @@ -655,10 +651,6 @@ namespace DS4Control case "Click": return X360Controls.LeftMouse; case "Right Click": return X360Controls.RightMouse; case "Middle Click": return X360Controls.MiddleMouse; - case "Mouse Up": return X360Controls.MouseUp; - case "Mouse Down": return X360Controls.MouseDown; - case "Mouse Left": return X360Controls.MouseLeft; - case "Mouse Right": return X360Controls.MouseRight; case "(Unbound)": return X360Controls.Unbound; } diff --git a/DS4Control/TPadModeSwitcher.cs b/DS4Control/TPadModeSwitcher.cs index 3c2f0bc..697e7be 100644 --- a/DS4Control/TPadModeSwitcher.cs +++ b/DS4Control/TPadModeSwitcher.cs @@ -29,6 +29,7 @@ namespace DS4Control device.Touchpad.TouchesBegan -= currentMode.touchesBegan; device.Touchpad.TouchesMoved -= currentMode.touchesMoved; device.Touchpad.TouchesEnded -= currentMode.touchesEnded; + device.Touchpad.TouchUnchanged -= currentMode.touchUnchanged; setMode(ind); } @@ -40,9 +41,11 @@ namespace DS4Control device.Touchpad.TouchesBegan += tmode.touchesBegan; device.Touchpad.TouchesMoved += tmode.touchesMoved; device.Touchpad.TouchesEnded += tmode.touchesEnded; + device.Touchpad.TouchUnchanged += tmode.touchUnchanged; currentTypeInd = ind; LogDebug("Touchpad mode for " + device.MacAddress + " is now " + tmode.ToString()); Log.LogToTray("Touchpad mode for " + device.MacAddress + " is now " + tmode.ToString()); + Global.ControllerStatusChanged(this); } public override string ToString() diff --git a/DS4Control/TouchpadDisabled.cs b/DS4Control/TouchpadDisabled.cs index fab038a..0e560c7 100644 --- a/DS4Control/TouchpadDisabled.cs +++ b/DS4Control/TouchpadDisabled.cs @@ -25,6 +25,6 @@ namespace DS4Control public void touchButtonDown(object sender, TouchpadEventArgs arg) { } - public void untouched(object sender, TouchpadEventArgs nullUnused) { } + public void touchUnchanged(object sender, EventArgs unused) { } } } diff --git a/DS4Library/DS4Device.cs b/DS4Library/DS4Device.cs index e245a58..fd886c0 100644 --- a/DS4Library/DS4Device.cs +++ b/DS4Library/DS4Device.cs @@ -23,7 +23,28 @@ namespace DS4Library public byte blue; } - public enum ConnectionType : byte { USB, BT }; + public enum ConnectionType : byte { BT, USB }; // Prioritize Bluetooth when both are connected. + + /** + * 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; + public bool IsLightBarSet() + { + return LightBarExplicitlyOff || LightBarColor.red != 0 || LightBarColor.green != 0 || LightBarColor.blue != 0; + } + public bool IsRumbleSet() + { + return RumbleMotorsExplicitlyOff || RumbleMotorStrengthLeftHeavySlow != 0 || RumbleMotorStrengthRightLightFast != 0; + } + } public class DS4Device { @@ -36,31 +57,30 @@ namespace DS4Library private ConnectionType conType; private byte[] accel = new byte[6]; private byte[] gyro = new byte[6]; - private byte[] inputReport = new byte[64]; + private byte[] inputReport; private byte[] btInputReport = null; - private byte[] outputReport = null; + private byte[] outputReportBuffer, outputReport; private readonly DS4Touchpad touchpad = null; private byte rightLightFastRumble; private byte leftHeavySlowRumble; private DS4Color ligtBarColor; private byte ledFlashOn, ledFlashOff; - private bool isDirty = false; - private Thread updaterThread = null; + private Thread ds4Input, ds4Output; private int battery; - private int idleTimeout = 1200; - private DateTime lastActive = DateTime.Now; + private bool charging; public event EventHandler Report = null; public event EventHandler Removal = null; - public int IdleTimeout { get { return idleTimeout; } - set { idleTimeout = value; } } public HidDevice HidDevice { get { return hDevice; } } + public bool IsExclusive { get { return HidDevice.IsExclusive; } } + public bool IsDisconnecting { get; private set; } public string MacAddress { get { return Mac; } } public ConnectionType ConnectionType { get { return conType; } } public int Battery { get { return battery; } } + public bool Charging { get { return charging; } } public byte RightLightFastRumble { @@ -69,7 +89,6 @@ namespace DS4Library { if (value == rightLightFastRumble) return; rightLightFastRumble = value; - isDirty = true; } } @@ -80,7 +99,6 @@ namespace DS4Library { if (value == leftHeavySlowRumble) return; leftHeavySlowRumble = value; - isDirty = true; } } @@ -92,7 +110,6 @@ namespace DS4Library if (ligtBarColor.red != value.red || ligtBarColor.green != value.green || ligtBarColor.blue != value.blue) { ligtBarColor = value; - isDirty = true; } } } @@ -105,7 +122,6 @@ namespace DS4Library if (ledFlashOn != value) { ledFlashOn = value; - isDirty = true; } } } @@ -118,41 +134,51 @@ namespace DS4Library if (ledFlashOff != value) { ledFlashOff = value; - isDirty = true; } } } public DS4Touchpad Touchpad { get { return touchpad; } } + public static ConnectionType HidConnectionType(HidDevice hidDevice) + { + return hidDevice.Capabilities.InputReportByteLength == 64 ? ConnectionType.USB : ConnectionType.BT; + } + public DS4Device(HidDevice hidDevice) { hDevice = hidDevice; hDevice.MonitorDeviceEvents = true; - conType = hDevice.Capabilities.InputReportByteLength == 64 ? ConnectionType.USB : ConnectionType.BT; + conType = HidConnectionType(hDevice); Mac = hDevice.readSerial(); if (conType == ConnectionType.USB) { + inputReport = new byte[64]; outputReport = new byte[hDevice.Capabilities.OutputReportByteLength]; + outputReportBuffer = new byte[hDevice.Capabilities.OutputReportByteLength]; } else { btInputReport = new byte[BT_INPUT_REPORT_LENGTH]; + inputReport = new byte[btInputReport.Length - 2]; outputReport = new byte[BT_OUTPUT_REPORT_LENGTH]; + outputReportBuffer = new byte[BT_OUTPUT_REPORT_LENGTH]; } touchpad = new DS4Touchpad(); - isDirty = true; - sendOutputReport(); } public void StartUpdate() { - if (updaterThread == null) + if (ds4Input == null) { - updaterThread = new Thread(updateCurrentState); - updaterThread.Name = "DS4 Update thread :" + Mac; - Console.WriteLine(updaterThread.Name + " has started"); - updaterThread.Start(); + Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> start"); + sendOutputReport(true); // request the particular kind of input report we want + ds4Output = new Thread(performDs4Output); + ds4Output.Name = "DS4 Output thread: " + Mac; + ds4Output.Start(); + ds4Input = new Thread(performDs4Input); + ds4Input.Name = "DS4 Input thread: " + Mac; + ds4Input.Start(); } else Console.WriteLine("Thread already running for DS4: " + Mac); @@ -160,12 +186,29 @@ namespace DS4Library public void StopUpdate() { - if (updaterThread.ThreadState != System.Threading.ThreadState.Stopped || updaterThread.ThreadState != System.Threading.ThreadState.Aborted) + if (ds4Input.ThreadState != System.Threading.ThreadState.Stopped || ds4Input.ThreadState != System.Threading.ThreadState.Aborted) { try { - updaterThread.Abort(); - updaterThread = null; + ds4Input.Abort(); + ds4Input.Join(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + StopOutputUpdate(); + } + + private void StopOutputUpdate() + { + if (ds4Output.ThreadState != System.Threading.ThreadState.Stopped || ds4Output.ThreadState != System.Threading.ThreadState.Aborted) + { + try + { + ds4Output.Abort(); + ds4Output.Join(); } catch (Exception e) { @@ -174,177 +217,225 @@ namespace DS4Library } } - private void updateCurrentState() + private bool writeOutput() + { + if (conType == ConnectionType.BT) + { + return hDevice.WriteOutputReportViaControl(outputReport); + } + else + { + return hDevice.WriteOutputReportViaInterrupt(outputReport, 8); + } + } + + private void performDs4Output() + { + lock (outputReport) + { + while (writeOutput()) + { + 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); + } + } + } + + /** Is the device alive and receiving valid sensor input reports? */ + public bool IsAlive() + { + return priorInputReport30 != 0xff; + } + private byte priorInputReport30 = 0xff; + private void performDs4Input() { while (true) { if (conType != ConnectionType.USB) if (hDevice.ReadFile(btInputReport) == HidDevice.ReadStatus.Success) { - Array.Copy(btInputReport, 2, inputReport, 0, 64); + Array.Copy(btInputReport, 2, inputReport, 0, inputReport.Length); } else { - isDirty = true; - sendOutputReport(); // not sure why but without this Windows - //will not mark timed out controller as disonnected - if (Removal != null) + Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> disconnect"); + sendOutputReport(true); // Kick Windows into noticing the disconnection. + StopOutputUpdate(); + if (!IsDisconnecting && Removal != null) Removal(this, EventArgs.Empty); return; } else if (hDevice.ReadFile(inputReport) != HidDevice.ReadStatus.Success) { - if (Removal != null) + StopOutputUpdate(); + if (!IsDisconnecting && Removal != null) Removal(this, EventArgs.Empty); return; } if (ConnectionType == ConnectionType.BT && btInputReport[0] != 0x11) + { + //Received incorrect report, skip it + continue; + } + DateTime utcNow = System.DateTime.UtcNow; // timestamp with UTC in case system time zone changes + resetHapticState(); + if (cState == null) + cState = new DS4State(); + 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 = ((byte)inputReport[5] & (1 << 7)) != 0; + cState.Circle = ((byte)inputReport[5] & (1 << 6)) != 0; + cState.Cross = ((byte)inputReport[5] & (1 << 5)) != 0; + cState.Square = ((byte)inputReport[5] & (1 << 4)) != 0; + cState.DpadUp = ((byte)inputReport[5] & (1 << 3)) != 0; + cState.DpadDown = ((byte)inputReport[5] & (1 << 2)) != 0; + cState.DpadLeft = ((byte)inputReport[5] & (1 << 1)) != 0; + cState.DpadRight = ((byte)inputReport[5] & (1 << 0)) != 0; + + //Convert dpad into individual On/Off bits instead of a clock representation + byte dpad_state = 0; + + dpad_state = (byte)( + ((cState.DpadRight ? 1 : 0) << 0) | + ((cState.DpadLeft ? 1 : 0) << 1) | + ((cState.DpadDown ? 1 : 0) << 2) | + ((cState.DpadUp ? 1 : 0) << 3)); + + switch (dpad_state) { - //Received incorrect report, skip it - continue; + 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: cState.DpadUp = false; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = false; break; } - lock (cState) + cState.R3 = ((byte)inputReport[6] & (1 << 7)) != 0; + cState.L3 = ((byte)inputReport[6] & (1 << 6)) != 0; + cState.Options = ((byte)inputReport[6] & (1 << 5)) != 0; + cState.Share = ((byte)inputReport[6] & (1 << 4)) != 0; + cState.R1 = ((byte)inputReport[6] & (1 << 1)) != 0; + cState.L1 = ((byte)inputReport[6] & (1 << 0)) != 0; + + cState.PS = ((byte)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); + + charging = (inputReport[30] & 0x10) != 0; + battery = (inputReport[30] & 0x0f) * 10; + cState.Battery = (byte)battery; + if (inputReport[30] != priorInputReport30) { - if (cState == null) - cState = new DS4State(); - 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 = ((byte)inputReport[5] & (1 << 7)) != 0; - cState.Circle = ((byte)inputReport[5] & (1 << 6)) != 0; - cState.Cross = ((byte)inputReport[5] & (1 << 5)) != 0; - cState.Square = ((byte)inputReport[5] & (1 << 4)) != 0; - cState.DpadUp = ((byte)inputReport[5] & (1 << 3)) != 0; - cState.DpadDown = ((byte)inputReport[5] & (1 << 2)) != 0; - cState.DpadLeft = ((byte)inputReport[5] & (1 << 1)) != 0; - cState.DpadRight = ((byte)inputReport[5] & (1 << 0)) != 0; - - //Convert dpad into individual On/Off bits instead of a clock representation - byte dpad_state = 0; - - dpad_state = (byte)( - ((cState.DpadRight ? 1 : 0) << 0) | - ((cState.DpadLeft ? 1 : 0) << 1) | - ((cState.DpadDown ? 1 : 0) << 2) | - ((cState.DpadUp ? 1 : 0) << 3)); - - 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: cState.DpadUp = false; cState.DpadDown = false; cState.DpadLeft = false; cState.DpadRight = false; break; - } - - cState.R3 = ((byte)inputReport[6] & (1 << 7)) != 0; - cState.L3 = ((byte)inputReport[6] & (1 << 6)) != 0; - cState.Options = ((byte)inputReport[6] & (1 << 5)) != 0; - cState.Share = ((byte)inputReport[6] & (1 << 4)) != 0; - cState.R1 = ((byte)inputReport[6] & (1 << 1)) != 0; - cState.L1 = ((byte)inputReport[6] & (1 << 0)) != 0; - - cState.PS = ((byte)inputReport[7] & (1 << 0)) != 0; - cState.TouchButton = (inputReport[7] & (1 << 2 - 1)) != 0; - - // Store Gyro and Accel values - Array.Copy(inputReport, 14, accel, 0, 6); - Array.Copy(inputReport, 20, gyro, 0, 6); - - int charge = 0; - if (conType == ConnectionType.USB) - { - charge = (inputReport[30] - 16) * 10; - if (charge > 100) - charge = 100; - } - else - { - charge = (inputReport[30] + 1) * 10; - if (charge > 100) - charge = 100; - } - - cState.Battery = charge; - battery = charge; - - cState.Touch1 = (inputReport[0 + DS4Touchpad.TOUCHPAD_DATA_OFFSET] >> 7) != 0 ? false : true; // >= 1 touch detected - cState.Touch2 = (inputReport[4 + DS4Touchpad.TOUCHPAD_DATA_OFFSET] >> 7) != 0 ? false : true; // 2 touches detected - - cState.ReportTimeStamp = DateTime.UtcNow; - } - if (ConnectionType == ConnectionType.BT && !isIdle(cState)) - { - lastActive = DateTime.Now; - } - if (ConnectionType == ConnectionType.BT && lastActive + TimeSpan.FromSeconds(idleTimeout) < DateTime.Now) - { - DisconnectBT(); + priorInputReport30 = inputReport[30]; + Console.WriteLine(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + "> power subsystem octet: 0x" + inputReport[30].ToString("x02")); } - touchpad.handleTouchpad(inputReport, cState); - - sendOutputReport(); - - if ((!pState.PS || !pState.Options) && cState.PS && cState.Options) + // XXX DS4State mapping needs fixup, turn touches into an array[4] of structs. And include the touchpad details there instead. + for (int touches = inputReport[-1 + DS4Touchpad.TOUCHPAD_DATA_OFFSET - 1], touchOffset = 0; touches > 0; touches--, touchOffset += 9) { - DisconnectBT(); + 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); + // Even when idling there is still a touch packet indicating no touch 1 or 2 + touchpad.handleTouchpad(inputReport, cState, touchOffset); } + + /* 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.BT && (!pState.PS || !pState.Options) && cState.PS && cState.Options) + { + if (DisconnectBT()) + return; // all done + } + // XXX fix initialization ordering so the null checks all go away if (Report != null) Report(this, EventArgs.Empty); - lock (pState) - { - if (pState == null) - pState = new DS4State(); - cState.Copy(pState); - } + sendOutputReport(false); + + if (pState == null) + pState = new DS4State(); + cState.Copy(pState); } } - private void sendOutputReport() + private void sendOutputReport(bool synchronous) { - if (isDirty) + setTestRumble(); + setHapticState(); + if (conType == ConnectionType.BT) { - if (conType == ConnectionType.BT) + outputReportBuffer[0] = 0x11; + outputReportBuffer[1] = 0x80; + outputReportBuffer[3] = 0xff; + 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 + } + else + { + outputReportBuffer[0] = 0x05; + outputReportBuffer[1] = 0xff; + 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 + } + lock (outputReport) + { + if (synchronous) { - outputReport[0] = 0x11; - outputReport[1] = 128; - outputReport[3] = 0xff; - outputReport[6] = rightLightFastRumble; //fast motor - outputReport[7] = leftHeavySlowRumble; //slow motor - outputReport[8] = LightBarColor.red; //red - outputReport[9] = LightBarColor.green; //green - outputReport[10] = LightBarColor.blue; //blue - outputReport[11] = ledFlashOn; //flash on duration - outputReport[12] = ledFlashOff; //flash off duration - - if (hDevice.WriteOutputReportViaControl(outputReport)) - isDirty = false; + outputReportBuffer.CopyTo(outputReport, 0); + try + { + writeOutput(); + } + catch + { + // If it's dead already, don't worry about it. + } } else { - outputReport[0] = 0x5; - outputReport[1] = 0xFF; - outputReport[4] = rightLightFastRumble; //fast motor - outputReport[5] = leftHeavySlowRumble; //slow motor - outputReport[6] = LightBarColor.red; //red - outputReport[7] = LightBarColor.green; //green - outputReport[8] = LightBarColor.blue; //blue - outputReport[9] = ledFlashOn; //flash on duration - outputReport[10] = ledFlashOff; //flash off duration - if (hDevice.WriteOutputReportViaInterrupt(outputReport)) + bool output = false; + for (int i = 0; !output && i < outputReport.Length; i++) + output = outputReport[i] != outputReportBuffer[i]; + if (output) { - isDirty = false; + outputReportBuffer.CopyTo(outputReport, 0); + Monitor.Pulse(outputReport); } } } @@ -367,7 +458,6 @@ namespace DS4Library } long lbtAddr = BitConverter.ToInt64(btAddr, 0); - 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); @@ -383,52 +473,46 @@ namespace DS4Library } NativeMethods.BluetoothFindRadioClose(searchHandle); - Console.WriteLine("Disconnect successul: " + success); + Console.WriteLine("Disconnect successful: " + success); + success = true; // XXX return value indicates failure, but it still works? if(success) + { + IsDisconnecting = true; + StopOutputUpdate(); if (Removal != null) Removal(this, EventArgs.Empty); + } return success; } return false; } - public void setLightbarColor(byte red, byte green, byte blue) - { - if (red != ligtBarColor.red || green != ligtBarColor.green || blue != ligtBarColor.blue) - { - isDirty = true; - ligtBarColor.red = red; - ligtBarColor.green = green; - ligtBarColor.blue = blue; - } - } - - public void setLightbarDuration(byte onDuration, byte offDuration) - { - LightBarOffDuration = offDuration; - LightBarOnDuration = onDuration; - } - + private DS4HapticState testRumble = new DS4HapticState(); public void setRumble(byte rightLightFastMotor, byte leftHeavySlowMotor) { - RightLightFastRumble = rightLightFastMotor; - LeftHeavySlowRumble = leftHeavySlowMotor; + testRumble.RumbleMotorStrengthRightLightFast = rightLightFastMotor; + testRumble.RumbleMotorStrengthLeftHeavySlow = leftHeavySlowMotor; + testRumble.RumbleMotorsExplicitlyOff = rightLightFastMotor == 0 && leftHeavySlowMotor == 0; + } + + private void setTestRumble() + { + if (testRumble.IsRumbleSet()) + { + pushHapticState(testRumble); + if (testRumble.RumbleMotorsExplicitlyOff) + testRumble.RumbleMotorsExplicitlyOff = false; + } } public DS4State getCurrentState() { - lock (cState) - { - return cState.Clone(); - } + return cState.Clone(); } public DS4State getPreviousState() { - lock (pState) - { - return pState.Clone(); - } + return pState.Clone(); } public void getExposedState(DS4StateExposed expState, DS4State state) @@ -440,37 +524,60 @@ namespace DS4Library public void getCurrentState(DS4State state) { - lock (cState) - { - cState.Copy(state); - } + cState.Copy(state); } public void getPreviousState(DS4State state) { - lock (pState) - { - pState.Copy(state); - } + pState.Copy(state); } - private bool isIdle(DS4State cState) + private DS4HapticState[] hapticState = new DS4HapticState[1]; + private int hapticStackIndex = 0; + private void resetHapticState() { - 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; - if (Math.Abs(cState.LX - 127) > 10 || (Math.Abs(cState.LY - 127) > 10 )) - return false; - if (Math.Abs(cState.RX - 127) > 10 || (Math.Abs(cState.RY - 127) > 10)) - return false; - if (cState.Touch1 || cState.Touch2 || cState.TouchButton) - return false; - return true; + hapticStackIndex = 0; + } + + // Use the "most recently set" haptic state for each of light bar/motor. + private void setHapticState() + { + int i = 0; + DS4Color lightBarColor = LightBarColor; + byte lightBarFlashDurationOn = LightBarOnDuration, lightBarFlashDurationOff = LightBarOffDuration; + byte rumbleMotorStrengthLeftHeavySlow = LeftHeavySlowRumble, rumbleMotorStrengthRightLightFast = rightLightFastRumble; + foreach (DS4HapticState haptic in hapticState) + { + if (i++ == hapticStackIndex) + break; // rest haven't been used this time + if (haptic.IsLightBarSet()) + { + lightBarColor = haptic.LightBarColor; + lightBarFlashDurationOn = haptic.LightBarFlashDurationOn; + lightBarFlashDurationOff = haptic.LightBarFlashDurationOff; + } + if (haptic.IsRumbleSet()) + { + rumbleMotorStrengthLeftHeavySlow = haptic.RumbleMotorStrengthLeftHeavySlow; + rumbleMotorStrengthRightLightFast = haptic.RumbleMotorStrengthRightLightFast; + } + } + LightBarColor = lightBarColor; + LightBarOnDuration = lightBarFlashDurationOn; + LightBarOffDuration = lightBarFlashDurationOff; + LeftHeavySlowRumble = rumbleMotorStrengthLeftHeavySlow; + RightLightFastRumble = rumbleMotorStrengthRightLightFast; + } + + public void pushHapticState(DS4HapticState hs) + { + if (hapticStackIndex == hapticState.Length) + { + DS4HapticState[] newHaptics = new DS4HapticState[hapticState.Length + 1]; + Array.Copy(hapticState, newHaptics, hapticState.Length); + hapticState = newHaptics; + } + hapticState[hapticStackIndex++] = hs; } override diff --git a/DS4Library/DS4Devices.cs b/DS4Library/DS4Devices.cs index f5bd518..c669014 100644 --- a/DS4Library/DS4Devices.cs +++ b/DS4Library/DS4Devices.cs @@ -8,6 +8,7 @@ namespace DS4Library public class DS4Devices { private static Dictionary Devices = new Dictionary(); + private static HashSet DevicePaths = new HashSet(); public static bool isExclusiveMode = false; //enumerates ds4 controllers in the system @@ -17,30 +18,33 @@ namespace DS4Library { int[] pid = { 0x5C4 }; IEnumerable hDevices = HidDevices.Enumerate(0x054C, pid); + // Sort Bluetooth first in case USB is also connected on the same controller. + hDevices = hDevices.OrderBy((HidDevice d) => { return DS4Device.HidConnectionType(d); }); foreach (HidDevice hDevice in hDevices) { + if (DevicePaths.Contains(hDevice.DevicePath)) + continue; // BT/USB endpoint already open once if (!hDevice.IsOpen) + { hDevice.OpenDevice(isExclusiveMode); + // TODO in exclusive mode, try to hold both open when both are connected + if (isExclusiveMode && !hDevice.IsOpen) + hDevice.OpenDevice(false); + } if (hDevice.IsOpen) { - byte[] buffer = new byte[38]; - buffer[0] = 0x2; - hDevice.readFeatureData(buffer); if (Devices.ContainsKey(hDevice.readSerial())) - continue; + continue; // happens when the BT endpoint already is open and the USB is plugged into the same host else { DS4Device ds4Device = new DS4Device(hDevice); - ds4Device.StartUpdate(); ds4Device.Removal += On_Removal; Devices.Add(ds4Device.MacAddress, ds4Device); + DevicePaths.Add(hDevice.DevicePath); + ds4Device.StartUpdate(); } } - else - { - throw new Exception("ERROR: Can't open DS4 Controller. Try quitting other applications like Steam, Uplay, etc.)"); - } } } @@ -49,31 +53,43 @@ namespace DS4Library //allows to get DS4Device by specifying unique MAC address //format for MAC address is XX:XX:XX:XX:XX:XX public static DS4Device getDS4Controller(string mac) - { - DS4Device device = null; - try + { + lock (Devices) { - Devices.TryGetValue(mac, out device); + DS4Device device = null; + try + { + Devices.TryGetValue(mac, out device); + } + catch (ArgumentNullException) { } + return device; } - catch (ArgumentNullException) { } - return device; } //returns DS4 controllers that were found and are running public static IEnumerable getDS4Controllers() { - return Devices.Values; + lock (Devices) + { + DS4Device[] controllers = new DS4Device[Devices.Count]; + Devices.Values.CopyTo(controllers, 0); + return controllers; + } } public static void stopControllers() - { - IEnumerable devices = getDS4Controllers(); - foreach (DS4Device device in devices) + { + lock (Devices) { - device.StopUpdate(); - device.HidDevice.CloseDevice(); + IEnumerable devices = getDS4Controllers(); + foreach (DS4Device device in devices) + { + device.StopUpdate(); + device.HidDevice.CloseDevice(); + } + Devices.Clear(); + DevicePaths.Clear(); } - Devices.Clear(); } //called when devices is diconnected, timed out or has input reading failure @@ -84,6 +100,7 @@ namespace DS4Library DS4Device device = (DS4Device)sender; device.HidDevice.CloseDevice(); Devices.Remove(device.MacAddress); + DevicePaths.Remove(device.HidDevice.DevicePath); } } } diff --git a/DS4Library/DS4State.cs b/DS4Library/DS4State.cs index 761ccdb..fe19030 100644 --- a/DS4Library/DS4State.cs +++ b/DS4Library/DS4State.cs @@ -7,27 +7,33 @@ namespace DS4Library { public class DS4State { + public DateTime ReportTimeStamp; public bool Square, Triangle, Circle, Cross; public bool DpadUp, DpadDown, DpadLeft, DpadRight; public bool L1, L3, R1, R3; public bool Share, Options, PS, Touch1, Touch2, TouchButton; + public byte Touch1Identifier, Touch2Identifier; public byte LX, RX, LY, RY, L2, R2; - public int Battery; - public DateTime ReportTimeStamp; + public byte FrameCounter; // 0, 1, 2...62, 63, 0.... + public byte TouchPacketCounter; // we break these out automatically + public byte Battery; // 0 for charging, 10/20/30/40/50/60/70/80/90/100 for percentage of full + public DS4State() { - ReportTimeStamp = DateTime.UtcNow; Square = Triangle = Circle = Cross = false; DpadUp = DpadDown = DpadLeft = DpadRight = false; L1 = L3 = R1 = R3 = false; Share = Options = PS = Touch1 = Touch2 = TouchButton = false; LX = RX = LY = RY = 127; L2 = R2 = 0; + FrameCounter = 255; // only actually has 6 bits, so this is a null indicator + TouchPacketCounter = 255; // 8 bits, no great junk value Battery = 0; } public DS4State(DS4State state) { + ReportTimeStamp = state.ReportTimeStamp; Square = state.Square; Triangle = state.Triangle; Circle = state.Circle; @@ -46,14 +52,17 @@ namespace DS4Library Options = state.Options; PS = state.PS; Touch1 = state.Touch1; + Touch1Identifier = state.Touch1Identifier; Touch2 = state.Touch2; + Touch2Identifier = state.Touch2Identifier; TouchButton = state.TouchButton; + TouchPacketCounter = state.TouchPacketCounter; LX = state.LX; RX = state.RX; LY = state.LY; RY = state.RY; + FrameCounter = state.FrameCounter; Battery = state.Battery; - ReportTimeStamp = state.ReportTimeStamp; } public DS4State Clone() @@ -63,6 +72,7 @@ namespace DS4Library public void Copy(DS4State state) { + state.ReportTimeStamp = ReportTimeStamp; state.Square = Square; state.Triangle = Triangle; state.Circle = Circle; @@ -81,14 +91,17 @@ namespace DS4Library state.Options = Options; state.PS = PS; state.Touch1 = Touch1; + state.Touch1Identifier = Touch1Identifier; state.Touch2 = Touch2; + state.Touch2Identifier = Touch2Identifier; state.TouchButton = TouchButton; + state.TouchPacketCounter = TouchPacketCounter; state.LX = LX; state.RX = RX; state.LY = LY; state.RY = RY; + state.FrameCounter = FrameCounter; state.Battery = Battery; - state.ReportTimeStamp = ReportTimeStamp; } } diff --git a/DS4Library/DS4StateExposed.cs b/DS4Library/DS4StateExposed.cs index 5057563..f0c6715 100644 --- a/DS4Library/DS4StateExposed.cs +++ b/DS4Library/DS4StateExposed.cs @@ -53,21 +53,21 @@ namespace DS4Library /// Pitch upward/backward /// Add double the previous result to this delta and divide by three. - public int AccelX { get { return (UInt16)(accel[0] << 8) | accel[1]; } } + public int AccelX { get { return (Int16)((UInt16)(accel[0] << 8) | accel[1]); } } /// Yaw leftward/counter-clockwise/turn to port or larboard side /// Add double the previous result to this delta and divide by three. - public int AccelY { get { return (UInt16)(accel[2] << 8) | accel[3]; } } + public int AccelY { get { return (Int16)((UInt16)(accel[2] << 8) | accel[3]); } } /// roll left/L side of controller down/starboard raising up /// Add double the previous result to this delta and divide by three. - public int AccelZ { get { return (UInt16)(accel[4] << 8) | accel[5]; } } + public int AccelZ { get { return (Int16)((UInt16)(accel[4] << 8) | accel[5]); } } /// R side of controller upward /// Add double the previous result to this delta and divide by three. - public int GyroX { get { return (UInt16)(gyro[0] << 8) | gyro[1]; } } + public int GyroX { get { return (Int16)((UInt16)(gyro[0] << 8) | gyro[1]); } } /// touchpad and button face side of controller upward /// Add double the previous result to this delta and divide by three. - public int GyroY { get { return (UInt16)(gyro[2] << 8) | gyro[3]; } } + public int GyroY { get { return (Int16)((UInt16)(gyro[2] << 8) | gyro[3]); } } /// Audio/expansion ports upward and light bar/shoulders/bumpers/USB port downward /// Add double the previous result to this delta and divide by three. - public int GyroZ { get { return (UInt16)(gyro[4] << 8) | gyro[5]; } } + public int GyroZ { get { return (Int16)((UInt16)(gyro[4] << 8) | gyro[5]); } } } } diff --git a/DS4Library/DS4Touchpad.cs b/DS4Library/DS4Touchpad.cs index e7df81d..cbd3e51 100644 --- a/DS4Library/DS4Touchpad.cs +++ b/DS4Library/DS4Touchpad.cs @@ -8,11 +8,10 @@ namespace DS4Library public class TouchpadEventArgs : EventArgs { public readonly Touch[] touches = null; + public readonly System.DateTime timeStamp; public readonly bool touchButtonPressed; - public readonly DateTime timeStamp; - public TouchpadEventArgs(DateTime timeStamp, bool tButtonDown, Touch t0, Touch t1 = null) + public TouchpadEventArgs(System.DateTime utcTimestamp, bool tButtonDown, Touch t0, Touch t1 = null) { - this.timeStamp = timeStamp; if (t1 != null) { touches = new Touch[2]; @@ -25,6 +24,7 @@ namespace DS4Library touches[0] = t0; } touchButtonPressed = tButtonDown; + this.timeStamp = utcTimestamp; } } @@ -49,128 +49,161 @@ namespace DS4Library public class DS4Touchpad { - public event EventHandler TouchesBegan = null; - public event EventHandler TouchesMoved = null; - public event EventHandler TouchesEnded = null; - public event EventHandler TouchButtonDown = null; - public event EventHandler TouchButtonUp = null; - public readonly static int TOUCHPAD_DATA_OFFSET = 35; - internal static int lastTouchPadX, lastTouchPadY, - lastTouchPadX2, lastTouchPadY2; // tracks 0, 1 or 2 touches; we maintain touch 1 and 2 separately - internal static bool lastTouchPadIsDown; - internal static bool lastIsActive; - internal static bool lastIsActive2; - internal static byte lastTouchID, lastTouchID2; + public event EventHandler TouchesBegan = null; // finger one or two landed (or both, or one then two, or two then one; any touches[] count increase) + public event EventHandler TouchesMoved = null; // deltaX/deltaY are set because one or both fingers were already down on a prior sensor reading + public event EventHandler TouchesEnded = null; // all fingers lifted + public event EventHandler TouchButtonDown = null; // touchpad pushed down until the button clicks + public event EventHandler TouchButtonUp = null; // touchpad button released + public event EventHandler TouchUnchanged = null; // no status change for the touchpad itself... but other sensors may have changed, or you may just want to do some processing - public void handleTouchpad(byte[] data, DS4State sensors) + public readonly static int TOUCHPAD_DATA_OFFSET = 35; + internal int lastTouchPadX1, lastTouchPadY1, + lastTouchPadX2, lastTouchPadY2; // tracks 0, 1 or 2 touches; we maintain touch 1 and 2 separately + internal bool lastTouchPadIsDown; + internal bool lastIsActive1, lastIsActive2; + internal byte lastTouchID1, lastTouchID2; + internal byte[] previousPacket = new byte[8]; + + // We check everything other than the not bothering with not-very-useful TouchPacketCounter. + private bool PacketChanged(byte[] data, int touchPacketOffset) + { + bool changed = false; + for (int i = 0; i < previousPacket.Length; i++) + { + byte oldValue = previousPacket[i]; + previousPacket[i] = data[i + TOUCHPAD_DATA_OFFSET + touchPacketOffset]; + if (previousPacket[i] != oldValue) + changed = true; + } + return changed; + } + + public void handleTouchpad(byte[] data, DS4State sensors, int touchPacketOffset = 0) { bool touchPadIsDown = sensors.TouchButton; - byte touchID = (byte)(data[0 + TOUCHPAD_DATA_OFFSET] & 0x7F); - byte touchID2 = (byte)(data[4 + TOUCHPAD_DATA_OFFSET] & 0x7F); - int currentX = data[1 + TOUCHPAD_DATA_OFFSET] + ((data[2 + TOUCHPAD_DATA_OFFSET] & 0xF) * 255); - int currentY = ((data[2 + TOUCHPAD_DATA_OFFSET] & 0xF0) >> 4) + (data[3 + TOUCHPAD_DATA_OFFSET] * 16); - int currentX2 = data[5 + TOUCHPAD_DATA_OFFSET] + ((data[6 + TOUCHPAD_DATA_OFFSET] & 0xF) * 255); - int currentY2 = ((data[6 + TOUCHPAD_DATA_OFFSET] & 0xF0) >> 4) + (data[7 + TOUCHPAD_DATA_OFFSET] * 16); - - if (sensors.Touch1) + if (!PacketChanged(data, touchPacketOffset) && touchPadIsDown == lastTouchPadIsDown) { - if (!lastTouchPadIsDown && touchPadIsDown && TouchButtonDown != null) + if (TouchUnchanged != null) + TouchUnchanged(this, EventArgs.Empty); + return; + } + + byte touchID1 = (byte)(data[0 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] & 0x7F); + byte touchID2 = (byte)(data[4 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] & 0x7F); + int currentX1 = data[1 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] + ((data[2 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] & 0xF) * 255); + int currentY1 = ((data[2 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] & 0xF0) >> 4) + (data[3 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] * 16); + int currentX2 = data[5 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] + ((data[6 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] & 0xF) * 255); + int currentY2 = ((data[6 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] & 0xF0) >> 4) + (data[7 + TOUCHPAD_DATA_OFFSET + touchPacketOffset] * 16); + + TouchpadEventArgs args; + if (sensors.Touch1 || sensors.Touch2) + { + if ((sensors.Touch1 && !lastIsActive1) || (sensors.Touch2 && !lastIsActive2)) { - TouchpadEventArgs args = null; - Touch t0 = new Touch(currentX, currentY, touchID); - if (sensors.Touch2) + if (TouchesBegan != null) { - Touch t1 = new Touch(currentX2, currentY2, touchID2); - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0, t1); + if (sensors.Touch1 && sensors.Touch2) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX1, currentY1, touchID1), new Touch(currentX2, currentY2, touchID2)); + else if (sensors.Touch1) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX1, currentY1, touchID1)); + else + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX2, currentY2, touchID2)); + + TouchesBegan(this, args); + } + } + else if (sensors.Touch1 == lastIsActive1 && sensors.Touch2 == lastIsActive2 && TouchesMoved != null) + { + Touch tPrev, t0, t1; + + if (sensors.Touch1 && sensors.Touch2) + { + tPrev = new Touch(lastTouchPadX1, lastTouchPadY1, lastTouchID1); + t0 = new Touch(currentX1, currentY1, touchID1, tPrev); + tPrev = new Touch(lastTouchPadX2, lastTouchPadY2, lastTouchID2); + t1 = new Touch(currentX2, currentY2, touchID2, tPrev); + } + else if (sensors.Touch1) + { + tPrev = new Touch(lastTouchPadX1, lastTouchPadY1, lastTouchID1); + t0 = new Touch(currentX1, currentY1, touchID1, tPrev); + t1 = null; } else - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0); + { + tPrev = new Touch(lastTouchPadX2, lastTouchPadY2, lastTouchID2); + t0 = new Touch(currentX2, currentY2, touchID2, tPrev); + t1 = null; + } + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0, t1); + + TouchesMoved(this, args); + } + + if (!lastTouchPadIsDown && touchPadIsDown && TouchButtonDown != null) + { + if (sensors.Touch1 && sensors.Touch2) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX1, currentY1, touchID1), new Touch(currentX2, currentY2, touchID2)); + else if (sensors.Touch1) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX1, currentY1, touchID1)); + else + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX2, currentY2, touchID2)); + TouchButtonDown(this, args); } else if (lastTouchPadIsDown && !touchPadIsDown && TouchButtonUp != null) { - TouchpadEventArgs args = null; - Touch t0 = new Touch(currentX, currentY, touchID); - if (sensors.Touch2) - { - Touch t1 = new Touch(currentX2, currentY2, touchID2); - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0, t1); - } + if (sensors.Touch1 && sensors.Touch2) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX1, currentY1, touchID1), new Touch(currentX2, currentY2, touchID2)); + else if (sensors.Touch1) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX1, currentY1, touchID1)); else - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0); + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(currentX2, currentY2, touchID2)); + TouchButtonUp(this, args); } - if (!lastIsActive || (sensors.Touch2 && !lastIsActive2)) + if (sensors.Touch1) { - TouchpadEventArgs args = null; - Touch t0 = new Touch(currentX, currentY, touchID); - if (sensors.Touch2 && !lastIsActive2) - { - Touch t1 = new Touch(currentX2, currentY2, touchID2); - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0, t1); - } - else - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0); - if (TouchesBegan != null) - TouchesBegan(this, args); + lastTouchPadX1 = currentX1; + lastTouchPadY1 = currentY1; } - else if (lastIsActive) + if (sensors.Touch2) { - if (TouchesMoved != null) - { - TouchpadEventArgs args = null; - - Touch t0Prev = new Touch(lastTouchPadX, lastTouchPadY, lastTouchID); - Touch t0 = new Touch(currentX, currentY, touchID, t0Prev); - if (sensors.Touch1 && sensors.Touch2) - { - Touch t1Prev = new Touch(lastTouchPadX2, lastTouchPadY2, lastTouchID2); - Touch t1 = new Touch(currentX2, currentY2, touchID2, t1Prev); - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0, t1); - } - else - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0); - TouchesMoved(this, args); - } + lastTouchPadX2 = currentX2; + lastTouchPadY2 = currentY2; } - - lastTouchPadX = currentX; - lastTouchPadY = currentY; - lastTouchPadX2 = currentX2; - lastTouchPadY2 = currentY2; lastTouchPadIsDown = touchPadIsDown; } else { - if (lastIsActive) + if (touchPadIsDown && !lastTouchPadIsDown) { - TouchpadEventArgs args = null; - Touch t0 = new Touch(currentX, currentY, touchID); - if (lastIsActive2) - { - Touch t1 = new Touch(currentX2, currentY2, touchID2); - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0, t1); - } - else - args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, t0); - if (TouchesEnded != null) - TouchesEnded(this, args); + if (TouchButtonDown != null) + TouchButtonDown(this, new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, null, null)); + } + else if (!touchPadIsDown && lastTouchPadIsDown) + { + if (TouchButtonUp != null) + TouchButtonUp(this, new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, null, null)); } - if (touchPadIsDown && !lastTouchPadIsDown && TouchButtonDown != null) + if ((lastIsActive1 || lastIsActive2) && TouchesEnded != null) { - TouchButtonDown(this, new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, null, null)); - } - else if (!touchPadIsDown && lastTouchPadIsDown && TouchButtonUp != null) - { - TouchButtonUp(this, new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, null, null)); + if (lastIsActive1 && lastIsActive2) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(lastTouchPadX1, lastTouchPadY1, touchID1), new Touch(lastTouchPadX2, lastTouchPadY2, touchID2)); + else if (lastIsActive1) + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(lastTouchPadX1, lastTouchPadY1, touchID1)); + else + args = new TouchpadEventArgs(sensors.ReportTimeStamp, sensors.TouchButton, new Touch(lastTouchPadX2, lastTouchPadY2, touchID2)); + + TouchesEnded(this, args); } } - lastIsActive = sensors.Touch1; + lastIsActive1 = sensors.Touch1; lastIsActive2 = sensors.Touch2; - lastTouchID = touchID; + lastTouchID1 = touchID1; lastTouchID2 = touchID2; lastTouchPadIsDown = touchPadIsDown; } diff --git a/DS4Service/DS4Service.Designer.cs b/DS4Service/DS4Service.Designer.cs index 386a0fe..7693eea 100644 --- a/DS4Service/DS4Service.Designer.cs +++ b/DS4Service/DS4Service.Designer.cs @@ -13,13 +13,9 @@ /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { - if (disposing) + if (disposing && (components != null)) { - if (components != null) - components.Dispose(); - //CA2213 Disposable fields should be disposed - if (rootHub != null) - rootHub.Dispose(); + components.Dispose(); } base.Dispose(disposing); } diff --git a/DS4Tool/AboutBox.Designer.cs b/DS4Tool/AboutBox.Designer.cs index 79fce19..500559a 100644 --- a/DS4Tool/AboutBox.Designer.cs +++ b/DS4Tool/AboutBox.Designer.cs @@ -35,7 +35,7 @@ // // OkButton // - this.OkButton.Location = new System.Drawing.Point(113, 202); + this.OkButton.Location = new System.Drawing.Point(260, 118); this.OkButton.Name = "OkButton"; this.OkButton.Size = new System.Drawing.Size(75, 23); this.OkButton.TabIndex = 0; @@ -52,28 +52,27 @@ this.textBox1.Multiline = true; this.textBox1.Name = "textBox1"; this.textBox1.ReadOnly = true; - this.textBox1.Size = new System.Drawing.Size(260, 155); + this.textBox1.Size = new System.Drawing.Size(260, 61); this.textBox1.TabIndex = 1; - this.textBox1.Text = "DS4 Tool 1.3 RC3\r\n\r\nCreated By:\r\nArtur Dzmitryieu(InhexSTER)\r\nelectrobrains\r\nHect" + - "icSeptic\r\nJays2Kings\r\n\r\nBased on DS3 Scp Server from\r\nScarlet.Crush"; + this.textBox1.Text = "DS4Windows is Open Source software!\r\n\r\nInsert link to GPL here (at some point.)"; this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; // // linkLabel1 // this.linkLabel1.AutoSize = true; - this.linkLabel1.Location = new System.Drawing.Point(59, 175); + this.linkLabel1.Location = new System.Drawing.Point(9, 86); this.linkLabel1.Name = "linkLabel1"; - this.linkLabel1.Size = new System.Drawing.Size(183, 13); + this.linkLabel1.Size = new System.Drawing.Size(324, 13); this.linkLabel1.TabIndex = 2; this.linkLabel1.TabStop = true; - this.linkLabel1.Text = "https://code.google.com/p/ds4-tool/"; + this.linkLabel1.Text = "https://code.google.com/r/brianfundakowskifeldman-ds4windows/"; this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); // // AboutBox // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(284, 230); + this.ClientSize = new System.Drawing.Size(347, 153); this.Controls.Add(this.linkLabel1); this.Controls.Add(this.textBox1); this.Controls.Add(this.OkButton); diff --git a/DS4Tool/AboutBox.cs b/DS4Tool/AboutBox.cs index dd825ec..83548ee 100644 --- a/DS4Tool/AboutBox.cs +++ b/DS4Tool/AboutBox.cs @@ -28,7 +28,7 @@ namespace ScpServer private void AboutBox_Load(object sender, EventArgs e) { - linkLabel1.Links.Add(0,35,"https://code.google.com/p/ds4-tool/"); + linkLabel1.Links.Add(0, linkLabel1.Text.Length, "https://code.google.com/r/brianfundakowskifeldman-ds4windows/"); } } } diff --git a/DS4Tool/CustomMapping.Designer.cs b/DS4Tool/CustomMapping.Designer.cs index 7ef941d..c331b63 100644 --- a/DS4Tool/CustomMapping.Designer.cs +++ b/DS4Tool/CustomMapping.Designer.cs @@ -62,6 +62,8 @@ this.bnRX2 = new System.Windows.Forms.Button(); this.bnL3 = new System.Windows.Forms.Button(); this.bnR3 = new System.Windows.Forms.Button(); + this.TouchTip = new System.Windows.Forms.Label(); + this.ReapTip = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox)).BeginInit(); this.SuspendLayout(); // @@ -300,7 +302,7 @@ this.bnDown.ForeColor = System.Drawing.SystemColors.WindowText; this.bnDown.Location = new System.Drawing.Point(85, 184); this.bnDown.Name = "bnDown"; - this.bnDown.Size = new System.Drawing.Size(19, 22); + this.bnDown.Size = new System.Drawing.Size(19, 29); this.bnDown.TabIndex = 53; this.bnDown.Text = "Down Button"; this.bnDown.UseVisualStyleBackColor = false; @@ -318,7 +320,7 @@ this.bnRight.ForeColor = System.Drawing.SystemColors.WindowText; this.bnRight.Location = new System.Drawing.Point(106, 164); this.bnRight.Name = "bnRight"; - this.bnRight.Size = new System.Drawing.Size(21, 18); + this.bnRight.Size = new System.Drawing.Size(27, 22); this.bnRight.TabIndex = 53; this.bnRight.Text = "Right Button"; this.bnRight.UseVisualStyleBackColor = false; @@ -336,7 +338,7 @@ this.bnLeft.ForeColor = System.Drawing.SystemColors.WindowText; this.bnLeft.Location = new System.Drawing.Point(57, 163); this.bnLeft.Name = "bnLeft"; - this.bnLeft.Size = new System.Drawing.Size(26, 20); + this.bnLeft.Size = new System.Drawing.Size(26, 23); this.bnLeft.TabIndex = 53; this.bnLeft.Text = "Left Button"; this.bnLeft.UseVisualStyleBackColor = false; @@ -352,9 +354,9 @@ this.bnOptions.FlatAppearance.MouseOverBackColor = System.Drawing.SystemColors.Control; this.bnOptions.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.bnOptions.ForeColor = System.Drawing.SystemColors.WindowText; - this.bnOptions.Location = new System.Drawing.Point(283, 125); + this.bnOptions.Location = new System.Drawing.Point(286, 121); this.bnOptions.Name = "bnOptions"; - this.bnOptions.Size = new System.Drawing.Size(13, 22); + this.bnOptions.Size = new System.Drawing.Size(19, 30); this.bnOptions.TabIndex = 53; this.bnOptions.Text = "Start"; this.bnOptions.UseVisualStyleBackColor = false; @@ -370,9 +372,9 @@ this.bnShare.FlatAppearance.MouseOverBackColor = System.Drawing.SystemColors.Control; this.bnShare.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.bnShare.ForeColor = System.Drawing.SystemColors.WindowText; - this.bnShare.Location = new System.Drawing.Point(131, 127); + this.bnShare.Location = new System.Drawing.Point(131, 124); this.bnShare.Name = "bnShare"; - this.bnShare.Size = new System.Drawing.Size(12, 23); + this.bnShare.Size = new System.Drawing.Size(14, 29); this.bnShare.TabIndex = 53; this.bnShare.Text = "Back"; this.bnShare.UseVisualStyleBackColor = false; @@ -388,9 +390,9 @@ this.bnTouchpad.FlatAppearance.MouseOverBackColor = System.Drawing.SystemColors.Control; this.bnTouchpad.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.bnTouchpad.ForeColor = System.Drawing.SystemColors.WindowText; - this.bnTouchpad.Location = new System.Drawing.Point(151, 131); + this.bnTouchpad.Location = new System.Drawing.Point(151, 161); this.bnTouchpad.Name = "bnTouchpad"; - this.bnTouchpad.Size = new System.Drawing.Size(64, 56); + this.bnTouchpad.Size = new System.Drawing.Size(128, 26); this.bnTouchpad.TabIndex = 53; this.bnTouchpad.Text = "Click"; this.bnTouchpad.UseVisualStyleBackColor = false; @@ -426,7 +428,7 @@ this.bnTouchUpper.ForeColor = System.Drawing.SystemColors.WindowText; this.bnTouchUpper.Location = new System.Drawing.Point(150, 104); this.bnTouchUpper.Name = "bnTouchUpper"; - this.bnTouchUpper.Size = new System.Drawing.Size(125, 26); + this.bnTouchUpper.Size = new System.Drawing.Size(129, 32); this.bnTouchUpper.TabIndex = 53; this.bnTouchUpper.Text = "Middle Click"; this.bnTouchUpper.UseVisualStyleBackColor = false; @@ -442,9 +444,9 @@ this.bnTouchMulti.FlatAppearance.MouseOverBackColor = System.Drawing.SystemColors.Control; this.bnTouchMulti.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.bnTouchMulti.ForeColor = System.Drawing.SystemColors.WindowText; - this.bnTouchMulti.Location = new System.Drawing.Point(215, 131); + this.bnTouchMulti.Location = new System.Drawing.Point(151, 135); this.bnTouchMulti.Name = "bnTouchMulti"; - this.bnTouchMulti.Size = new System.Drawing.Size(64, 56); + this.bnTouchMulti.Size = new System.Drawing.Size(128, 26); this.bnTouchMulti.TabIndex = 53; this.bnTouchMulti.Text = "Right Click"; this.bnTouchMulti.UseVisualStyleBackColor = false; @@ -471,12 +473,11 @@ // this.lbControls.FormattingEnabled = true; this.lbControls.Items.AddRange(new object[] { - ""}); + ""}); this.lbControls.Location = new System.Drawing.Point(418, 67); this.lbControls.Name = "lbControls"; this.lbControls.Size = new System.Drawing.Size(248, 212); this.lbControls.TabIndex = 54; - this.lbControls.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); // // bnLY2 // @@ -640,12 +641,36 @@ this.bnR3.Text = "Right Stick"; this.bnR3.UseVisualStyleBackColor = false; // + // TouchTip + // + this.TouchTip.AutoSize = true; + this.TouchTip.Location = new System.Drawing.Point(164, 34); + this.TouchTip.Name = "TouchTip"; + this.TouchTip.Size = new System.Drawing.Size(109, 52); + this.TouchTip.TabIndex = 55; + this.TouchTip.Text = "Touchpad:\r\nTop: Upper Pad \r\nMiddle: Multi-Touch \r\nBottom: Single Touch"; + this.TouchTip.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.TouchTip.Visible = false; + // + // ReapTip + // + this.ReapTip.AutoSize = true; + this.ReapTip.Location = new System.Drawing.Point(134, 59); + this.ReapTip.Name = "ReapTip"; + this.ReapTip.Size = new System.Drawing.Size(169, 26); + this.ReapTip.TabIndex = 55; + this.ReapTip.Text = "Double Tap a key to toggle repeat\r\n(Excludes TouchPad)"; + this.ReapTip.TextAlign = System.Drawing.ContentAlignment.TopCenter; + this.ReapTip.Visible = false; + // // CustomMapping // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(244)))), ((int)(((byte)(244)))), ((int)(((byte)(244))))); this.ClientSize = new System.Drawing.Size(684, 310); + this.Controls.Add(this.ReapTip); + this.Controls.Add(this.TouchTip); this.Controls.Add(this.pictureBox); this.Controls.Add(this.lbControls); this.Controls.Add(this.cbScanCode); @@ -728,5 +753,7 @@ private System.Windows.Forms.Button bnRX2; private System.Windows.Forms.Button bnL3; private System.Windows.Forms.Button bnR3; + private System.Windows.Forms.Label TouchTip; + private System.Windows.Forms.Label ReapTip; } } \ No newline at end of file diff --git a/DS4Tool/CustomMapping.cs b/DS4Tool/CustomMapping.cs index 45e6493..149b53e 100644 --- a/DS4Tool/CustomMapping.cs +++ b/DS4Tool/CustomMapping.cs @@ -16,7 +16,7 @@ namespace ScpServer { private int device; private bool handleNextKeyPress = false; - private bool MouseMoveAdded = false; + private bool ReapTipShown = false; private List comboBoxes = new List(); private List