forked from Clones/Controlify
remove reliance on usagePage and usage to detect controllers and run controller creation on main thread
This commit is contained in:
@ -50,57 +50,45 @@ public class Controlify {
|
||||
config().load();
|
||||
|
||||
controllerHIDService = new ControllerHIDService();
|
||||
controllerHIDService.start();
|
||||
|
||||
// find already connected controllers
|
||||
for (int i = 0; i <= GLFW.GLFW_JOYSTICK_LAST; i++) {
|
||||
if (GLFW.glfwJoystickPresent(i)) {
|
||||
int jid = i;
|
||||
controllerHIDService.awaitNextController(device -> {
|
||||
var controller = Controller.createOrGet(jid, device);
|
||||
LOGGER.info("Controller found: " + controller.name());
|
||||
for (int jid = 0; jid <= GLFW.GLFW_JOYSTICK_LAST; jid++) {
|
||||
if (GLFW.glfwJoystickPresent(jid)) {
|
||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
LOGGER.info("Controller found: " + controller.name());
|
||||
|
||||
if (config().currentControllerUid().equals(controller.uid()))
|
||||
setCurrentController(controller);
|
||||
if (config().currentControllerUid().equals(controller.uid()))
|
||||
setCurrentController(controller);
|
||||
|
||||
if (!config().loadOrCreateControllerData(controller)) {
|
||||
calibrationQueue.add(controller);
|
||||
}
|
||||
});
|
||||
if (!config().loadOrCreateControllerData(controller)) {
|
||||
calibrationQueue.add(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controllerHIDService.setOnQueueEmptyEvent(() -> {
|
||||
if (currentController() == Controller.DUMMY && config().isFirstLaunch()) {
|
||||
this.setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||
}
|
||||
});
|
||||
|
||||
controllerHIDService.start();
|
||||
|
||||
// listen for new controllers
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
controllerHIDService.awaitNextController(device -> {
|
||||
var firstController = Controller.CONTROLLERS.values().isEmpty();
|
||||
var controller = Controller.createOrGet(jid, device);
|
||||
LOGGER.info("Controller connected: " + controller.name());
|
||||
var firstController = Controller.CONTROLLERS.values().isEmpty();
|
||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
LOGGER.info("Controller connected: " + controller.name());
|
||||
|
||||
if (firstController) {
|
||||
this.setCurrentController(controller);
|
||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||
}
|
||||
if (firstController) {
|
||||
this.setCurrentController(controller);
|
||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||
}
|
||||
|
||||
if (!config().loadOrCreateControllerData(currentController)) {
|
||||
calibrationQueue.add(currentController);
|
||||
}
|
||||
if (!config().loadOrCreateControllerData(currentController)) {
|
||||
calibrationQueue.add(currentController);
|
||||
}
|
||||
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
Component.translatable("controlify.toast.controller_connected.title"),
|
||||
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
||||
));
|
||||
});
|
||||
minecraft.getToasts().addToast(SystemToast.multiline(
|
||||
minecraft,
|
||||
SystemToast.SystemToastIds.PERIODIC_NOTIFICATION,
|
||||
Component.translatable("controlify.toast.controller_connected.title"),
|
||||
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
||||
));
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
var controller = Controller.CONTROLLERS.remove(jid);
|
||||
if (controller != null) {
|
||||
|
@ -7,7 +7,6 @@ import dev.isxander.yacl.gui.controllers.cycling.CyclingControllerElement;
|
||||
|
||||
public class CyclingControllerElementComponentProcessor implements ComponentProcessor {
|
||||
private final CyclingControllerElement cyclingController;
|
||||
private int lastInput = 0;
|
||||
private boolean prevLeft, prevRight;
|
||||
|
||||
|
||||
|
@ -5,9 +5,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
||||
import org.hid4java.HidDevice;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -23,7 +21,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
||||
private final ControllerBindings<S> bindings;
|
||||
protected C config, defaultConfig;
|
||||
|
||||
public AbstractController(int joystickId, @Nullable HidDevice hidDevice) {
|
||||
public AbstractController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
if (joystickId > GLFW.GLFW_JOYSTICK_LAST || joystickId < 0)
|
||||
throw new IllegalArgumentException("Joystick ID " + joystickId + " is out of range!");
|
||||
if (!GLFW.glfwJoystickPresent(joystickId))
|
||||
@ -32,13 +30,12 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
||||
this.joystickId = joystickId;
|
||||
this.guid = GLFW.glfwGetJoystickGUID(joystickId);
|
||||
|
||||
if (hidDevice != null) {
|
||||
this.uid = UUID.nameUUIDFromBytes(hidDevice.getPath().getBytes()).toString();
|
||||
this.type = ControllerType.getTypeForHID(new HIDIdentifier(hidDevice.getVendorId(), hidDevice.getProductId()));
|
||||
if (hidInfo.path().isPresent()) {
|
||||
this.uid = UUID.nameUUIDFromBytes(hidInfo.path().get().getBytes()).toString();
|
||||
} else {
|
||||
this.uid = "unidentified-guid-" + UUID.nameUUIDFromBytes(this.guid.getBytes());
|
||||
this.type = ControllerType.UNKNOWN;
|
||||
}
|
||||
this.type = hidInfo.type();
|
||||
|
||||
var joystickName = GLFW.glfwGetJoystickName(joystickId);
|
||||
String name = type != ControllerType.UNKNOWN || joystickName == null ? type.friendlyName() : joystickName;
|
||||
|
@ -4,10 +4,9 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import org.hid4java.HidDevice;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -39,18 +38,18 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
|
||||
|
||||
Map<Integer, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
||||
|
||||
static Controller<?, ?> createOrGet(int joystickId, @Nullable HidDevice device) {
|
||||
static Controller<?, ?> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
if (CONTROLLERS.containsKey(joystickId)) {
|
||||
return CONTROLLERS.get(joystickId);
|
||||
}
|
||||
|
||||
if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK) {
|
||||
GamepadController controller = new GamepadController(joystickId, device);
|
||||
GamepadController controller = new GamepadController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(joystickId, controller);
|
||||
return controller;
|
||||
}
|
||||
|
||||
JoystickController controller = new JoystickController(joystickId, device);
|
||||
JoystickController controller = new JoystickController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(joystickId, controller);
|
||||
return controller;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -16,8 +17,8 @@ public record ControllerType(String friendlyName, String identifier) {
|
||||
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
||||
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "controllers/controller_identification.json5");
|
||||
|
||||
public static ControllerType getTypeForHID(HIDIdentifier hid) {
|
||||
if (typeMap != null) return typeMap.getOrDefault(hid, UNKNOWN);
|
||||
public static void ensureTypeMapFilled() {
|
||||
if (typeMap != null) return;
|
||||
|
||||
typeMap = new HashMap<>();
|
||||
try {
|
||||
@ -39,12 +40,6 @@ public record ControllerType(String friendlyName, String identifier) {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
var type = typeMap.getOrDefault(hid, UNKNOWN);
|
||||
if (type == UNKNOWN) {
|
||||
Controlify.LOGGER.warn("Controller type unknown! Please report the make and model of your controller and give the following details: " + hid);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static void readControllerIdFiles(JsonReader reader) throws IOException {
|
||||
@ -90,4 +85,9 @@ public record ControllerType(String friendlyName, String identifier) {
|
||||
}
|
||||
reader.endArray();
|
||||
}
|
||||
|
||||
public static ImmutableMap<HIDIdentifier, ControllerType> getTypeMap() {
|
||||
ensureTypeMapFilled();
|
||||
return ImmutableMap.copyOf(typeMap);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.controller.gamepad;
|
||||
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import org.hid4java.HidDevice;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWGamepadState;
|
||||
|
||||
@ -9,8 +9,8 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
|
||||
private GamepadState state = GamepadState.EMPTY;
|
||||
private GamepadState prevState = GamepadState.EMPTY;
|
||||
|
||||
public GamepadController(int joystickId, HidDevice hidDevice) {
|
||||
super(joystickId, hidDevice);
|
||||
public GamepadController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
if (!GLFW.glfwJoystickIsGamepad(joystickId))
|
||||
throw new IllegalArgumentException("Joystick " + joystickId + " is not a gamepad!");
|
||||
|
||||
|
@ -5,31 +5,18 @@ import dev.isxander.controlify.controller.ControllerType;
|
||||
import org.hid4java.*;
|
||||
import org.hid4java.event.HidServicesEvent;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.*;
|
||||
|
||||
public class ControllerHIDService implements HidServicesListener {
|
||||
// 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
|
||||
);
|
||||
|
||||
private final HidServicesSpecification specification;
|
||||
private final Queue<Consumer<HidDevice>> deviceQueue;
|
||||
private Runnable onQueueEmpty = () -> {};
|
||||
|
||||
private final Map<String, HIDIdentifier> unconsumedHIDs;
|
||||
private boolean disabled = false;
|
||||
|
||||
public ControllerHIDService() {
|
||||
this.deviceQueue = new ArrayDeque<>();
|
||||
|
||||
this.specification = new HidServicesSpecification();
|
||||
specification.setAutoStart(false);
|
||||
specification.setScanInterval(2000); // long interval, so we can guarantee this runs after GLFW hook
|
||||
this.unconsumedHIDs = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@ -44,44 +31,27 @@ public class ControllerHIDService implements HidServicesListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void awaitNextController(Consumer<HidDevice> consumer) {
|
||||
if (disabled) {
|
||||
consumer.accept(null);
|
||||
return;
|
||||
public ControllerHIDInfo fetchType() {
|
||||
var typeMap = ControllerType.getTypeMap();
|
||||
for (var entry : unconsumedHIDs.entrySet()) {
|
||||
var path = entry.getKey();
|
||||
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));
|
||||
}
|
||||
}
|
||||
deviceQueue.add(consumer);
|
||||
|
||||
Controlify.LOGGER.warn("Controller type unknown! Please report the make and model of your controller and give the following details: " + unconsumedHIDs);
|
||||
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hidDeviceAttached(HidServicesEvent event) {
|
||||
var device = event.getHidDevice();
|
||||
|
||||
if (isController(device)) {
|
||||
if (deviceQueue.peek() != null) {
|
||||
try {
|
||||
deviceQueue.poll().accept(device);
|
||||
|
||||
if (deviceQueue.isEmpty()) {
|
||||
onQueueEmpty.run();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Controlify.LOGGER.error("Failed to handle controller device attach event.", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
Controlify.LOGGER.error("Unhandled controller: " + ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())).friendlyName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isController(HidDevice device) {
|
||||
var isGenericDesktopControlOrGameControl = device.getUsagePage() == 0x1 || device.getUsagePage() == 0x5;
|
||||
var isController = CONTROLLER_USAGE_IDS.contains(device.getUsage());
|
||||
return isGenericDesktopControlOrGameControl && isController;
|
||||
}
|
||||
|
||||
public void setOnQueueEmptyEvent(Runnable runnable) {
|
||||
this.onQueueEmpty = runnable;
|
||||
unconsumedHIDs.put(device.getPath(), new HIDIdentifier(device.getVendorId(), device.getProductId()));
|
||||
}
|
||||
|
||||
public boolean isDisabled() {
|
||||
@ -90,11 +60,14 @@ public class ControllerHIDService implements HidServicesListener {
|
||||
|
||||
@Override
|
||||
public void hidDeviceDetached(HidServicesEvent event) {
|
||||
|
||||
unconsumedHIDs.remove(event.getHidDevice().getPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hidFailure(HidServicesEvent event) {
|
||||
|
||||
}
|
||||
|
||||
public record ControllerHIDInfo(ControllerType type, Optional<String> path) {
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ package dev.isxander.controlify.controller.joystick;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import org.hid4java.HidDevice;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -17,8 +16,8 @@ public class JoystickController extends AbstractController<JoystickState, Joysti
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
public JoystickController(int joystickId, @Nullable HidDevice hidDevice) {
|
||||
super(joystickId, hidDevice);
|
||||
public JoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
|
||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||
|
Reference in New Issue
Block a user