Changed HID backend to purejavahid

moved some magic values to the settings class
This commit is contained in:
Maschell 2017-03-16 19:33:50 +01:00
parent a479bb14ee
commit e0a392442d
19 changed files with 294 additions and 154 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "purejavahidapi"]
path = purejavahidapi
url = https://github.com/nyholku/purejavahidapi

12
pom.xml
View File

@ -21,13 +21,9 @@
<id>jitpack.io</id> <!-- JitPack allows github repo to be used as a maven repo -->
<url>https://jitpack.io</url> <!-- For documentation: http://jitpack.io/ -->
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.hid4java</groupId>
<artifactId>hid4java</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -39,5 +35,11 @@
<artifactId>jxinput</artifactId>
<version>1eb4087</version> <!-- JXInput 0.7 -->
</dependency>
<dependency>
<groupId>purejavahidapi</groupId>
<artifactId>purejavahidapi</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
</project>

View File

@ -23,19 +23,17 @@ package net.ash.HIDToVPADNetworkClient;
import javax.swing.SwingUtilities;
import org.hid4java.HidManager;
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 Thread(ActiveControllerManager.getInstance()).start();
@ -43,15 +41,14 @@ public class Main {
} catch (Exception e) {
e.printStackTrace();
fatal();
}
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
GuiMain.createGUI();
}
});
}
public static void fatal() {
System.err.println("HID To VPAD Network Client encountered an irrecoverable error.");
System.err.println("Exiting...");
@ -59,7 +56,6 @@ public class Main {
}
public static void initiateShutdown() {
HidManager.getHidServices().shutdown();
System.exit(0);
}
}

View File

@ -25,6 +25,7 @@ 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;
/**
@ -43,6 +44,9 @@ public abstract class Controller implements Runnable{
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;
@ -56,7 +60,7 @@ public abstract class Controller implements Runnable{
public void run() {
boolean shutdownState = shutdown;
while(!shutdownState){
Utilities.sleep(1000);
Utilities.sleep(Settings.DETECT_CONTROLLER_INTERVAL);
while(isActive()) {
byte[] newData = pollLatestData();
if(newData != null){
@ -74,7 +78,7 @@ public abstract class Controller implements Runnable{
}
protected void doSleepAfterPollingData() {
Utilities.sleep(10);
Utilities.sleep(Settings.SLEEP_AFER_POLLING);
}
@Synchronized("dataLock")
@ -189,7 +193,24 @@ public abstract class Controller implements Runnable{
return true;
}
public enum ControllerType {
HID4JAVA, LINUX, XINPUT13,XINPUT14
@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();
}

View File

@ -1,80 +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.Arrays;
import org.hid4java.HidDevice;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException;
import net.ash.HIDToVPADNetworkClient.util.HID4JavaManager;
import net.ash.HIDToVPADNetworkClient.util.Utilities;
public class Hid4JavaController extends Controller {
public Hid4JavaController(String identifier) throws ControllerInitializationFailedException {
super(ControllerType.HID4JAVA, identifier);
}
private static final int PACKET_LENGTH = 128;
@Getter @Setter(AccessLevel.PRIVATE)
private HidDevice hidDevice;
@Override
public boolean initController(String identifier) {
HidDevice device = HID4JavaManager.getDeviceByPath(identifier);
//device.setNonBlocking(true); //TODO: What does this do? This is done no in a extra Thread so it shouldn't matter.
device.open();
Utilities.sleep(20); //What a bit until is opened.
if (device == null | !device.isOpen()) return false;
setHidDevice(device);
//System.out.println("ctrl: " + device.isOpen() + " " + device.getLastErrorMessage());
return true;
}
@Override
public byte[] pollLatestData() {
byte[] data = new byte[PACKET_LENGTH];
int length = getHidDevice().read(data);
if(length <= 0) return null;
//if(length > data.length) System.out.println("WTF?");
return Arrays.copyOf(data, length);
}
@Override
public void destroyDriver() {
getHidDevice().close();
}
@Override
public short getVID() {
return getHidDevice().getVendorId();
}
@Override
public short getPID() {
return getHidDevice().getProductId();
}
}

View File

@ -144,4 +144,9 @@ public class LinuxDevInputController extends Controller implements Runnable{
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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,30 @@
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();
}
}

View File

@ -27,4 +27,9 @@ 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();
}
}

View File

@ -27,4 +27,9 @@ 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();
}
}

View File

@ -52,7 +52,7 @@ public class XInputController extends Controller {
try {
device = XInputDevice.getDeviceFor(pad);
} catch (XInputNotLoadedException e) {
//Log?
//TODO: Log?
}
if(device == null) return false;
setDevice(device);
@ -103,8 +103,8 @@ public class XInputController extends Controller {
axesDataShoulderButtons |= axes.rtRaw << 0;
buttonState |= axesDataShoulderButtons << 16;
data.putInt(axesData).putInt(buttonState);
data.putInt(axesData).putInt(buttonState);
return(data.array());
}
return null;
@ -125,4 +125,9 @@ public class XInputController extends Controller {
public short getPID() {
return 0x1337;
}
@Override
public String getInfoText(){
return "XInput on " + getIdentifier();
}
}

View File

@ -65,18 +65,7 @@ public class GuiControllerListItem extends JPanel implements ActionListener {
}
private String getFlavorText() {
switch (controller.getType()) { //TODO: String.format with value from Settings.
case XINPUT13:
return "XInput 1.3 on " + controller.getIdentifier();
case XINPUT14:
return "XInput 1.4 on " + controller.getIdentifier();
case HID4JAVA:
return "USB HID on " + controller.getIdentifier();
case LINUX:
return "Linux controller on " + controller.getIdentifier();
default:
return controller.toString();
}
return controller.getInfoText();
}
@Override

View File

@ -27,12 +27,14 @@ 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();
@ -66,8 +68,7 @@ public class ActiveControllerManager implements Runnable{
}
}).start();
}
private Map<Controller,NetworkHIDDevice> activeControllers = new HashMap<>();
public void updateControllerStates() {
List<Controller> currentControllers = ControllerManager.getActiveControllers();
@ -79,7 +80,7 @@ public class ActiveControllerManager implements Runnable{
for(Controller c: currentControllers){
if(!activeControllers.containsKey(c)){
System.out.println("Added " + c); //TODO: real logging
log.info("Added " + c);
toAdd.add(c);
}
}
@ -87,7 +88,7 @@ public class ActiveControllerManager implements Runnable{
//removing all old
for(Controller c : activeControllers.keySet()){
if(!currentControllers.contains(c)){
System.out.println("Removed " + c); //TODO: real logging
log.info("Removed " + c);
toRemove.add(c);
}
}
@ -138,4 +139,18 @@ public class ActiveControllerManager implements Runnable{
}
}
}
/**
*
* @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<Controller, NetworkHIDDevice> entry: activeControllers.entrySet()){
if(entry.getValue().getHidHandle() == HIDhandle){
return entry.getKey();
}
}
return null;
}
}

View File

@ -23,15 +23,13 @@ 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 org.hid4java.HidDevice;
import org.hid4java.HidManager;
import com.ivan.xinput.XInputDevice;
import com.ivan.xinput.XInputDevice14;
import com.ivan.xinput.exceptions.XInputNotLoadedException;
@ -39,12 +37,14 @@ 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.Hid4JavaController;
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<String,Controller> attachedControllers = new HashMap<>();
@ -64,12 +64,8 @@ public class ControllerManager{
} else if (os.contains("Windows")) {
connectedDevices.putAll(detectWindowsControllers());
}
/*TODO: Enable HID4Java again. Currently it's disabled because
* We can either use the WiiU directly OR have XInput anyway.
* Adding an option menu for enabling it?
*/
//connectedDevices.putAll(detectHIDDevices());
connectedDevices.putAll(detectHIDDevices());
//Remove detached devices
List<String> toRemove = new ArrayList<>();
@ -89,11 +85,13 @@ public class ControllerManager{
if(!attachedControllers.containsKey(deviceIdentifier)){
Controller c = null;
switch(entry.getValue()){
case HID4JAVA:
case PureJAVAHid:
try {
c= new Hid4JavaController(deviceIdentifier);
c= PureJavaHidController.getInstance(deviceIdentifier);
} catch (ControllerInitializationFailedException e) {
//e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
break;
case LINUX:
@ -142,11 +140,13 @@ public class ControllerManager{
private static Map<String, ControllerType> detectHIDDevices() {
Map<String,ControllerType> connectedDevices = new HashMap<>();
for (HidDevice device : HidManager.getHidServices().getAttachedHidDevices()) {
if(device.getUsage() == 0x05 || device.getUsage() == 0x04){
connectedDevices.put(device.getPath(),ControllerType.HID4JAVA);
for (HidDeviceInfo info : PureJavaHidApi.enumerateDevices()) {
if(info.getUsagePage() == 0x05 || info.getUsagePage() == 0x04 || (info.getVendorId() == 0x57e)){
connectedDevices.put(info.getPath(),ControllerType.PureJAVAHid);
}
}
return connectedDevices;
}

View File

@ -96,4 +96,26 @@ public class NetworkHIDDevice {
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;
}
}

View File

@ -25,17 +25,15 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.omg.Messaging.SyncScopeHelper;
import lombok.Getter;
import lombok.Synchronized;
import lombok.extern.java.Log;
import net.ash.HIDToVPADNetworkClient.manager.ControllerManager;
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;
@Log
@ -83,8 +81,8 @@ public class NetworkManager implements Runnable{
int i = 0;
while(true){
proccessCommands();
Utilities.sleep(10);//TODO: move magic value to Settings
if(i++ > 1000/10){//TODO: move magic value to Settings
Utilities.sleep(Settings.PROCESS_CMD_INTERVAL);
if(i++ > Settings.PING_INTERVAL/Settings.PROCESS_CMD_INTERVAL){
ping();
i = 0;
}
@ -154,7 +152,7 @@ public class NetworkManager implements Runnable{
result = true;
}
}else{
Utilities.sleep(500); //TODO: move magic value to Settings
Utilities.sleep(Settings.SENDING_CMD_SLEEP_IF_NOT_CONNECTED); //TODO: move magic value to Settings
}
//Add the command again on errors

View File

@ -27,7 +27,6 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.SynchronousQueue;
import lombok.AccessLevel;
import lombok.Getter;

View File

@ -21,31 +21,30 @@
*******************************************************************************/
package net.ash.HIDToVPADNetworkClient.util;
import org.hid4java.HidDevice;
import org.hid4java.HidManager;
import org.hid4java.HidServices;
import java.io.IOException;
import java.util.List;
public class HID4JavaManager {
import purejavahidapi.HidDevice;
import purejavahidapi.HidDeviceInfo;
import purejavahidapi.PureJavaHidApi;
public class PureJavaHidApiManager {
private HID4JavaManager(){}
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){
HidDevice result = null;
HidServices services = HidManager.getHidServices();
if(services == null) return result;
for (HidDevice device : services.getAttachedHidDevices()) {
if (device.getPath().equals(path)) {
result = device;
break;
public static HidDevice getDeviceByPath(String path) throws IOException{
List<HidDeviceInfo> devList = PureJavaHidApi.enumerateDevices();
for (HidDeviceInfo info : devList) {
if(info.getPath().equals(path)){
return PureJavaHidApi.openDevice(info);
}
}
return result;
return null;
}
}

View File

@ -25,6 +25,10 @@ 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(){}