forked from Clones/Controlify
better controller hid identification - should fix the 50/50 controller not found
This commit is contained in:
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
xbox_one
|
Reference in New Issue
Block a user