From fff503652ff0a5ce95906a6a3b6d422e6bc724ca Mon Sep 17 00:00:00 2001 From: Ash Date: Tue, 31 Jan 2017 19:12:28 +1100 Subject: [PATCH] Initial commit --- .classpath | 20 ++ .gitignore | 16 ++ .project | 23 ++ pom.xml | 26 ++ src/net/ash/HIDToVPADNetworkClient/Main.java | 69 +++++ .../controller/Controller.java | 99 +++++++ .../controller/ControllerData.java | 48 ++++ .../controller/ControllerManager.java | 126 +++++++++ .../controller/Hid4JavaController.java | 111 ++++++++ .../controller/LinuxDevInputController.java | 227 +++++++++++++++ .../LinuxDevInputControllerLegacyDriver.java | 187 +++++++++++++ .../gui/ControllerDetector.java | 89 ++++++ .../gui/GuiCloseListener.java | 53 ++++ .../gui/GuiController.java | 59 ++++ .../gui/GuiControllerList.java | 106 +++++++ .../gui/GuiControllerListItem.java | 74 +++++ .../gui/GuiControllerType.java | 27 ++ .../gui/GuiInputControls.java | 115 ++++++++ .../gui/GuiInteractionManager.java | 105 +++++++ .../HIDToVPADNetworkClient/gui/GuiMain.java | 94 +++++++ .../network/NetworkManager.java | 258 ++++++++++++++++++ .../network/Protocol.java | 37 +++ .../network/TCPClient.java | 104 +++++++ .../network/UDPClient.java | 81 ++++++ .../util/BlockingIOStabbifier.java | 105 +++++++ .../util/HandleFoundry.java | 34 +++ .../util/WakeupThread.java | 52 ++++ 27 files changed, 2345 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 pom.xml create mode 100644 src/net/ash/HIDToVPADNetworkClient/Main.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/controller/Controller.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/controller/Hid4JavaController.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputController.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/ControllerDetector.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiCloseListener.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/gui/GuiMain.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/network/Protocol.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/util/HandleFoundry.java create mode 100644 src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..149cb3c --- /dev/null +++ b/.classpath @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3adf196 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +bin/ +target/ +.settings/ diff --git a/.project b/.project new file mode 100644 index 0000000..ce934f0 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + HIDToVPADNetworkClient + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e8f7344 --- /dev/null +++ b/pom.xml @@ -0,0 +1,26 @@ + + 4.0.0 + net.ash + HIDToVPADNetworkClient + 0.0.1-SNAPSHOT + + src + + + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + + + + org.hid4java + hid4java + 0.4.0 + + + \ No newline at end of file diff --git a/src/net/ash/HIDToVPADNetworkClient/Main.java b/src/net/ash/HIDToVPADNetworkClient/Main.java new file mode 100644 index 0000000..bfb8759 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/Main.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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; + +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.network.NetworkManager; + +/* Ash's todo list + * TODO finish Hid4JavaController + * TODO locale + */ + +public class Main { + public static void main(String[] args) { + System.out.println("Hello World!"); + + try { + new ControllerManager(); + new NetworkManager(); + } catch (Exception e) { + e.printStackTrace(); + fatal(); + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + GuiMain.createGUI(); + } + }); + + new ControllerDetector().start(); + } + + 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 new file mode 100644 index 0000000..b95ab9a --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/Controller.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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; + +/** + * Main controller interface, extended by controller drivers. + *

+ * See {@link LinuxDevInputController} for a full implementation. + * @author ash + */ +public interface Controller { + /** + * Sets up the driver. + *
+ * During this method call, a connection will be made with the controller hardware (if required). + * + * @param arg Driver-specific init argument, see {@link ControllerManager} and {@link ControllerDetector}. + * @return Whether initialization was successful. + */ + public boolean initController(Object arg); + + /** + * Allocates and returns a copy of the latest data available from the controller. + * + * @return A ControllerData instance containing the latest controller data. + */ + public ControllerData getLatestData(); + + /** + * 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. + *
+ * 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(); +} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java b/src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java new file mode 100644 index 0000000..cb00c16 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/ControllerData.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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; + +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; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java b/src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java new file mode 100644 index 0000000..3ddf81e --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/ControllerManager.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * 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 new file mode 100644 index 0000000..8b9948c --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/Hid4JavaController.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * 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 new file mode 100644 index 0000000..d0ff86b --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputController.java @@ -0,0 +1,227 @@ +/******************************************************************************* + * 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.BufferedInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; + +import net.ash.HIDToVPADNetworkClient.util.HandleFoundry; + +public class LinuxDevInputController extends Thread implements Controller { + 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; + + 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]); + } + + @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 (;;) + } + + private long buttonState = 0; + private byte[] axisState = new byte[NUM_SUPPORTED_AXIS]; + private void processNextControllerEvent() { + //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(); + } catch (IOException e) { + System.err.println("[LinuxDevInputController] Couldn't read from controller!"); + e.printStackTrace(); + System.out.println("[LinuxDevInputController] Detaching..."); + ControllerManager.instance().detachController(this); + return; + } + + //Treat init events as normal (clear init bit) + type &= ~JS_EVENT_INIT; + + if (type == JS_EVENT_BUTTON) { + if (number >= Long.SIZE) { + System.out.println("[LinuxDevInputController] Button number " + number + " out of range; ignoring"); + return; + } + + if (value != 0) { + //Set bit with button number + buttonState |= (1 << number); + } else { + //Clear bit with button number + buttonState &= ~(1 << number); + } + } else if (type == JS_EVENT_AXIS) { + if (number >= NUM_SUPPORTED_AXIS) { + System.out.println("[LinuxDevInputController] Axis number " + number + " out of range; ignoring"); + return; + } + //Do byteswap + value = (short)(((value & 0xFF) << Byte.SIZE) | ((value & 0xFF00) >> Byte.SIZE)); + //Convert to unsigned byte and store + axisState[number] = (byte)(((value + Short.MAX_VALUE + 1) >> 8) & 0xFF); + } + + byte[] newData = new byte[CONTROLLER_DATA_SIZE]; + //Copy in button states + for (int i = 0; i < Long.BYTES; i++) { + newData[i] = (byte)((buttonState >> (i * Byte.SIZE)) & 0xFF); + } + //Copy in axis data + for (int i = Long.BYTES; i < CONTROLLER_DATA_SIZE; i++) { + newData[i] = axisState[i - Long.BYTES]; + } + synchronized (controllerData) { + controllerData.data = newData; + } + } + + @Override + public ControllerData getLatestData() { + synchronized (controllerData) { + return new ControllerData(getVID(), getPID(), getHandle(), controllerData.getData()); + } + } + + @Override + public boolean initController(Object arg) { + 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; + } + + @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]"); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java b/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java new file mode 100644 index 0000000..ce7c694 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/controller/LinuxDevInputControllerLegacyDriver.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * 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/gui/ControllerDetector.java b/src/net/ash/HIDToVPADNetworkClient/gui/ControllerDetector.java new file mode 100644 index 0000000..4fa7527 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/ControllerDetector.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * 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 new file mode 100644 index 0000000..41148eb --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiCloseListener.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * 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.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 windowClosed(WindowEvent arg0) {} + + @Override + public void windowDeactivated(WindowEvent arg0) {} + + @Override + public void windowDeiconified(WindowEvent arg0) {} + + @Override + public void windowIconified(WindowEvent arg0) {} + + @Override + public void windowOpened(WindowEvent arg0) {} +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java new file mode 100644 index 0000000..34b8399 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiController.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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; + +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)*/; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java new file mode 100644 index 0000000..03402cd --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerList.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.swing.AbstractButton; +import javax.swing.BoxLayout; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +public class GuiControllerList extends JPanel { + private static final long serialVersionUID = 1L; + + private JPanel innerScrollPanel; + private ActionListener currentActionListener = null; + + public GuiControllerList() { + super(new BorderLayout()); + + 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); + } + + public synchronized List getControllers() { + List controllers = new ArrayList(); + + 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()); + } + } + + List newComponents = new ArrayList(); + for (GuiController controller : controllers) { + GuiControllerListItem i; + if (components.containsKey(controller.hashCode())) { + i = new GuiControllerListItem(components.get(controller.hashCode())); + } else { + i = new GuiControllerListItem(controller); + } + 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!"); + } + } + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java new file mode 100644 index 0000000..ef6db03 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerListItem.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionListener; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; + +public class GuiControllerListItem extends JPanel { + private static final long serialVersionUID = 1L; + + private GuiController data = null; + private JCheckBox checkbox; + + public GuiControllerListItem(GuiController data) { + super(new BorderLayout()); + + setMinimumSize(new Dimension (300, 30)); + setPreferredSize(new Dimension(300, 30)); + setMaximumSize(new Dimension(2000, 30)); + + this.data = data; + + checkbox = new JCheckBox(getFlavorText()); + checkbox.setSelected(data.getActiveState()); + add(checkbox); + } + + 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(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java new file mode 100644 index 0000000..297aa43 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiControllerType.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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; + +public enum GuiControllerType { + HID4JAVA, + LINUX +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java new file mode 100644 index 0000000..999a7f3 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInputControls.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * 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.Dimension; +import java.awt.FlowLayout; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +public class GuiInputControls extends JPanel { + private static final long serialVersionUID = 1L; + private static GuiInputControls instance = null; + + private static final String DEFAULT_PACKET_INTERVAL = "1000"; + + 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) { + throw new Exception("GuiInputControls already has an instance!"); + } + instance = this; + + setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); + setPreferredSize(new Dimension(220, 150)); + + connectButton = new JButton("Connect"); + connectButton.setAlignmentX(Component.CENTER_ALIGNMENT); + + + ipTextBox = new JTextField(); + ipTextBox.setColumns(15); + 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(Box.createRigidArea(new Dimension(1, 4))); + add(connectButton); + add(Box.createRigidArea(new Dimension(1, 8))); + + add(statusLabel); + + add(Box.createVerticalGlue()); + + connectButton.addActionListener(GuiInteractionManager.instance()); + } + + public static GuiInputControls instance() { + return instance; + } + + public JTextField getPacketIntervalTextBox() { + return packetIntervalTextBox; + } + + public JTextField getIpTextBox() { + return ipTextBox; + } + + public JButton getConnectButton() { + return connectButton; + } + + public JLabel getStatusLabel() { + return statusLabel; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java new file mode 100644 index 0000000..3f3ef75 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiInteractionManager.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * 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 new file mode 100644 index 0000000..bb36d4d --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/gui/GuiMain.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * 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.BorderLayout; +import java.awt.Dimension; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JPanel; + +import net.ash.HIDToVPADNetworkClient.Main; + +public class GuiMain extends JPanel { + private static final long serialVersionUID = 1L; + private static GuiMain instance; + + public static void createGUI() { + JFrame frame = new JFrame("HID To VPAD Network Client"); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.addWindowListener(new GuiCloseListener()); + + instance = new GuiMain(); + JComponent newContentPane = instance; + newContentPane.setOpaque(true); + frame.setContentPane(newContentPane); + + frame.pack(); + frame.setVisible(true); + } + + private GuiControllerList leftControllerList; + private GuiInputControls rightSideControls; + 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(); + } catch (Exception e) { + e.printStackTrace(); + Main.fatal(); + } + 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/network/NetworkManager.java b/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java new file mode 100644 index 0000000..f4e1b3c --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/NetworkManager.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * 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.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.gui.GuiInteractionManager; + +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; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java b/src/net/ash/HIDToVPADNetworkClient/network/Protocol.java new file mode 100644 index 0000000..00bc96a --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/Protocol.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; + +public class Protocol { + public static final int TCP_PORT = 8112; + public static final int UDP_PORT = 8113; + + public static final byte TCP_HANDSHAKE = 0x12; + public static final byte TCP_SAME_CLIENT = 0x20; + public static final byte TCP_NEW_CLIENT = 0x21; + + public static final byte TCP_CMD_ATTACH = 0x01; + public static final byte TCP_CMD_DETACH = 0x02; + public static final byte TCP_CMD_PING = (byte)0xF0; + + public static final byte UDP_CMD_DATA = 0x03; +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java b/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java new file mode 100644 index 0000000..5ebb771 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/TCPClient.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2017 Ash (QuarkTheAwesome) & Maschell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ +package net.ash.HIDToVPADNetworkClient.network; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +import net.ash.HIDToVPADNetworkClient.controller.Controller; +import net.ash.HIDToVPADNetworkClient.controller.ControllerData; + +public class TCPClient { + private Socket sock; + private DataInputStream in; + private DataOutputStream out; + + 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()); + } + + public enum HandshakeReturnCode { + BAD_HANDSHAKE, + SAME_CLIENT, + NEW_CLIENT + } + + 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!"); + 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 + return true; + } + + public synchronized void abort() throws Exception { + sock.close(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java b/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java new file mode 100644 index 0000000..f41ea5f --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/network/UDPClient.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2017 Ash (QuarkTheAwesome) & Maschell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ +package net.ash.HIDToVPADNetworkClient.network; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +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; + +public class UDPClient { + private DatagramSocket sock; + private InetAddress host; + + private ByteArrayOutputStream outBytes; + private DataOutputStream out; + + public UDPClient() { + outBytes = new ByteArrayOutputStream(); + out = new DataOutputStream(outBytes); + } + + 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); + + sock.send(packet); + + //System.out.println(Arrays.toString(payload)); //XXX debug text + + outBytes.reset(); + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java b/src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java new file mode 100644 index 0000000..c54227a --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/util/BlockingIOStabbifier.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * 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 new file mode 100644 index 0000000..6abcc63 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/util/HandleFoundry.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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. + *******************************************************************************/ +/* + * WHY HAVE A FACTORY WHEN YOU CAN HAVE A FOUNDRY? + */ + +package net.ash.HIDToVPADNetworkClient.util; + +public class HandleFoundry { + private static int h = 1; + + public static int next() { + return h++; + } +} diff --git a/src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java b/src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java new file mode 100644 index 0000000..0aee430 --- /dev/null +++ b/src/net/ash/HIDToVPADNetworkClient/util/WakeupThread.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * 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 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(); + } + } + } +}