1
0
forked from Clones/Controlify

better controller hid identification - should fix the 50/50 controller not found

This commit is contained in:
isXander
2023-04-05 23:54:10 +01:00
parent ca56271c6e
commit 3b1566cdc1
4 changed files with 113 additions and 69 deletions

View File

@ -47,8 +47,7 @@ public record ControllerType(String friendlyName, String identifier) {
while (reader.hasNext()) { while (reader.hasNext()) {
String friendlyName = null; String friendlyName = null;
String identifier = null; String identifier = null;
int vendorId = -1; Set<HIDIdentifier> hids = new HashSet<>();
Set<Integer> productIds = new HashSet<>();
reader.beginObject(); reader.beginObject();
while (reader.hasNext()) { while (reader.hasNext()) {
@ -57,11 +56,24 @@ public record ControllerType(String friendlyName, String identifier) {
switch (name) { switch (name) {
case "name" -> friendlyName = reader.nextString(); case "name" -> friendlyName = reader.nextString();
case "identifier" -> identifier = reader.nextString(); case "identifier" -> identifier = reader.nextString();
case "vendor" -> vendorId = reader.nextInt(); case "hids" -> {
case "product" -> {
reader.beginArray(); reader.beginArray();
while (reader.hasNext()) { while (reader.hasNext()) {
productIds.add(reader.nextInt()); int vendorId = -1;
int productId = -1;
reader.beginArray();
while (reader.hasNext()) {
if (vendorId == -1) {
vendorId = reader.nextInt();
} else if (productId == -1) {
productId = reader.nextInt();
} else {
Controlify.LOGGER.warn("Too many values in HID array. Skipping...");
reader.skipValue();
}
}
reader.endArray();
hids.add(new HIDIdentifier(vendorId, productId));
} }
reader.endArray(); reader.endArray();
} }
@ -73,14 +85,14 @@ public record ControllerType(String friendlyName, String identifier) {
} }
reader.endObject(); reader.endObject();
if (friendlyName == null || identifier == null || vendorId == -1 || productIds.isEmpty()) { if (friendlyName == null || identifier == null || hids.isEmpty()) {
Controlify.LOGGER.warn("Invalid entry in HID DB. Skipping..."); Controlify.LOGGER.warn("Invalid entry in HID DB. Skipping...");
continue; continue;
} }
var type = new ControllerType(friendlyName, identifier); var type = new ControllerType(friendlyName, identifier);
for (int productId : productIds) { for (var hid : hids) {
typeMap.put(new HIDIdentifier(vendorId, productId), type); typeMap.put(hid, type);
} }
} }
reader.endArray(); reader.endArray();

View File

@ -3,28 +3,34 @@ package dev.isxander.controlify.controller.hid;
import dev.isxander.controlify.Controlify; import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.ControllerType; import dev.isxander.controlify.controller.ControllerType;
import org.hid4java.*; import org.hid4java.*;
import org.hid4java.event.HidServicesEvent;
import java.util.*; import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
public class ControllerHIDService implements HidServicesListener { public class ControllerHIDService {
private final HidServicesSpecification specification; private final HidServicesSpecification specification;
private HidServices services; private HidServices services;
private final Map<String, HIDIdentifier> unconsumedHIDs; private final Queue<HIDIdentifierWithPath> unconsumedControllerHIDs;
private final Map<String, HidDevice> attachedDevices = new HashMap<>();
private boolean disabled = false; private boolean disabled = false;
// https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages#usage-page
private static final Set<Integer> CONTROLLER_USAGE_IDS = Set.of(
0x04, // Joystick
0x05, // Gamepad
0x08 // Multi-axis Controller
);
public ControllerHIDService() { public ControllerHIDService() {
this.specification = new HidServicesSpecification(); this.specification = new HidServicesSpecification();
specification.setAutoStart(false); specification.setAutoStart(false);
this.unconsumedHIDs = new LinkedHashMap<>(); specification.setScanMode(ScanMode.NO_SCAN);
this.unconsumedControllerHIDs = new ArrayBlockingQueue<>(50);
} }
public void start() { public void start() {
try { try {
services = HidManager.getHidServices(specification); services = HidManager.getHidServices(specification);
services.addHidServicesListener(this);
services.start(); services.start();
} catch (HidException e) { } catch (HidException e) {
Controlify.LOGGER.error("Failed to start controller HID service! If you are on Linux using flatpak or snap, this is likely because your launcher has not added libusb to their package.", e); Controlify.LOGGER.error("Failed to start controller HID service! If you are on Linux using flatpak or snap, this is likely because your launcher has not added libusb to their package.", e);
@ -33,48 +39,74 @@ public class ControllerHIDService implements HidServicesListener {
} }
public ControllerHIDInfo fetchType() { public ControllerHIDInfo fetchType() {
services.scan(); doScanOnThisThread();
try {
// wait for scan to complete on separate thread HIDIdentifierWithPath hid = unconsumedControllerHIDs.poll();
Thread.sleep(1000); if (hid == null) {
} catch (InterruptedException e) { Controlify.LOGGER.warn("No controller found via USB hardware scan! This prevents identifying controller type.");
throw new RuntimeException(e); return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
} }
var typeMap = ControllerType.getTypeMap(); ControllerType type = ControllerType.getTypeMap().getOrDefault(hid.identifier(), ControllerType.UNKNOWN);
for (var entry : unconsumedHIDs.entrySet()) { if (type == ControllerType.UNKNOWN)
var path = entry.getKey(); Controlify.LOGGER.warn("Controller found via USB hardware scan, but it was not found in the controller identification database! (HID: {})", hid.identifier());
var hid = entry.getValue();
var type = typeMap.get(hid);
if (type != null) {
Controlify.LOGGER.info("identified controller type " + type);
unconsumedHIDs.remove(path);
return new ControllerHIDInfo(type, Optional.of(path));
}
}
Controlify.LOGGER.warn("Controller type unknown! Please report the make and model of your controller and give the following details: " + unconsumedHIDs); unconsumedControllerHIDs.removeIf(h -> hid.path().equals(h.path()));
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
}
@Override return new ControllerHIDInfo(type, Optional.of(hid.path()));
public void hidDeviceAttached(HidServicesEvent event) {
var device = event.getHidDevice();
unconsumedHIDs.put(device.getPath(), new HIDIdentifier(device.getVendorId(), device.getProductId()));
} }
public boolean isDisabled() { public boolean isDisabled() {
return disabled; return disabled;
} }
@Override private void doScanOnThisThread() {
public void hidDeviceDetached(HidServicesEvent event) { List<String> removeList = new ArrayList<String>();
unconsumedHIDs.remove(event.getHidDevice().getPath());
List<HidDevice> attachedHidDeviceList = services.getAttachedHidDevices();
for (HidDevice attachedDevice : attachedHidDeviceList) {
if (!this.attachedDevices.containsKey(attachedDevice.getId())) {
// Device has become attached so add it but do not open
attachedDevices.put(attachedDevice.getId(), attachedDevice);
// add an unconsumed identifier that can be removed if not disconnected
HIDIdentifier identifier = new HIDIdentifier(attachedDevice.getVendorId(), attachedDevice.getProductId());
if (isController(attachedDevice))
unconsumedControllerHIDs.add(new HIDIdentifierWithPath(attachedDevice.getPath(), identifier));
}
}
for (Map.Entry<String, HidDevice> entry : attachedDevices.entrySet()) {
String deviceId = entry.getKey();
HidDevice hidDevice = entry.getValue();
if (!attachedHidDeviceList.contains(hidDevice)) {
// Keep track of removals
removeList.add(deviceId);
// remove device from unconsumed list
unconsumedControllerHIDs.removeIf(device -> this.attachedDevices.get(deviceId).getPath().equals(device.path()));
}
}
if (!removeList.isEmpty()) {
// Update the attached devices map
removeList.forEach(this.attachedDevices.keySet()::remove);
}
} }
@Override private boolean isController(HidDevice device) {
public void hidFailure(HidServicesEvent event) { boolean isControllerType = ControllerType.getTypeMap().containsKey(new HIDIdentifier(device.getVendorId(), device.getProductId()));
boolean isGenericDesktopControlOrGameControl = device.getUsagePage() == 0x1 || device.getUsagePage() == 0x5;
boolean isSelfIdentifiedController = CONTROLLER_USAGE_IDS.contains(device.getUsage());
return ControllerType.getTypeMap().containsKey(new HIDIdentifier(device.getVendorId(), device.getProductId()))
|| (isGenericDesktopControlOrGameControl && isSelfIdentifiedController);
} }
public record ControllerHIDInfo(ControllerType type, Optional<String> path) { public record ControllerHIDInfo(ControllerType type, Optional<String> path) {
@ -82,4 +114,7 @@ public class ControllerHIDService implements HidServicesListener {
return path.map(p -> UUID.nameUUIDFromBytes(p.getBytes())).map(UUID::toString); return path.map(p -> UUID.nameUUIDFromBytes(p.getBytes())).map(UUID::toString);
} }
} }
private record HIDIdentifierWithPath(String path, HIDIdentifier identifier) {
}
} }

View File

@ -3,50 +3,46 @@
"name": "Xbox One Controller", "name": "Xbox One Controller",
"identifier": "xbox_one", "identifier": "xbox_one",
"vendor": 0x45e, "hids": [
"product": [ [0x45e, 0x2ff],
0x2ff, [0x45e, 0x2ea],
0x2ea, [0x45e, 0xb12],
0xb12, [0x45e, 0x2dd],
0x2dd, [0x45e, 0x2e6],
0x2e3, [0x45e, 0x2fd],
0x2e6, [0x45e, 0x2e3],
0x2fd, [0x45e, 0x2d1],
0x2d1, [0x45e, 0x289],
0x289, [0x45e, 0x202],
0x202, [0x45e, 0x285],
0x285, [0x45e, 0x288],
0x288, [0x45e, 0xb13],
0xb13,
] ]
}, },
{ {
"name": "Dualshock 4 Controller", "name": "Dualshock 4 Controller",
"identifier": "dualshock4", "identifier": "dualshock4",
"vendor": 0x54c, "hids": [
"product": [ [0x54c, 0x5c4],
0x5c4, [0x54c, 0x9cc],
0x9cc, [0x54c, 0xba0],
0xba0,
] ]
}, },
{ {
"name": "Steam Deck", "name": "Steam Deck",
"identifier": "steam_deck", "identifier": "steam_deck",
"vendor": 0x28de, "hids": [
"product": [ [0x28de, 0x1205],
0x1205,
] ]
}, },
{ {
"name": "Stadia Controller", "name": "Stadia Controller",
"identifier": "stadia", "identifier": "stadia",
"vendor": 0x18d1, "hids": [
"product": [ [0x18d1, 0x9400],
0x9400,
] ]
} }
] ]

View File

@ -0,0 +1 @@
xbox_one