From 7b2624c12e3c5f071fa06f34c777dbe78cffd7ed Mon Sep 17 00:00:00 2001 From: isXander Date: Sat, 11 Feb 2023 14:19:08 +0000 Subject: [PATCH] better HID service failure handling --- .../dev/isxander/controlify/Controlify.java | 4 ++++ .../controlify/config/ControlifyConfig.java | 4 ++-- .../controlify/controller/Controller.java | 16 ++++++------- .../controller/hid/ControllerHIDService.java | 24 +++++++++++++++---- .../mixins/core/MinecraftMixin.java | 17 +++++++++++++ .../assets/controlify/lang/en_us.json | 7 ++++-- 6 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index e85df0d..62d951e 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -150,6 +150,10 @@ public class Controlify { return virtualMouseHandler; } + public ControllerHIDService controllerHIDService() { + return controllerHIDService; + } + public InputMode currentInputMode() { return currentInputMode; } diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java index dedf469..d574985 100644 --- a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -58,7 +58,7 @@ public class ControlifyConfig { for (var controller : Controller.CONTROLLERS.values()) { // `add` replaces if already existing - newControllerData.add(controller.uid().toString(), generateControllerConfig(controller)); + newControllerData.add(controller.uid(), generateControllerConfig(controller)); } controllerData = newControllerData; @@ -92,7 +92,7 @@ public class ControlifyConfig { } public void loadOrCreateControllerData(Controller controller) { - var uid = controller.uid().toString(); + var uid = controller.uid(); if (controllerData.has(uid)) { applyControllerConfig(controller, controllerData.getAsJsonObject(uid)); } else { diff --git a/src/main/java/dev/isxander/controlify/controller/Controller.java b/src/main/java/dev/isxander/controlify/controller/Controller.java index d5a562e..a2fdb44 100644 --- a/src/main/java/dev/isxander/controlify/controller/Controller.java +++ b/src/main/java/dev/isxander/controlify/controller/Controller.java @@ -2,8 +2,8 @@ package dev.isxander.controlify.controller; import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.controller.hid.HIDIdentifier; -import dev.isxander.controlify.event.ControlifyEvents; import org.hid4java.HidDevice; +import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWGamepadState; @@ -15,13 +15,13 @@ import java.util.UUID; public final class Controller { public static final Map CONTROLLERS = new HashMap<>(); - public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, UUID.randomUUID(), ControllerType.UNKNOWN); + public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, UUID.randomUUID().toString(), ControllerType.UNKNOWN); private final int joystickId; private final String guid; private final String name; private final boolean gamepad; - private final UUID uid; + private final String uid; private final ControllerType type; private ControllerState state = ControllerState.EMPTY; @@ -30,7 +30,7 @@ public final class Controller { private final ControllerBindings bindings = new ControllerBindings(this); private ControllerConfig config, defaultConfig; - public Controller(int joystickId, String guid, String name, boolean gamepad, UUID uid, ControllerType type) { + public Controller(int joystickId, String guid, String name, boolean gamepad, String uid, ControllerType type) { this.joystickId = joystickId; this.guid = guid; this.name = name; @@ -93,7 +93,7 @@ public final class Controller { return guid; } - public UUID uid() { + public String uid() { return uid; } @@ -136,7 +136,7 @@ public final class Controller { return Objects.hash(guid); } - public static Controller create(int id, HidDevice device) { + public static Controller create(int id, @Nullable HidDevice device) { if (id > GLFW.GLFW_JOYSTICK_LAST) throw new IllegalArgumentException("Invalid joystick id: " + id); if (CONTROLLERS.containsKey(id)) @@ -145,8 +145,8 @@ public final class Controller { String guid = GLFW.glfwGetJoystickGUID(id); boolean gamepad = GLFW.glfwJoystickIsGamepad(id); String fallbackName = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id); - UUID uid = UUID.nameUUIDFromBytes(device.getPath().getBytes(StandardCharsets.UTF_8)); - ControllerType type = ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())); + String uid = device != null ? UUID.nameUUIDFromBytes(device.getPath().getBytes(StandardCharsets.UTF_8)).toString() : "unidentified-" + UUID.randomUUID(); + ControllerType type = device != null ? ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())) : ControllerType.UNKNOWN; String name = type != ControllerType.UNKNOWN || fallbackName == null ? type.friendlyName() : fallbackName; int tries = 1; while (CONTROLLERS.values().stream().map(Controller::name).anyMatch(name::equals)) { diff --git a/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java b/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java index c5844bb..13a9056 100644 --- a/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java +++ b/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java @@ -21,6 +21,8 @@ public class ControllerHIDService implements HidServicesListener { private final HidServicesSpecification specification; private final Queue> deviceQueue; + private boolean disabled = false; + public ControllerHIDService() { this.deviceQueue = new ArrayDeque<>(); @@ -30,13 +32,23 @@ public class ControllerHIDService implements HidServicesListener { } public void start() { - var services = HidManager.getHidServices(specification); - services.addHidServicesListener(this); + try { + var services = HidManager.getHidServices(specification); + services.addHidServicesListener(this); - services.start(); + services.start(); + } catch (HidException e) { + Controlify.LOGGER.error("Failed to start controller HID service!", e); + disabled = true; + } + disabled = true; } public void awaitNextController(Consumer consumer) { + if (disabled) { + consumer.accept(null); + return; + } deviceQueue.add(consumer); } @@ -46,7 +58,7 @@ public class ControllerHIDService implements HidServicesListener { if (isController(device)) { if (deviceQueue.peek() != null) { - deviceQueue.poll().accept(event.getHidDevice()); + deviceQueue.poll().accept(device); } else { Controlify.LOGGER.error("Unhandled controller: " + ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())).friendlyName()); } @@ -59,6 +71,10 @@ public class ControllerHIDService implements HidServicesListener { return isGenericDesktopControlOrGameControl && isController; } + public boolean isDisabled() { + return disabled; + } + @Override public void hidDeviceDetached(HidServicesEvent event) { diff --git a/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java b/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java index c61b370..2a5174d 100644 --- a/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java @@ -1,10 +1,15 @@ package dev.isxander.controlify.mixins.core; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.gui.screen.BetaNoticeScreen; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.toasts.SystemToast; +import net.minecraft.client.gui.components.toasts.ToastComponent; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.main.GameConfig; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.resources.ReloadInstance; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,6 +25,8 @@ public abstract class MinecraftMixin { @Shadow public abstract float getFrameTime(); + @Shadow public abstract ToastComponent getToasts(); + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER)) private void onInputInitialized(CallbackInfo ci) { Controlify.instance().onInitializeInput(); @@ -35,4 +42,14 @@ public abstract class MinecraftMixin { if (Controlify.instance().config().isFirstLaunch()) setScreen(new BetaNoticeScreen()); } + + @ModifyExpressionValue(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/ReloadableResourceManager;createReload(Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Ljava/util/List;)Lnet/minecraft/server/packs/resources/ReloadInstance;")) + private ReloadInstance onReloadResources(ReloadInstance resourceReload) { + resourceReload.done().thenRun(() -> { + if (Controlify.instance().controllerHIDService().isDisabled()) { + getToasts().addToast(SystemToast.multiline((Minecraft) (Object) this, SystemToast.SystemToastIds.UNSECURE_SERVER_WARNING, Component.translatable("controlify.error.hid"), Component.translatable("controlify.error.hid.desc"))); + } + }); + return resourceReload; + } } diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 8ce9662..1a1b067 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -46,7 +46,7 @@ "controlify.gui.format.open": "OPEN URL", "controlify.gui.error.title": "Could not open Controlify settings", - "controlify.gui.error.message": "You cannot change Controlify setttings when you have no controllers connected. Please connect a controller first.", + "controlify.gui.error.message": "You cannot change Controlify settings when you have no controllers connected. Please connect a controller first.", "controlify.gui.button": "Controller Settings...", @@ -114,5 +114,8 @@ "controlify.beta.title": "Controlify Beta Notice", "controlify.beta.message": "You are currently using Controlify Beta.\n\nThis mod is a work in progress and will contain many bugs. Please, if you spot a bug in this mod or have a suggestion to make it even better, please create an issue on the %s!\n\nYou can always find the link to the issue tracker in Controlify's settings menu.", "controlify.beta.message.link": "issue tracker", - "controlify.beta.button": "Open Issue Tracker..." + "controlify.beta.button": "Open Issue Tracker...", + + "controlify.error.hid": "Controller Detection Disabled", + "controlify.error.hid.desc": "Controlify could not start the controller detection system used to identify and distinguish between multiple controllers. This means controller config will not be able to be saved between play sessions. This is likely due to a system fault and you should check logs for further information." }