diff --git a/pom.xml b/pom.xml index 2d99230..b7537f4 100644 --- a/pom.xml +++ b/pom.xml @@ -60,27 +60,47 @@ maven-assembly-plugin - - - - net.ash.HIDToVPADNetworkClient.Main - - - - jar-with-dependencies - - - ${jar_dir} - - make-assembly + + + + net.ash.HIDToVPADNetworkClient.Main + + + + jar-with-dependencies + + + ${jar_dir} + HIDToVPADNetworkClient + package attached + + make-HIDTest + + + + de.mas.HIDTest.Main + + + + jar-with-dependencies + + + ${jar_dir} + HIDTest + + package + + attached + + diff --git a/src/de/mas/HIDTest/AutoRunFromConsole.java b/src/de/mas/HIDTest/AutoRunFromConsole.java new file mode 100644 index 0000000..8740c81 --- /dev/null +++ b/src/de/mas/HIDTest/AutoRunFromConsole.java @@ -0,0 +1,278 @@ +package de.mas.HIDTest; + +import java.io.File; +import java.io.IOException; +import java.security.CodeSource; +import java.util.Properties; +import javax.swing.JOptionPane; + +/** + * Created by Reddit user king_of_the_universe / StackOverflow user Dreamspace President / dreamspace-president.com + *

+ * 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. + */ +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", "").toLowerCase(); + if (osName.startsWith("windows")) { + return OSType.WINDOWS; + } else if (osName.startsWith("linux")) { + return OSType.LINUX; + } else if (osName.startsWith("mac os") || osName.startsWith("macos") || osName.startsWith("darwin")) { + return OSType.MACOS; + } + + 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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). + *

+ * 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. + */ + 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) { + + String executableName = getExecutableName(fallbackExecutableName); + + if (useSaferApproach) { + if (isRunFromIDE(psvmArguments)) { + return; + } + } else { + if (executableName == null) { + // Running from IDE. + return; + } + } + + if (isRunningInConsole()) { + return; + } + + if (executableName == null) { + showFailMessageAndExit(); + } + + startExecutableInConsole(executableName, stayOpenAfterEnd); + + 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. + * + * @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) { + + String launchString = null; + + switch (getOsType()) { + case UNDETERMINED: + break; + case WINDOWS: + if (stayOpenAfterEnd) { + 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+"\""; + } + break; + case LINUX: + break; + case MACOS: + // launchString="/usr/bin/open -a Terminal /path/to/the/executable"; + break; + } + + if (launchString == null) { + showFailMessageAndExit(); + } + + try { + Runtime.getRuntime().exec(launchString); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /** + * @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. + */ + 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). + */ + public static String getExecutableName(final String fallbackExecutableName) { + + // APPROACH 1 - THE ONE EVERYBODY ON STACKOVERFLOW IS REPEATING + String executableNameFromClass = null; + final CodeSource codeSource = AutoRunFromConsole.class.getProtectionDomain().getCodeSource(); + if (codeSource == null) { + System.err.println("UNEXPECTED: Main.class.getProtectionDomain().getCodeSource() returned null"); + } else { + final String path = codeSource.getLocation().getPath(); + if (path == null || path.isEmpty()) { + System.err.println("UNEXPECTED: codeSource.getLocation().getPath() returned null or empty"); + } else { + + executableNameFromClass = new File(path).getName(); + + } + } + + + // 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); + + + if (isThisProbablyTheExecutable(executableNameFromClass)) { + return executableNameFromClass; + } + + if (isThisProbablyTheExecutable(executableNameFromJavaClassPathProperty)) { + return executableNameFromJavaClassPathProperty; + } + + if (isThisProbablyTheExecutable(executableNameFromSunJavaCommandProperty)) { + return executableNameFromSunJavaCommandProperty; + } + + if (isThisProbablyTheExecutable(fallbackExecutableName)) { + return fallbackExecutableName; + } + + 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. + */ + private static boolean isThisProbablyTheExecutable(final String candidateName) { + + if (candidateName == null || !candidateName.toLowerCase().endsWith(".jar")) { + return false; + } + + final File file = new File(candidateName); + return file.exists() && file.isFile(); + } +} \ No newline at end of file diff --git a/src/de/mas/HIDTest/Main.java b/src/de/mas/HIDTest/Main.java new file mode 100644 index 0000000..46c3761 --- /dev/null +++ b/src/de/mas/HIDTest/Main.java @@ -0,0 +1,54 @@ +package de.mas.HIDTest; + +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.manager.ControllerManager; +import net.ash.HIDToVPADNetworkClient.util.Settings; +import net.ash.HIDToVPADNetworkClient.util.Utilities; + +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() { + while (true) { + ControllerManager.detectControllers(); + Utilities.sleep(Settings.DETECT_CONTROLLER_INTERVAL); + } + } + }).start(); + + System.out.println("Scanning for controllers..."); + + Utilities.sleep(1000); + + 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()){ + attached = true; + System.out.print(String.format("VID: %04X PID %04X", c.getVID(),c.getPID()) +" data: " + Utilities.ByteArrayToString(c.getLatestData()) + " | " ); + } + } + if(attached) System.out.print("\r"); + Utilities.sleep(15); + } + } + }).start(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java b/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java index ffb138e..3ed078c 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java @@ -35,6 +35,9 @@ public class SwitchProController extends PureJavaHidController { @Override public byte[] 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; diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java index a61b06a..4294597 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java @@ -21,6 +21,7 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.controller; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import com.ivan.xinput.XInputAxes; @@ -32,9 +33,11 @@ import com.ivan.xinput.exceptions.XInputNotLoadedException; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import lombok.extern.java.Log; import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; import net.ash.HIDToVPADNetworkClient.util.Utilities; +@Log public class XInputController extends Controller { // the pad number will be appended to this String. public static final String XINPUT_INDENTIFER = "\\\\?\\XINPUT\\"; @@ -52,7 +55,7 @@ public class XInputController extends Controller { try { device = XInputDevice.getDeviceFor(pad); } catch (XInputNotLoadedException e) { - // TODO: Log? + e.printStackTrace(); } if (device == null) return false; setDevice(device); @@ -61,7 +64,15 @@ public class XInputController extends Controller { @Override public byte[] pollLatestData() { - if (device.poll()) { + boolean newData = false; + try{ + newData = device.poll(); + }catch(BufferUnderflowException e){ + log.info("Error reading the XInput data " + e.getMessage()); + setActive(false); + Thread.currentThread().stop(); + } + if (newData) { ByteBuffer data = ByteBuffer.allocate(8); XInputComponents components = device.getComponents(); diff --git a/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java b/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java index 3c2416b..f1dd1b7 100644 --- a/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java @@ -69,7 +69,7 @@ public final class PureJavaHidApiManager { List connectedGamepads = new ArrayList(); for (HidDeviceInfo info : PureJavaHidApi.enumerateDevices()) { - if (info.getUsagePage() == 0x05 || info.getUsagePage() == 0x04 || (info.getVendorId() == 0x57e) || (info.getVendorId() == 0x054c)) { + if (info.getUsagePage() == 0x05 ||info.getUsagePage() == 0x01 || info.getUsagePage() == 0x04 || (info.getVendorId() == 0x57e) || (info.getVendorId() == 0x054c)) { connectedGamepads.add(info); } }