Added option for disable auto-scanning for controllers

Alternative: buttons for scanning manually.
- Change the OSX implementation. You need to scan for controller
manually to get it working.
- OSX doesn’t check is a connection is still open when sending. Added a
PING response to check if the client is still connected.
!!!!!!!!!
- Changed the protocol! For this network client, you’ll need the newest
nighty of HIDtoVPAD!
!!!!!!!!!
This commit is contained in:
Maschell 2017-04-03 07:55:39 -07:00
parent 79f4d738de
commit 25e8bc6faf
13 changed files with 219 additions and 89 deletions

View File

@ -1,6 +1,9 @@
package net.ash.HIDToVPADNetworkClient.controller;
import java.util.Arrays;
import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException;
import net.ash.HIDToVPADNetworkClient.util.Settings;
public class DS4NewController extends PureJavaHidController {
public static final short DS4_NEW_CONTROLLER_VID = 0x54C;
@ -8,12 +11,19 @@ public class DS4NewController extends PureJavaHidController {
public DS4NewController(String identifier) throws ControllerInitializationFailedException {
super(identifier);
// truncate package to 6;
this.PACKET_LENGTH = 6;
if (Settings.isMacOSX()) {
this.PACKET_LENGTH = 7;
} else {
this.PACKET_LENGTH = 6;
}
}
@Override
public byte[] pollLatestData() {
if (Settings.isMacOSX()) { // for some reason the controller has one extra byte at the beginning under OSX
return Arrays.copyOfRange(currentData, 1, 7);
}
return currentData.clone();
}

View File

@ -28,11 +28,13 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.Synchronized;
import lombok.extern.java.Log;
import net.ash.HIDToVPADNetworkClient.exeption.ControllerInitializationFailedException;
import net.ash.HIDToVPADNetworkClient.util.PureJavaHidApiManager;
import purejavahidapi.HidDevice;
import purejavahidapi.InputReportListener;
@Log
public class PureJavaHidController extends Controller implements InputReportListener {
private final Object dataLock = new Object();
protected byte[] currentData = new byte[1];
@ -43,12 +45,20 @@ public class PureJavaHidController extends Controller implements InputReportList
public static Controller getInstance(String deviceIdentifier) throws IOException, ControllerInitializationFailedException {
HidDevice device = PureJavaHidApiManager.getDeviceByPath(deviceIdentifier);
short vid = 0;
short pid = 0;
if (device != null) {
vid = device.getHidDeviceInfo().getVendorId();
pid = device.getHidDeviceInfo().getProductId();
device.close();
}
// 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) {
if (vid == SwitchProController.SWITCH_PRO_CONTROLLER_VID && pid == SwitchProController.SWITCH_PRO_CONTROLLER_PID) {
return new SwitchProController(deviceIdentifier);
} else if (device.getHidDeviceInfo().getVendorId() == DS4NewController.DS4_NEW_CONTROLLER_VID
&& device.getHidDeviceInfo().getProductId() == DS4NewController.DS4_NEW_CONTROLLER_PID) {
} else if (vid == DS4NewController.DS4_NEW_CONTROLLER_VID && pid == DS4NewController.DS4_NEW_CONTROLLER_PID) {
return new DS4NewController(deviceIdentifier);
} else {
return new PureJavaHidController(deviceIdentifier);
@ -64,6 +74,9 @@ public class PureJavaHidController extends Controller implements InputReportList
HidDevice device;
try {
device = PureJavaHidApiManager.getDeviceByPath(identifier);
if (device == null) {
return false;
}
device.setInputReportListener(this);
setHidDevice(device);
@ -83,7 +96,16 @@ public class PureJavaHidController extends Controller implements InputReportList
@Override
public void destroyDriver() {
getHidDevice().close();
try {
getHidDevice().close();
} catch (IllegalStateException e) {
if (e.getMessage().equals("device not open")) {
log.info("Error closing the device." + e.getMessage());
} else {
throw e;
}
}
}
@Override

View File

@ -35,7 +35,7 @@ public class SwitchProController extends PureJavaHidController {
@Override
public byte[] pollLatestData() {
if(currentData == null || currentData.length < 10){
if (currentData == null || currentData.length < 10) {
return new byte[0];
}
// remove unused data (because only changed data will be sent)

View File

@ -55,7 +55,7 @@ public class XInputController extends Controller {
try {
device = XInputDevice.getDeviceFor(pad);
} catch (XInputNotLoadedException e) {
e.printStackTrace();
e.printStackTrace();
}
if (device == null) return false;
setDevice(device);
@ -65,9 +65,9 @@ public class XInputController extends Controller {
@Override
public byte[] pollLatestData() {
boolean newData = false;
try{
try {
newData = device.poll();
}catch(BufferUnderflowException e){
} catch (BufferUnderflowException e) {
log.info("Error reading the XInput data " + e.getMessage());
setActive(false);
Thread.currentThread().stop();

View File

@ -38,10 +38,11 @@ import javax.swing.SwingUtilities;
import javax.swing.Timer;
import lombok.Getter;
import net.ash.HIDToVPADNetworkClient.manager.ControllerManager;
import net.ash.HIDToVPADNetworkClient.network.NetworkManager;
import net.ash.HIDToVPADNetworkClient.util.Settings;
public final class GuiInputControls extends JPanel implements ActionListener {
public final class GuiInputControls extends JPanel {
private static final long serialVersionUID = 1L;
@Getter private static GuiInputControls instance = new GuiInputControls();
@ -59,6 +60,41 @@ public final class GuiInputControls extends JPanel implements ActionListener {
final JButton connectButton = new JButton(CONNECT);
connectButton.setAlignmentX(Component.CENTER_ALIGNMENT);
final JCheckBox cbautoScanForController = new JCheckBox();
if (Settings.isMacOSX()) {
cbautoScanForController.setEnabled(false);
} else {
cbautoScanForController.setSelected(Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS);
}
cbautoScanForController.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean selected = ((JCheckBox) e.getSource()).isSelected();
Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS = selected;
cbautoScanForController.setSelected(selected);
}
});
final JButton scanButton = new JButton("Scan for Controllers");
scanButton.setAlignmentX(Component.CENTER_ALIGNMENT);
scanButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ControllerManager.detectControllers();
}
});
}
});
JPanel scanWrap = new JPanel();
scanWrap.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
scanWrap.add(new JLabel("Auto Scan for Controllers: "));
scanWrap.add(cbautoScanForController);
ipTextBox = new JTextField();
ipTextBox.setColumns(15);
ipTextBox.setText(Settings.getIpAddr());
@ -67,9 +103,6 @@ public final class GuiInputControls extends JPanel implements ActionListener {
ipTextBoxWrap.add(ipTextBox);
ipTextBoxWrap.setMaximumSize(new Dimension(1000, 20));
JLabel statusLabel = new JLabel("Ready.");
statusLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
final JCheckBox cbautoActivateController = new JCheckBox();
cbautoActivateController.setSelected(Settings.AUTO_ACTIVATE_CONTROLLER);
cbautoActivateController.addActionListener(new ActionListener() {
@ -93,14 +126,33 @@ public final class GuiInputControls extends JPanel implements ActionListener {
add(Box.createRigidArea(new Dimension(1, 4)));
add(connectButton);
add(Box.createRigidArea(new Dimension(1, 8)));
add(statusLabel);
add(Box.createRigidArea(new Dimension(1, 4)));
add(scanButton);
add(scanWrap);
add(Box.createVerticalGlue());
add(autoActivateWrap);
add(Box.createVerticalGlue());
connectButton.addActionListener(this);
connectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Settings.setIpAddr(ipTextBox.getText());
if (NetworkManager.getInstance().isReconnecting()) {
} else {
if (NetworkManager.getInstance().isConnected()) {
NetworkManager.getInstance().disconnect();
} else {
NetworkManager.getInstance().connect(ipTextBox.getText());
}
}
}
});
}
});
int delay = 100; // milliseconds
ActionListener taskPerformer = new ActionListener() {
@ -119,23 +171,4 @@ public final class GuiInputControls extends JPanel implements ActionListener {
};
new Timer(delay, taskPerformer).start();
}
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Settings.setIpAddr(ipTextBox.getText());
if (NetworkManager.getInstance().isReconnecting()) {
} else {
if (NetworkManager.getInstance().isConnected()) {
NetworkManager.getInstance().disconnect();
} else {
NetworkManager.getInstance().connect(ipTextBox.getText());
}
}
}
});
}
}

View File

@ -58,7 +58,7 @@ public class GuiMain extends JPanel implements MessageBoxListener {
super(new BorderLayout());
GuiControllerList leftControllerList = new GuiControllerList();
leftControllerList.setPreferredSize(new Dimension(300, 100));
leftControllerList.setPreferredSize(new Dimension(400, 200));
add(leftControllerList, BorderLayout.CENTER);
GuiInputControls rightSideControls = GuiInputControls.getInstance();
add(rightSideControls, BorderLayout.LINE_END);

View File

@ -51,9 +51,13 @@ public final class ActiveControllerManager implements Runnable {
new Thread(new Runnable() {
@Override
public void run() {
// Scan once initially
ControllerManager.detectControllers();
while (true) {
updateControllerStates();
ControllerManager.detectControllers();
if (Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS) {
ControllerManager.detectControllers();
}
Utilities.sleep(Settings.DETECT_CONTROLLER_INTERVAL);
}
}

View File

@ -52,7 +52,8 @@ import purejavahidapi.HidDeviceInfo;
@Log
public final class ControllerManager {
private static Map<String, Controller> attachedControllers = new HashMap<String, Controller>();
private static final Map<String, Controller> attachedControllers = new HashMap<String, Controller>();
private static final Map<String, HidDeviceInfo> connectedDevicesInfo = new HashMap<String, HidDeviceInfo>();
private static boolean threwUnsatisfiedLinkError = false;
@ -63,7 +64,7 @@ public final class ControllerManager {
/**
* Detects all attached controller.
*/
@Synchronized("attachedControllers")
public static void detectControllers() {
Map<String, ControllerType> connectedDevices = new HashMap<String, ControllerType>();
@ -77,20 +78,30 @@ public final class ControllerManager {
// Remove detached devices
List<String> toRemove = new ArrayList<String>();
for (String s : attachedControllers.keySet()) {
if (!connectedDevices.containsKey(s)) {
toRemove.add(s);
synchronized (attachedControllers) {
for (String s : attachedControllers.keySet()) {
System.out.println(s);
if (!connectedDevices.containsKey(s)) {
toRemove.add(s);
}
}
}
for (String remove : toRemove) {
attachedControllers.get(remove).destroyAll();
attachedControllers.remove(remove);
synchronized (attachedControllers) {
attachedControllers.get(remove).destroyAll();
attachedControllers.remove(remove);
}
}
// Add attached devices!
for (Entry<String, ControllerType> entry : connectedDevices.entrySet()) {
String deviceIdentifier = entry.getKey();
if (!attachedControllers.containsKey(deviceIdentifier)) {
boolean contains = false;
synchronized (attachedControllers) {
contains = attachedControllers.containsKey(deviceIdentifier);
}
if (!contains) {
Controller c = null;
switch (entry.getValue()) {
case PureJAVAHid:
@ -131,7 +142,9 @@ public final class ControllerManager {
c.setActive(true);
}
new Thread(c).start();
attachedControllers.put(deviceIdentifier, c);
synchronized (attachedControllers) {
attachedControllers.put(deviceIdentifier, c);
}
}
}
}
@ -144,12 +157,13 @@ public final class ControllerManager {
private static Map<String, ControllerType> detectHIDDevices() {
Map<String, ControllerType> connectedDevices = new HashMap<String, ControllerType>();
System.out.println("detectHIDDevices");
for (HidDeviceInfo info : PureJavaHidApiManager.getAttachedController()) {
String path = info.getPath();
if (Settings.isMacOSX()) path = path.substring(0, 13);
connectedDevices.put(path, ControllerType.PureJAVAHid);
synchronized (connectedDevicesInfo) {
connectedDevicesInfo.put(path, info);
}
}
return connectedDevices;
@ -214,10 +228,10 @@ public final class ControllerManager {
return result;
}
@Synchronized("attachedControllers")
public static List<Controller> getActiveControllers() {
List<Controller> active = new ArrayList<Controller>();
for (Controller c : attachedControllers.values()) {
List<Controller> attached = getAttachedControllers();
for (Controller c : attached) {
if (c.isActive()) {
active.add(c);
}
@ -225,11 +239,16 @@ public final class ControllerManager {
return active;
}
@Synchronized("attachedControllers")
public static void deactivateAllAttachedControllers() {
for (Controller c : attachedControllers.values()) {
List<Controller> attached = getAttachedControllers();
for (Controller c : attached) {
c.setActive(false);
}
}
@Synchronized("connectedDevicesInfo")
public static HidDeviceInfo getDeviceInfoByPath(String path) {
return connectedDevicesInfo.get(path);
}
}

View File

@ -161,6 +161,18 @@ public final class NetworkManager implements Runnable {
private void sendPing(PingCommand command) {
if (sendTCP(Protocol.getRawPingDataToSend(command))) {
log.info("PING");
byte pong;
try {
pong = tcpClient.recvByte();
if (pong != Protocol.TCP_CMD_PONG) {
disconnect();
}
log.info("got PONG!");
} catch (IOException e) {
log.info("Failed to get PONG. Disconnecting.");
tcpClient.checkShouldRetry();
}
} else {
log.info("Sending the PING failed");
}

View File

@ -41,6 +41,7 @@ final class Protocol {
static final byte TCP_CMD_ATTACH = 0x01;
static final byte TCP_CMD_DETACH = 0x02;
static final byte TCP_CMD_PING = (byte) 0xF0;
static final byte TCP_CMD_PONG = (byte) 0xF1;
static final byte UDP_CMD_DATA = 0x03;

View File

@ -90,20 +90,24 @@ final class TCPClient {
out.write(rawCommand);
out.flush();
} catch (IOException e) {
try {
if (shouldRetry++ < Settings.MAXIMUM_TRIES_FOR_RECONNECTING) {
log.info("Trying again to connect! Attempt number " + shouldRetry);
connect(ip);
} else {
abort();
}
} catch (Exception e1) {
// e1.printStackTrace();
}
checkShouldRetry();
throw e;
}
}
protected void checkShouldRetry() {
try {
if (shouldRetry++ < Settings.MAXIMUM_TRIES_FOR_RECONNECTING) {
log.info("Trying again to connect! Attempt number " + shouldRetry);
connect(ip);
} else {
abort();
}
} catch (Exception e1) {
// e1.printStackTrace();
}
}
void send(int value) throws IOException {
send(ByteBuffer.allocate(4).putInt(value).array());
}

View File

@ -25,6 +25,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.ash.HIDToVPADNetworkClient.manager.ControllerManager;
import purejavahidapi.HidDevice;
import purejavahidapi.HidDeviceInfo;
import purejavahidapi.PureJavaHidApi;
@ -43,25 +44,17 @@ public final class PureJavaHidApiManager {
* @throws IOException
*/
public static HidDevice getDeviceByPath(String path) throws IOException {
List<HidDeviceInfo> devList = PureJavaHidApi.enumerateDevices();
HidDevice result = null;
for (HidDeviceInfo info : devList) {
result = openDeviceByPath(info, path);
if (result != null) return result;
HidDeviceInfo deviceinfo = ControllerManager.getDeviceInfoByPath(path);
if (deviceinfo != null) {
HidDevice result = PureJavaHidApi.openDevice(deviceinfo);
if (result != null) {
return result;
}
}
return result;
}
private static HidDevice openDeviceByPath(HidDeviceInfo info, String expected_path) throws IOException {
if (info == null) return null;
String real_path = info.getPath();
if (Settings.isMacOSX()) real_path = real_path.substring(0, 13);
if (real_path.equals(expected_path)) {
return PureJavaHidApi.openDevice(info);
}
/*
* List<HidDeviceInfo> devList = PureJavaHidApi.enumerateDevices(); HidDevice result = null; for (HidDeviceInfo info : devList) { String real_path =
* info.getPath(); if (real_path.equals(path)) { return PureJavaHidApi.openDevice(info); } }
*/
return null;
}
@ -69,14 +62,35 @@ public final class PureJavaHidApiManager {
List<HidDeviceInfo> connectedGamepads = new ArrayList<HidDeviceInfo>();
for (HidDeviceInfo info : PureJavaHidApi.enumerateDevices()) {
if (info.getUsagePage() == 0x05 ||info.getUsagePage() == 0x01 || info.getUsagePage() == 0x04 || (info.getVendorId() == 0x57e) || (info.getVendorId() == 0x054c)) {
if(((info.getVendorId() == 0x045e) && ((info.getProductId() == 0x02ff) || (info.getProductId() == 0x02a1))) && Settings.isWindows()){ //Skip Xbox pads on windows. We have XInput
if (isGamepad(info)) {
// Skip Xbox controller under windows. We should use XInput instead.
if (isXboxController(info) && Settings.isWindows()) {
continue;
}
connectedGamepads.add(info);
}
}
return connectedGamepads;
}
public static boolean isGamepad(HidDeviceInfo info) {
if (info == null) return false;
short usagePage = info.getUsagePage();
return (usagePage == 0x05 || usagePage == 0x01 || usagePage == 0x04 || isNintendoController(info) || isPlaystationController(info));
}
private static boolean isPlaystationController(HidDeviceInfo info) {
if (info == null) return false;
return (info.getVendorId() == 0x054c);
}
private static boolean isNintendoController(HidDeviceInfo info) {
if (info == null) return false;
return (info.getVendorId() == 0x57e);
}
private static boolean isXboxController(HidDeviceInfo info) {
if (info == null) return false;
return (info.getVendorId() == 0x045e) && ((info.getProductId() == 0x02ff) || (info.getProductId() == 0x02a1));
}
}

View File

@ -44,9 +44,11 @@ public final class Settings {
public static final int PING_INTERVAL = 1000;
public static final int PROCESS_CMD_INTERVAL = 10;
public static boolean SCAN_AUTOMATICALLY_FOR_CONTROLLERS = !isMacOSX(); // It doesn't work on OSX
public static boolean DEBUG_UDP_OUTPUT = false;
public static boolean SEND_DATA_ONLY_ON_CHANGE = false;
public static boolean AUTO_ACTIVATE_CONTROLLER = true;
public static boolean AUTO_ACTIVATE_CONTROLLER = false;
@Getter @Setter private static String ipAddr = "192.168.0.35"; // @Maschell, you're welcome
@ -88,6 +90,7 @@ public final class Settings {
Settings.ipAddr = prop.getProperty("ipAddr");
String autoActivatingControllerString = prop.getProperty("autoActivatingController");
String sendDataOnlyOnChanges = prop.getProperty("sendDataOnlyOnChanges");
String scanAutomaticallyForControllers = prop.getProperty("scanAutomaticallyForControllers");
if (autoActivatingControllerString != null) { // We don't combine the if statements to keep the default value.
if ("true".equals(autoActivatingControllerString)) {
@ -103,6 +106,13 @@ public final class Settings {
Settings.SEND_DATA_ONLY_ON_CHANGE = false;
}
}
if (scanAutomaticallyForControllers != null) { // We don't combine the if statements to keep the default value.
if ("true".equals(scanAutomaticallyForControllers)) {
Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS = true;
} else {
Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS = false;
}
}
log.info("Loaded config successfully!");
}
@ -125,6 +135,7 @@ public final class Settings {
prop.setProperty("ipAddr", Settings.ipAddr);
prop.setProperty("autoActivatingController", Boolean.toString(Settings.AUTO_ACTIVATE_CONTROLLER));
prop.setProperty("sendDataOnlyOnChanges", Boolean.toString(Settings.SEND_DATA_ONLY_ON_CHANGE));
prop.setProperty("scanAutomaticallyForControllers", Boolean.toString(Settings.SCAN_AUTOMATICALLY_FOR_CONTROLLERS));
try {
FileOutputStream outStream = new FileOutputStream(configFile);