diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ce5d03..4db69da 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ quilt_mappings = "1" fabric_loader = "0.14.16" fabric_api = "0.74.1+1.19.4" mixin_extras = "0.2.0-beta.1" -yet_another_config_lib = "2.3.0+beta.2+update.1.19.4-SNAPSHOT" +yet_another_config_lib = "2.3.0+beta.3+update.1.19.4-SNAPSHOT" mod_menu = "6.1.0-alpha.1" hid4java = "0.7.0" quilt_json5 = "1.0.3" diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index 22bf635..053ff67 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -54,57 +54,49 @@ public class Controlify implements ControlifyApi { 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(); + if (currentController() == Controller.DUMMY && config().isFirstLaunch()) { + this.setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null)); + } // 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.setInputMode(InputMode.CONTROLLER); - } + if (firstController) { + this.setCurrentController(controller); + this.setInputMode(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) { diff --git a/src/main/java/dev/isxander/controlify/compatibility/yacl/CyclingControllerElementComponentProcessor.java b/src/main/java/dev/isxander/controlify/compatibility/yacl/CyclingControllerElementComponentProcessor.java index c209032..c6d4cb0 100644 --- a/src/main/java/dev/isxander/controlify/compatibility/yacl/CyclingControllerElementComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/compatibility/yacl/CyclingControllerElementComponentProcessor.java @@ -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; diff --git a/src/main/java/dev/isxander/controlify/controller/AbstractController.java b/src/main/java/dev/isxander/controlify/controller/AbstractController.java index 3120fef..9e36662 100644 --- a/src/main/java/dev/isxander/controlify/controller/AbstractController.java +++ b/src/main/java/dev/isxander/controlify/controller/AbstractController.java @@ -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 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> 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; } diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerType.java b/src/main/java/dev/isxander/controlify/controller/ControllerType.java index 60f4202..b67ac4e 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerType.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerType.java @@ -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 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 getTypeMap() { + ensureTypeMapFilled(); + return ImmutableMap.copyOf(typeMap); + } } diff --git a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java index 0e9d9af..edaf9e7 100644 --- a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java @@ -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 CONTROLLER_USAGE_IDS = Set.of( - 0x04, // Joystick - 0x05, // Gamepad - 0x08 // Multi-axis Controller - ); - private final HidServicesSpecification specification; - private final Queue> deviceQueue; - private Runnable onQueueEmpty = () -> {}; + private final Map 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 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 path) { + } } diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java index 2c7f26d..bb83cc5 100644 --- a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java @@ -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