From 70b5da2206080fe518c28543fb30647ef84ccbf8 Mon Sep 17 00:00:00 2001 From: Maschell Date: Thu, 13 Apr 2017 15:32:55 +0200 Subject: [PATCH] Added Rumble support (currently XInput only), changed protocol version, code cleanup Updated the code conventions.xml! --- eclipse_code_convention.xml | 2 +- src/de/mas/HIDTest/AutoRunFromConsole.java | 133 ++++++++---------- src/de/mas/HIDTest/Main.java | 29 ++-- .../controller/HidController.java | 2 +- .../controller/XInput13Controller.java | 2 +- .../controller/XInput14Controller.java | 2 +- .../controller/XInputController.java | 12 +- .../gui/GuiOptionsWindow.java | 89 ++++++------ .../HIDToVPADNetworkClient/hid/HidDevice.java | 4 +- .../hid/HidManager.java | 13 +- .../hid/hid4java/Hid4JavaHidDevice.java | 7 +- .../hid/purejavahid/PureJavaHidDevice.java | 9 +- .../manager/ControllerManager.java | 14 +- .../network/NetworkManager.java | 1 + .../network/Protocol.java | 51 ++++++- .../network/UDPServer.java | 66 +++++++++ .../HIDToVPADNetworkClient/util/Settings.java | 42 +++--- .../util/StatusReport.java | 12 +- .../util/Utilities.java | 16 +-- 19 files changed, 315 insertions(+), 191 deletions(-) create mode 100644 src/net/ash/HIDToVPADNetworkClient/network/UDPServer.java diff --git a/eclipse_code_convention.xml b/eclipse_code_convention.xml index 9910e50..35b8bea 100644 --- a/eclipse_code_convention.xml +++ b/eclipse_code_convention.xml @@ -26,7 +26,7 @@ - + diff --git a/src/de/mas/HIDTest/AutoRunFromConsole.java b/src/de/mas/HIDTest/AutoRunFromConsole.java index 8740c81..523e769 100644 --- a/src/de/mas/HIDTest/AutoRunFromConsole.java +++ b/src/de/mas/HIDTest/AutoRunFromConsole.java @@ -11,36 +11,32 @@ import javax.swing.JOptionPane; *

* v[(2), 2015-11-13 13:00 UTC] *

- * One static method call will start a new instance of *THIS* application in the console and will EXIT the current - * instance. SO FAR ONLY WORKS ON WINDOWS! Users of other systems need to assist here. The methods are all in place. + * One static method call will start a new instance of *THIS* application in the console and will EXIT the current instance. SO FAR ONLY WORKS ON WINDOWS! Users + * of other systems need to assist here. The methods are all in place. */ final public class AutoRunFromConsole { - final private static String FAILMESSAGE_TITLE = "Please run in console."; final private static String FAILMESSAGE_BODY = "This application must be run in the console (or \"command box\").\n\nIn there, you have to type:\n\njava -jar nameofprogram.jar"; - static void showFailMessageAndExit() { JOptionPane.showMessageDialog(null, FAILMESSAGE_BODY, FAILMESSAGE_TITLE, JOptionPane.INFORMATION_MESSAGE); System.exit(0); } - private enum OSType { UNDETERMINED, WINDOWS, LINUX, MACOS } - private static OSType getOsType() { - // final String osName = System.getProperty("os.name"); - // final String osVersion = System.getProperty("os.version"); - // final String osArchitecture = System.getProperty("os.arch"); - // System.out.println("\n\nOSNAME: " + osName); - // System.out.println("\n\nOSVERSION: " + osVersion); - // System.out.println("\n\nOSARCHITECTURE: " + osArchitecture); + // final String osName = System.getProperty("os.name"); + // final String osVersion = System.getProperty("os.version"); + // final String osArchitecture = System.getProperty("os.arch"); + // System.out.println("\n\nOSNAME: " + osName); + // System.out.println("\n\nOSVERSION: " + osVersion); + // System.out.println("\n\nOSARCHITECTURE: " + osArchitecture); final String osName = System.getProperty("os.name", "").toLowerCase(); if (osName.startsWith("windows")) { @@ -54,60 +50,55 @@ final public class AutoRunFromConsole { return OSType.UNDETERMINED; } - /** - * Checks if the program is currently running in console, and if not, starts the program from console and EXITS this - * instance of the program. Should be (one of) the first calls in your program. + * Checks if the program is currently running in console, and if not, starts the program from console and EXITS this instance of the program. Should be (one + * of) the first calls in your program. *

- * This is the less safe variant of the method: To check if you're currently in the IDE, it just tries to find the - * executable name and if it exists in the current path. This should word perfectly at all times in IntelliJ - I - * don't know what values getExecutableName() returns inside Eclipse, but I suspect it will work just as well. + * This is the less safe variant of the method: To check if you're currently in the IDE, it just tries to find the executable name and if it exists in the + * current path. This should word perfectly at all times in IntelliJ - I don't know what values getExecutableName() returns inside Eclipse, but I suspect it + * will work just as well. *

- * It's also less safe because you can't give a fallback executable name, but I believe it should do the trick in - * all situations. + * It's also less safe because you can't give a fallback executable name, but I believe it should do the trick in all situations. *

- * If this is used on a system other than Windows, a message box is shown telling the user to start the program from - * the console. BECAUSE I DON'T KNOW HOW TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole(); + * If this is used on a system other than Windows, a message box is shown telling the user to start the program from the console. BECAUSE I DON'T KNOW HOW + * TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole(); */ public static void runYourselfInConsole(final boolean stayOpenAfterEnd) { runYourselfInConsole(false, stayOpenAfterEnd, null, null); } - /** - * Checks if the program is currently running in console, and if not, starts the program from console and EXITS this - * instance of the program. Should be (one of) the first calls in your program. + * Checks if the program is currently running in console, and if not, starts the program from console and EXITS this instance of the program. Should be (one + * of) the first calls in your program. *

- * This is the safer variant of the method: The first command line argument GIVEN BY THE IDE'S RUN CONFIGURATION - * should be "ide" (Case is ignored.), which this method will use to determine if it's running from the IDE. + * This is the safer variant of the method: The first command line argument GIVEN BY THE IDE'S RUN CONFIGURATION should be "ide" (Case is ignored.), which + * this method will use to determine if it's running from the IDE. *

- * It is also safer because you can give a fallback executable name in case getExecutableName() could not determine - * it. + * It is also safer because you can give a fallback executable name in case getExecutableName() could not determine it. *

- * Ultimately, it is safer because if the executable could not be determined, it shows a message box telling the - * user to start the program from the console. + * Ultimately, it is safer because if the executable could not be determined, it shows a message box telling the user to start the program from the console. *

- * You will probably never make use of this variant. It's meant to be a solution if all else seems to fail (e.g. - * customer calls and you need a quick fix). + * You will probably never make use of this variant. It's meant to be a solution if all else seems to fail (e.g. customer calls and you need a quick fix). *

- * If this is used on a system other than Windows, a message box is shown telling the user to start the program from - * the console. BECAUSE I DON'T KNOW HOW TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole(); + * If this is used on a system other than Windows, a message box is shown telling the user to start the program from the console. BECAUSE I DON'T KNOW HOW + * TO OPEN A CONSOLE ON OTHER SYSTEMS. SEE startExecutableInConsole(); * - * @param psvmArguments The arguments given to the main method. - * @param fallbackExecutableName Can be null. In case getExecutableName() can't determine the proper name, the - * fallback is used. + * @param psvmArguments + * The arguments given to the main method. + * @param fallbackExecutableName + * Can be null. In case getExecutableName() can't determine the proper name, the fallback is used. */ public static void runYourselfInConsole(final String[] psvmArguments, final String fallbackExecutableName, final boolean stayOpenAfterEnd) { runYourselfInConsole(true, stayOpenAfterEnd, psvmArguments, fallbackExecutableName); } - /** * see the other two methods */ - private static void runYourselfInConsole(final boolean useSaferApproach, final boolean stayOpenAfterEnd, final String[] psvmArguments, final String fallbackExecutableName) { + private static void runYourselfInConsole(final boolean useSaferApproach, final boolean stayOpenAfterEnd, final String[] psvmArguments, + final String fallbackExecutableName) { String executableName = getExecutableName(fallbackExecutableName); @@ -135,18 +126,18 @@ final public class AutoRunFromConsole { System.exit(0); } - /** * Opens a console window and starts the Java executable there. *

- * If this is used on a system other than Windows, a message box is shown telling the user to start the program from - * the console. BECAUSE I DON'T KNOW HOW TO OPEN A CONSOLE ON OTHER SYSTEMS. + * If this is used on a system other than Windows, a message box is shown telling the user to start the program from the console. BECAUSE I DON'T KNOW HOW + * TO OPEN A CONSOLE ON OTHER SYSTEMS. * - * @param executableName the full file name of the executable (without path) - * @param stayOpenAfterEnd If true (and if someone can figure out the necessary parameters for other systems than - * Windows), the console will not close once the executable has terminated. This is useful - * e.g. if you want to give some kind of bye bye message because you actually assumed that - * people start the program from console manually. + * @param executableName + * the full file name of the executable (without path) + * @param stayOpenAfterEnd + * If true (and if someone can figure out the necessary parameters for other systems than Windows), the console will not close once the + * executable has terminated. This is useful e.g. if you want to give some kind of bye bye message because you actually assumed that people start + * the program from console manually. */ private static void startExecutableInConsole(final String executableName, final boolean stayOpenAfterEnd) { @@ -157,9 +148,9 @@ final public class AutoRunFromConsole { break; case WINDOWS: if (stayOpenAfterEnd) { - launchString = "cmd /c start cmd /k java -jar \"" + executableName+"\""; // No, using /k directly here DOES NOT do the trick. + launchString = "cmd /c start cmd /k java -jar \"" + executableName + "\""; // No, using /k directly here DOES NOT do the trick. } else { - launchString = "cmd /c start java -jar \"" + executableName+"\""; + launchString = "cmd /c start java -jar \"" + executableName + "\""; } break; case LINUX: @@ -180,35 +171,31 @@ final public class AutoRunFromConsole { } } - /** - * @param args the args as given to PSVM - * @return whether the first command line argument was "ide" (ignoring case). Don't forget to change your IDE's run - * configuration accordingly. + * @param args + * the args as given to PSVM + * @return whether the first command line argument was "ide" (ignoring case). Don't forget to change your IDE's run configuration accordingly. */ private static boolean isRunFromIDE(final String[] args) { return args != null && args.length > 0 && args[0].equalsIgnoreCase("ide"); } - /** - * @return if System.console() is available. DOES NOT WORK properly from IDE, will return false then even though it - * should be true. Use isRunFromIDE or other means additionally. + * @return if System.console() is available. DOES NOT WORK properly from IDE, will return false then even though it should be true. Use isRunFromIDE or + * other means additionally. */ private static boolean isRunningInConsole() { return System.console() != null; } - /** - * @param fallbackExecutableName Can be null. In the very unlikely case this method can't determine the executable, - * the fallback will also be checked. But if the fallback also doesn't exist AS A FILE - * in the CURRENT path, null will be returned regardless, even if you're sure that - * your fallback should be correct. - * @return the name of the running jar file, OR NULL if it could not be determined (which should be a certainty - * while in IDE, hence can be abused for determining that). + * @param fallbackExecutableName + * Can be null. In the very unlikely case this method can't determine the executable, the fallback will also be checked. But if the fallback also + * doesn't exist AS A FILE in the CURRENT path, null will be returned regardless, even if you're sure that your fallback should be correct. + * @return the name of the running jar file, OR NULL if it could not be determined (which should be a certainty while in IDE, hence can be abused for + * determining that). */ public static String getExecutableName(final String fallbackExecutableName) { @@ -228,18 +215,15 @@ final public class AutoRunFromConsole { } } - // APPROACH 2 - QUERY SYSTEM PROPERTIES final Properties properties = System.getProperties(); final String executableNameFromJavaClassPathProperty = properties.getProperty("java.class.path"); final String executableNameFromSunJavaCommandProperty = properties.getProperty("sun.java.command"); - - // System.out.println("\n\nexecutableNameFromClass:\n" + executableNameFromClass); - // System.out.println("\n\nexecutableNameFromJavaClassPathProperty:\n" + executableNameFromJavaClassPathProperty); - // System.out.println("\n\nexecutableNameFromSunJavaCommandProperty:\n" + executableNameFromSunJavaCommandProperty); - // System.out.println("\n\nfallbackExecutableName:\n" + fallbackExecutableName); - + // System.out.println("\n\nexecutableNameFromClass:\n" + executableNameFromClass); + // System.out.println("\n\nexecutableNameFromJavaClassPathProperty:\n" + executableNameFromJavaClassPathProperty); + // System.out.println("\n\nexecutableNameFromSunJavaCommandProperty:\n" + executableNameFromSunJavaCommandProperty); + // System.out.println("\n\nfallbackExecutableName:\n" + fallbackExecutableName); if (isThisProbablyTheExecutable(executableNameFromClass)) { return executableNameFromClass; @@ -260,11 +244,10 @@ final public class AutoRunFromConsole { return null; } - /** - * @param candidateName suspected name of the running java executable - * @return if name is not null, ends with ".jar" (Case is ignored.), and points to a FILE existing in the CURRENT - * directory. + * @param candidateName + * suspected name of the running java executable + * @return if name is not null, ends with ".jar" (Case is ignored.), and points to a FILE existing in the CURRENT directory. */ private static boolean isThisProbablyTheExecutable(final String candidateName) { diff --git a/src/de/mas/HIDTest/Main.java b/src/de/mas/HIDTest/Main.java index 46c3761..14f936a 100644 --- a/src/de/mas/HIDTest/Main.java +++ b/src/de/mas/HIDTest/Main.java @@ -9,9 +9,9 @@ public class Main { public static void main(String[] args) { AutoRunFromConsole.runYourselfInConsole(true); - + Settings.AUTO_ACTIVATE_CONTROLLER = false; - + new Thread(new Runnable() { @Override public void run() { @@ -21,31 +21,32 @@ public class Main { } } }).start(); - + System.out.println("Scanning for controllers..."); - + Utilities.sleep(1000); - - for(Controller c : ControllerManager.getAttachedControllers()){ + + for (Controller c : ControllerManager.getAttachedControllers()) { c.setActive(true); } - + Utilities.sleep(1000); - + Settings.AUTO_ACTIVATE_CONTROLLER = true; - + new Thread(new Runnable() { @Override public void run() { while (true) { - boolean attached = false; - for(Controller c : ControllerManager.getAttachedControllers()){ - if(c.isActive()){ + boolean attached = false; + for (Controller c : ControllerManager.getAttachedControllers()) { + if (c.isActive()) { attached = true; - System.out.print(String.format("VID: %04X PID %04X", c.getVID(),c.getPID()) +" data: " + Utilities.ByteArrayToString(c.getLatestData()) + " | " ); + System.out.print(String.format("VID: %04X PID %04X", c.getVID(), c.getPID()) + " data: " + + Utilities.ByteArrayToString(c.getLatestData()) + " | "); } } - if(attached) System.out.print("\r"); + if (attached) System.out.print("\r"); Utilities.sleep(15); } } diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java b/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java index 6bc5167..b3ccf52 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/HidController.java @@ -120,6 +120,6 @@ public class HidController extends Controller { } String name = getHidDevice().getProductString(); - return String.format("%s (0x%04X:0x%04X) on %s",(name != null) ? name: "USB HID", getVID(),getPID(),getIdentifier()); + return String.format("%s (0x%04X:0x%04X) on %s", (name != null) ? name : "USB HID", getVID(), getPID(), getIdentifier()); } } \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java index 1b8026c..e69cd17 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java @@ -30,6 +30,6 @@ public class XInput13Controller extends XInputController { @Override public String getInfoText() { - return String.format("XInput 1.3 (0x%04X:0x%04X) on ", getVID(),getPID()) + getIdentifier(); + return String.format("XInput 1.3 (0x%04X:0x%04X) on ", getVID(), getPID()) + getIdentifier(); } } diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java index cf3dd45..090d5dd 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java @@ -30,6 +30,6 @@ public class XInput14Controller extends XInputController { @Override public String getInfoText() { - return String.format("XInput 1.4 (0x%04X:0x%04X) on ", getVID(),getPID()) + getIdentifier(); + return String.format("XInput 1.4 (0x%04X:0x%04X) on ", getVID(), getPID()) + getIdentifier(); } } diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java index 3863888..5a23b56 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java @@ -35,6 +35,7 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.java.Log; import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import net.ash.HIDToVPADNetworkClient.util.Settings; import net.ash.HIDToVPADNetworkClient.util.Utilities; @Log @@ -116,6 +117,15 @@ public class XInputController extends Controller { buttonState |= axesDataShoulderButtons << 16; data.putInt(axesData).putInt(buttonState); + if (isRumble()) { + int strength = Settings.RUMBLE_STRENGTH; + if (strength < 0) strength = 0; + if (strength > 100) strength = 100; + int value = (int) (65535 * (Settings.RUMBLE_STRENGTH / 100.0)); + device.setVibration(value, value); + } else { + device.setVibration(0, 0); + } return (data.array()); } return new byte[0]; @@ -139,6 +149,6 @@ public class XInputController extends Controller { @Override public String getInfoText() { - return String.format("XInput (0x%04X:%0x04X) on ", getVID(),getPID()) + getIdentifier(); + return String.format("XInput (0x%04X:%0x04X) on ", getVID(), getPID()) + getIdentifier(); } } diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiOptionsWindow.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiOptionsWindow.java index a3efaf6..82b6042 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiOptionsWindow.java +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiOptionsWindow.java @@ -54,71 +54,71 @@ import net.ash.HIDToVPADNetworkClient.util.StatusReport; public class GuiOptionsWindow extends JPanel { private static final long serialVersionUID = 1L; private static final GuiOptionsWindow instance = new GuiOptionsWindow(); - + private final List tabs = new ArrayList(); - + public static void showWindow() { showWindow(null); } - + public static void showWindow(Component parent) { instance.setOpaque(true); for (Tab t : instance.tabs) { t.updateTab(); } - + JFrame window = new JFrame("Options"); window.setContentPane(instance); window.pack(); window.setLocationRelativeTo(parent); window.setVisible(true); } - + private GuiOptionsWindow() { super(new GridLayout(1, 1)); - + log.info("Hello from the Options window!"); - + setPreferredSize(new Dimension(600, 200)); - + JTabbedPane tabPane = new JTabbedPane(); tabPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - + Tab controllerTab = new ControllerTab(); tabs.add(controllerTab); tabPane.addTab("Controllers", controllerTab); - + Tab infoTab = new InfoTab(); tabs.add(infoTab); tabPane.addTab("Info", infoTab); - + add(tabPane); } private class ControllerTab extends Tab { private static final long serialVersionUID = 1L; - + private final ControllerFilteringList cFilterList; private final JCheckBox cBoxScanForControllers; private final JCheckBox cBoxAutoActivateControllers; - + private ControllerTab() { super(new GridLayout(1, 2)); - + cFilterList = new ControllerFilteringList(); - + for (Settings.ControllerFiltering.Type type : Settings.ControllerFiltering.Type.values()) { if (!type.isSupportedOnPlatform()) continue; ControllerFilteringListItem item = new ControllerFilteringListItem(type); cFilterList.add(item); } - + add(cFilterList); - + JPanel rightSideControls = new JPanel(); rightSideControls.setLayout(new BoxLayout(rightSideControls, BoxLayout.PAGE_AXIS)); rightSideControls.add(Box.createVerticalGlue()); - + cBoxScanForControllers = new JCheckBox("Automatically scan for controllers"); cBoxScanForControllers.setAlignmentX(Component.CENTER_ALIGNMENT); cBoxScanForControllers.addActionListener(new ActionListener() { @@ -128,9 +128,9 @@ public class GuiOptionsWindow extends JPanel { } }); rightSideControls.add(cBoxScanForControllers); - + rightSideControls.add(Box.createVerticalStrut(2)); - + cBoxAutoActivateControllers = new JCheckBox("Automatically activate controllers"); cBoxAutoActivateControllers.setAlignmentX(Component.CENTER_ALIGNMENT); cBoxAutoActivateControllers.addActionListener(new ActionListener() { @@ -140,11 +140,11 @@ public class GuiOptionsWindow extends JPanel { } }); rightSideControls.add(cBoxAutoActivateControllers); - + rightSideControls.add(Box.createVerticalGlue()); add(rightSideControls); } - + @Override public void updateTab() { for (ControllerFilteringListItem c : cFilterList.items) { @@ -153,45 +153,45 @@ public class GuiOptionsWindow extends JPanel { cBoxScanForControllers.setSelected(Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS); cBoxAutoActivateControllers.setSelected(Settings.AUTO_ACTIVATE_CONTROLLER); } - + private class ControllerFilteringList extends JPanel { private static final long serialVersionUID = 1L; - + private List items = new ArrayList(); private JPanel innerPanel; - + private ControllerFilteringList() { super(new BorderLayout()); setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 0)); - + innerPanel = new JPanel(); innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.PAGE_AXIS)); - + JScrollPane innerPanelWrap = new JScrollPane(innerPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); innerPanelWrap.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); add(innerPanelWrap, BorderLayout.CENTER); - + JLabel controllerFilterText = new JLabel("Controllers to show:"); controllerFilterText.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); add(controllerFilterText, BorderLayout.PAGE_START); } - + public Component add(ControllerFilteringListItem c) { items.add(c); return innerPanel.add(c); } } - + private class ControllerFilteringListItem extends JPanel { private static final long serialVersionUID = 1L; - + private final JCheckBox cBox; private final Settings.ControllerFiltering.Type type; - + private ControllerFilteringListItem(Settings.ControllerFiltering.Type typeIn) { super(new GridLayout(1, 1)); this.type = typeIn; - + cBox = new JCheckBox(type.getName()); cBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); cBox.addActionListener(new ActionListener() { @@ -202,38 +202,38 @@ public class GuiOptionsWindow extends JPanel { }); add(cBox); } - + public void updateItem() { cBox.setSelected(Settings.ControllerFiltering.getFilterState(type)); } - - //I can't believe I didn't figure this out for GuiControllerList + + // I can't believe I didn't figure this out for GuiControllerList @Override public Dimension getMaximumSize() { return new Dimension(Integer.MAX_VALUE, getPreferredSize().height); } } } - + private class InfoTab extends Tab { private static final long serialVersionUID = 1L; - + private final JTextArea infoText; private final JScrollPane infoTextWrap; - + private InfoTab() { super(); setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5)); - + infoText = new JTextArea(); infoText.setEditable(false); infoTextWrap = new JScrollPane(infoText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); infoTextWrap.setAlignmentX(Component.CENTER_ALIGNMENT); add(infoTextWrap); - + add(Box.createVerticalStrut(10)); - + JButton copyButton = new JButton("Copy"); copyButton.addActionListener(new ActionListener() { @Override @@ -253,13 +253,16 @@ public class GuiOptionsWindow extends JPanel { infoText.setCaretPosition(0); } } - + private abstract class Tab extends JPanel { private static final long serialVersionUID = 1L; + public abstract void updateTab(); + public Tab(LayoutManager l) { super(l); } + public Tab() { super(); } diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java b/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java index 2e33db2..45cd56b 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/HidDevice.java @@ -62,7 +62,7 @@ public interface HidDevice { * @return usage page */ short getUsagePage(); - + /** * Retuns the Usage ID of this HID-Device * @@ -76,7 +76,7 @@ public interface HidDevice { * @return path */ String getPath(); - + /** * Returns the name of the HID device * diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java b/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java index 35795b9..c02efef 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/HidManager.java @@ -37,7 +37,7 @@ public class HidManager { public static HidDevice getDeviceByPath(String path) throws IOException { return backend.getDeviceByPath(path); } - + public static List getAttachedControllers() { List connectedGamepads = new ArrayList(); @@ -64,7 +64,7 @@ public class HidManager { } return connectedGamepads; } - + public static List getAllAttachedControllers() { return backend.enumerateDevices(); } @@ -72,15 +72,16 @@ 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)); + return (info.getProductString().toLowerCase().contains("gamepad") || usage == 0x05 || usage == 0x04 || isNintendoController(info) + || isPlaystationController(info)); } - + public static boolean isKeyboard(HidDevice info) { if (info == null) return false; short usage = info.getUsageID(); return (usage == 0x06); } - + public static boolean isMouse(HidDevice info) { if (info == null) return false; short usage = info.getUsageID(); @@ -105,7 +106,7 @@ public class HidManager { public static String getBackendType() { return backend.getClass().getSimpleName(); } - + static { if (Settings.isMacOSX()) { backend = new Hid4JavaHidManagerBackend(); diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java b/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java index 362b3da..8b0f591 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/hid4java/Hid4JavaHidDevice.java @@ -70,17 +70,18 @@ class Hid4JavaHidDevice implements HidDevice { public String getProductString() { return myDevice.getProduct(); } - + @Override public String toString() { - return "Hid4JavaHidDevice [vid= " + getVendorId() + ", pid= " + getProductId() + ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID()) + ", data=" + Arrays.toString(data) + "]"; + return "Hid4JavaHidDevice [vid= " + getVendorId() + ", pid= " + getProductId() + ", usage= " + String.format("%04X:%04X", getUsagePage(), getUsageID()) + + ", data=" + Arrays.toString(data) + "]"; } @Override public short getUsageID() { return (short) myDevice.getUsage(); } - + @Override public short getUsagePage() { return (short) myDevice.getUsagePage(); diff --git a/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java b/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java index 3f4669c..861e5bd 100644 --- a/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java +++ b/src/net/ash/HIDToVPADNetworkClient/hid/purejavahid/PureJavaHidDevice.java @@ -62,6 +62,7 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { } private static boolean hasShownUdevErrorMessage = false; + @Override public boolean open() { boolean result = true; @@ -100,7 +101,7 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { public short getUsagePage() { return myDeviceInfo.getUsagePage(); } - + @Override public short getUsageID() { return myDeviceInfo.getUsageID(); @@ -115,10 +116,10 @@ class PureJavaHidDevice implements HidDevice, InputReportListener { public String getProductString() { return myDeviceInfo.getProductString().trim(); } - + @Override public String toString() { - 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) + "]"; + 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) + "]"; } } diff --git a/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java b/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java index 4c11e84..abe0719 100644 --- a/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java @@ -24,6 +24,7 @@ package net.ash.HIDToVPADNetworkClient.manager; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.nio.BufferUnderflowException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -168,7 +169,7 @@ public final class ControllerManager { private static Map detectXInputControllers() { Map result = new HashMap(); if (!Settings.ControllerFiltering.getFilterState(Settings.ControllerFiltering.Type.XINPUT)) return result; - + ControllerType type = ControllerType.XINPUT13; // Try and catch missing C++ redist @@ -195,8 +196,13 @@ public final class ControllerManager { XInputDevice device; try { device = XInputDevice.getDeviceFor(i); - if (device.poll() && device.isConnected()) { // Check if it is this controller is connected - result.put(XInputController.XINPUT_INDENTIFER + i, type); + try { + if (device.poll() && device.isConnected()) { // Check if it is this controller is connected + result.put(XInputController.XINPUT_INDENTIFER + i, type); + } + } catch (BufferUnderflowException e) { + // + log.info("XInput error."); } } catch (XInputNotLoadedException e) { // This shouln't happen? @@ -211,7 +217,7 @@ public final class ControllerManager { private static Map detectLinuxControllers() { Map result = new HashMap(); if (!Settings.ControllerFiltering.getFilterState(Settings.ControllerFiltering.Type.LINUX)) return result; - + File devInput = new File("/dev/input"); if (!devInput.exists()) return result; diff --git a/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java b/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java index b21e5bf..18db519 100644 --- a/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java @@ -74,6 +74,7 @@ public final class NetworkManager implements Runnable { @Override public void run() { int i = 0; + new Thread(UDPServer.getInstance(), "UDP Server").start(); while (true) { proccessCommands(); Utilities.sleep(Settings.PROCESS_CMD_INTERVAL); diff --git a/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java b/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java index f8e90ee..a407a35 100644 --- a/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java @@ -25,24 +25,29 @@ import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.List; import lombok.extern.java.Log; +import net.ash.HIDToVPADNetworkClient.controller.Controller; import net.ash.HIDToVPADNetworkClient.manager.ActiveControllerManager; import net.ash.HIDToVPADNetworkClient.util.MessageBox; import net.ash.HIDToVPADNetworkClient.util.MessageBoxManager; +import net.ash.HIDToVPADNetworkClient.util.Utilities; @Log final class Protocol { - private static ProtocolVersion currentProtocol = ProtocolVersion.MY_VERSION; + public static ProtocolVersion currentProtocol = ProtocolVersion.MY_VERSION; static final int TCP_PORT = 8112; static final int UDP_PORT = 8113; + static final int UDP_CLIENT_PORT = 8114; static final byte TCP_HANDSHAKE_VERSION_1 = 0x12; static final byte TCP_HANDSHAKE_VERSION_2 = 0x13; + static final byte TCP_HANDSHAKE_VERSION_3 = 0x14; static final byte TCP_HANDSHAKE_ABORT = 0x30; - static final byte TCP_HANDSHAKE = TCP_HANDSHAKE_VERSION_2; // default version. + static final byte TCP_HANDSHAKE = TCP_HANDSHAKE_VERSION_3; // default version. static final byte TCP_SAME_CLIENT = 0x20; static final byte TCP_NEW_CLIENT = 0x21; @@ -58,6 +63,7 @@ final class Protocol { static final byte TCP_CMD_ATTACH_CONFIG_NOT_FOUND = (byte) 0xE1; static final byte TCP_CMD_ATTACH_USERDATA_OKAY = (byte) 0xE8; static final byte TCP_CMD_ATTACH_USERDATA_BAD = (byte) 0xE9; + private static boolean showVersionInfo = false; private Protocol() { } @@ -125,6 +131,9 @@ final class Protocol { } else if (wiiuProtocolVersion == ProtocolVersion.Version2) { // We want to do version 2! tcpClient.send(ProtocolVersion.Version2.getVersionByte()); + } else if (wiiuProtocolVersion == ProtocolVersion.Version3) { + // We want to do version 3! + tcpClient.send(ProtocolVersion.Version3.getVersionByte()); } if (wiiuProtocolVersion == ProtocolVersion.Version1) { @@ -151,6 +160,14 @@ final class Protocol { if (resultHandshake == HandshakeReturnCode.GOOD_HANDSHAKE) { ActiveControllerManager.getInstance().attachAllActiveControllers(); log.info("Handshake was successful! Using protocol version " + (currentProtocol.getVersionByte() - TCP_HANDSHAKE_VERSION_1 + 1)); + if (currentProtocol.getVersionByte() != ProtocolVersion.MY_VERSION.versionByte) { + String message = "Using an old network protocol. You may want to update this network client and HIDtoVPAD for the best experience."; + log.info(message); + if (!showVersionInfo) { + MessageBoxManager.addMessageBox(message, MessageBox.MESSAGE_INFO); + showVersionInfo = true; + } + } return resultHandshake; } else { log.info("[TCP] Handshaking failed"); @@ -160,8 +177,12 @@ final class Protocol { } public enum ProtocolVersion { - MY_VERSION((byte) TCP_HANDSHAKE), Version1((byte) TCP_HANDSHAKE_VERSION_1), Version2((byte) TCP_HANDSHAKE_VERSION_2), Abort( - (byte) TCP_HANDSHAKE_ABORT), UNKOWN((byte) 0x00); + Abort((byte) TCP_HANDSHAKE_ABORT), + UNKOWN((byte) 0x00), + Version1((byte) TCP_HANDSHAKE_VERSION_1), + Version2((byte) TCP_HANDSHAKE_VERSION_2), + Version3((byte) TCP_HANDSHAKE_VERSION_3), + MY_VERSION((byte) TCP_HANDSHAKE); private final byte versionByte; private ProtocolVersion(byte versionByte) { @@ -174,6 +195,8 @@ final class Protocol { return ProtocolVersion.Version1; case TCP_HANDSHAKE_VERSION_2: return ProtocolVersion.Version2; + case TCP_HANDSHAKE_VERSION_3: + return ProtocolVersion.Version3; case TCP_HANDSHAKE_ABORT: return ProtocolVersion.Abort; default: @@ -186,4 +209,24 @@ final class Protocol { } } + public static void parseUDPServerData(byte[] data) { + if (currentProtocol.versionByte >= TCP_HANDSHAKE_VERSION_3) { + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.order(ByteOrder.BIG_ENDIAN); + byte cmd = buffer.get(); + if (cmd == 0x01) { + int handle = buffer.getInt(); + byte rumble = buffer.get(); + Controller c = ActiveControllerManager.getInstance().getControllerByHIDHandle(handle); + if (c != null) { + if (rumble == 0x01) { + c.startRumble(); + } else { + c.stopRumble(); + } + } + } + } + } + } diff --git a/src/net/ash/HIDToVPADNetworkClient/network/UDPServer.java b/src/net/ash/HIDToVPADNetworkClient/network/UDPServer.java new file mode 100644 index 0000000..f9f9ccf --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/UDPServer.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2017 Ash (QuarkTheAwesome) & Maschell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ +package net.ash.HIDToVPADNetworkClient.network; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.util.Arrays; + +import lombok.extern.java.Log; + +@Log +public final class UDPServer implements Runnable { + private final DatagramSocket sock; + + private UDPServer() throws SocketException { + sock = new DatagramSocket(Protocol.UDP_CLIENT_PORT); + } + + static UDPServer getInstance() { + UDPServer result = null; + try { + result = new UDPServer(); + } catch (Exception e) { + // handle? + } + return result; + } + + @Override + public void run() { + log.info("UDPServer running."); + byte[] receiveData = new byte[1400]; + while (true) { + DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); + try { + sock.receive(receivePacket); + } catch (IOException e) { + continue; + } + byte[] data = Arrays.copyOf(receivePacket.getData(), receivePacket.getLength()); + Protocol.parseUDPServerData(data); + } + + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/util/Settings.java b/src/net/ash/HIDToVPADNetworkClient/util/Settings.java index bf98d64..7163d12 100644 --- a/src/net/ash/HIDToVPADNetworkClient/util/Settings.java +++ b/src/net/ash/HIDToVPADNetworkClient/util/Settings.java @@ -47,6 +47,8 @@ public final class Settings { public static final int DETECT_CONTROLLER_ACTIVE_INTERVAL = 100; + public static final int RUMBLE_STRENGTH = 50; // in % TODO: Create setting for this. + public static boolean SCAN_AUTOMATICALLY_FOR_CONTROLLERS = true; public static boolean DEBUG_UDP_OUTPUT = false; @@ -125,7 +127,7 @@ public final class Settings { } else { ControllerFiltering.setDefaultFilterStates(); } - + log.info("Loaded config successfully!"); } @@ -149,7 +151,7 @@ public final class Settings { prop.setProperty("sendDataOnlyOnChanges", Boolean.toString(Settings.SEND_DATA_ONLY_ON_CHANGE)); prop.setProperty("scanAutomaticallyForControllers", Boolean.toString(Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS)); prop.setProperty("filterStates", ControllerFiltering.getFilterStates()); - + try { FileOutputStream outStream = new FileOutputStream(configFile); prop.store(outStream, "HIDToVPADNetworkClient"); @@ -197,59 +199,65 @@ public final class Settings { } public enum Platform { - LINUX (0x1), WINDOWS (0x2), MAC_OS_X (0x4), UNKNOWN (0x8); - + LINUX(0x1), WINDOWS(0x2), MAC_OS_X(0x4), UNKNOWN(0x8); + private int mask; + private Platform(int mask) { this.mask = mask; } } - - //TODO rename this to something less nonsensical + + // TODO rename this to something less nonsensical public static class ControllerFiltering { public static enum Type { - HIDGAMEPAD (0, "HID Gamepads", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask), - XINPUT (5, "XInput controllers", Platform.WINDOWS.mask), - HIDKEYBOARD (1, "HID Keyboards", Platform.LINUX.mask | Platform.MAC_OS_X.mask), - HIDMOUSE (2, "HID Mice", Platform.LINUX.mask), - HIDOTHER (3, "Other HIDs", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask), - LINUX (4, "Linux controllers", Platform.LINUX.mask), - ; - + HIDGAMEPAD(0, "HID Gamepads", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask), + XINPUT(5, "XInput controllers", Platform.WINDOWS.mask), + HIDKEYBOARD(1, "HID Keyboards", Platform.LINUX.mask | Platform.MAC_OS_X.mask), + HIDMOUSE(2, "HID Mice", Platform.LINUX.mask), + HIDOTHER(3, "Other HIDs", Platform.LINUX.mask | Platform.WINDOWS.mask | Platform.MAC_OS_X.mask), + LINUX(4, "Linux controllers", Platform.LINUX.mask),; + private int index; @Getter private String name; private int platforms; + private Type(int index, String name, int platforms) { this.index = index; this.name = name; this.platforms = platforms; } + public boolean isSupportedOnPlatform() { return (platforms & getPlattform().mask) != 0; } } - + private static boolean[] filterStates = new boolean[Type.values().length]; + public static String getFilterStates() { return Arrays.toString(filterStates); } + public static void loadFilterStates(String newFilterStates) { boolean[] newFilterStatesParsed = Utilities.stringToBoolArray(newFilterStates); if (newFilterStatesParsed.length != filterStates.length) { - //TODO handle changes in filtering more gracefully + // TODO handle changes in filtering more gracefully log.warning("Number of controller filters in config does not match reality, using defaults..."); setDefaultFilterStates(); } else { filterStates = newFilterStatesParsed; } } - + public static void setFilterState(Type filter, boolean state) { filterStates[filter.index] = state; } + public static boolean getFilterState(Type filter) { return filterStates[filter.index]; } + public static void setDefaultFilterStates() { filterStates[Type.HIDGAMEPAD.index] = true; filterStates[Type.HIDKEYBOARD.index] = false; diff --git a/src/net/ash/HIDToVPADNetworkClient/util/StatusReport.java b/src/net/ash/HIDToVPADNetworkClient/util/StatusReport.java index fa6a735..51313ce 100644 --- a/src/net/ash/HIDToVPADNetworkClient/util/StatusReport.java +++ b/src/net/ash/HIDToVPADNetworkClient/util/StatusReport.java @@ -11,30 +11,30 @@ public class StatusReport { StringBuilder report = new StringBuilder(); report.append("HID to VPAD Network Client\n\nRunning on "); report.append(Settings.getPlattform()); - + report.append(System.lineSeparator()).append("HID Backend: "); report.append(HidManager.getBackendType()); - + report.append(System.lineSeparator()).append("Currently "); report.append((NetworkManager.getInstance().isConnected()) ? "Connected.\n" : "Disconnected.").append(System.lineSeparator()); report.append((NetworkManager.getInstance().isReconnecting()) ? "" : "Not "); report.append("Reconnecting."); - + report.append(System.lineSeparator()).append(System.lineSeparator()).append("Currently attached controllers:"); for (Controller c : ControllerManager.getAttachedControllers()) { report.append(System.lineSeparator()); report.append(c.toString()); } - + report.append(System.lineSeparator()).append(System.lineSeparator()).append("Filtering settings:").append(System.lineSeparator()); report.append(Settings.ControllerFiltering.getFilterStates()); - + report.append(System.lineSeparator()).append(System.lineSeparator()).append("All HIDs:"); for (HidDevice d : HidManager.getAllAttachedControllers()) { report.append(System.lineSeparator()); report.append(d.toString()); } - + return report.toString(); } } diff --git a/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java b/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java index abf5a79..edb28cc 100644 --- a/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java +++ b/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java @@ -80,10 +80,10 @@ public final class Utilities { public static short signedShortToByte(short value) { return signedShortToByte((int) value); } - + /** - * Arrays.toString(boolean[]) in reverse. - * https://stackoverflow.com/questions/456367/ + * Arrays.toString(boolean[]) in reverse. https://stackoverflow.com/questions/456367/ + * * @param string * @return array */ @@ -91,20 +91,20 @@ public final class Utilities { String[] strings = string.replace("[", "").replace("]", "").split(", "); boolean result[] = new boolean[strings.length]; for (int i = 0; i < result.length; i++) { - result[i] = Boolean.parseBoolean(strings[i]); + result[i] = Boolean.parseBoolean(strings[i]); } return result; } - - public static String getStringFromInputStream(InputStream is) throws IOException{ + + public static String getStringFromInputStream(InputStream is) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; - + while ((length = is.read(buffer)) != -1) { baos.write(buffer, 0, length); } - + return baos.toString("UTF-8"); } }