diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b922ecb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "purejavahidapi"] + path = purejavahidapi + url = https://github.com/nyholku/purejavahidapi diff --git a/pom.xml b/pom.xml index e8f7344..7fff789 100644 --- a/pom.xml +++ b/pom.xml @@ -16,11 +16,30 @@ + + + jitpack.io + https://jitpack.io + + + - org.hid4java - hid4java - 0.4.0 + org.projectlombok + lombok + 1.16.12 + provided + + com.github.strikerx3 + jxinput + 1eb4087 + + + + purejavahidapi + purejavahidapi + 0.0.1 + \ No newline at end of file diff --git a/purejavahidapi b/purejavahidapi new file mode 160000 index 0000000..9c9fc20 --- /dev/null +++ b/purejavahidapi @@ -0,0 +1 @@ +Subproject commit 9c9fc20fe3c90603aabc32ad66c9df6bbf696c8b diff --git a/src/net/ash/HIDToVPADNetworkClient/Main.java b/src/net/ash/HIDToVPADNetworkClient/Main.java index bfb8759..bdffeea 100644 --- a/src/net/ash/HIDToVPADNetworkClient/Main.java +++ b/src/net/ash/HIDToVPADNetworkClient/Main.java @@ -23,47 +23,39 @@ package net.ash.HIDToVPADNetworkClient; import javax.swing.SwingUtilities; -import org.hid4java.HidManager; - -import net.ash.HIDToVPADNetworkClient.controller.ControllerManager; -import net.ash.HIDToVPADNetworkClient.gui.ControllerDetector; import net.ash.HIDToVPADNetworkClient.gui.GuiMain; +import net.ash.HIDToVPADNetworkClient.manager.ActiveControllerManager; import net.ash.HIDToVPADNetworkClient.network.NetworkManager; /* Ash's todo list - * TODO finish Hid4JavaController + * TODO finish HidController * TODO locale */ public class Main { - public static void main(String[] args) { + public static void main(String[] args) { System.out.println("Hello World!"); - try { - new ControllerManager(); - new NetworkManager(); + new Thread(ActiveControllerManager.getInstance()).start(); + new Thread(NetworkManager.getInstance()).start(); } catch (Exception e) { e.printStackTrace(); fatal(); - } - + } SwingUtilities.invokeLater(new Runnable() { public void run() { GuiMain.createGUI(); } }); - - new ControllerDetector().start(); } - - public static void fatal() { + + public static void fatal() { System.err.println("HID To VPAD Network Client encountered an irrecoverable error."); System.err.println("Exiting..."); System.exit(1); } public static void initiateShutdown() { - HidManager.getHidServices().shutdown(); System.exit(0); } } diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/Controller.java b/src/net/ash/HIDToVPADNetworkClient/controller/Controller.java index b95ab9a..3f007c4 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/Controller.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/Controller.java @@ -21,13 +21,84 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.controller; +import lombok.Getter; +import lombok.Synchronized; +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import net.ash.HIDToVPADNetworkClient.manager.ControllerManager; +import net.ash.HIDToVPADNetworkClient.util.Settings; +import net.ash.HIDToVPADNetworkClient.util.Utilities; + /** * Main controller interface, extended by controller drivers. *

* See {@link LinuxDevInputController} for a full implementation. * @author ash */ -public interface Controller { +public abstract class Controller implements Runnable{ + private boolean active; + @Getter private final ControllerType type; + @Getter private final String identifier; + private byte[] latestData = null; + + boolean shutdown = false; + boolean shutdownDone = false; + private Object dataLock = new Object(); + private Object shutdownLock = new Object(); + + private Object rumbleLock = new Object(); + private boolean rumble = false; + + public Controller(ControllerType type, String identifier) throws ControllerInitializationFailedException{ + this.type = type; + this.identifier = identifier; + if(!initController(identifier)){ + throw new ControllerInitializationFailedException(); + } + } + + @Override + public void run() { + boolean shutdownState = shutdown; + while(!shutdownState){ + Utilities.sleep(Settings.DETECT_CONTROLLER_INTERVAL); + while(isActive()) { + byte[] newData = pollLatestData(); + if(newData != null){ + setLatestData(newData); + } + doSleepAfterPollingData(); + } + synchronized (shutdownLock) { + shutdownState = shutdown; + } + } + synchronized (shutdownLock) { + shutdownDone = true; + } + } + + protected void doSleepAfterPollingData() { + Utilities.sleep(Settings.SLEEP_AFER_POLLING); + } + + @Synchronized("dataLock") + private void setLatestData(byte[] newData) { + this.latestData = newData; + } + + @Synchronized("dataLock") + public byte[] getLatestData() { + if(latestData != null){ + byte[] data = this.latestData.clone(); + this.latestData = null; + return data; + }else{ + return null; + } + } + + public abstract byte[] pollLatestData(); + /** * Sets up the driver. *
@@ -36,64 +107,110 @@ public interface Controller { * @param arg Driver-specific init argument, see {@link ControllerManager} and {@link ControllerDetector}. * @return Whether initialization was successful. */ - public boolean initController(Object arg); + public abstract boolean initController(String identifier); + /** - * Allocates and returns a copy of the latest data available from the controller. - * - * @return A ControllerData instance containing the latest controller data. + * Destroys the controller driver and ends the polling thread. */ - public ControllerData getLatestData(); + public void destroyAll(){ + destroyDriver(); + endThread(); + } /** - * Used to tell a driver whether or not to poll the controller. - *
- * Is currently only ever used to initialize a driver (poll=true). - * destroy() is called for deinitialization. - *

- * Candidate to be removed during refactoring. - * - * @param poll Whether or not the driver should poll the controller. - */ - public void setPollingState(boolean poll); + * Destroys the controller driver. + */ + public abstract void destroyDriver(); + - /** - * Destroys the controller driver. - *
- * Will not return until all threads are stopped and resources are freed. - */ - public void destroy(); - - /** - * Sets the deviceSlot and padSlot to be returned by {@link #getDeviceSlot() getDeviceSlot} and {@link #getPadSlot() getPadSlot}. - * @param deviceSlot Value to be returned by {@link #getDeviceSlot() getDeviceSlot} - * @param padSlot Value to be returned by {@link #getPadSlot() getPadSlot} - */ - public void setSlotData(short deviceSlot, byte padSlot); - - /** - * Gets the previously set device slot (see {@link #setSlotData(short, byte) setSlotData}) - * @return The controller's device slot. - */ - public short getDeviceSlot(); - - /** - * Gets the previously set pad slot (see {@link #setSlotData(short, byte) setSlotData}) - * @return The controller's pad slot. - */ - public byte getPadSlot(); - - /** - * Returns a unique handle for this controller driver. - *
- * Please note that this is unique to the driver, not the controller it's connected to. - * @return The driver's handle. - */ - public int getHandle(); - - /** - * Gets the controller's ID. This is often identical to the argument to {@link #initController(Object) initController}. - * @return The controller's ID. - */ - public String getID(); -} + private void endThread() { + new Thread(new Runnable() { + @Override + public void run() { + setActive(false); + + synchronized (shutdownLock) { + shutdown = true; + } + + boolean done = false; + int i = 0; + while(!done){ + synchronized (shutdownLock) { + done = shutdownDone; + } + Utilities.sleep(50); + if(i++ > 50) System.out.println("Thread doesn't stop!!"); + } + } + }).start(); + } + + public abstract short getVID(); + + public abstract short getPID(); + + @Synchronized("shutdownLock") + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Override + public String toString(){ + return getType() + " " + getIdentifier(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((identifier == null) ? 0 : identifier.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Controller other = (Controller) obj; + if (identifier == null) { + if (other.identifier != null) + return false; + } else if (!identifier.equals(other.identifier)) + return false; + if (type != other.type) + return false; + return true; + } + + @Synchronized("rumbleLock") + public boolean isRumble() { + return rumble; + } + + @Synchronized("rumbleLock") + public void startRumble() { + this.rumble = true; + } + + @Synchronized("rumbleLock") + public void stopRumble() { + this.rumble = false; + } + + public enum ControllerType { + PureJAVAHid, LINUX, XINPUT13,XINPUT14 + } + + public abstract String getInfoText(); +} \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java b/src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java deleted file mode 100644 index 3ddf81e..0000000 --- a/src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java +++ /dev/null @@ -1,126 +0,0 @@ -/******************************************************************************* - * 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.controller; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -import net.ash.HIDToVPADNetworkClient.gui.GuiController; -import net.ash.HIDToVPADNetworkClient.network.NetworkManager; - -public class ControllerManager { - private static ControllerManager instance; - - public ControllerManager() throws Exception { - if (instance != null) { - throw new Exception("ControllerManager already has an instance!"); - } - instance = this; - } - - public boolean startConnection(String ip, List controllersIn) { - List controllers = setupControllers(controllersIn); - - //Boot up all drivers - for (Controller c : controllers) { - c.setPollingState(true); - - NetworkManager.instance().addController(c); - } - - NetworkManager.instance().connect(ip); - return NetworkManager.instance().isConnected(); - } - - public void stopConnection() { - NetworkManager.instance().removeAllControllers(); - NetworkManager.instance().disconnect(); - } - - public void updateControllers(List controllersIn) { - HashSet m = new HashSet(); - for (GuiController g : controllersIn) { - if (!g.getActiveState()) continue; - m.add(g.getId().hashCode()); - } - - HashMap d = NetworkManager.instance().getControllers(); - for (Controller c : d.values()) { - if (!m.contains(c.getID().hashCode())) { - NetworkManager.instance().removeController(c); - } - } - - List list = new ArrayList(); - for (GuiController g : controllersIn) { - if (!d.containsKey(g.getId().hashCode())) { - list.add(g); - } - } - - for (Controller c : setupControllers(list)) { - c.setPollingState(true); - NetworkManager.instance().addController(c); - } - } - - private List setupControllers(List controllers) { - List out = new ArrayList(); - - for (GuiController g : controllers) { - if (!g.getActiveState()) continue; - - Controller ctrl; - - switch (g.getType()) { - case HID4JAVA: - ctrl = new Hid4JavaController(); - break; - case LINUX: - ctrl = new LinuxDevInputController(); - break; - default: - System.out.println("[ControllerManager] Unsupported controller type " + g.getType().name()); - continue; - } - - ctrl.initController(g.getId()); - - out.add(ctrl); - } - - return out; - } - - public void detachController(Controller c) { - HashMap m = NetworkManager.instance().getControllers(); - if (m.containsKey(c.getID().hashCode())) { - NetworkManager.instance().removeController(c); - } - } - - public static ControllerManager instance() { - return instance; - } -} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/Hid4JavaController.java b/src/net/ash/HIDToVPADNetworkClient/controller/Hid4JavaController.java deleted file mode 100644 index 8b9948c..0000000 --- a/src/net/ash/HIDToVPADNetworkClient/controller/Hid4JavaController.java +++ /dev/null @@ -1,111 +0,0 @@ -/******************************************************************************* - * 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.controller; - -import org.hid4java.HidDevice; -import org.hid4java.HidManager; - -import net.ash.HIDToVPADNetworkClient.util.HandleFoundry; - -public class Hid4JavaController implements Controller { - static final int PACKET_LENGTH = 64; - - private String path = null; - private HidDevice controller = null; - - @Override - public boolean initController(Object arg) { - for (HidDevice device : HidManager.getHidServices().getAttachedHidDevices()) { - if (device.getPath().equals(arg.toString())) { - controller = device; - controller.setNonBlocking(true); - - path = arg.toString(); - - break; - } - } - - System.out.println("ctrl: " + controller.open() + " " + controller.getLastErrorMessage()); - if (controller == null | !controller.isOpen()) return false; - return true; - } - - @Override - public void setPollingState(boolean poll) { - - } - - @Override - public ControllerData getLatestData() { - ControllerData data = new ControllerData(getVID(), getPID(), getHandle(), new byte[1200]); - System.out.println("Data size: " + controller.read(data.data, 500)); - System.out.println("hrm: " + controller.getLastErrorMessage()); - return data; - } - - @Override - public void destroy() { - controller.close(); - } - - private short getVID() { - return controller.getVendorId(); - } - - private short getPID() { - return controller.getProductId(); - } - - private short deviceSlot = 0; - private byte padSlot = 0; - - @Override - public void setSlotData(short deviceSlot, byte padSlot) { - this.deviceSlot = deviceSlot; - this.padSlot = padSlot; - } - - @Override - public short getDeviceSlot() { - return deviceSlot; - } - - @Override - public byte getPadSlot() { - return padSlot; - } - - private int handle = 0; - @Override - public int getHandle() { - if (handle == 0) { - handle = HandleFoundry.next(); - } - return handle; - } - - @Override - public String getID() { - return path; - } -} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputController.java b/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputController.java index d0ff86b..3092337 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputController.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputController.java @@ -26,67 +26,65 @@ import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; -import net.ash.HIDToVPADNetworkClient.util.HandleFoundry; +import lombok.Getter; +import lombok.Setter; +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; -public class LinuxDevInputController extends Thread implements Controller { - public static final int NUM_SUPPORTED_AXIS = 10; //possibly off-by-one +public class LinuxDevInputController extends Controller implements Runnable{ + public LinuxDevInputController(String identifier) throws ControllerInitializationFailedException { + super(ControllerType.LINUX, identifier); + } + + public static final int NUM_SUPPORTED_AXIS = 10; //possibly off-by-one public static final int CONTROLLER_DATA_SIZE = Long.BYTES + (Byte.BYTES * NUM_SUPPORTED_AXIS); private static final byte JS_EVENT_BUTTON = 0x01; private static final byte JS_EVENT_INIT = (byte)0x80; private static final byte JS_EVENT_AXIS = 0x02; - private DataInputStream controller; - private ControllerData controllerData; + @Getter @Setter private DataInputStream controller; - private String id; - - private short deviceSlot = 0; - private byte padSlot = 0; - - private boolean shouldProcessEvents = false; - private boolean shutdown = false; - - public LinuxDevInputController() { - super("LinuxDevInputController"); - controllerData = new ControllerData((short)0, (short)0, 0, new byte[CONTROLLER_DATA_SIZE]); - } + @Getter @Setter private short VID; + @Getter @Setter private short PID; @Override - public void run() { - for (;;) { - for (;;) { - if (!shouldProcessEvents) break; - - processNextControllerEvent(); - } - - if (shutdown) break; - /* Not polling. Wait for setPollingState to wake us up. */ - try { - this.wait(); - } catch (InterruptedException e) {} - } //for (;;) - } - + public boolean initController(String identifier) { + try { + controller = new DataInputStream(new BufferedInputStream(new FileInputStream(identifier))); + } catch (Exception e) { + System.err.println("[LinuxDevInputController] Couldn't open " + identifier + " as file!"); + e.printStackTrace(); + return false; + } + + setVID((short)(identifier.hashCode() & 0xFFFF)); + setPID((short)((identifier.hashCode() >> Short.BYTES) & 0xFFFF)); + System.out.println("[LinuxDevInputController] " + identifier.toString() + " fakevid: " + Integer.toHexString((int)getVID() & 0xFFFF) + " fakepid: " + Integer.toHexString((int)getPID() & 0xFFFF)); + + return true; + } + private long buttonState = 0; private byte[] axisState = new byte[NUM_SUPPORTED_AXIS]; - private void processNextControllerEvent() { + + @Override + public byte[] pollLatestData() { + DataInputStream inputStream = getController(); //Read out next event from controller /*int time;*/ short value; byte type, number; try { - /*time = */controller.readInt(); - value = controller.readShort(); - type = controller.readByte(); - number = controller.readByte(); + /*time = */inputStream.readInt(); + value = inputStream.readShort(); + type = inputStream.readByte(); + number = inputStream.readByte(); } catch (IOException e) { System.err.println("[LinuxDevInputController] Couldn't read from controller!"); e.printStackTrace(); System.out.println("[LinuxDevInputController] Detaching..."); - ControllerManager.instance().detachController(this); - return; + setActive(false); + return null; } //Treat init events as normal (clear init bit) @@ -95,7 +93,7 @@ public class LinuxDevInputController extends Thread implements Controller { if (type == JS_EVENT_BUTTON) { if (number >= Long.SIZE) { System.out.println("[LinuxDevInputController] Button number " + number + " out of range; ignoring"); - return; + return null; } if (value != 0) { @@ -108,7 +106,7 @@ public class LinuxDevInputController extends Thread implements Controller { } else if (type == JS_EVENT_AXIS) { if (number >= NUM_SUPPORTED_AXIS) { System.out.println("[LinuxDevInputController] Axis number " + number + " out of range; ignoring"); - return; + return null; } //Do byteswap value = (short)(((value & 0xFF) << Byte.SIZE) | ((value & 0xFF00) >> Byte.SIZE)); @@ -125,103 +123,30 @@ public class LinuxDevInputController extends Thread implements Controller { for (int i = Long.BYTES; i < CONTROLLER_DATA_SIZE; i++) { newData[i] = axisState[i - Long.BYTES]; } - synchronized (controllerData) { - controllerData.data = newData; - } + return newData; } @Override - public ControllerData getLatestData() { - synchronized (controllerData) { - return new ControllerData(getVID(), getPID(), getHandle(), controllerData.getData()); - } - } - + protected void doSleepAfterPollingData() { + //This is event driven (aka pollLatestData() is blocking anyway until we have data), we don't need to sleep it all. + } + @Override - public boolean initController(Object arg) { + public void destroyDriver() { try { - controller = new DataInputStream(new BufferedInputStream(new FileInputStream(arg.toString()))); - } catch (Exception e) { - System.err.println("[LinuxDevInputController] Couldn't open " + arg.toString() + " as file!"); - e.printStackTrace(); - return false; - } - - fakevid = (short)(arg.hashCode() & 0xFFFF); - fakepid = (short)((arg.hashCode() >> Short.BYTES) & 0xFFFF); - System.out.println("[LinuxDevInputController] " + arg.toString() + " fakevid: " + Integer.toHexString((int)fakevid & 0xFFFF) + " fakepid: " + Integer.toHexString((int)fakepid & 0xFFFF)); - - id = arg.toString(); - - return true; - } - - @Override - public void setPollingState(boolean poll) { - shouldProcessEvents = poll; - if (this.getState() == Thread.State.NEW) { - this.start(); - } else if (this.getState() == Thread.State.WAITING){ - this.notify(); - } - } - - @Override - public void destroy() { - shutdown = true; - setPollingState(false); - - if (!this.equals(Thread.currentThread())) { - while (this.getState() != Thread.State.TERMINATED) {} - } - - try { - controller.close(); - } catch (IOException e) {} - } - - private short fakevid; - private short getVID() { - return fakevid; - } - - private short fakepid; - private short getPID() { - return fakepid; - } - - @Override - public String getID() { - return id; - } - - private int handle = 0; - @Override - public int getHandle() { - if (handle == 0) { - handle = HandleFoundry.next(); - } - return handle; - } - - @Override - public void setSlotData(short deviceSlot, byte padSlot) { - this.deviceSlot = deviceSlot; - this.padSlot = padSlot; - } - - @Override - public short getDeviceSlot() { - return deviceSlot; - } - - @Override - public byte getPadSlot() { - return padSlot; + controller.close(); + } catch (IOException e) { + } } @Override public String toString() { - return "[" + super.toString() + ";VID," + Integer.toHexString((int)getVID() & 0xFFFF) + ";PID," + Integer.toHexString((int)getPID() & 0xFFFF) + ";run," + shouldProcessEvents + ((controller == null) ? ";uninitialised]" : ";initialised]"); + return "[" + super.toString() + ";VID," + Integer.toHexString((int)getVID() & 0xFFFF) + ";PID," + Integer.toHexString((int)getPID() & 0xFFFF) + ";run," + isActive() + ((controller == null) ? ";uninitialised]" : ";initialised]"); } + + @Override + public String getInfoText() { + return "Linux controller on " + getIdentifier(); + } + } diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java b/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java deleted file mode 100644 index ce7c694..0000000 --- a/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************* - * 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. - *******************************************************************************/ -//This file is only here so I can remember just how shocking my first attempt -//at a driver like this was. It should never, NEVER be used. - -/* I'M SO SORRY - * Please do not use this - */ - -package net.ash.HIDToVPADNetworkClient.controller; - -import java.io.DataInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; - -import net.ash.HIDToVPADNetworkClient.util.BlockingIOStabbifier; -import net.ash.HIDToVPADNetworkClient.util.WakeupThread; - -@Deprecated -public class LinuxDevInputControllerLegacyDriver extends Thread implements Controller { - private DataInputStream controller; - private String path; - - @Override - public boolean initController(Object arg) { - try { - path = (String)arg; - } catch (ClassCastException e) { - System.err.println("LinuxDevInputController recieved bad argument!"); - e.printStackTrace(); - return false; - } - - try { - controller = new DataInputStream(new BlockingIOStabbifier(path)); - } catch (FileNotFoundException e) { - System.err.println("Could not open " + path + "!"); - e.printStackTrace(); - return false; - } - data = new ControllerData(); - data.data = new byte[100]; //100 BUTTON MAX - return true; - } - - private boolean shouldPoll = false; - WakeupThread wakeup; - //@Override - public void setPollingState(boolean poll, int pollInterval) { - if (poll) { - if (this.getState() == Thread.State.NEW) { - shouldPoll = poll; - this.start(); - } - wakeup = new WakeupThread(LinuxDevInputControllerThreadLock); - wakeup.setTimeout(pollInterval); - wakeup.start(); - } - shouldPoll = poll; - } - - Object LinuxDevInputControllerThreadLock = new Object(); - @Override - public void run() { - for (;;) { - while (shouldPoll) { - updateControllerData(); - - synchronized (LinuxDevInputControllerThreadLock) { - try { - LinuxDevInputControllerThreadLock.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - wakeup.tryStop(); - synchronized (LinuxDevInputControllerThreadLock) { - try { - LinuxDevInputControllerThreadLock.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } - - private ControllerData data; - - private void updateControllerData() { - try { - while (controller.available() > 8) { /* 8 bytes to a joystick packet */ - /*int time = */controller.readInt(); - short val = controller.readShort(); - byte type = controller.readByte(); - byte num = controller.readByte(); - num *= 2; - if (type == 0x1 && !(num >= 100)) { - synchronized (data) { - data.data[num] = (byte)(val & 0xFF); - data.data[num + 1] = (byte)((val >> 8) & 0xFF); - } - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public ControllerData getLatestData() { - synchronized (data) { - return data; - } - } - - public short getPID() { - return data.getPID(); - } - - public short getVID() { - return data.getVID(); - } - - public void setFakePid(short pid) { - //this.data.pid = pid; - } - - public void setFakeVid(short vid) { - //this.data.vid = vid; - } - - @Override - public void setSlotData(short deviceSlot, byte padSlot) { - // Auto-generated method stub - - } - - @Override - public short getDeviceSlot() { - // Auto-generated method stub - return 0; - } - - @Override - public byte getPadSlot() { - // Auto-generated method stub - return 0; - } - - @Override - public int getHandle() { - // Auto-generated method stub - return 0; - } - - @Override - public String getID() { - // Auto-generated method stub - return null; - } - - @Override - public void setPollingState(boolean poll) { - // Auto-generated method stub - - } -} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/PureJavaHidController.java b/src/net/ash/HIDToVPADNetworkClient/controller/PureJavaHidController.java new file mode 100644 index 0000000..74a8caa --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/PureJavaHidController.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * 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.controller; + +import java.io.IOException; +import java.util.Arrays; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.Synchronized; +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import net.ash.HIDToVPADNetworkClient.util.PureJavaHidApiManager; +import purejavahidapi.HidDevice; +import purejavahidapi.InputReportListener; + +public class PureJavaHidController extends Controller implements InputReportListener { + public static Controller getInstance(String deviceIdentifier) throws IOException, ControllerInitializationFailedException { + HidDevice device = PureJavaHidApiManager.getDeviceByPath(deviceIdentifier); + //We use a special version to optimize the data for the switch pro controller + if(device.getHidDeviceInfo().getVendorId() == SwitchProController.SWITCH_PRO_CONTROLLER_VID && + device.getHidDeviceInfo().getProductId() == SwitchProController.SWITCH_PRO_CONTROLLER_PID){ + return new SwitchProController(deviceIdentifier); + }else{ + return new PureJavaHidController(deviceIdentifier); + } + } + + public PureJavaHidController(String identifier) throws ControllerInitializationFailedException { + super(ControllerType.PureJAVAHid, identifier); + } + + private Object dataLock = new Object(); + protected byte[] currentData = new byte[1]; + + protected int PACKET_LENGTH = 64; + + @Getter @Setter(AccessLevel.PRIVATE) + private HidDevice hidDevice; + + @Override + public boolean initController(String identifier) { + HidDevice device; + try { + device = PureJavaHidApiManager.getDeviceByPath(identifier); + + device.setInputReportListener(this); + setHidDevice(device); + return true; + + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + @Override + @Synchronized("dataLock") + public byte[] pollLatestData() { + return currentData.clone(); + } + + @Override + public void destroyDriver() { + getHidDevice().close(); + } + + @Override + public short getVID() { + return getHidDevice().getHidDeviceInfo().getVendorId(); + } + + @Override + public short getPID() { + return getHidDevice().getHidDeviceInfo().getProductId(); + } + + @Override + @Synchronized("dataLock") + public void onInputReport(HidDevice source, byte reportID, byte[] reportData, int reportLength) { + if(isActive()){ + int length = PACKET_LENGTH; + if(reportLength < length){ + length = reportLength; + } + currentData = Arrays.copyOfRange(reportData, 0, length); + } + } + + @Override + public String getInfoText() { + //TODO: + if(getVID() == 0x57e){ + if(getPID() == 0x2006){ + return "Joy-Con (L) on " + getIdentifier(); + }else if(getPID() == 0x2007){ + return "Joy-Con (R) on " + getIdentifier(); + } + } + + return "USB HID on " + getIdentifier(); + } +} \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java b/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java new file mode 100644 index 0000000..f8fa377 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/SwitchProController.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.controller; + +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; + +public class SwitchProController extends PureJavaHidController { + public static final short SWITCH_PRO_CONTROLLER_VID = 0x57e; + public static final short SWITCH_PRO_CONTROLLER_PID = 0x2009; + + + public SwitchProController(String identifier) throws ControllerInitializationFailedException { + super(identifier); + //truncate package to 11; + this.PACKET_LENGTH = 11; + } + + @Override + public byte[] pollLatestData() { + //remove unused data (because only changed data will be sent) + currentData[3] = 0; + currentData[5] = 0; + currentData[7] = 0; + currentData[9] = 0; + return currentData.clone(); + } + + @Override + public String getInfoText(){ + return "Switch Pro Controller on " + getIdentifier(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java similarity index 76% rename from src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java rename to src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java index cb00c16..454740e 100644 --- a/src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInput13Controller.java @@ -21,28 +21,15 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.controller; -public class ControllerData { - private short vid; - private short pid; - protected byte[] data; - - public ControllerData(short vid, short pid, int handle, byte[] data) { - this.vid = vid; - this.pid = pid; - this.data = data; - } - - public ControllerData() {} - - public short getVID() { - return vid; - } - - public short getPID() { - return pid; - } - - public byte[] getData() { - return data; - } +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; + +public class XInput13Controller extends XInputController { + public XInput13Controller(String identifier) throws ControllerInitializationFailedException { + super(ControllerType.XINPUT13, identifier); + } + + @Override + public String getInfoText(){ + return "XInput 1.3 on " + getIdentifier(); + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java new file mode 100644 index 0000000..1fe49a8 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInput14Controller.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.controller; + +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; + +public class XInput14Controller extends XInputController { + public XInput14Controller(String identifier) throws ControllerInitializationFailedException { + super(ControllerType.XINPUT14, identifier); + } + + @Override + public String getInfoText(){ + return "XInput 1.4 on " + getIdentifier(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java b/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java new file mode 100644 index 0000000..8b090fd --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/XInputController.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * 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.controller; + +import java.nio.ByteBuffer; + +import com.ivan.xinput.XInputAxes; +import com.ivan.xinput.XInputButtons; +import com.ivan.xinput.XInputComponents; +import com.ivan.xinput.XInputDevice; +import com.ivan.xinput.exceptions.XInputNotLoadedException; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import net.ash.HIDToVPADNetworkClient.util.Utilities; + +public class XInputController extends Controller { + //the pad number will be appended to this String. + public static final String XINPUT_INDENTIFER = "\\\\?\\XINPUT\\"; + + @Getter @Setter(AccessLevel.PRIVATE) private XInputDevice device; + + public XInputController(ControllerType type, String identifier) throws ControllerInitializationFailedException { + super(type, identifier); + } + + @Override + public boolean initController(String identifier) { + int pad = Integer.parseInt(identifier.substring(XINPUT_INDENTIFER.length())); + XInputDevice device = null; + try { + device = XInputDevice.getDeviceFor(pad); + } catch (XInputNotLoadedException e) { + //TODO: Log? + } + if(device == null) return false; + setDevice(device); + return true; + } + + @Override + public byte[] pollLatestData() { + if(device.poll()){ + ByteBuffer data = ByteBuffer.allocate(8); + XInputComponents components = device.getComponents(); + + XInputButtons buttons = components.getButtons(); + + int buttonState = 0; + if(buttons.a) buttonState |= (1 << 0); + if(buttons.b) buttonState |= (1 << 1); + if(buttons.x) buttonState |= (1 << 2); + if(buttons.y) buttonState |= (1 << 3); + + if(buttons.left) buttonState |= (1 << 4); + if(buttons.up) buttonState |= (1 << 5); + if(buttons.right) buttonState |= (1 << 6); + if(buttons.down) buttonState |= (1 << 7); + + if(buttons.back) buttonState |= (1 << 8); + if(buttons.start) buttonState |= (1 << 9); + if(buttons.lShoulder) buttonState |= (1 << 10); + if(buttons.rShoulder) buttonState |= (1 << 11); + if(buttons.lThumb) buttonState |= (1 << 12); + if(buttons.rThumb) buttonState |= (1 << 13); + if(buttons.unknown) buttonState |= (1 << 14); + if (XInputDevice.isGuideButtonSupported()) { + if (buttons.guide) buttonState |= (1 << 15); + } + + XInputAxes axes = components.getAxes(); + int axesData = 0; + + axesData |= Utilities.signedShortToByte(axes.lxRaw) << 24; + axesData |= Utilities.signedShortToByte(axes.lyRaw) << 16; + axesData |= Utilities.signedShortToByte(axes.rxRaw) << 8; + axesData |= Utilities.signedShortToByte(axes.ryRaw) << 0; + + short axesDataShoulderButtons = 0; + + axesDataShoulderButtons |= axes.ltRaw << 8; + axesDataShoulderButtons |= axes.rtRaw << 0; + + buttonState |= axesDataShoulderButtons << 16; + data.putInt(axesData).putInt(buttonState); + + return(data.array()); + } + return null; + } + + @Override + public void destroyDriver() { + //not needed + } + + //TODO: Other values for VID/PID? I guess other people had this idea too... + @Override + public short getVID() { + return 0x7331; + } + + @Override + public short getPID() { + return 0x1337; + } + + @Override + public String getInfoText(){ + return "XInput on " + getIdentifier(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java b/src/net/ash/HIDToVPADNetworkClient/exeption/ControllerInitializationFailedException.java similarity index 86% rename from src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java rename to src/net/ash/HIDToVPADNetworkClient/exeption/ControllerInitializationFailedException.java index 297aa43..bc09711 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java +++ b/src/net/ash/HIDToVPADNetworkClient/exeption/ControllerInitializationFailedException.java @@ -19,9 +19,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ -package net.ash.HIDToVPADNetworkClient.gui; +package net.ash.HIDToVPADNetworkClient.exeption; -public enum GuiControllerType { - HID4JAVA, - LINUX +public class ControllerInitializationFailedException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; } diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/ControllerDetector.java b/src/net/ash/HIDToVPADNetworkClient/gui/ControllerDetector.java deleted file mode 100644 index 4fa7527..0000000 --- a/src/net/ash/HIDToVPADNetworkClient/gui/ControllerDetector.java +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************* - * 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.gui; - -import java.io.File; -import java.io.FilenameFilter; -import java.util.ArrayList; -import java.util.List; - -import org.hid4java.HidDevice; -import org.hid4java.HidManager; - -public class ControllerDetector extends Thread { - private static final int POLL_TIMEOUT = 1000; - private static final int SHORT_POLL_TIMEOUT = 100; - - private int lastHashCode = 0; - @Override - public void run() { - for (;;) { - if (GuiMain.instance() != null) { - List tmp = detectControllers(); - if (lastHashCode != tmp.hashCode()) { - lastHashCode = tmp.hashCode(); - GuiMain.instance().updateControllerList(tmp); - } - try { - Thread.sleep(POLL_TIMEOUT); - } catch (InterruptedException e) {} - } else { - try { - Thread.sleep(SHORT_POLL_TIMEOUT); - } catch (InterruptedException e) {} - } - } - } - - private static List detectControllers() { - List controllers = new ArrayList(); - - String os = System.getProperty("os.name"); - //System.out.println("[ControllerDetector] OS: " + os); - - if (os.contains("Linux")) { - detectLinuxControllers(controllers); - } else if (os.contains("Windows")) { - System.out.println("Running on Windows! XInput coming soon."); //XXX debug text (win32) - } - - for (HidDevice device : HidManager.getHidServices().getAttachedHidDevices()) { - controllers.add(new GuiController(GuiControllerType.HID4JAVA, device.getPath())); - } - return controllers; - } - - private static void detectLinuxControllers(List controllers) { - File devInput = new File("/dev/input"); - if (!devInput.exists()) return; - - File[] linuxControllers = devInput.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.startsWith("js"); //js0, js1, etc... - } - }); - - for (File controller : linuxControllers) { - controllers.add(new GuiController(GuiControllerType.LINUX, controller.getAbsolutePath())); - } - } -} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiCloseListener.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiCloseListener.java index 41148eb..7e42d36 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiCloseListener.java +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiCloseListener.java @@ -25,29 +25,29 @@ import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import net.ash.HIDToVPADNetworkClient.Main; - public class GuiCloseListener implements WindowListener { - @Override - public void windowClosing(WindowEvent arg0) { - Main.initiateShutdown(); - } - - @Override - public void windowActivated(WindowEvent arg0) {} + @Override + public void windowClosing(WindowEvent arg0) { + Main.initiateShutdown(); + } + + @Override + public void windowActivated(WindowEvent arg0) {} - @Override - public void windowClosed(WindowEvent arg0) {} + @Override + public void windowClosed(WindowEvent arg0) {} - @Override - public void windowDeactivated(WindowEvent arg0) {} + @Override + public void windowDeactivated(WindowEvent arg0) {} - @Override - public void windowDeiconified(WindowEvent arg0) {} + @Override + public void windowDeiconified(WindowEvent arg0) {} - @Override - public void windowIconified(WindowEvent arg0) {} + @Override + public void windowIconified(WindowEvent arg0) {} - @Override - public void windowOpened(WindowEvent arg0) {} + @Override + public void windowOpened(WindowEvent arg0) {} } + diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java index 03402cd..51ba72d 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java @@ -23,21 +23,25 @@ package net.ash.HIDToVPADNetworkClient.gui; import java.awt.BorderLayout; import java.awt.Component; +import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; -import javax.swing.AbstractButton; import javax.swing.BoxLayout; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.Timer; + +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.manager.ControllerManager; public class GuiControllerList extends JPanel { private static final long serialVersionUID = 1L; private JPanel innerScrollPanel; - private ActionListener currentActionListener = null; public GuiControllerList() { super(new BorderLayout()); @@ -45,62 +49,60 @@ public class GuiControllerList extends JPanel { innerScrollPanel = new JPanel(); innerScrollPanel.setLayout(new BoxLayout(innerScrollPanel, BoxLayout.PAGE_AXIS)); add(new JScrollPane(innerScrollPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); + + int delay = 1000; //milliseconds + ActionListener taskPerformer = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + updateControllerList(); + } + }; + new Timer(delay, taskPerformer).start(); } - public synchronized List getControllers() { - List controllers = new ArrayList(); + public synchronized void updateControllerList() { + //System.out.println("[GuiControllerList] Updating controller list..."); //XXX debug text - for (Component component : innerScrollPanel.getComponents()) { - if (component instanceof GuiControllerListItem) { - controllers.add(((GuiControllerListItem)component).getData()); - } - } - - return controllers; - } - - public synchronized void updateControllerList(List controllers) { - System.out.println("[GuiControllerList] Updating controller list..."); //XXX debug text - - HashMap components = new HashMap(); - for (Component component : innerScrollPanel.getComponents()) { - if (component instanceof GuiControllerListItem) { - components.put(component.hashCode(), ((GuiControllerListItem)component).getData()); - } - } + boolean repaintNeeded = false; + + List attachedControllers = ControllerManager.getAttachedControllers(); List newComponents = new ArrayList(); - for (GuiController controller : controllers) { - GuiControllerListItem i; - if (components.containsKey(controller.hashCode())) { - i = new GuiControllerListItem(components.get(controller.hashCode())); - } else { + + Map components = new HashMap<>(); + for (Component component : innerScrollPanel.getComponents()) { + if (component instanceof GuiControllerListItem) { + GuiControllerListItem comp = (GuiControllerListItem) component; + Controller cont = comp.getController(); + if(attachedControllers.contains(cont)){ + components.put(cont,comp); + }else{//Controller removed + repaintNeeded = true; + } + } + } + + //Build new list of components. + for (Controller controller : attachedControllers) { + GuiControllerListItem i = null; + if (components.containsKey(controller)) { + newComponents.add(components.get(controller)); + }else{ //New controller was added + repaintNeeded = true; i = new GuiControllerListItem(controller); + newComponents.add(i); } - newComponents.add(i); } - innerScrollPanel.removeAll(); - for (GuiControllerListItem component : newComponents) { - component.addActionListener(currentActionListener); - innerScrollPanel.add(component); - } - - //TODO research performance impact - 300+ms on swing.RepaintManager? - innerScrollPanel.revalidate(); - revalidate(); - innerScrollPanel.repaint(); - repaint(); - } - - public synchronized void setActionListener(ActionListener l) { - currentActionListener = l; - for (Component c : innerScrollPanel.getComponents()) { - try { - ((AbstractButton)c).addActionListener(l); - } catch (ClassCastException e) { - System.out.println("[GuiControllerList] Bad cast on " + c.getClass().getName() + " to AbstractButton!"); - } + if(repaintNeeded){ + innerScrollPanel.removeAll(); + for (GuiControllerListItem component : newComponents) { + innerScrollPanel.add(component); + } + + innerScrollPanel.revalidate(); + revalidate(); + innerScrollPanel.repaint(); + repaint(); } } } diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java index ef6db03..7b254d0 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java @@ -23,52 +23,80 @@ package net.ash.HIDToVPADNetworkClient.gui; import java.awt.BorderLayout; import java.awt.Dimension; +import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JCheckBox; import javax.swing.JPanel; +import javax.swing.Timer; -public class GuiControllerListItem extends JPanel { +import lombok.Getter; +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.network.NetworkManager; + +public class GuiControllerListItem extends JPanel implements ActionListener { private static final long serialVersionUID = 1L; - private GuiController data = null; + @Getter private final Controller controller; private JCheckBox checkbox; - public GuiControllerListItem(GuiController data) { + public GuiControllerListItem(Controller data) { super(new BorderLayout()); setMinimumSize(new Dimension (300, 30)); setPreferredSize(new Dimension(300, 30)); setMaximumSize(new Dimension(2000, 30)); - this.data = data; + this.controller = data; checkbox = new JCheckBox(getFlavorText()); - checkbox.setSelected(data.getActiveState()); + checkbox.setSelected(data.isActive()); + checkbox.addActionListener(this); add(checkbox); + + int delay = 100; //milliseconds + ActionListener taskPerformer = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + checkbox.setEnabled(NetworkManager.getInstance().isConnected()); + checkbox.setSelected(controller.isActive()); + } + }; + new Timer(delay, taskPerformer).start(); } private String getFlavorText() { - switch (data.getType()) { - case HID4JAVA: - return "USB HID on " + data.getId().toString(); - case LINUX: - return "Linux controller on " + data.getId().toString(); - default: - return data.toString(); - } - } - - public void addActionListener(ActionListener l) { - checkbox.addActionListener(l); - } - - public GuiController getData() { - return data; - } - - @Override - public int hashCode() { - return data.hashCode(); + return controller.getInfoText(); } + + @Override + public void actionPerformed(ActionEvent e) { + boolean selected = ((JCheckBox) e.getSource()).isSelected(); + controller.setActive(selected); + checkbox.setSelected(controller.isActive()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((controller == null) ? 0 : controller.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GuiControllerListItem other = (GuiControllerListItem) obj; + if (controller == null) { + if (other.controller != null) + return false; + } else if (!controller.equals(other.controller)) + return false; + return true; + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java index 999a7f3..47a0da1 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java @@ -24,6 +24,8 @@ package net.ash.HIDToVPADNetworkClient.gui; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import javax.swing.Box; import javax.swing.BoxLayout; @@ -31,19 +33,25 @@ import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.Timer; -public class GuiInputControls extends JPanel { +import net.ash.HIDToVPADNetworkClient.network.NetworkManager; + +public class GuiInputControls extends JPanel implements ActionListener { private static final long serialVersionUID = 1L; private static GuiInputControls instance = null; - private static final String DEFAULT_PACKET_INTERVAL = "1000"; + private static final String CONNECT = "Connect"; + private static final String DISCONNECT = "Disconnect"; + private static final String RECONNECTING = "Reconnecting"; private JButton connectButton; private JTextField ipTextBox; private JPanel ipTextBoxWrap; private JTextField packetIntervalTextBox; - private JPanel piTextBoxWrap; private JLabel statusLabel; + public GuiInputControls() throws Exception { super(); if (instance != null) { @@ -54,33 +62,24 @@ public class GuiInputControls extends JPanel { setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); setPreferredSize(new Dimension(220, 150)); - connectButton = new JButton("Connect"); + connectButton = new JButton(CONNECT); connectButton.setAlignmentX(Component.CENTER_ALIGNMENT); ipTextBox = new JTextField(); ipTextBox.setColumns(15); + ipTextBox.setText("192.168.0.35"); ipTextBoxWrap = new JPanel(new FlowLayout()); ipTextBoxWrap.add(new JLabel("IP: ")); ipTextBoxWrap.add(ipTextBox); ipTextBoxWrap.setMaximumSize(new Dimension(1000, 20)); - - packetIntervalTextBox = new JTextField(); - packetIntervalTextBox.setColumns(3); - packetIntervalTextBox.setText(DEFAULT_PACKET_INTERVAL); - //TODO sanitize input - piTextBoxWrap = new JPanel(new FlowLayout()); - piTextBoxWrap.add(new JLabel("Packet interval: ")); - piTextBoxWrap.add(packetIntervalTextBox); - piTextBoxWrap.setMaximumSize(new Dimension(1000, 20)); - + statusLabel = new JLabel("Ready."); statusLabel.setAlignmentX(Component.CENTER_ALIGNMENT); add(Box.createVerticalGlue()); - add(ipTextBoxWrap); - add(piTextBoxWrap); + add(ipTextBoxWrap); add(Box.createRigidArea(new Dimension(1, 4))); add(connectButton); @@ -90,7 +89,24 @@ public class GuiInputControls extends JPanel { add(Box.createVerticalGlue()); - connectButton.addActionListener(GuiInteractionManager.instance()); + connectButton.addActionListener(this); + + int delay = 100; //milliseconds + ActionListener taskPerformer = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if(NetworkManager.getInstance().isReconnecting()){ + connectButton.setText(RECONNECTING); + connectButton.setEnabled(false); + }else if(NetworkManager.getInstance().isConnected()){ + connectButton.setText(DISCONNECT); + connectButton.setEnabled(true); + }else{ + connectButton.setText(CONNECT); + connectButton.setEnabled(true); + } + } + }; + new Timer(delay, taskPerformer).start(); } public static GuiInputControls instance() { @@ -112,4 +128,22 @@ public class GuiInputControls extends JPanel { public JLabel getStatusLabel() { return statusLabel; } + + @Override + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if(NetworkManager.getInstance().isReconnecting()){ + + }else{ + if(NetworkManager.getInstance().isConnected()){ + NetworkManager.getInstance().disconnect(); + }else{ + NetworkManager.getInstance().connect(ipTextBox.getText()); + } + } + } + }); + + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java deleted file mode 100644 index 3f3ef75..0000000 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * 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.gui; - -import java.awt.Component; -import java.awt.Container; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JButton; -import javax.swing.JCheckBox; - -import net.ash.HIDToVPADNetworkClient.controller.ControllerManager; -import net.ash.HIDToVPADNetworkClient.network.NetworkManager; - -public class GuiInteractionManager implements ActionListener { - private static GuiInteractionManager instance = null; - - public GuiInteractionManager() throws Exception { - if (instance != null) { - throw new Exception("GuiInputControls already has an instance!"); - } - instance = this; - } - - @Override - public void actionPerformed(ActionEvent e) { - /* - * Swing GUI events - */ - if (e.getSource() instanceof Component) { - Component source = (Component)e.getSource(); - Container parent = source.getParent(); - - /* - * Action handler code for GuiControllerListItem - */ - if (parent instanceof GuiControllerListItem) { - GuiControllerListItem rparent = (GuiControllerListItem)parent; - JCheckBox rsource = (JCheckBox)source; - - rparent.getData().setActiveState(rsource.isSelected()); - - if (NetworkManager.instance().isRunning()) { - ControllerManager.instance().updateControllers(GuiMain.instance().getControllers()); - } - /* - * Action handler for GuiInputControls - */ - } else if (parent instanceof GuiInputControls) { - GuiInputControls rparent = (GuiInputControls)parent; - /* - * (Dis)connect button - */ - if (source instanceof JButton) { - if (NetworkManager.instance().isRunning()) { - disconnect(); - } else { - NetworkManager.instance().setPacketInterval(Integer.parseInt(rparent.getPacketIntervalTextBox().getText())); - if (ControllerManager.instance().startConnection(GuiMain.instance().getIPText(), GuiMain.instance().getControllers())) { - rparent.getIpTextBox().setEnabled(false); - rparent.getPacketIntervalTextBox().setEnabled(false); - rparent.getConnectButton().setText("Disconnect"); - GuiInputControls.instance().getStatusLabel().setText("Connected!"); - } else { - ControllerManager.instance().stopConnection(); - GuiInputControls.instance().getStatusLabel().setText("Connection Failed!"); - } - } - } - } - } - } - - public void disconnect() { - ControllerManager.instance().stopConnection(); - GuiInputControls.instance().getIpTextBox().setEnabled(true); - GuiInputControls.instance().getPacketIntervalTextBox().setEnabled(true); - GuiInputControls.instance().getConnectButton().setText("Connect"); - GuiInputControls.instance().getStatusLabel().setText("Disconnected."); - } - - public static GuiInteractionManager instance() { - return instance; - } -} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiMain.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiMain.java index bb36d4d..ebd08e8 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiMain.java +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiMain.java @@ -23,7 +23,6 @@ package net.ash.HIDToVPADNetworkClient.gui; import java.awt.BorderLayout; import java.awt.Dimension; -import java.util.List; import javax.swing.JComponent; import javax.swing.JFrame; @@ -54,18 +53,9 @@ public class GuiMain extends JPanel { public GuiMain() { super(new BorderLayout()); - try { - new GuiInteractionManager(); - } catch (Exception e) { - e.printStackTrace(); - Main.fatal(); - } - - leftControllerList = new GuiControllerList(); leftControllerList.setPreferredSize(new Dimension(300, 100)); add(leftControllerList, BorderLayout.CENTER); - leftControllerList.setActionListener(GuiInteractionManager.instance()); try { rightSideControls = new GuiInputControls(); @@ -76,18 +66,6 @@ public class GuiMain extends JPanel { add(rightSideControls, BorderLayout.LINE_END); } - public void updateControllerList(List controllers) { - leftControllerList.updateControllerList(controllers); - } - - public List getControllers() { - return leftControllerList.getControllers(); - } - - public String getIPText() { - return rightSideControls.getIpTextBox().getText(); - } - public static GuiMain instance() { return instance; } diff --git a/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java b/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java new file mode 100644 index 0000000..65d3118 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/manager/ActiveControllerManager.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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.manager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import lombok.extern.java.Log; +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.network.NetworkHIDDevice; +import net.ash.HIDToVPADNetworkClient.network.NetworkManager; +import net.ash.HIDToVPADNetworkClient.util.Settings; +import net.ash.HIDToVPADNetworkClient.util.Utilities; + +@Log +public class ActiveControllerManager implements Runnable{ + private static ActiveControllerManager instance = new ActiveControllerManager(); + + private ActiveControllerManager(){ + } + + public static ActiveControllerManager getInstance(){ + return instance; + } + + @Override + public void run() { //TODO: Add mechanism to stop these threads? + new Thread(new Runnable() { + @Override + public void run() { + while(true){ + updateControllerStates(); + ControllerManager.detectControllers(); + Utilities.sleep(Settings.DETECT_CONTROLLER_INTERVAL); + } + } + }).start(); + + new Thread(new Runnable() { + @Override + public void run() { + while(true){ + handleControllerInputs(); + Utilities.sleep(Settings.HANDLE_INPUTS_INTERVAL); + } + } + }).start(); + } + + private Map activeControllers = new HashMap<>(); + public void updateControllerStates() { + List currentControllers = ControllerManager.getActiveControllers(); + + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + synchronized (activeControllers) { + //Adding all missing. + + for(Controller c: currentControllers){ + if(!activeControllers.containsKey(c)){ + log.info("Added " + c); + toAdd.add(c); + } + } + + //removing all old + for(Controller c : activeControllers.keySet()){ + if(!currentControllers.contains(c)){ + log.info("Removed " + c); + toRemove.add(c); + } + } + } + + addController(toAdd); + removeController(toRemove); + } + + private void removeController(List toRemove) { + synchronized (activeControllers) { + for(Controller c : toRemove){ + NetworkManager.getInstance().removeHIDDevice(activeControllers.get(c)); + c.destroyDriver(); + activeControllers.remove(c); + } + } + } + + private void addController(List toAdd) { + synchronized (activeControllers) { + for(Controller c : toAdd){ + NetworkHIDDevice hiddevice = new NetworkHIDDevice(c.getVID(), c.getPID()); + hiddevice.sendAttach(); + NetworkManager.getInstance().addHIDDevice(hiddevice); + activeControllers.put(c,hiddevice); + } + } + } + + private void handleControllerInputs() { + synchronized (activeControllers) { + for(Entry entry : activeControllers.entrySet()){ + byte[] data = entry.getKey().getLatestData(); + if(data != null){ + NetworkHIDDevice device = entry.getValue(); + device.sendRead(data); + } + } + } + } + + public void attachAllActiveControllers() { + synchronized (activeControllers) { + for(Entry entry : activeControllers.entrySet()){ + NetworkHIDDevice device = entry.getValue(); + device.sendAttach(); + } + } + } + + /** + * + * @param HIDhandle + * @return returns the controller for the given handle. returns null if the controller with the given handle is not found. + */ + public Controller getControllerByHIDHandle(int HIDhandle) { + for(Entry entry: activeControllers.entrySet()){ + if(entry.getValue().getHidHandle() == HIDhandle){ + return entry.getKey(); + } + } + return null; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java b/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java new file mode 100644 index 0000000..931b529 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/manager/ControllerManager.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * 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.manager; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.ivan.xinput.XInputDevice; +import com.ivan.xinput.XInputDevice14; +import com.ivan.xinput.exceptions.XInputNotLoadedException; + +import lombok.Synchronized; +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.controller.Controller.ControllerType; +import net.ash.HIDToVPADNetworkClient.controller.PureJavaHidController; +import net.ash.HIDToVPADNetworkClient.controller.LinuxDevInputController; +import net.ash.HIDToVPADNetworkClient.controller.XInput13Controller; +import net.ash.HIDToVPADNetworkClient.controller.XInput14Controller; +import net.ash.HIDToVPADNetworkClient.controller.XInputController; +import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException; +import purejavahidapi.HidDeviceInfo; +import purejavahidapi.PureJavaHidApi; + +public class ControllerManager{ + private static Map attachedControllers = new HashMap<>(); + + /** + * Detects all attached controller. + */ + @Synchronized("attachedControllers") + public static void detectControllers() { + String os = System.getProperty("os.name"); + //System.out.println("[ControllerDetector] OS: " + os); + + Map connectedDevices = new HashMap<>(); + + if (os.contains("Linux")) { + connectedDevices.putAll(detectLinuxControllers()); + } else if (os.contains("Windows")) { + connectedDevices.putAll(detectWindowsControllers()); + } + + connectedDevices.putAll(detectHIDDevices()); + + //Remove detached devices + List toRemove = new ArrayList<>(); + for(String s : attachedControllers.keySet()){ + if(!connectedDevices.containsKey(s)){ + toRemove.add(s); + } + } + for(String remove : toRemove){ + attachedControllers.get(remove).destroyAll(); + attachedControllers.remove(remove); + } + + //Add attached devices! + for(Entry entry : connectedDevices.entrySet()){ + String deviceIdentifier = entry.getKey(); + if(!attachedControllers.containsKey(deviceIdentifier)){ + Controller c = null; + switch(entry.getValue()){ + case PureJAVAHid: + try { + c= PureJavaHidController.getInstance(deviceIdentifier); + } catch (ControllerInitializationFailedException e) { + //e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + break; + case LINUX: + try { + c = new LinuxDevInputController(deviceIdentifier); + } catch (ControllerInitializationFailedException e) { + //e.printStackTrace(); + } + break; + /* + * TODO: + * Currently the XInput will be set active automatically. + * But this should move to something for the settings? + */ + case XINPUT14: + try { + c = new XInput14Controller(deviceIdentifier); + c.setActive(true); + } catch (ControllerInitializationFailedException e) { + //e.printStackTrace(); + } + break; + case XINPUT13: + try { + c = new XInput13Controller(deviceIdentifier); + c.setActive(true); + } catch (ControllerInitializationFailedException e) { + //e.printStackTrace(); + } + break; + default: + break; + } + if(c != null){ //I don't like that starting the Thread happens here =/ + new Thread(c).start(); + attachedControllers.put(deviceIdentifier,c); + } + } + } + } + + @Synchronized("attachedControllers") + public static List getAttachedControllers() { + return new ArrayList<>(attachedControllers.values()); + } + + private static Map detectHIDDevices() { + Map connectedDevices = new HashMap<>(); + + for (HidDeviceInfo info : PureJavaHidApi.enumerateDevices()) { + if(info.getUsagePage() == 0x05 || info.getUsagePage() == 0x04 || (info.getVendorId() == 0x57e)){ + connectedDevices.put(info.getPath(),ControllerType.PureJAVAHid); + } + } + + return connectedDevices; + } + + private static Map detectWindowsControllers() { + Map result = new HashMap<>(); + ControllerType type = ControllerType.XINPUT13; + if(XInputDevice.isAvailable() || XInputDevice14.isAvailable()) { + if(XInputDevice14.isAvailable()){ + type = ControllerType.XINPUT14; + } + for(int i =0;i<4;i++){ + 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); + } + } catch (XInputNotLoadedException e) { + //This shouln't happen? + e.printStackTrace(); + } + } + } + + return result; + } + + private static Map detectLinuxControllers() { + Map result = new HashMap<>(); + File devInput = new File("/dev/input"); + if (!devInput.exists()) return result; + + File[] linuxControllers = devInput.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.startsWith("js"); //js0, js1, etc... + } + }); + + for (File controller : linuxControllers) { + result.put(controller.getAbsolutePath(),ControllerType.LINUX); + } + + return result; + } + + @Synchronized("attachedControllers") + public static List getActiveControllers() { + List active = new ArrayList<>(); + for(Controller c : attachedControllers.values()){ + if(c.isActive()){ + active.add(c); + } + } + return active; + } + + @Synchronized("attachedControllers") + public static void deactivateAllAttachedControllers() { + for(Controller c : attachedControllers.values()){ + c.setActive(false); + } + } + +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/NetworkHIDDevice.java b/src/net/ash/HIDToVPADNetworkClient/network/NetworkHIDDevice.java new file mode 100644 index 0000000..c9a3af4 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/NetworkHIDDevice.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import net.ash.HIDToVPADNetworkClient.network.commands.AttachCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.DetachCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.DeviceCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.ReadCommand; +import net.ash.HIDToVPADNetworkClient.util.HandleFoundry; + +public class NetworkHIDDevice { + @Getter private final short vid; + @Getter private final short pid; + + @Getter @Setter private short deviceslot; + @Getter @Setter private byte padslot; + + @Getter private int hidHandle = HandleFoundry.next(); + @Getter(AccessLevel.PRIVATE) private List commands = new ArrayList<>(); + + @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE) private ReadCommand latestRead; + + private Object readCommandLock = new Object(); + + public NetworkHIDDevice(short vid, short pid){ + this.vid = vid; + this.pid = pid; + } + + private void addCommand(DeviceCommand command){ + this.commands.add(command); + } + + private void clearCommands(){ + this.commands.clear(); + } + + public void sendAttach(){ + addCommand(new AttachCommand(getHidHandle(), getVid(), getPid(),this)); + } + + public void sendDetach(){ + addCommand(new DetachCommand(getHidHandle(),this)); + } + + private byte[] lastdata = null; + public void sendRead(byte[] data){ + if(!Arrays.equals(lastdata, data)){ + synchronized (readCommandLock) { + setLatestRead(new ReadCommand(getHidHandle(),data, this)); //Only get the latest Value. + } + lastdata = data.clone(); + } + } + + public Collection getCommandList() { + List commands = new ArrayList<>(); + commands.addAll(getCommands()); + DeviceCommand lastRead; + + synchronized (readCommandLock) { + if((lastRead = getLatestRead()) != null){ + commands.add(lastRead); + setLatestRead(null); + } + } + + clearCommands(); + return commands; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + hidHandle; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NetworkHIDDevice other = (NetworkHIDDevice) obj; + if (hidHandle != other.hidHandle) + return false; + return true; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java b/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java index f4e1b3c..46c6ea6 100644 --- a/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java @@ -19,240 +19,329 @@ * 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.util.ArrayList; +import java.util.List; -import java.util.HashMap; -import java.util.concurrent.ThreadLocalRandom; +import lombok.Getter; +import lombok.Synchronized; +import lombok.extern.java.Log; +import net.ash.HIDToVPADNetworkClient.network.commands.AttachCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.DetachCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.DeviceCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.PingCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.ReadCommand; +import net.ash.HIDToVPADNetworkClient.util.Settings; +import net.ash.HIDToVPADNetworkClient.util.Utilities; -import net.ash.HIDToVPADNetworkClient.controller.Controller; -import net.ash.HIDToVPADNetworkClient.gui.GuiInteractionManager; +@Log +public class NetworkManager implements Runnable{ + private final TCPClient tcpClient = new TCPClient(); + private UDPClient udpClient = null; + + private static NetworkManager instance = null; + + private List ownCommands = new ArrayList<>(); + + private NetworkManager() { + + } + + public static NetworkManager getInstance(){ + if(instance == null){ + instance = new NetworkManager(); + } + return instance; + } + + @Getter private List devices = new ArrayList<>(); + + public void addHIDDevice(NetworkHIDDevice device){ + if(!getDevices().contains(device)){ + synchronized (devices) { + getDevices().add(device); + } + } + } + + /* + * We want to remove them at the end of a cycle. To make sure the detach was send before removing. + */ + @Getter private List toRemove = new ArrayList<>(); + @Synchronized("toRemove") + public void removeHIDDevice(NetworkHIDDevice device) { + device.sendDetach(); + toRemove.add(device); + } -public class NetworkManager implements Runnable { - private static final int PING_INTERVAL = 1000; - - public static int clientID; - - private static NetworkManager instance = null; - - private Object controllersLock = new Object(); - private HashMap controllers = new HashMap(); - - private Thread networkThread; - - private TCPClient tcp; - private UDPClient udp; - - private int packetInterval = 100; - - private enum NetworkState { - DISCONNECTED, - FRESH, //Connected, no handshake - CONNECTED - } - private NetworkState networkState = NetworkState.DISCONNECTED; - - public NetworkManager() throws Exception { - if (instance != null) { - throw new Exception("NetworkManager already has an instance!"); - } - instance = this; - - networkThread = new Thread(this); - tcp = new TCPClient(); - udp = new UDPClient(); - - pingThread.start(); - - clientID = ThreadLocalRandom.current().nextInt(); - System.out.println("[NetworkManager] clientID: " + clientID); - } - - private int runLoopCounter = 0; - @Override - public void run() { - for (;;) { - for (;;) { - /* - * Socket is connected, handshake needed - */ - if (networkState == NetworkState.FRESH) { - try { - switch (tcp.doHandshake()) { - - case BAD_HANDSHAKE: - tcp.abort(); - networkState = NetworkState.DISCONNECTED; - continue; - - case NEW_CLIENT: - synchronized (controllersLock) { - for (Controller c : controllers.values()) { - tcp.sendAttach(c); - } - } - networkState = NetworkState.CONNECTED; - break; - - case SAME_CLIENT: - networkState = NetworkState.CONNECTED; - break; - - default: - tcp.abort(); - networkState = NetworkState.DISCONNECTED; - continue; - - } - } catch (Exception e) { - e.printStackTrace(); - } - } else if (networkState == NetworkState.CONNECTED) { - synchronized (controllersLock) { - try { - udp.send(controllers); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (runLoopCounter++ == PING_INTERVAL / packetInterval) { - synchronized(pingThread) { - pingThread.notify(); - } - runLoopCounter = 0; - } - - sleep(packetInterval); - } else if (networkState == NetworkState.DISCONNECTED) break; - } //for (;;) - - try { - synchronized (networkThread) { - networkThread.wait(); - } - } catch (InterruptedException e) {} - } - } - - public void connect(String ip) { - System.out.println("[NetworkManager] Connecting to " + ip + "..."); //XXX debug text - try { - udp.connect(ip); - tcp.connect(ip); - } catch (Exception e) { - System.err.println("[NetworkManager] Couldn't connect to Wii U!"); - e.printStackTrace(); - return; - } - networkState = NetworkState.FRESH; - if (networkThread.getState() == Thread.State.NEW) { - networkThread.start(); - } else if (networkThread.getState() == Thread.State.WAITING) { - synchronized (networkThread) { - networkThread.notify(); - } - } - } - - public void disconnect() { - networkState = NetworkState.DISCONNECTED; - - if (!Thread.currentThread().equals(networkThread) && networkThread.getState() != Thread.State.NEW) { - while (networkThread.getState() != Thread.State.WAITING) {sleep(1);} - } - - try { - tcp.abort(); - } catch (Exception e) { - e.printStackTrace(); - } - - System.out.println("[NetworkManager] Disconnected."); - } - - /* - * Is there an active connection? - */ - public boolean isConnected() { - return networkState == NetworkState.FRESH || networkState == NetworkState.CONNECTED; - } - - /* - * Is the active connection good for data? - */ - public boolean isRunning() { - return networkState == NetworkState.CONNECTED; - } - - public HashMap getControllers() { - return controllers; - } - - public void setPacketInterval(int packetInterval) { - this.packetInterval = packetInterval; - } - - public void addController(Controller controller) { - synchronized (controllersLock) { - if (isRunning()) { - try { - tcp.sendAttach(controller); - } catch (Exception e) {return;}; - } - controllers.put(controller.getID().hashCode(), controller); - } - } - - public void removeController(Controller controller) { - synchronized (controllersLock) { - if (isRunning()) { - try { - tcp.sendDetach(controller); - } catch (Exception e) {return;}; - } - controller.destroy(); - controllers.remove(controller.getID().hashCode()); - } - } - - public void removeAllControllers() { - synchronized (controllersLock) { - for (Controller c : controllers.values()) { - if (isRunning()) { - try { - tcp.sendDetach(c); - } catch (Exception e) {continue;}; - } - c.destroy(); - controllers.remove(c.getID().hashCode()); - } - } - } - - private Thread pingThread = new Thread(new Runnable() { - public void run() { - for (;;) { - synchronized (pingThread) { - try { - pingThread.wait(); - } catch (InterruptedException e) {} - } - - if (!tcp.ping()) { - System.out.println("[NetworkManager] Ping failed, disconnecting..."); - GuiInteractionManager.instance().disconnect(); - } - } - } - }, "Ping Thread"); - - private void sleep(long ticks) { - try { - Thread.sleep(ticks); - } catch (InterruptedException e) {} - } - - public static NetworkManager instance() { - return instance; - } + @Override + public void run() { + int i = 0; + while(true){ + proccessCommands(); + Utilities.sleep(Settings.PROCESS_CMD_INTERVAL); + if(i++ > Settings.PING_INTERVAL/Settings.PROCESS_CMD_INTERVAL){ + ping(); + i = 0; + } + } + } + + private void ping() { + if(isConnected() || tcpClient.isShouldRetry())sendingCommand(new PingCommand()); + } + + public void proccessCommands(){ + List commands = new ArrayList<>(); + commands.addAll(ownCommands); //TODO: Does this need a synchronized block? It _should_ be only access from this thread. Need to think about it + ownCommands.clear(); + synchronized (toRemove) { + synchronized (devices) { + for(NetworkHIDDevice device : getDevices()){ + commands.addAll(device.getCommandList()); + } + } + } + + if(commands.isEmpty())return; + + //Split up into "read commands" and other commands. + List readCommands = new ArrayList<>(); + { + for(DeviceCommand command : commands){ + if(command instanceof ReadCommand){ + readCommands.add((ReadCommand) command); + } + } + commands.removeAll(readCommands); + } + + if(!readCommands.isEmpty()){ + sendingRead(readCommands); + } + + if(!commands.isEmpty()){ + for(DeviceCommand command : commands){ + sendingCommand(command); + } + } + + synchronized (toRemove) { + synchronized (devices) { + for(NetworkHIDDevice d: toRemove){ + commands.remove(d); + } + } + } + } + + private void sendingCommand(DeviceCommand command) { + boolean result = false; + if(isConnected() || tcpClient.isShouldRetry()){ + if(command instanceof AttachCommand){ + result = sendAttach((AttachCommand) command); + }else if(command instanceof DetachCommand){ + result = sendDetach((DetachCommand) command); + }else if(command instanceof PingCommand){ + sendPing((PingCommand) command); + result = true; + }else{ + log.info("UNKNOWN COMMAND!"); + result = true; + } + }else{ + Utilities.sleep(Settings.SENDING_CMD_SLEEP_IF_NOT_CONNECTED); //TODO: move magic value to Settings + } + + //Add the command again on errors + if(!result){ + addCommand(command); + } + } + + //TODO: PONG from WiiU? Hey Quark ;) + private void sendPing(PingCommand command) { + if(sendTCP(Protocol.getRawPingDataToSend(command))){ + log.info("PING"); + }else{ + log.info("Sending the PING failed"); + } + } + + private boolean sendDetach(DetachCommand command) { + byte[] sendData; + try { + sendData = Protocol.getRawDetachDataToSend(command); + if(sendTCP(sendData)){ + log.info("Success detach command for device (" + command.getSender() + ") sent!"); + }else{ + log.info("Sending the detach command for device (" + command.getSender() + ") failed. sendTCP failed"); + return false; + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + //TODO: Maybe move it into the Protocol class? + private boolean sendAttach(AttachCommand command) { + //Send the TCP command + byte[] sendData = null; + try { + sendData = Protocol.getRawAttachDataToSend(command); + }catch (IOException e) { + log.info("Building the attach command for device (" + command.getSender() + ") failed." + e.getMessage()); + return false; + } + if(sendTCP(sendData)){ + byte configFound = 0; + try { + configFound = recvTCPByte(); + } catch (IOException e1) { + e1.printStackTrace(); + } + if(configFound == Protocol.TCP_CMD_ATTACH_CONFIG_FOUND){ + //log.info("Config on the console found!"); + }else if(configFound == Protocol.TCP_CMD_ATTACH_CONFIG_NOT_FOUND){ + log.info("NO CONFIG FOUND."); + return false; + }else if (configFound == 0){ + log.info("Failed to get byte."); + disconnect(); + return false; + } + + byte userDataOkay = 0; + try { + userDataOkay = recvTCPByte(); + } catch (IOException e1) { + e1.printStackTrace(); + } + if(userDataOkay == Protocol.TCP_CMD_ATTACH_USERDATA_OKAY){ + //log.info("userdata okay!"); + }else if(userDataOkay == Protocol.TCP_CMD_ATTACH_USERDATA_BAD){ + log.info("USERDATA BAD."); + return false; + }else if (userDataOkay == 0){ + log.info("Failed to get byte."); + disconnect(); + return false; + } + + //We receive our device slot and pad slot + short deviceslot = -1; + byte padslot = -1; + try { + deviceslot = recvTCPShort(); + padslot = recvTCPByte(); + } catch (IOException e) { + log.info("Recieving data after sending a attach failed for device (" + command.getSender() + ") failed." + e.getMessage()); + return false; + } + + if(deviceslot < 0 || padslot < 0){ + log.info("Recieving data after sending a attach failed for device (" + command.getSender() + ") failed. We need to disconnect =("); + disconnect(); + return false; + } + + //Let's save them for later. + NetworkHIDDevice sender = command.getSender(); + if(sender != null){ + sender.setDeviceslot(deviceslot); + sender.setPadslot(padslot); + }else{ + log.info("Something really went wrong. Got an attach event with out an " + NetworkHIDDevice.class.getSimpleName()); + return false; + } + log.info("Attaching done!"); + return true; + }else{ + log.info("Sending the attach command for device (" + command.getSender() + ") failed. sendTCP failed"); + return false; + } + } + + private void sendingRead(List readCommands) { + byte[] rawCommand; + try { + rawCommand = Protocol.getRawReadDataToSend(readCommands); + System.out.println("UDP Packet: "+ Utilities.ByteArrayToString(rawCommand)); + sendUDP(rawCommand); + } catch (IOException e) { + System.out.println("Sending read data failed."); + } + } + + private boolean sendUDP(byte[] rawCommand) { + boolean result = false; + if(udpClient != null){ + try { + udpClient.send(rawCommand); + result = true; + } catch (Exception e) { + // + result = false; + } + } + return result; + } + + private boolean sendTCP(byte[] rawCommand) { + boolean result = false; + if(tcpClient != null){ + try { + tcpClient.send(rawCommand); + result = true; + } catch (Exception e) { + result = false; + } + } + return result; + } + + public void disconnect() { + //ControllerManager.deactivateAllAttachedControllers(); + tcpClient.abort(); + } + + private short recvTCPShort() throws IOException { + return tcpClient.recvShort(); + } + + private byte recvTCPByte() throws IOException { + return tcpClient.recvByte(); + } + + public boolean isConnected() { + return (tcpClient != null && tcpClient.isConnected()); + } + + public boolean connect(String ip) { + boolean result = false; + log.info("Trying to connect to: " + ip); + try { + tcpClient.connect(ip); + System.out.println("TCP Connected!"); + udpClient = UDPClient.createUDPClient(ip); + if(udpClient != null){ + result = true; + } + } catch (Exception e) { + System.out.println("Error while connecting: " + e.getMessage()); + } + return result; + } + + public void addCommand(DeviceCommand command) { + this.ownCommands.add(command); + } + + public boolean isReconnecting() { + return !tcpClient.isConnected() && tcpClient.isShouldRetry(); + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java b/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java index 00bc96a..7695a66 100644 --- a/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java @@ -21,6 +21,19 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.network; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +import lombok.extern.java.Log; +import net.ash.HIDToVPADNetworkClient.network.commands.AttachCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.DetachCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.PingCommand; +import net.ash.HIDToVPADNetworkClient.network.commands.ReadCommand; + +@Log public class Protocol { public static final int TCP_PORT = 8112; public static final int UDP_PORT = 8113; @@ -34,4 +47,65 @@ public class Protocol { public static final byte TCP_CMD_PING = (byte)0xF0; public static final byte UDP_CMD_DATA = 0x03; + + + public static final byte TCP_CMD_ATTACH_CONFIG_FOUND = (byte) 0xE0; + public static final byte TCP_CMD_ATTACH_CONFIG_NOT_FOUND = (byte) 0xE1; + public static final byte TCP_CMD_ATTACH_USERDATA_OKAY = (byte) 0xE8; + public static final byte TCP_CMD_ATTACH_USERDATA_BAD = (byte) 0xE9; + + private Protocol(){} + + public enum HandshakeReturnCode { + BAD_HANDSHAKE, + SAME_CLIENT, + NEW_CLIENT + } + + public static byte[] getRawAttachDataToSend(AttachCommand command) throws IOException { + return ByteBuffer.allocate(9) + .put(Protocol.TCP_CMD_ATTACH) + .putInt(command.getHandle()) + .putShort(command.getVid()) + .putShort(command.getPid()) + .array(); + } + + public static byte[] getRawDetachDataToSend(DetachCommand command) throws IOException { + return ByteBuffer.allocate(5) + .put(Protocol.TCP_CMD_DETACH) + .putInt(command.getHandle()) + .array(); + } + + public static byte[] getRawPingDataToSend(PingCommand command){ + return new byte[]{Protocol.TCP_CMD_PING}; + } + + public static byte[] getRawReadDataToSend(List readCommands) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeByte(Protocol.UDP_CMD_DATA); + dos.writeByte(readCommands.size()); + + for(ReadCommand command : readCommands){ + NetworkHIDDevice sender = command.getSender(); + byte[] data = command.getData(); + if(data.length > 0xFF){ + log.info("Tried to send too much data. Maximum is 0xFF bytes read command."); + continue; + } + + byte newLength = (byte)(data.length & 0xFF); + + dos.writeInt(command.getHandle()); + dos.writeShort(sender.getDeviceslot()); + dos.writeByte(sender.getPadslot()); + + dos.write(newLength); + dos.write(data, 0, newLength); + } + + return bos.toByteArray(); + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java b/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java index 5ebb771..d17a3f4 100644 --- a/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java @@ -26,79 +26,122 @@ import java.io.DataOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; +import java.nio.ByteBuffer; -import net.ash.HIDToVPADNetworkClient.controller.Controller; -import net.ash.HIDToVPADNetworkClient.controller.ControllerData; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.java.Log; +import net.ash.HIDToVPADNetworkClient.manager.ActiveControllerManager; +import net.ash.HIDToVPADNetworkClient.network.Protocol.HandshakeReturnCode; +import net.ash.HIDToVPADNetworkClient.util.HandleFoundry; +import net.ash.HIDToVPADNetworkClient.util.Settings; +@Log public class TCPClient { private Socket sock; private DataInputStream in; private DataOutputStream out; + @Getter private int clientID = HandleFoundry.next(); + + @Getter @Setter(AccessLevel.PRIVATE) + private int shouldRetry = Settings.MAXIMUM_TRIES_FOR_RECONNECTING; + + private String ip; public TCPClient() { } public synchronized void connect(String ip) throws Exception { + sock = new Socket(); sock.connect(new InetSocketAddress(ip, Protocol.TCP_PORT), 2000); in = new DataInputStream(sock.getInputStream()); out = new DataOutputStream(sock.getOutputStream()); + + HandshakeReturnCode resultHandshake = doHandshake(); + if(resultHandshake == HandshakeReturnCode.BAD_HANDSHAKE){ + log.info("[TCP] Handshaking failed"); + throw new Exception(); + }else{ + if(resultHandshake == HandshakeReturnCode.NEW_CLIENT && this.ip != null){ + //We check the IP to be sure it's the first time we connect to a WiiU. //TODO: Sending a ID from the WiiU which will be compared? + //we are new to the client. + ActiveControllerManager.getInstance().attachAllActiveControllers(); + }else if(resultHandshake == HandshakeReturnCode.SAME_CLIENT){ + + } + this.ip = ip; + shouldRetry = 0; + } } - public enum HandshakeReturnCode { - BAD_HANDSHAKE, - SAME_CLIENT, - NEW_CLIENT + private synchronized HandshakeReturnCode doHandshake() throws Exception { + if (recvByte() != Protocol.TCP_HANDSHAKE) return HandshakeReturnCode.BAD_HANDSHAKE; + send(clientID); + log.info("[TCP] Handshaking..."); + HandshakeReturnCode test = (recvByte() == Protocol.TCP_NEW_CLIENT) ? HandshakeReturnCode.NEW_CLIENT : HandshakeReturnCode.SAME_CLIENT; + return test; } - public synchronized HandshakeReturnCode doHandshake() throws Exception { - if (in.readByte() != Protocol.TCP_HANDSHAKE) return HandshakeReturnCode.BAD_HANDSHAKE; - - out.writeInt(NetworkManager.clientID); - out.flush(); - System.out.println("[TCP] Handshaking..."); - return (in.readByte() == Protocol.TCP_NEW_CLIENT) ? HandshakeReturnCode.NEW_CLIENT : HandshakeReturnCode.SAME_CLIENT; - } - - public synchronized void sendAttach(Controller c) throws Exception { - System.out.println("[TCPClient] Attach " + c); //XXX debug text - out.writeByte(Protocol.TCP_CMD_ATTACH); - - - out.writeInt(c.getHandle()); - ControllerData d = c.getLatestData(); //GetLatestData allocates a new ControllerData - out.writeShort(d.getVID()); - out.writeShort(d.getPID()); - out.flush(); - - short deviceSlot = in.readShort(); - byte padSlot = in.readByte(); - c.setSlotData(deviceSlot, padSlot); - - System.out.println("Attached! deviceSlot: " + Integer.toHexString((int)deviceSlot & 0xFFFF) + " padSlot: " + Integer.toHexString((int)padSlot & 0xFF)); - } - - public synchronized void sendDetach(Controller c) throws Exception { - System.out.println("[TCPClient] Detach " + c); - out.write(Protocol.TCP_CMD_DETACH); - - out.writeInt(c.getHandle()); - out.flush(); - } - - public synchronized boolean ping() { - //System.out.println("Ping!"); + public synchronized boolean abort(){ try { - out.writeByte(Protocol.TCP_CMD_PING); - out.flush(); - } catch (IOException e) { - return false; - } - //TODO convince Maschell to make the client actually respond to pings + shouldRetry = Settings.MAXIMUM_TRIES_FOR_RECONNECTING; + sock.close(); + clientID = HandleFoundry.next(); + } catch (IOException e) { + System.out.println(e.getMessage()); //TODO: handle + return false; + } return true; } - - public synchronized void abort() throws Exception { - sock.close(); - } + + public synchronized void send(byte[] rawCommand) throws IOException { + try{ + out.write(rawCommand); + out.flush(); + }catch(IOException e){ + try { + if(shouldRetry++ < Settings.MAXIMUM_TRIES_FOR_RECONNECTING){ + System.out.println("Trying again to connect! Attempt number " + shouldRetry); + connect(ip); //TODO: this is for reconnecting when the WiiU switches the application. But this breaks disconnecting, woops. + }else{ + abort(); + } + } catch (Exception e1) { + //e1.printStackTrace(); + } + throw e; + } + } + + public synchronized void send(int value) throws IOException { + send(ByteBuffer.allocate(4).putInt(value).array()); + } + + public synchronized byte recvByte() throws IOException { + try{ + return in.readByte(); + }catch(IOException e){ + System.out.println(e.getMessage()); + throw e; + } + } + + public synchronized short recvShort() throws IOException { + try{ + return in.readShort(); + }catch(IOException e){ + System.out.println(e.getMessage()); + throw e; + } + } + + public synchronized boolean isConnected(){ + return (sock != null && sock.isConnected() && !sock.isClosed()); + } + + public boolean isShouldRetry() { + return this.shouldRetry < Settings.MAXIMUM_TRIES_FOR_RECONNECTING; + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java b/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java index f41ea5f..41951a4 100644 --- a/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java @@ -21,61 +21,33 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.network; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; +import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; -import java.util.HashMap; - -import net.ash.HIDToVPADNetworkClient.controller.Controller; -import net.ash.HIDToVPADNetworkClient.controller.ControllerData; +import java.net.SocketException; +import java.net.UnknownHostException; public class UDPClient { - private DatagramSocket sock; - private InetAddress host; + private final DatagramSocket sock; + private final InetAddress host; - private ByteArrayOutputStream outBytes; - private DataOutputStream out; - - public UDPClient() { - outBytes = new ByteArrayOutputStream(); - out = new DataOutputStream(outBytes); + private UDPClient(String ip) throws SocketException, UnknownHostException{ + sock = new DatagramSocket(); + host = InetAddress.getByName(ip); + } + public static UDPClient createUDPClient(String ip){ + UDPClient result = null; + try { + result = new UDPClient(ip); + } catch (Exception e) { + //handle? + } + return result; } - public void connect(String ip) throws Exception { - sock = new DatagramSocket(); - host = InetAddress.getByName(ip); - } - - public void send(HashMap controllers) throws Exception { - out.writeByte(Protocol.UDP_CMD_DATA); - - out.writeByte((byte)(controllers.size() & 0xFF)); - - for (Controller c : controllers.values()) { - out.writeInt(c.getHandle()); - out.writeShort(c.getDeviceSlot()); - out.writeByte(c.getPadSlot()); - - ControllerData d = c.getLatestData(); - try { - out.writeInt(d.getData().length); - out.write(d.getData(), 0, d.getData().length); - } catch (NullPointerException e) { - out.writeInt(1); - out.writeByte(0x00); - } - } - - out.flush(); - byte[] payload = outBytes.toByteArray(); - DatagramPacket packet = new DatagramPacket(payload, payload.length, host, Protocol.UDP_PORT); - + public void send(byte[] data) throws IOException { + DatagramPacket packet = new DatagramPacket(data, data.length, host, Protocol.UDP_PORT); sock.send(packet); - - //System.out.println(Arrays.toString(payload)); //XXX debug text - - outBytes.reset(); } -} +} \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java b/src/net/ash/HIDToVPADNetworkClient/network/commands/AttachCommand.java similarity index 63% rename from src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java rename to src/net/ash/HIDToVPADNetworkClient/network/commands/AttachCommand.java index 34b8399..b0fe1b5 100644 --- a/src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java +++ b/src/net/ash/HIDToVPADNetworkClient/network/commands/AttachCommand.java @@ -19,41 +19,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ -package net.ash.HIDToVPADNetworkClient.gui; +package net.ash.HIDToVPADNetworkClient.network.commands; +import lombok.Getter; +import net.ash.HIDToVPADNetworkClient.network.NetworkHIDDevice; -public class GuiController { - private boolean activeState = false; - private GuiControllerType type; - private Object id; - - public GuiController(GuiControllerType type, Object id) { - this.type = type; - this.id = id; - } - - public Object getId() { - return id; - } - - public GuiControllerType getType() { - return type; - } - - public boolean getActiveState() { - return activeState; - } - - public void setActiveState(boolean activeState) { - this.activeState = activeState; - } - - @Override - public String toString() { - return "GuiController, active: " + activeState + ", type: " + type.toString() + ", id: " + id.toString(); - } - - @Override - public int hashCode() { - return id.hashCode() + type.hashCode() /*+ (activeState ? 1 : 0)*/; - } +public class AttachCommand extends DeviceCommand { + @Getter private final short vid; + @Getter private final short pid; + + public AttachCommand(int hidHandle, short vid, short pid, NetworkHIDDevice sender) { + super(hidHandle,sender); + this.vid = vid; + this.pid = pid; + } + + @Override + public String toString() { + return "AttachCommand [vid=" + vid + ", pid=" + pid+ ", handle=" + getHandle() + ", sender=" + getSender() + "]"; + } } diff --git a/src/net/ash/HIDToVPADNetworkClient/network/commands/DetachCommand.java b/src/net/ash/HIDToVPADNetworkClient/network/commands/DetachCommand.java new file mode 100644 index 0000000..54aa0ca --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/commands/DetachCommand.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.commands; + +import net.ash.HIDToVPADNetworkClient.network.NetworkHIDDevice; + +public class DetachCommand extends DeviceCommand{ + public DetachCommand(int hidHandle, NetworkHIDDevice sender){ + super(hidHandle,sender); + } + + @Override + public String toString() { + return "DetachCommand [handle=" + getHandle() + ", sender=" + getSender() + "]"; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/commands/DeviceCommand.java b/src/net/ash/HIDToVPADNetworkClient/network/commands/DeviceCommand.java new file mode 100644 index 0000000..02395c9 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/commands/DeviceCommand.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.commands; +import lombok.Data; +import net.ash.HIDToVPADNetworkClient.network.NetworkHIDDevice; + +@Data +public abstract class DeviceCommand { + private final int handle; + private final NetworkHIDDevice sender; + private final Class type; + + protected DeviceCommand(int hidHandle,NetworkHIDDevice sender){ + this.handle = hidHandle; + this.sender = sender; + this.type = this.getClass(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/commands/PingCommand.java b/src/net/ash/HIDToVPADNetworkClient/network/commands/PingCommand.java new file mode 100644 index 0000000..35ff8e5 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/commands/PingCommand.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.commands; + +import net.ash.HIDToVPADNetworkClient.network.NetworkHIDDevice; + +public class PingCommand extends DeviceCommand { + public PingCommand() { + this((short) 0,null); + } + private PingCommand(int hidHandle, NetworkHIDDevice sender) { + super(hidHandle, sender); + } + + @Override + public String toString() { + return "PingCommand []"; + } +} \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/network/commands/ReadCommand.java b/src/net/ash/HIDToVPADNetworkClient/network/commands/ReadCommand.java new file mode 100644 index 0000000..eb18cd5 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/commands/ReadCommand.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.commands; + +import lombok.Getter; +import net.ash.HIDToVPADNetworkClient.network.NetworkHIDDevice; + +public class ReadCommand extends DeviceCommand{ + @Getter private final byte[] data; + public ReadCommand(int hidHandle,byte[] data, NetworkHIDDevice sender) { + super(hidHandle, sender); + this.data = data; + } + + @Override + public String toString() { + return "ReadCommand [handle=" + getHandle() + ", sender=" + getSender() + "]"; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java b/src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java deleted file mode 100644 index c54227a..0000000 --- a/src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * 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.util; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -public class BlockingIOStabbifier extends InputStream implements Runnable { - private byte[] buffer; - private BufferedInputStream file; - private ByteArrayOutputStream baos; - private boolean running; - - int baosSize = 0; - int bufferRemaining = 0; - int bufferOffset = -1; - - public void run() { - while (running) { - try { - synchronized (baos) { - baos.write(file.read()); - baosSize++; - } - Thread.sleep(1); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void allocate() { - synchronized (baos) { - buffer = baos.toByteArray(); - bufferRemaining = baos.size(); - bufferOffset = -1; - baos.reset(); - baosSize = 0; - } - } - - @Override - public int read() throws IOException { - if (bufferRemaining == 0) { - allocate(); - if (bufferRemaining == 0) { - return -1; - } - } - bufferRemaining--; - bufferOffset++; - return buffer[bufferOffset] & 0xFF; - } - - @Override - public boolean markSupported() { - return false; - } - - @Override - public void close() throws IOException { - running = false; - file.close(); - baos.close(); - super.close(); - } - - @Override - public int available() { - return bufferRemaining + baosSize; - } - - public BlockingIOStabbifier(String path) throws FileNotFoundException { - file = new BufferedInputStream(new FileInputStream(path)); - - baos = new ByteArrayOutputStream(); - buffer = new byte[0]; - - running = true; - new Thread(this).start(); - } -} diff --git a/src/net/ash/HIDToVPADNetworkClient/util/HandleFoundry.java b/src/net/ash/HIDToVPADNetworkClient/util/HandleFoundry.java index 6abcc63..a68121c 100644 --- a/src/net/ash/HIDToVPADNetworkClient/util/HandleFoundry.java +++ b/src/net/ash/HIDToVPADNetworkClient/util/HandleFoundry.java @@ -25,10 +25,15 @@ package net.ash.HIDToVPADNetworkClient.util; +import java.util.concurrent.ThreadLocalRandom; + public class HandleFoundry { - private static int h = 1; + //We start with a random value, so we have at each startup a different clientID! + private static int h = ThreadLocalRandom.current().nextInt(1, 10000); + + private HandleFoundry(){} public static int next() { return h++; } -} +} \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java b/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java new file mode 100644 index 0000000..ab60196 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/util/PureJavaHidApiManager.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * 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.util; + +import java.io.IOException; +import java.util.List; + +import purejavahidapi.HidDevice; +import purejavahidapi.HidDeviceInfo; +import purejavahidapi.PureJavaHidApi; + +public class PureJavaHidApiManager { + + private PureJavaHidApiManager(){} + + /** + * Searches the corresponding HIDDevice for the given path + * @param path Path of the HIDDevice + * @return It the device is found, it will be returned. Otherwise null is returned. + * @throws IOException + */ + public static HidDevice getDeviceByPath(String path) throws IOException{ + List devList = PureJavaHidApi.enumerateDevices(); + for (HidDeviceInfo info : devList) { + if(info.getPath().equals(path)){ + return PureJavaHidApi.openDevice(info); + } + } + return null; + } +} \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java b/src/net/ash/HIDToVPADNetworkClient/util/Settings.java similarity index 71% rename from src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java rename to src/net/ash/HIDToVPADNetworkClient/util/Settings.java index 0aee430..919fc1c 100644 --- a/src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java +++ b/src/net/ash/HIDToVPADNetworkClient/util/Settings.java @@ -21,32 +21,15 @@ *******************************************************************************/ package net.ash.HIDToVPADNetworkClient.util; -public class WakeupThread extends Thread { - private Object wakeup; - private int timeout; - private boolean run; - public WakeupThread(Object wakeup) { - super(); - this.wakeup = wakeup; - run = true; - } - public void setTimeout(int timeout) { - this.timeout = timeout; - } - public void tryStop() { - run = false; - } - @Override - public void run() { - while (run) { - synchronized (wakeup) { - wakeup.notify(); - } - try { - Thread.sleep(timeout); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } +public class Settings { + public static final int DETECT_CONTROLLER_INTERVAL = 1000; + public static final int HANDLE_INPUTS_INTERVAL = 15; + public static final int MAXIMUM_TRIES_FOR_RECONNECTING = 10; + public static final int SLEEP_AFER_POLLING = 10; + public static final int SENDING_CMD_SLEEP_IF_NOT_CONNECTED = 500; + public static final int PING_INTERVAL = 1000; + public static final int PROCESS_CMD_INTERVAL = 10; + + private Settings(){} + } diff --git a/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java b/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java new file mode 100644 index 0000000..c66b1d3 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/util/Utilities.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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.util; + +public class Utilities{ + + private Utilities(){} + + /** + * Let me just sleep! + * @param ms sleep duration in ms + */ + public static void sleep(int ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + } + } + + /** + * Convert a byte array to a formated String + * @param ba byte array + * @return String representing the binary data + */ + public static String ByteArrayToString(byte[] ba){ + if(ba == null) return null; + StringBuilder hex = new StringBuilder(ba.length * 2); + for(byte b : ba){ + hex.append(String.format("%02X", b)); + } + return hex.toString(); + } + + /** + * Converts a signed short value to a unsigned byte + * @param value short value + * @return converted value + */ + public static short signedShortToByte(int value){ + return (short) (((((short)value) + Short.MAX_VALUE + 1) >> 8) & 0xFF); + } + + /** + * Converts a signed short value to a unsigned byte + * @param value short value + * @return converted value + */ + public static short signedShortToByte(short value){ + return signedShortToByte((int) value); + } +}