diff --git a/pom.xml b/pom.xml index 16a76eb..1bd83e5 100644 --- a/pom.xml +++ b/pom.xml @@ -137,15 +137,20 @@ jxinput 1eb4087 - + org.hid4java hid4java 0.4.0 + + purejavahidapi + purejavahidapi + 0.0.1 + diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/DS4NewController.java b/src/net/ash/HIDToVPADNetworkClient/controller/DS4NewController.java index e4aa4c4..1bc8a0e 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/DS4NewController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/DS4NewController.java @@ -2,8 +2,10 @@ package net.ash.HIDToVPADNetworkClient.controller; import java.util.Arrays; + import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; import net.ash.HIDToVPADNetworkClient.util.Settings; +import net.ash.HIDToVPADNetworkClient.util.Utilities; public class DS4NewController extends HidController { public static final short DS4_NEW_CONTROLLER_VID = 0x54C; @@ -11,18 +13,13 @@ public class DS4NewController extends HidController { public DS4NewController(String identifier) throws ControllerInitializationFailedException { super(identifier); - if (Settings.isMacOSX()) { - this.MAX_PACKET_LENGTH = 7; - } else { - this.MAX_PACKET_LENGTH = 6; - } + this.MAX_PACKET_LENGTH = 7; } @Override public byte[] pollLatestData() { byte[] currentData = super.pollLatestData(); - if (Settings.isMacOSX() && currentData != null && currentData.length > 6) { // for some reason the controller has one extra byte at the beginning under - // OSX + if(currentData.length >= 7){ currentData = Arrays.copyOfRange(currentData, 1, 7); } return currentData; diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java b/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java index b3ccf52..15444d2 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java @@ -33,7 +33,8 @@ import net.ash.HIDToVPADNetworkClient.hid.HidManager; @Log public class HidController extends Controller { - @Getter @Setter(AccessLevel.PRIVATE) private HidDevice hidDevice; + @Getter @Setter(AccessLevel.PRIVATE) + protected HidDevice hidDevice; public static Controller getInstance(String deviceIdentifier) throws IOException, ControllerInitializationFailedException { @@ -68,6 +69,8 @@ public class HidController extends Controller { if (device == null || !device.open()) { return false; } + + log.info("HidDevice opened!"); setHidDevice(device); diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java b/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java index bc606b7..d064414 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java @@ -21,32 +21,209 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.controller; -import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import lombok.extern.java.Log; +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import net.ash.HIDToVPADNetworkClient.manager.ActiveControllerManager; +import net.ash.HIDToVPADNetworkClient.manager.ControllerManager; +import net.ash.HIDToVPADNetworkClient.util.MessageBoxManager; +import net.ash.HIDToVPADNetworkClient.util.Utilities; +import purejavahidapi.linux.UdevLibrary.hidraw_report_descriptor; + +@Log public class SwitchProController extends HidController { public static final short SWITCH_PRO_CONTROLLER_VID = 0x57e; public static final short SWITCH_PRO_CONTROLLER_PID = 0x2009; - + + private static final int SWITCH_PRO_USB_BUTTON_A = 0x08000000; + private static final int SWITCH_PRO_USB_BUTTON_B = 0x04000000; + private static final int SWITCH_PRO_USB_BUTTON_X = 0x02000000; + private static final int SWITCH_PRO_USB_BUTTON_Y = 0x01000000; + private static final int SWITCH_PRO_USB_BUTTON_PLUS = 0x00020000; + private static final int SWITCH_PRO_USB_BUTTON_MINUS = 0x00010000; + private static final int SWITCH_PRO_USB_BUTTON_HOME = 0x00100000; + private static final int SWITCH_PRO_USB_BUTTON_SCREENSHOT = 0x00200000; + private static final int SWITCH_PRO_USB_BUTTON_R = 0x40000000; + private static final int SWITCH_PRO_USB_BUTTON_ZR = 0x80000000; + private static final int SWITCH_PRO_USB_BUTTON_STICK_R = 0x00040000; + private static final int SWITCH_PRO_USB_BUTTON_L = 0x00004000; + private static final int SWITCH_PRO_USB_BUTTON_ZL = 0x00008000; + private static final int SWITCH_PRO_USB_BUTTON_STICK_L = 0x00080000; + + private static final byte SWITCH_PRO_USB_BUTTON_LEFT_VALUE = 0x08; + private static final byte SWITCH_PRO_USB_BUTTON_RIGHT_VALUE = 0x04; + private static final byte SWITCH_PRO_USB_BUTTON_DOWN_VALUE = 0x01; + private static final byte SWITCH_PRO_USB_BUTTON_UP_VALUE = 0x02; + + + private static final int SWITCH_PRO_BT_BUTTON_A = 0x02000000; + private static final int SWITCH_PRO_BT_BUTTON_B = 0x01000000; + private static final int SWITCH_PRO_BT_BUTTON_X = 0x08000000; + private static final int SWITCH_PRO_BT_BUTTON_Y = 0x04000000; + + private static final int SWITCH_PRO_BT_BUTTON_PLUS = 0x00020000; + private static final int SWITCH_PRO_BT_BUTTON_MINUS = 0x00010000; + private static final int SWITCH_PRO_BT_BUTTON_HOME = 0x00100000; + + private static final int SWITCH_PRO_BT_BUTTON_R = 0x20000000; + private static final int SWITCH_PRO_BT_BUTTON_ZR = 0x80000000; + private static final int SWITCH_PRO_BT_BUTTON_STICK_R = 0x00080000; + private static final int SWITCH_PRO_BT_BUTTON_L = 0x10000000; + private static final int SWITCH_PRO_BT_BUTTON_ZL = 0x40000000; + private static final int SWITCH_PRO_BT_BUTTON_STICK_L = 0x00040000; + + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_N_VALUE = 0x00; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_NE_VALUE = 0x01; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_E_VALUE = 0x02; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_SE_VALUE = 0x03; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_S_VALUE = 0x04; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_SW_VALUE = 0x05; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_W_VALUE = 0x06; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_NW_VALUE = 0x07; + private static final byte SWITCH_PRO_BT_BUTTON_DPAD_NEUTRAL_VALUE = 0x08; + + private static boolean isUSB = false; + + private final byte[] CMD_readInput = new byte[]{(byte) 0x92,0x00,0x01,0x00,0x00,0x00,0x00,0x1F}; + private final byte[] CMD_getMAC = new byte[]{0x01}; + private final byte[] CMD_DO_HANDSHAKE = new byte[]{0x02}; + private final byte[] CMD_SWITCH_BAUDRATE = new byte[]{0x03}; + private final byte[] CMD_HID_ONLY = new byte[]{0x04}; + public SwitchProController(String identifier) throws ControllerInitializationFailedException { super(identifier); // truncate package to 11; this.MAX_PACKET_LENGTH = 11; + + if((isUSB = doUSBHandshake())){ + log.info("Switch Pro Controller USB Handshake successful!"); + }else{ + log.info("Switch Pro Controller USB Handshake failed! The controller is probably connected via Bluetooth"); + } } @Override public byte[] pollLatestData() { + if(isUSB){ + sendReadCmd(); + } + byte[] currentData = super.pollLatestData(); + if (currentData == null || currentData.length < 10) { return new byte[0]; } - // remove unused data (because only changed data will be sent) - currentData[3] = 0; - currentData[5] = 0; - currentData[7] = 0; - currentData[9] = 0; + if(isUSB){ + if(currentData[0] == (byte)0x81 && currentData[1] == (byte)0x92){ + + currentData = Arrays.copyOfRange(currentData, 0x0D,0x20); + int buttons = ByteBuffer.wrap(currentData).getInt() & 0xFFFFFF00; + + byte LX = (byte) ((short) ((currentData[0x04] << 8 &0xFF00) | (((short)currentData[0x03])&0xFF)) >> 0x04); + byte LY = currentData[0x05]; + byte RX = (byte) ((short) ((currentData[0x07] << 8 &0xFF00) | (((short)currentData[0x06])&0xFF)) >> 0x04); + byte RY = currentData[0x08]; + byte[] btData = new byte[0x0B]; + + int newButtons = 0; + + if((buttons & SWITCH_PRO_USB_BUTTON_A) == SWITCH_PRO_USB_BUTTON_A) newButtons |= SWITCH_PRO_BT_BUTTON_A; + if((buttons & SWITCH_PRO_USB_BUTTON_B) == SWITCH_PRO_USB_BUTTON_B) newButtons |= SWITCH_PRO_BT_BUTTON_B; + if((buttons & SWITCH_PRO_USB_BUTTON_X) == SWITCH_PRO_USB_BUTTON_X) newButtons |= SWITCH_PRO_BT_BUTTON_X; + if((buttons & SWITCH_PRO_USB_BUTTON_Y) == SWITCH_PRO_USB_BUTTON_Y) newButtons |= SWITCH_PRO_BT_BUTTON_Y; + + if((buttons & SWITCH_PRO_USB_BUTTON_PLUS) == SWITCH_PRO_USB_BUTTON_PLUS) newButtons |= SWITCH_PRO_BT_BUTTON_PLUS; + if((buttons & SWITCH_PRO_USB_BUTTON_MINUS) == SWITCH_PRO_USB_BUTTON_MINUS) newButtons |= SWITCH_PRO_BT_BUTTON_MINUS; + if((buttons & SWITCH_PRO_USB_BUTTON_HOME) == SWITCH_PRO_USB_BUTTON_HOME) newButtons |= SWITCH_PRO_BT_BUTTON_HOME; + //if((buttons & SWITCH_PRO_USB_BUTTON_SCREENSHOT) == SWITCH_PRO_USB_BUTTON_SCREENSHOT) newButtons |= SWITCH_PRO_BT_BUTTON_SCREENSHOT; + + if((buttons & SWITCH_PRO_USB_BUTTON_R) == SWITCH_PRO_USB_BUTTON_R) newButtons |= SWITCH_PRO_BT_BUTTON_R; + if((buttons & SWITCH_PRO_USB_BUTTON_ZR) == SWITCH_PRO_USB_BUTTON_ZR) newButtons |= SWITCH_PRO_BT_BUTTON_ZR; + if((buttons & SWITCH_PRO_USB_BUTTON_STICK_R) == SWITCH_PRO_USB_BUTTON_STICK_R) newButtons |= SWITCH_PRO_BT_BUTTON_STICK_R; + + if((buttons & SWITCH_PRO_USB_BUTTON_L) == SWITCH_PRO_USB_BUTTON_L) newButtons |= SWITCH_PRO_BT_BUTTON_L; + if((buttons & SWITCH_PRO_USB_BUTTON_ZL) == SWITCH_PRO_USB_BUTTON_ZL) newButtons |= SWITCH_PRO_BT_BUTTON_ZL; + if((buttons & SWITCH_PRO_USB_BUTTON_STICK_L) == SWITCH_PRO_USB_BUTTON_STICK_L) newButtons |= SWITCH_PRO_BT_BUTTON_STICK_L; + + byte dpad = currentData[2]; + byte dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_NEUTRAL_VALUE; + + if(((dpad & SWITCH_PRO_USB_BUTTON_UP_VALUE) == SWITCH_PRO_USB_BUTTON_UP_VALUE) && + ((dpad & SWITCH_PRO_USB_BUTTON_RIGHT_VALUE) == SWITCH_PRO_USB_BUTTON_RIGHT_VALUE)){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_NE_VALUE; + }else if(((dpad & SWITCH_PRO_USB_BUTTON_DOWN_VALUE) == SWITCH_PRO_USB_BUTTON_DOWN_VALUE) && + ((dpad & SWITCH_PRO_USB_BUTTON_RIGHT_VALUE) == SWITCH_PRO_USB_BUTTON_RIGHT_VALUE)){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_SE_VALUE; + }else if(((dpad & SWITCH_PRO_USB_BUTTON_DOWN_VALUE) == SWITCH_PRO_USB_BUTTON_DOWN_VALUE) && + ((dpad & SWITCH_PRO_USB_BUTTON_LEFT_VALUE) == SWITCH_PRO_USB_BUTTON_LEFT_VALUE)){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_SW_VALUE; + }else if(((dpad & SWITCH_PRO_USB_BUTTON_UP_VALUE) == SWITCH_PRO_USB_BUTTON_UP_VALUE) && + ((dpad & SWITCH_PRO_USB_BUTTON_LEFT_VALUE) == SWITCH_PRO_USB_BUTTON_LEFT_VALUE)){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_NW_VALUE; + }else if((dpad & SWITCH_PRO_USB_BUTTON_UP_VALUE) == SWITCH_PRO_USB_BUTTON_UP_VALUE){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_N_VALUE; + }else if((dpad & SWITCH_PRO_USB_BUTTON_RIGHT_VALUE) == SWITCH_PRO_USB_BUTTON_RIGHT_VALUE){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_E_VALUE; + }else if((dpad & SWITCH_PRO_USB_BUTTON_DOWN_VALUE) == SWITCH_PRO_USB_BUTTON_DOWN_VALUE){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_S_VALUE; + }else if((dpad & SWITCH_PRO_USB_BUTTON_LEFT_VALUE) == SWITCH_PRO_USB_BUTTON_LEFT_VALUE){ + dpadResult = SWITCH_PRO_BT_BUTTON_DPAD_W_VALUE; + } + + btData[0] = (byte) ((newButtons >> 24) & 0xFF); + btData[1] = (byte) ((newButtons >> 16) & 0xFF); + btData[2] |= dpadResult; + btData[4] = LX; + btData[6] = (byte) (LY * -1) ; + btData[8] = RX; + btData[10] = (byte) (RY * -1) ; + currentData = btData; + //System.out.println(Utilities.ByteArrayToString(currentData)); + }else{ + return new byte[0]; + } + }else{ + currentData = Arrays.copyOfRange(currentData, 0x01,12); + currentData[3] = 0; + currentData[5] = 0; + currentData[7] = 0; + currentData[9] = 0; + } + + return currentData; } + private boolean sendReadCmd() { + return (hidDevice.hid_write(CMD_readInput, CMD_readInput.length,(byte) 0x80) > 0); + } + + private boolean doUSBHandshake() { + byte[] read_buf = new byte[0x40]; + int res = 0; + + if(res >= 0){ + hidDevice.hid_read(read_buf,read_buf.length); + + res = hidDevice.hid_write(CMD_DO_HANDSHAKE,CMD_DO_HANDSHAKE.length,(byte) 0x80); + if(res < 0) return false; + + res = hidDevice.hid_write(CMD_HID_ONLY,CMD_HID_ONLY.length,(byte) 0x80); + if(res < 0) return false; + + res = hidDevice.hid_read(read_buf,read_buf.length+1); + if(read_buf[0] != 0x00){ + MessageBoxManager.addMessageBox("You need to reattach the Switch Pro Controller or restart the network client!"); + return false; + } + return true; + + } + return false; + } + @Override public String getInfoText() { return "Switch Pro Controller on " + getIdentifier(); diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java b/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java index 45cd56b..2e5829f 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java @@ -83,4 +83,10 @@ public interface HidDevice { * @return product string (name) */ String getProductString(); + + int hid_write(byte[] data, int length, byte reportID); + + int hid_read(byte[] data); + + int hid_read(byte[] data, int timeoutMillis); } diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java b/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java index c02efef..7b6ce49 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java @@ -72,8 +72,8 @@ public class HidManager { public static boolean isGamepad(HidDevice info) { if (info == null) return false; short usage = info.getUsageID(); - return (info.getProductString().toLowerCase().contains("gamepad") || usage == 0x05 || usage == 0x04 || isNintendoController(info) - || isPlaystationController(info)); + String productString = info.getProductString(); + return ((productString != null && productString.toLowerCase().contains("gamepad")) || usage == 0x05 || usage == 0x04 || isNintendoController(info) || isPlaystationController(info)); } public static boolean isKeyboard(HidDevice info) { @@ -108,7 +108,7 @@ public class HidManager { } static { - if (Settings.isMacOSX()) { + if (Settings.isMacOSX()) { // PureJavaHid is not properly implemented for OSX! backend = new Hid4JavaHidManagerBackend(); } else if (Settings.isWindows()) { backend = new PureJavaHidManagerBackend(); diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java b/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java index 8b0f591..5e829bc 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java @@ -56,7 +56,7 @@ class Hid4JavaHidDevice implements HidDevice { @Override public byte[] getLatestData() { - int length = myDevice.read(data); + int length = hid_read(data); if (length <= 0) return new byte[0]; return Arrays.copyOf(data, length); } @@ -86,4 +86,19 @@ class Hid4JavaHidDevice implements HidDevice { public short getUsagePage() { return (short) myDevice.getUsagePage(); } + + @Override + public int hid_write(byte[] data, int length,byte reportId) { + return myDevice.write(data, length, reportId); + } + + @Override + public int hid_read(byte[] data) { + return myDevice.read(data); + } + + @Override + public int hid_read(byte[] data,int timeoutMillis) { + return myDevice.read(data,timeoutMillis); + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java b/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java index 861e5bd..a9b9ab0 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java @@ -30,27 +30,21 @@ import net.ash.HIDToVPADNetworkClient.hid.HidDevice; import net.ash.HIDToVPADNetworkClient.util.MessageBox; import net.ash.HIDToVPADNetworkClient.util.MessageBoxManager; import net.ash.HIDToVPADNetworkClient.util.Settings; +import net.ash.HIDToVPADNetworkClient.util.Utilities; import purejavahidapi.HidDeviceInfo; import purejavahidapi.InputReportListener; @Log -class PureJavaHidDevice implements HidDevice, InputReportListener { +class PureJavaHidDevice implements HidDevice { private purejavahidapi.HidDevice myDevice = null; private final purejavahidapi.HidDeviceInfo myDeviceInfo; - private final Object dataLock = new Object(); protected byte[] currentData = new byte[0]; public PureJavaHidDevice(HidDeviceInfo info) { this.myDeviceInfo = info; } - @Override - @Synchronized("dataLock") - public void onInputReport(purejavahidapi.HidDevice source, byte reportID, byte[] reportData, int reportLength) { - currentData = Arrays.copyOfRange(reportData, 0, reportLength); - } - @Override public short getVendorId() { return myDeviceInfo.getVendorId(); @@ -68,7 +62,6 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { boolean result = true; try { myDevice = purejavahidapi.PureJavaHidApi.openDevice(myDeviceInfo); - myDevice.setInputReportListener(this); } catch (IOException e) { result = false; if (e.getMessage().contains("errno 13") && Settings.isLinux()) { @@ -83,6 +76,8 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { e.printStackTrace(); } } + + return result; } @@ -91,10 +86,12 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { myDevice.close(); } + private byte[] data = new byte[64]; @Override - @Synchronized("dataLock") - public byte[] getLatestData() { - return currentData.clone(); + public byte[] getLatestData() { + int length = hid_read(data); + if (length <= 0) return new byte[0]; + return Arrays.copyOf(data, length); } @Override @@ -114,7 +111,9 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { @Override public String getProductString() { - return myDeviceInfo.getProductString().trim(); + String result = myDeviceInfo.getProductString(); + if(result != null) result = result.trim(); + return result; } @Override @@ -122,4 +121,27 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { return "PureJavaHidDevice [vid= " + String.format("%04X", getVendorId()) + ", pid= " + String.format("%04X", getProductId()) + ", path= " + getPath().trim() + ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID()) + ", data=" + Arrays.toString(currentData) + "]"; } + + @Override + public int hid_write(byte[] data, int length, byte reportID){ + try{ + return myDevice.setOutputReport(reportID, data, length); + }catch(IllegalStateException e){ + return -1; + } + } + + @Override + public int hid_read(byte[] data) { + return hid_read(data,0); + } + + @Override + public int hid_read(byte[] data, int timeoutMillis) { + try{ + return myDevice.getInputReport(data,timeoutMillis); + }catch(IllegalStateException e){ + return -1; + } + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java b/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java index e57cd4a..a3a0a89 100644 --- a/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java @@ -166,6 +166,5 @@ public final class ActiveControllerManager implements Runnable { } } return null; - } - + } }