From c9b0870af304c26a182710b181194ae5bad1229f Mon Sep 17 00:00:00 2001 From: isXander Date: Fri, 3 Feb 2023 19:23:08 +0000 Subject: [PATCH] controller hid identification + ps4 buttons --- build.gradle.kts | 7 ++ gradle/libs.versions.toml | 2 + .../dev/isxander/controlify/Controlify.java | 59 +++++++++------ .../isxander/controlify/bindings/Bind.java | 6 +- .../controlify/bindings/ControllerTheme.java | 28 ++++++++ .../controlify/config/ControlifyConfig.java | 4 +- .../config/gui/BindButtonController.java | 6 +- .../controlify/config/gui/YACLHelper.java | 11 ++- .../controlify/controller/Controller.java | 43 ++++++++--- .../controller/ControllerConfig.java | 4 ++ .../controlify/controller/ControllerType.java | 63 ++++++++++++++++ .../controller/hid/ControllerHIDService.java | 68 ++++++++++++++++++ .../controller/hid/HIDIdentifier.java | 4 ++ .../controlify/gui/ButtonRenderer.java | 5 +- .../assets/controlify/lang/en_us.json | 6 ++ .../gui/buttons/dualshock/ps4_bumper_left.png | Bin 224 -> 0 bytes .../buttons/dualshock/ps4_bumper_right.png | Bin 238 -> 0 bytes .../gui/buttons/dualshock/ps4_dpad_down.png | Bin 262 -> 0 bytes .../gui/buttons/dualshock/ps4_dpad_left.png | Bin 244 -> 0 bytes .../gui/buttons/dualshock/ps4_dpad_right.png | Bin 232 -> 0 bytes .../gui/buttons/dualshock/ps4_dpad_up.png | Bin 253 -> 0 bytes .../dualshock/ps4_face_button_down.png | Bin 310 -> 0 bytes .../dualshock/ps4_face_button_left.png | Bin 294 -> 0 bytes .../dualshock/ps4_face_button_right.png | Bin 320 -> 0 bytes .../buttons/dualshock/ps4_face_button_up.png | Bin 302 -> 0 bytes .../buttons/dualshock/ps4_left_trigger.png | Bin 257 -> 0 bytes .../buttons/dualshock/ps4_right_trigger.png | Bin 271 -> 0 bytes .../buttons/dualshock/ps4_select_button.png | Bin 242 -> 0 bytes .../gui/buttons/dualshock/ps4_stick_left.png | Bin 380 -> 0 bytes .../gui/buttons/dualshock/ps4_stick_right.png | Bin 384 -> 0 bytes .../gui/buttons/dualshock/ps4_touchpad.png | Bin 282 -> 0 bytes .../gui/buttons/dualshock4/a_button.png | Bin 0 -> 1600 bytes .../gui/buttons/dualshock4/b_button.png | Bin 0 -> 1606 bytes .../textures/gui/buttons/dualshock4/back.png | Bin 0 -> 1557 bytes .../gui/buttons/dualshock4/dpad_down.png | Bin 0 -> 1542 bytes .../gui/buttons/dualshock4/dpad_left.png | Bin 0 -> 1523 bytes .../gui/buttons/dualshock4/dpad_right.png | Bin 0 -> 1514 bytes .../gui/buttons/dualshock4/dpad_up.png | Bin 0 -> 1534 bytes .../gui/buttons/dualshock4/left_bumper.png | Bin 0 -> 1503 bytes .../gui/buttons/dualshock4/left_stick.png | Bin 0 -> 1604 bytes .../gui/buttons/dualshock4/left_trigger.png | Bin 0 -> 1538 bytes .../gui/buttons/dualshock4/right_bumper.png | Bin 0 -> 1521 bytes .../gui/buttons/dualshock4/right_stick.png | Bin 0 -> 1609 bytes .../gui/buttons/dualshock4/right_trigger.png | Bin 0 -> 1544 bytes .../textures/gui/buttons/dualshock4/start.png | Bin 0 -> 1748 bytes .../gui/buttons/dualshock4/x_button.png | Bin 0 -> 1577 bytes .../gui/buttons/dualshock4/y_button.png | Bin 0 -> 1588 bytes .../gui/buttons/xbox/left_bumper_big.png | Bin 220 -> 0 bytes .../gui/buttons/xbox/left_trigger_big.png | Bin 269 -> 0 bytes .../gui/buttons/xbox/right_bumper_big.png | Bin 246 -> 0 bytes .../gui/buttons/xbox/right_trigger_big.png | Bin 276 -> 0 bytes .../textures/gui/buttons/xbox/start.png | Bin 1556 -> 264 bytes .../textures/gui/buttons/xbox/xbox_dpad.png | Bin 1587 -> 0 bytes src/main/resources/hiddb.json5 | 30 ++++++++ 54 files changed, 302 insertions(+), 44 deletions(-) create mode 100644 src/main/java/dev/isxander/controlify/bindings/ControllerTheme.java create mode 100644 src/main/java/dev/isxander/controlify/controller/ControllerType.java create mode 100644 src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java create mode 100644 src/main/java/dev/isxander/controlify/controller/hid/HIDIdentifier.java delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_bumper_left.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_bumper_right.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_dpad_down.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_dpad_left.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_dpad_right.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_dpad_up.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_down.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_left.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_right.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_up.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_left_trigger.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_right_trigger.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_select_button.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_stick_left.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_stick_right.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_touchpad.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/a_button.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/b_button.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/back.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_down.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_left.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_right.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_up.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_bumper.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_trigger.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_bumper.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_trigger.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/start.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/x_button.png create mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/y_button.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_bumper_big.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_trigger_big.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_bumper_big.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_trigger_big.png delete mode 100644 src/main/resources/assets/controlify/textures/gui/buttons/xbox/xbox_dpad.png create mode 100644 src/main/resources/hiddb.json5 diff --git a/build.gradle.kts b/build.gradle.kts index 21d536f..eaf5d85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,13 @@ dependencies { implementation(libs.mixin.extras) annotationProcessor(libs.mixin.extras) include(libs.mixin.extras) + + implementation(libs.hid4java) + include(libs.hid4java) +} + +machete { + } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9fd9628..c0c13b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ fabric_api = "0.73.3+1.19.4" mixin_extras = "0.2.0-beta.1" yet_another_config_lib = "2.2.0+update.1.19.4-SNAPSHOT" mod_menu = "6.0.0-beta.1" +hid4java = "0.7.0" [libraries] minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } @@ -23,6 +24,7 @@ fabric_api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fab mixin_extras = { module = "com.github.llamalad7:mixinextras", version.ref = "mixin_extras" } yet_another_config_lib = { module = "dev.isxander:yet-another-config-lib", version.ref = "yet_another_config_lib" } mod_menu = { module = "com.terraformersmc:modmenu", version.ref = "mod_menu" } +hid4java = { module = "org.hid4java:hid4java", version.ref = "hid4java" } [plugins] loom = { id = "fabric-loom", version.ref = "loom" } diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index c92193c..c8e4372 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -5,6 +5,7 @@ import dev.isxander.controlify.compatibility.screen.ScreenProcessorProvider; import dev.isxander.controlify.config.ControlifyConfig; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerState; +import dev.isxander.controlify.controller.hid.ControllerHIDService; import dev.isxander.controlify.event.ControlifyEvents; import dev.isxander.controlify.ingame.InGameInputHandler; import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor; @@ -24,51 +25,65 @@ public class Controlify { private InGameInputHandler inGameInputHandler; private VirtualMouseHandler virtualMouseHandler; private InputMode currentInputMode; + private ControllerHIDService controllerHIDService; private final ControlifyConfig config = new ControlifyConfig(); public void onInitializeInput() { Minecraft minecraft = Minecraft.getInstance(); + inGameInputHandler = new InGameInputHandler(Controller.DUMMY); // initialize with dummy controller before connection in case of no controllers + controllerHIDService = new ControllerHIDService(); + // find already connected controllers for (int i = 0; i < GLFW.GLFW_JOYSTICK_LAST; i++) { if (GLFW.glfwJoystickPresent(i)) { - setCurrentController(Controller.byId(i)); - LOGGER.info("Controller found: " + currentController.name()); + int jid = i; + controllerHIDService.awaitNextDevice(device -> { + setCurrentController(Controller.create(jid, device)); + LOGGER.info("Controller found: " + currentController.name()); + }); } } + controllerHIDService.start(); + // load after initial controller discovery config().load(); // listen for new controllers GLFW.glfwSetJoystickCallback((jid, event) -> { if (event == GLFW.GLFW_CONNECTED) { - setCurrentController(Controller.byId(jid)); - LOGGER.info("Controller connected: " + currentController.name()); - this.setCurrentInputMode(InputMode.CONTROLLER); + controllerHIDService.awaitNextDevice(device -> { + setCurrentController(Controller.create(jid, device)); + LOGGER.info("Controller connected: " + currentController.name() + " (" + device.getPath() + ")"); + this.setCurrentInputMode(InputMode.CONTROLLER); - config().load(); // load config again if a configuration already exists for this controller - config().save(); // save config if it doesn't exist + config().load(); // load config again if a configuration already exists for this controller + config().save(); // save config if it doesn't exist + + minecraft.getToasts().addToast(SystemToast.multiline( + minecraft, + SystemToast.SystemToastIds.PERIODIC_NOTIFICATION, + Component.translatable("controlify.toast.controller_connected.title"), + Component.translatable("controlify.toast.controller_connected.description") + )); + }); - minecraft.getToasts().addToast(SystemToast.multiline( - minecraft, - SystemToast.SystemToastIds.PERIODIC_NOTIFICATION, - Component.translatable("controlify.toast.controller_connected.title"), - Component.translatable("controlify.toast.controller_connected.description") - )); } else if (event == GLFW.GLFW_DISCONNECTED) { var controller = Controller.CONTROLLERS.remove(jid); - setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null)); - LOGGER.info("Controller disconnected: " + controller.name()); - this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER); + if (controller != null) { + setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null)); + LOGGER.info("Controller disconnected: " + controller.name()); + this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER); - minecraft.getToasts().addToast(SystemToast.multiline( - minecraft, - SystemToast.SystemToastIds.PERIODIC_NOTIFICATION, - Component.translatable("controlify.toast.controller_disconnected.title"), - Component.translatable("controlify.toast.controller_disconnected.description", controller.name()) - )); + minecraft.getToasts().addToast(SystemToast.multiline( + minecraft, + SystemToast.SystemToastIds.PERIODIC_NOTIFICATION, + Component.translatable("controlify.toast.controller_disconnected.title"), + Component.translatable("controlify.toast.controller_disconnected.description", controller.name()) + )); + } } }); diff --git a/src/main/java/dev/isxander/controlify/bindings/Bind.java b/src/main/java/dev/isxander/controlify/bindings/Bind.java index db81e57..544ae7d 100644 --- a/src/main/java/dev/isxander/controlify/bindings/Bind.java +++ b/src/main/java/dev/isxander/controlify/bindings/Bind.java @@ -28,12 +28,10 @@ public enum Bind { private final BiFunction state; private final String identifier; - private final ResourceLocation textureLocation; Bind(BiFunction state, String identifier) { this.state = state; this.identifier = identifier; - this.textureLocation = new ResourceLocation("controlify", "textures/gui/buttons/xbox/" + identifier + ".png"); } Bind(Function state, String identifier) { @@ -48,8 +46,8 @@ public enum Bind { return identifier; } - public ResourceLocation textureLocation() { - return textureLocation; + public ResourceLocation textureLocation(Controller controller) { + return new ResourceLocation("controlify", "textures/gui/buttons/" + controller.config().theme.id(controller) + "/" + identifier + ".png"); } public static Bind fromIdentifier(String identifier) { diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerTheme.java b/src/main/java/dev/isxander/controlify/bindings/ControllerTheme.java new file mode 100644 index 0000000..aa4227a --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerTheme.java @@ -0,0 +1,28 @@ +package dev.isxander.controlify.bindings; + +import dev.isxander.controlify.controller.Controller; +import dev.isxander.yacl.api.NameableEnum; +import net.minecraft.network.chat.Component; + +import java.util.function.Function; + +public enum ControllerTheme implements NameableEnum { + AUTO(c -> c.type().theme().id(c)), + XBOX_ONE(c -> "xbox"), + DUALSHOCK4(c -> "dualshock4"); + + private final Function idGetter; + + ControllerTheme(Function idGetter) { + this.idGetter = idGetter; + } + + public String id(Controller controller) { + return idGetter.apply(controller); + } + + @Override + public Component getDisplayName() { + return Component.translatable("controlify.controller_theme." + name().toLowerCase()); + } +} diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java index 6546a5e..b7af1f4 100644 --- a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -57,7 +57,7 @@ public class ControlifyConfig { for (var controller : Controller.CONTROLLERS.values()) { // `add` replaces if already existing // TODO: find a better way to identify controllers, GUID will report the same for multiple controllers of the same model - newControllerData.add(controller.guid(), generateControllerConfig(controller)); + newControllerData.add(controller.uid(), generateControllerConfig(controller)); } controllerData = newControllerData; @@ -84,7 +84,7 @@ public class ControlifyConfig { JsonObject controllers = object.getAsJsonObject("controllers"); if (controllers != null) { for (var controller : Controller.CONTROLLERS.values()) { - var settings = controllers.getAsJsonObject(controller.guid()); + var settings = controllers.getAsJsonObject(controller.uid()); if (settings != null) { applyControllerConfig(controller, settings); } diff --git a/src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java b/src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java index ddc9c52..74066a0 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java +++ b/src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java @@ -19,9 +19,11 @@ import org.lwjgl.glfw.GLFW; public class BindButtonController implements Controller { private final Option option; + private final dev.isxander.controlify.controller.Controller controller; - public BindButtonController(Option option) { + public BindButtonController(Option option, dev.isxander.controlify.controller.Controller controller) { this.option = option; + this.controller = controller; } @Override @@ -52,7 +54,7 @@ public class BindButtonController implements Controller { if (awaitingControllerInput) { textRenderer.drawShadow(matrices, awaitingText, getDimension().xLimit() - textRenderer.width(awaitingText) - getXPadding(), getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF); } else { - ButtonRenderer.drawButton(control.option().pendingValue(), matrices, getDimension().xLimit() - ButtonRenderer.BUTTON_SIZE / 2, getDimension().centerY(), ButtonRenderer.BUTTON_SIZE); + ButtonRenderer.drawButton(control.option().pendingValue(), control.controller, matrices, getDimension().xLimit() - ButtonRenderer.BUTTON_SIZE / 2, getDimension().centerY(), ButtonRenderer.BUTTON_SIZE); } } diff --git a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java index 23275a4..96ebd3a 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java +++ b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java @@ -2,11 +2,13 @@ package dev.isxander.controlify.config.gui; import dev.isxander.controlify.Controlify; import dev.isxander.controlify.bindings.Bind; +import dev.isxander.controlify.bindings.ControllerTheme; import dev.isxander.controlify.config.GlobalSettings; import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.ControllerConfig; import dev.isxander.yacl.api.*; import dev.isxander.yacl.gui.controllers.cycling.CyclingListController; +import dev.isxander.yacl.gui.controllers.cycling.EnumController; import dev.isxander.yacl.gui.controllers.slider.FloatSliderController; import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController; import dev.isxander.yacl.gui.controllers.string.StringController; @@ -102,6 +104,13 @@ public class YACLHelper { .tooltip(Component.translatable("controlify.gui.right_trigger_threshold.tooltip")) .binding(def.rightTriggerActivationThreshold, () -> config.rightTriggerActivationThreshold, v -> config.rightTriggerActivationThreshold = v) .controller(opt -> new FloatSliderController(opt, 0, 1, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100)))) + .build()) + .option(Option.createBuilder(ControllerTheme.class) + .name(Component.translatable("controlify.gui.controller_theme")) + .tooltip(Component.translatable("controlify.gui.controller_theme.tooltip")) + .binding(def.theme, () -> config.theme, v -> config.theme = v) + .controller(EnumController::new) + .instant(true) .build()); category.group(configGroup.build()); @@ -111,7 +120,7 @@ public class YACLHelper { controlsGroup.option(Option.createBuilder(Bind.class) .name(control.name()) .binding(control.defaultBind(), control::currentBind, control::setCurrentBind) - .controller(BindButtonController::new) + .controller(opt -> new BindButtonController(opt, controller)) .build()); } category.group(controlsGroup.build()); diff --git a/src/main/java/dev/isxander/controlify/controller/Controller.java b/src/main/java/dev/isxander/controlify/controller/Controller.java index 0a9a3dc..1f726e3 100644 --- a/src/main/java/dev/isxander/controlify/controller/Controller.java +++ b/src/main/java/dev/isxander/controlify/controller/Controller.java @@ -1,7 +1,10 @@ package dev.isxander.controlify.controller; import dev.isxander.controlify.bindings.ControllerBindings; +import dev.isxander.controlify.bindings.ControllerTheme; +import dev.isxander.controlify.controller.hid.HIDIdentifier; import dev.isxander.controlify.event.ControlifyEvents; +import org.hid4java.HidDevice; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWGamepadState; @@ -11,12 +14,14 @@ import java.util.Objects; public final class Controller { public static final Map CONTROLLERS = new HashMap<>(); - public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false); + public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, "DUMMY", ControllerType.UNKNOWN); - private final int id; + private final int joystickId; private final String guid; private final String name; private final boolean gamepad; + private final String uid; + private final ControllerType type; private ControllerState state = ControllerState.EMPTY; private ControllerState prevState = ControllerState.EMPTY; @@ -24,11 +29,13 @@ public final class Controller { private final ControllerBindings bindings = new ControllerBindings(this); private ControllerConfig config = new ControllerConfig(); - public Controller(int id, String guid, String name, boolean gamepad) { - this.id = id; + public Controller(int joystickId, String guid, String name, boolean gamepad, String uid, ControllerType type) { + this.joystickId = joystickId; this.guid = guid; this.name = name; this.gamepad = gamepad; + this.uid = uid; + this.type = type; } public ControllerState state() { @@ -63,24 +70,32 @@ public final class Controller { } public boolean connected() { - return GLFW.glfwJoystickPresent(id); + return GLFW.glfwJoystickPresent(joystickId); } GLFWGamepadState getGamepadState() { GLFWGamepadState state = GLFWGamepadState.create(); if (gamepad) - GLFW.glfwGetGamepadState(id, state); + GLFW.glfwGetGamepadState(joystickId, state); return state; } public int id() { - return id; + return joystickId; } public String guid() { return guid; } + public String uid() { + return uid; + } + + public ControllerType type() { + return type; + } + public String name() { if (config().customName != null) return config().customName; @@ -112,7 +127,7 @@ public final class Controller { return Objects.hash(guid); } - public static Controller byId(int id) { + public static Controller create(int id, HidDevice device) { if (id > GLFW.GLFW_JOYSTICK_LAST) throw new IllegalArgumentException("Invalid joystick id: " + id); if (CONTROLLERS.containsKey(id)) @@ -120,10 +135,16 @@ public final class Controller { String guid = GLFW.glfwGetJoystickGUID(id); boolean gamepad = GLFW.glfwJoystickIsGamepad(id); - String name = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id); - if (name == null) name = Integer.toString(id); + String fallbackName = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id); + String uid = device.getPath(); + ControllerType type = ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())); + String name = type != ControllerType.UNKNOWN || fallbackName == null ? type.friendlyName() : fallbackName; + int tries = 1; + while (CONTROLLERS.values().stream().map(Controller::name).anyMatch(name::equals)) { + name = type.friendlyName() + " (" + tries++ + ")"; + } - Controller controller = new Controller(id, guid, name, gamepad); + Controller controller = new Controller(id, guid, name, gamepad, uid, type); CONTROLLERS.put(id, controller); return controller; diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java index 57330a8..a5d27bc 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java @@ -1,5 +1,7 @@ package dev.isxander.controlify.controller; +import dev.isxander.controlify.bindings.ControllerTheme; + public class ControllerConfig { public static final ControllerConfig DEFAULT = new ControllerConfig(); @@ -20,5 +22,7 @@ public class ControllerConfig { public float virtualMouseSensitivity = 1f; + public ControllerTheme theme = ControllerTheme.AUTO; + public String customName = null; } diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerType.java b/src/main/java/dev/isxander/controlify/controller/ControllerType.java new file mode 100644 index 0000000..397c61e --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/ControllerType.java @@ -0,0 +1,63 @@ +package dev.isxander.controlify.controller; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import dev.isxander.controlify.bindings.ControllerTheme; +import dev.isxander.controlify.controller.hid.HIDIdentifier; + +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +public enum ControllerType { + UNKNOWN("Unknown Controller", ControllerTheme.XBOX_ONE), + XBOX_ONE("Xbox Controller", ControllerTheme.XBOX_ONE), + XBOX_360("Xbox 360 Controller", ControllerTheme.XBOX_ONE), + DUALSHOCK4("PS4 Controller", ControllerTheme.DUALSHOCK4); + + private static final Gson GSON = new GsonBuilder().setLenient().create(); + private static Map typeMap = null; + + private final String friendlyName; + private final ControllerTheme theme; + + ControllerType(String friendlyName, ControllerTheme theme) { + this.friendlyName = friendlyName; + this.theme = theme; + } + + public String friendlyName() { + return friendlyName; + } + + public ControllerTheme theme() { + return theme; + } + + public static ControllerType getTypeForHID(HIDIdentifier hid) { + if (typeMap != null) return typeMap.getOrDefault(hid, UNKNOWN); + + typeMap = new HashMap<>(); + try { + try (var hidDb = ControllerType.class.getResourceAsStream("/hiddb.json5")) { + var json = GSON.fromJson(new InputStreamReader(hidDb), JsonObject.class); + for (var type : ControllerType.values()) { + if (!json.has(type.name().toLowerCase())) continue; + + var themeJson = json.getAsJsonObject(type.name().toLowerCase()); + + int vendorId = themeJson.get("vendor").getAsInt(); + for (var productIdEntry : themeJson.getAsJsonArray("product")) { + int productId = productIdEntry.getAsInt(); + typeMap.put(new HIDIdentifier(vendorId, productId), type); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return typeMap.getOrDefault(hid, UNKNOWN); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java b/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java new file mode 100644 index 0000000..64f5b75 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java @@ -0,0 +1,68 @@ +package dev.isxander.controlify.controller.hid; + +import dev.isxander.controlify.Controlify; +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; + +public class ControllerHIDService implements HidServicesListener { + // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages#usage-page + private static final Set CONTROLLER_USAGE_IDS = Set.of( + 0x04, // Joystick + 0x05, // Gamepad + 0x08 // Multi-axis Controller + ); + + private final HidServicesSpecification specification; + private final Queue> deviceQueue; + + 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 + } + + public void start() { + var services = HidManager.getHidServices(specification); + services.addHidServicesListener(this); + + services.start(); + } + + public void awaitNextDevice(Consumer consumer) { + deviceQueue.add(consumer); + } + + @Override + public void hidDeviceAttached(HidServicesEvent event) { + var device = event.getHidDevice(); + + if (isController(device)) { + if (deviceQueue.peek() != null) { + deviceQueue.poll().accept(event.getHidDevice()); + } + } + } + + private boolean isController(HidDevice device) { + var isGenericDesktopControlOrGameControl = device.getUsagePage() == 0x1 || device.getUsagePage() == 0x5; + var isController = CONTROLLER_USAGE_IDS.contains(device.getUsage()); + return isGenericDesktopControlOrGameControl && isController; + } + + @Override + public void hidDeviceDetached(HidServicesEvent event) { + + } + + @Override + public void hidFailure(HidServicesEvent event) { + + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/hid/HIDIdentifier.java b/src/main/java/dev/isxander/controlify/controller/hid/HIDIdentifier.java new file mode 100644 index 0000000..d3f82ed --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/hid/HIDIdentifier.java @@ -0,0 +1,4 @@ +package dev.isxander.controlify.controller.hid; + +public record HIDIdentifier(int vendorId, int productId) { +} diff --git a/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java b/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java index d121f01..4f27501 100644 --- a/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java +++ b/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java @@ -3,13 +3,14 @@ package dev.isxander.controlify.gui; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import dev.isxander.controlify.bindings.Bind; +import dev.isxander.controlify.controller.Controller; import net.minecraft.client.gui.GuiComponent; public class ButtonRenderer { public static final int BUTTON_SIZE = 22; - public static void drawButton(Bind button, PoseStack poseStack, int x, int y, int size) { - RenderSystem.setShaderTexture(0, button.textureLocation()); + public static void drawButton(Bind button, Controller controller, PoseStack poseStack, int x, int y, int size) { + RenderSystem.setShaderTexture(0, button.textureLocation(controller)); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); GuiComponent.blit(poseStack, x - size / 2, y - size / 2, 0, 0, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE); diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 93eeb77..e7c4d55 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -25,6 +25,8 @@ "controlify.gui.left_trigger_threshold.tooltip": "How far the left trigger needs to be pushed before registering as pressed.", "controlify.gui.right_trigger_threshold": "Right Trigger Threshold", "controlify.gui.right_trigger_threshold.tooltip": "How far the right trigger needs to be pushed before registering as pressed.", + "controlify.gui.controller_theme": "Controller Theme", + "controlify.gui.controller_theme.tooltip": "The theme to use for rendering controller buttons.", "controlify.gui.group.controls": "Controls", "controlify.gui.group.controls.tooltip": "Adjust the controller controls.", @@ -43,6 +45,10 @@ "controlify.toast.controller_disconnected.title": "Controller Disconnected", "controlify.toast.controller_disconnected.description": "'%s' was disconnected.", + "controlify.controller_theme.auto": "Auto", + "controlify.controller_theme.xbox_one": "Xbox", + "controlify.controller_theme.dualshock4": "PS4", + "controlify.binding.controlify.jump": "Jump", "controlify.binding.controlify.sneak": "Sneak", "controlify.binding.controlify.attack": "Attack", diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_bumper_left.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_bumper_left.png deleted file mode 100644 index a8e61b42ae271818dd2d6f60e5658f804a379992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmV<603ZK}P)CcJLPRHTjtYEjgTb@p&Rn+wARf_2_a+5;G9d#FYdanf|BZ}#nyAMSgJFyljwD< a0t^76T{v1DJue*q0000}1{rUgjp4pZ`n0fAj z6|T+qmBezIl9H09sJR{sUmv%SYk_+H1l6>(w3M0W4jw%C;n&yfw%6C!Cr2D>IX_7^ zdfNd*vj*>&;9y~wzv`NOH8nK{B<=>pObf6zY|vS5VVF>Cxsq-28|4^B^DUnQHy-r# zy0GYpvezepB0DC=fHqbj75JE lbVJ;=_XT!m@G&s)Fi2MT_c`(e#sWRS;OXk;vd$@?2>`>mS=0ak diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_dpad_down.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_dpad_down.png deleted file mode 100644 index 80980c1c90daa562ed0d56005b51645ab1e2d851..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmV+h0r~!kP)J`$=HZmmg%J8PN~52C8Y>55h?dUi^%wN@poahh0MObtK`_Dpp?RTWMZ8% zpaFgIstrmhaYtVFDJ8fBA)>V=SNkpui-H|~y>-sHW!q;hs@!sqYCsD%-GOW|#vEcK z$XZKouTrXtkz3b-wXC{kjC0O%&d)xu-$)1n+Xv(s;lP)rl4R7#$^f?U;!!^f?%Y4kw_2D2P!P3i4YfKYz0awY(;RkC^odA zZ+u10000~)nYNMlu!4Xqzc#CI zfFZl8uz_B1fB}=JD37Csci#kM_8QMQA~l^{HRu1A)Xbh5^ngpZV4_;bn&|fp0!bB7 zL0&y9P8~t3xF5`MK38d5&ocWk!?j!Qo6R^_w*}0d)B5+k?X8ranSw6acUOmSDDHXA z@%V_PIZLNu)09aQqr*4nwI*KtE7iIn;(c&jvru9{;!?5dTltFjs(%?=`KHLSPZq1>W=Z7*?5wEp0!&g6*9a3BjI3dt6mEkc>|@uzD^*I7 zHEc&#vJLeApaioy&vQ$nLI@faqya@yc-H~gKf^fbgU;0vICBBo(ljMq*O8`aW}{26 z5V#nnZ=X_1l4Y6M`wbQ>hW+tuL12!t$<+Cx0q4*A)r^=FqU8O!T?_=}JEN6@{ z^G>gYKtfv(?Hzd~HUefLp)JU24DNX+)yqNKk8cA-x0e6|0CG-+3&>@M^#A|>07*qo IM6N<$f_s&IPyhe` diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_left.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_left.png deleted file mode 100644 index d8c2780e057f2a0a05c079c688a68e8036a1db3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmV+>0oneEP)IXSFc_%Qq>}u1!iVuSj9mW0@)xMrx}cd@)5`| zbPHgBgM)+N_3PIR*RNkE=sOltEde=O%IJ$)L z#DL8=;YSw3eFL+YSYMHxyvZ#aNwx^)BdX;iXp%(Q9X#TZEdYUg_wIq|Cr_S$LzdipgkqSIk`i1FCPs`U#O5PLpg~xZ8q@-UmJnM6 sp`>nf{b)f=zD1)I->5}Y2LJ&E05g1Uk^8j)9smFU07*qoM6N<$f=tkJYXATM diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_right.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_face_button_right.png deleted file mode 100644 index bb60df999a5ec450c569d7f12154dd0ba4caf923..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 320 zcmV-G0l)r|42JEB0jXVKF4?#MBNu6pS1!W^*k}hPSgDjP#i)rA3Joa;b-+s=1b^869Z(1X zTi5|MvK{pQAkXD+7>0&K>$=8OK@yN8$>=@-yE%=MKG4=j$4b-g2< zK{9o#H7pf2&vPS9x@FJEsYX^Ykc6oYNklIXSFc_%Qq>}u1!iVuSj9mW0@)xMrx}cd@)5`| zbPHgBgM)+N_3PIR*RNkE=sPA-E!mn-1ZV&F@qoJ04z>HmXhK#lJgNGBO`V55j4rdgO8keAt@h$k}xdT z@Fj7IEQ0yU&dv@V@yHf{z`c9-!1R+RPrxBdmPMf4j%=8ck`i1FCPs`U#1=t}K!dO* zTc`yDEg`lDLgu0?W<(2W@+}&z_(m=(^ zPpD$i=g*%}3qJI+4wxWNiVjKu$l*j4s-qR(s6{jZ009O7M*dJzIj<>D00000NkvXX Hu0mjf5nO5u diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_right_trigger.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_right_trigger.png deleted file mode 100644 index 8d40b06743f94e052dae961d998e3be4d20dafd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmV+q0r38bP)t&g@+Dw`O`(taUC6Lxw=un~MI z6xwOO_Z~6EGT1H2P)0YD45exom63sBRB%=`3x}*LA&=EjFXWPSXO^b6{<06J&jbwg z)=;aaK*pG(h_~9rDOZrS_Lhw)rE@M-u=SP$O&#SaZ`^8>$ zrAf8Dy`O$YEd8YN^6a;u`DxYnMNdAtTFAa?!)~Dr)ra%M?A^2_uZ0WRJKLQlW{e81T(E&u)29G(MoH-o3EpUXO@geCw$CSh#= diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_stick_left.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock/ps4_stick_left.png deleted file mode 100644 index b40367258e00a8f39464753207605e17b80cb447..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 380 zcmV-?0fYXDP)004R= z004l4008;_004mL004C`008P>0026e000+nl3&F}0003ONklm zE?iklTJ=!w!p0N(rC94Ijdf=xt1S!x&R^ z649_zM5G9=O*XZHlwjL7EX(qg+W?FTQ*4&-c1-$aRlIp0cgA z&7gd8U5F})F*Xe`b!?jI?YsgrBgTlATvuCMbJm)?R-Ka!og>A%uEjHlx|5mVoI4c_ z<*0{VuHG&|HRgG4x~y*NxIZas*>}{^Uvy3M&Qo6hYJ?Cj&(CNH(%>|D5A|Sve?kAA a=sp0^agMQF<7&004R= z004l4008;_004mL004C`008P>0026e000+nl3&F}0003SNklaCISAp|h9sTJL7 zCC<5BvX(^fSY$q}5dg#(VXXx-gNW2*qCUp>+6=Aad7ihv_Z|S6u}iLd6mpYot$iZ! z-tQ)tm?%jpH4V`n*fiJM*=m=uloBqvuC{nNu_!F#WvnDoog=|Gj+>KYa%tBwQKYKT z5z1AU?YFO>8hzh4BW7Cx-8Mfq{%NIdg zGR90JA>>@pQNWK?-I!PRR7RZhL)ADB){i-QxlLGWqfXvx6U)X|*EI<0x^C5JlR&vz gIsX4{rRpZY0M-$AT&oFJdH?_b07*qoM6N<$f*S*PLI3~& diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/a_button.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/a_button.png new file mode 100644 index 0000000000000000000000000000000000000000..3f36fa8eb013bb8f54b34b390a35ccbded4ace08 GIT binary patch literal 1600 zcmbVMZEVzJ9PcJF+%j31!AX!b#f+KI_dD*cTP3&M$qwn9$GMF~&7NzYyK8psQ`#PP zn-P;uq9h(B1fwy05S$74Va5#6WGY~M2?33Xff)_S;upUxFouDM5`B7aK{7Scr0vr_ zzvuV+zwhtw+t%E;u8|;!=2&kej^As&d(}$({NhBm6Tj}WdUrVl(bVeQb;MhT+X!OC zPkLg=9a45kDl!L!Wo}x%ylc_%2 zHrzShmr%z?)sRMQeT?iXNSMHYE+7j=)^wzTOqF;gy!L*x6j_3}qcRot1j!+#pX@<4 zBtr~Gs~pFXycl3K-~$2Z-%JXeAh0~o3IUo6NupokM6z;GSkKl{Qati_MGb$+RK|5J ziDmQoJd+n0WT#m^6biANzzPD55wtUAx}ZRt&c-T31Ujm%Tds~w(qjZklyhYY+gf(Q zuxhlXQwbCf8Cw7r%QKwkR0*i5HJp{Rvn6p&WnmT?&~zP)u)yY!YC7^ocEK{nNN2pPi-}+rgpiG<3Kuo%Pe#;04 zrQAHQVK|Kg^`E7~V!K(d=gKaycY0UayRAQ~aNP9XvNmt#9OBnZa4pdn*YCFLt`qAg8bc2ajEl!VCLVtJy>ISqqmy-KS3S~_>-eL2 z=Imv4)9C4INsj#b!fQU|+3D4vg+7@&*8bhpv6I`0!hz4{jCi7OF>vF<;m(5{ zv-0J^hT^BkpPK*vx1Xg07wXTSKdP^La8q>O*S8u83_JYo8&^&b#J4xhOwN38X>PdT z%yYBFuL5vaecOWnPcWAwh<6_#HZQDQx;hm+Cw!EiyZCl^vE#_0rQwkkE03JM`TE|~ nzrG=azna$D8r7XkTQydt~A-NH-a= literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/b_button.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/b_button.png new file mode 100644 index 0000000000000000000000000000000000000000..3ffc30a3e1570bb95d5074dab9e012414131847e GIT binary patch literal 1606 zcmbVMeQ4Zd98SCGT(5H!*Etm=q>7d{-@RQgHg2`)cBkenz0#}H zpaZ)hn{Is93KV)Ob>R>9qxi99ZiVGxpKc4q_ zejo4ex3@2`bxp(V4G4m)iN}O~_+KBqH{A%oAHSWy5B^qLu|WqxLbn8O9Wrrv1A?sl zPD>5A1LDJ+Z0dxhm}x-xx&_e)(zVIABsm9MGz~JE!DAQRKY^i|!eiUQA|+ZqAgjej zZIB#Iq~y_@996JQ_oH1t2MKiGN~o{r4Ttl2ti;Q~aj;BcXbIxxc&s}RLC>e-6Q^$S}nYH-G5S=Y5V zlJq=}@L0mMGbA02MoEex83u<4+$kEaa{+j>{ zt|-<5lnuw~FY0?lj*ys@|*8%VBcr%W?nO;umj zWR&S3X!PD~nxUAU(|XkbAV@CYF_<+Lrx=`~Q&cAxhFPPA$|%uPG_`mQib*jHM{nf9 zktzya6-9ETf5RyF6 zlZ2)zTvSR+Y=jBpbd>4DWw|4bcSh9+u5MHrg`z+t9igk|1ye2rsjQw?{^$8eYz-EZ zl)rACpu~bZz{NBNW~W#wfg~8NjPe>_ImjbWu*$M_-4Q6$dRYKTm(On* z;h3uHNjB)tz(D=y)ghtXWKeTu7szY9EA3rXpH(<4`e0b=H*iwxSipd7%7*=D-w+KD zq^>D0bf-G^pE<;jk5P?#jxyVQRT~z6eiym>*448uFaB8Xw(n{Ca{RVJ{;iSA(|bK; zU9oxnd}eT5{I%`3#$NKK*G)9Map{8_ zhFT}711;|z3#k|8{E$9%>Zyg3NK^O3)aMJUb`73YKeev>S=TgEIMVp$M@z}+Q(v!o z?S;CzLo=^G_sTcnh0x6>m$0R&En|9X`uGZ#J+6lqi@!{sKbTAHADc@)*e&$kv*P^e zGvDH09oP|j*4goBGqPED?2h`)Km3LuawGD^()jM5{N}^wX0BxPhh9B6+%c;>fAP+9 vYd7q!pPd{a-bu7Ap7?&Bcd5Ac4{h-YA-(JOHrBJ(t|z8Fu7~Zp74|fF?k+aT6O*)e z*9isbmw_ER+(emZKTPTew_)8t=Fp*YBDfE_GT0ERpGH+Cs2@Z@pWGXBRK*37JjwHW ze!u_w!Sv{!u8#XUD2nPzjU+SVy*<3!?j+Ao-?#e6tKA*R`4rW;ExcQ(*N^p3RO>Y} zI~h!BPXQg<95nDW;>xy5&=l1_PW*Ar9mS5M;W3F+|TZ@*tBOuB(wRl_>^+3wXX% zDsd&5!(M?G+%?MP5nt7!*N56f9)^e~UeVCuik+m1kuwp2ufbo2by5I)WaCFr3< zfduM5ONS?R^I^}`T;Ol@uDW+qe>UKx>BD7n-jI*x#6k`kQy!T|2M)G9NoLu4Dw)W} zmVP{vxNDK#GPJPN$-TAb+wKRijJs`5pZMjTh3)bi-Q7KM@9JxB_ADR0^3~(_9=^W3 z+huv$pofRL4?&;L^{}e-V52 z%d3Yj_GjMho=CI$XIp!7A6Ab0Td3Z}@46oT?GyipW50hscmD(WqR&;PU)0>w?dP5? sZ?s=K6Y1NxdUopk-Zb^npC?nlQY-j~Ze3V;FC5&dq0!`tC-%Sa7jAn3@&Et; literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_down.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_down.png new file mode 100644 index 0000000000000000000000000000000000000000..59682a20fa69bbcca254d2858a664e05e6b953d2 GIT binary patch literal 1542 zcmbVMTWl0n7#^^q&{Q;v5Q5}55G=&G?rys?bR=zObvJQK(_O;S2jkh9v%BN&%o%3R zc6XboXpF&!5{*77AqK-sLqH8kq7aCpJm3YDib(@rVocQ3_Q3>#FP_=U1WDD%Br|hn z&Ue1=zrQp(@>tJ`wJR8g=}9H^G5WqgxbIv>pJ$I%w$j&fH@VAanC^RndkORU{xuBK zHEU&aB$s|%HL%S?6Xy|Mw_S>6n1O9|7aCM`PzdIL!jf)YzS|w3v2>QOQb9 zdMGnFk~JpFhGMeY9svV&l@i#9Kv1_Uj<41=w#lo~b?{qYK@&pC8ao&Wf?RqO3}X)g zg%>$P6h$CK`gs#ZVLysJ3}jK31xXU*eoj=>NK6$Ypnb7a&oc|^nEq&6jecoti4a#6 zgj%h}*CIUjih`sliXh5@EOQjW`3;A_I_LNsIt)7U4bO6kg&hzu!aS}Ljit7>oUq-H z*74hcq9GIN&=n+J44i5LO(Vp)Rj<+%Hw^(*kc}MTQ>+wX-4Z6)FX4Zn4!8d%K!cl3 zhZ>98V%wpFPZAT}4^obW%0iF&W5F zUIM*4Eyu()fAb9okPZpbSems6C(4{CWyQD}qgfNjTc|WPtwQ4_l$@1iRqCU_4vLnF z2?_i+oN1^9?AefdXxXrc1lK9DAdH(jj4Rlqa@3qiQ#+kjQ;tudW1y6-vD8W4vP@M{ z@_n!%#yLrm`ZzfnHMn>@U*PgGt(zF^k42S2=e&-MYLLp#dGmjs-|ktonPBCXd4d)T zYCug|KFv;}-2xf3yS=JdpqX|R8bLX0tP$i9nQZ&Fb;}WG(bh_ck}jU#7Q)8`QiC2E zEYd*zXXyykZXxKomJ7no-Zl4b=+6$EHhr)R=MDV`Cl+$(nDXd6%3MAQ=q&p(r4MG~ zQ!_K~^n3*FzWu`X*FNl-n_F1@<{w?_FJ<07Q(eF3o7-OAl)Goy9rwM`dwSrfbH8+b za(0cxo_O%rZOZE8rr{{6*W`+ly^&2Bk7y(5Ya6q(*16FL3L!OdUn-oJ1@vxNEW{R2;(fA{3& zh1%+k^G6QNZo0OroOt7hCysUh{@K-MhrW(ZJ$J$`FFkzxC^8{HCAq2mwa#Ubk}S!sZ)s&{m9Uh&;JEaDgg8V literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_left.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_left.png new file mode 100644 index 0000000000000000000000000000000000000000..268dafa0bacb9f67e476486f14a851fe554e630e GIT binary patch literal 1523 zcmbVMZHN?Q9G^t4b1y*&;yNsbIf#s#_g&9Y05ROs*maX6BiB ze$Vgsf8RGg_T1V;UxHznwb_g@LEn!=cXtSdVK zSDoB6nKrip3%dfeaUKaZ*QaQP8QNO&p;bnN&7-2@>D=v;3mog%IyaRvCDTu%l9Qalj8;%6oX>*)S;{dUm zAn}$YNvy063N}o_L6jO`6-iMyHe!T%JVSJj+S+o$^ZIT}HW-8| zEj3NzH`yBG^GedcUwh}3Q|>baH+;=SHA_wMS?4xBc9w2bEs{fH+P^5~cf=sdcCE?uXy>{Qkm&TT&U z^W~H4=h=0;8;3va$!z=K^{$7GEC$ZvQx^u9{fjrq^73Z~(l-mAs@DcCKL7DMrS*rO z__62Kol6g%?`7_I=X>An?wYvv+jpNFkk>3Vj&Lu$xRc{X zzjn)&xMCfGn0g|zX_bf_FJNeno7&$A5N?ndR7uVC4gQzYAMn7n4Sq3e$ySgjb+@n_ zl7;2DGG1lJO$-@K65-GdVwd_LVMJBhiVdFG+IPYWQd&P6 z1d4@B>YzYUL^*M)2efgD3tC~bCvIbjG>J$2IAT~O#Rhd6)2L4WfjZs(n*a;0Wu+RM z+TwYsL=?|1F*gPQ*%Td>y8)3(B%-Ylli4L^=0xI60P`V1F%8R`0ec69GG2Z)JcV(=_$njou!td!*(%(AS>NBSs>+OE^R4yBe=6)O8+HZw%At74-V z{Tt54(4nD+n1`;1YD5bB8V}OA!8~o!kjXJ~w4SzQ!J;3<$j78;8a#7SbX^-_g0q=@ zNEfOO(gY`y$qBlK6(N^%9Ia~Os#=wY=S_-RNh*iu?f-fHaOkpPLd_fINlGla1F+ym zEIZvn2`rGu2cxD7dTECUC)e5Fags-3^Mhsgh9l6YZPy7S-8{d2L`0pqjY2Y0V}bh5 zt0OVHrKINiE=bpV*W0_MK8J8t^vN*oH*Au2EaI~^6|#QR{k*V+^|E)0=1e*F%+Ft( z9zP3qjGi}EZ`uo99$R^RC4B9>T`!IBkBmNjS~^TNni&rV;gG%o*|bmQXeocZR#V=I3F5;F6p literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_up.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_up.png new file mode 100644 index 0000000000000000000000000000000000000000..b25322556ebbf8148c943ba4972584fb7f032d21 GIT binary patch literal 1534 zcmbVMTWB0r7@le(rnX8gsCX$SQ!Od&&ULq&IqkHjJ6p5ViAy@NsZjA`=Irj6ojH@4 zNq4d@1vL@{jW2yEc@kSN5ivy&EFzltPzY4e_+qPI^Q3603f8LCGkY0oiHaSVnREHh z_x+di|1TAX9^1J7{`CyQY%C0F!}Nbo{N8>W{Vu&*+f9Ex-r(VYVeZ%*zw4M2v-dGf z*O#_25{~GPLz6fhvdAdrBFCd>hS{?(@{l=(LokXfwyUz2&V0xM+fvz5Mi+E1hpYBr z!^ejjLx$NHGZl;7w-@Y*ASG~ch(P4j+yF)@+vJ6G953@MXhOmiB(hX6Ra)YfM1`P+MZ+oFlTo5y&Q8E##EVZ@egySW& zZqN=C4H+LHj~6*1cB%=q%mn96__e0EW%9U&9qfhy#fk~mtCEleRq_wi$@bp_XmE8s z(OAlYx}x zM9^2VU5nI%?bjW^8Va$>(yYlTK}rdtA@sux&6@C33#AjwE>GTok_<_L;x3qxJ1BZp zEEJ-D!&xRQ6W>A9L)$?W%zJKy1xegsj?{=x<)}GwQ(Mnz*2;EOnBzZ3~(i z3yY?xq;@G%ni`!>r}`CYzJ(Pmi89WPTA9vyjhGX0Dm&+`|9Soi-=@WcYB$XjmsorU z;GiAQ>`b;x;1C{fk7_n(rX3g%y-+3sH-%$+RGh(ejzyj$?DxdezcOkt9R+-tlU`r zBL98Q*9W`h?4>?-c=Ph|;V0+8CUm4E1be^v^Sz7Z=)|dWiw|7A^XaLXksa6m*!`9C z>cvN{{8GB?&dv8tO~14A;KDIwTzgm^+kfF9)^F^1dA_(}mBH(um2&IA@hy$=i*p|@ zpPrt*s={+Wy~z(fxJi&VEWURBgXt}2zFqq5Y`*J$t+@TBkA?@w`&ph`R9<=>% literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_bumper.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_bumper.png new file mode 100644 index 0000000000000000000000000000000000000000..42ffb2fe616ccb2227d6810bee6f57f1b3b62c1a GIT binary patch literal 1503 zcmbVMU1%Id9N)C1wY3LELE0iROc3M8?#}MsUGAn?OLCVsC*34o4-$*Ene5Ent-0OV z>~3st4s@jJs-~&>kc{%!$Y5lE?gNEhK&!Z1Jls_iGZWUM$j7SM zY&Mmqq41!lYWaL#MY^i%vOvh;vKNt-?1ck8h9V2?!0{u8dmv^cb9^x}p|G{%gzG1? zUf2zk2$|Xmw8bruv&uk>pbFNo&N)Mvi&y!5nRhk zG`6(GbrXp&8e0->bOW*_I;<@FOf9pJF9tRnTM}jt#NPP05HJ$)pu%~hm#V3r$v{sl z8W@~*Jj$ElBexu2MG`R+imVwj(q*JoP!4BA*3j_|%Hq_iF5iaIE4q%gEY4(mC~;LN ziO9dD-zE^`Fi5o8P1`mXsFvnzmV=O+V46ccIOF;84#@g2b9 zPAIao+%17)Y@s`9IG~+&OzikNo6wH)$SCYCJ9iv`4sEl}1nJiK?I1#4jhZB2BQ+7I z|GYY?uv?95uH%AwyLauqTk5k17eyZrlYS#6NylQIXj6gcM@yCrL@)cOR2-?~&Rl!z zT%I>Jow`e zFF$|&!4^+le=|6{dE%F~tCOFvUB0mX;;H^~zkT(P|N6qd)$__9U;bQva{aOXKDh1X n3+qp5A3yr-{^J|39Nm6V+I%=yUZW=_;}%>Rn<$CD;PN$1v? zY4?&;5(QEpR9<{2mRf>Dh=znj5Gh(924X;BR16UlFb!#x#0E%RqYrv!Z$VNuGRe%G z%Xhx-znuSny{~6${i+vMVHj2)>lXXbzrlNbPoVGTA89Y5-;-ANpo3vgJ?p(S*n#Qi zF|7868c(p1@TlLhR%vTC$=r zt#;>axFg>am-8cXSRpoT#5ZIGM4&?#;8|TW93dMaio60Ed&?Ao7a{IQgy{4H@r2Ze zM@<{zVLwgEG)?0S7xXJ20D_Qj$61ju&VSFFyp*vz4UKFK#KTp;?4T zyRIcrR3?-0XE?uUrzj>I4pTHsu`G!Yq?0pTkR=UgO@%>(j%=%ztC|MxF@j-p%#9Gp z){+ytRi!nYa-dMis4TE3#!q`r6@iLe#aUyvRuorc3TjY?hU*|KQ^i_o(>0y6`4809 z?Y{}2;7U?eV?|qZy(;0jUE|1&azIu@JMo+aseb5~W3~*t#*vw8JZ~%^YD3_fcHA_z zN~-!QCgW_t&*1BJs)k}_oVEuJKoPhwLZGa1B+ZgE6Q@H0kFrL;R6q1ZUf z3QUKFNSd||kcX-cQjoHY6oFUcCPYomw2>S#hbwAJk`OZ-7Z@^( zi4g)h=~q=nkQD|(Iw+G&D4>vGE)XIC)4`BjM+gQPJ{%6Q$;x@rl*hbOR?aK`^L($Z zqGAHtBlCDA=G_6ITXj%&a^(`(0Y}RtO~s387l7~y>G^uthXrMM_qkeRB z|NCb#tY&pg?2L!rxb`;l%pSb1YxlU;{^9uM+mDC#rWT+3p`~&C>T@0YY8p~%`>*`^ zeJNk#d(+&^t>ww(*xko4@!LA~zB>^=Ii(%hwdKOZ%@g{K-k)+a*#51}v(wz9@5=P2 zg`woppS26E8!!3>S}*?wr}9hh+!{PK+B&c;v8{!$u67Qbzw(X!^1;c#!sT6-ujbNd zV4-D?eRKZKp#SL~zpurttM#vqOif&8r8d4P!TxglT;X0`tZh4zf8~(77c>p=&0qXJ zaP4c*+_Y4f`{cVsVbXWD(7G^l==$jomg}N-&m6}-o15Kz>a==7nr@t1=zZ(U183g7 ubN}$#i-}8rHe}9!ym;^6SF`hfEKXo+x4(Hzsb9x?eLmLJBOc#8^y**x0wFyB literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_trigger.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5a8f1d3ef4a92ac98bb781e4b9264728352e53 GIT binary patch literal 1538 zcmbVMTWAzl7@kmTH8CKxk~1lluu!%Esx2WIA6 zzVm(m<^2DtiLw3NozHes6xE#>)h5Y*OZ;wFPrg?#+k41wqc{3OKv7*!$8QI9{z5NB zt^3Z(=EJ;x02tWepoymu7dak5Q`ErT$b-fV3h8N7v|N?BcV&^GEmLKt61t#!X;iXC zYd$(u8_OEC8ACCdy@T{X1PFnHLP$rB?FJxHnK~~Z<9L~8=sF~vQJJAwkk0E9bQ=4J zRyct*1VNxhImwx@4<=D!H!TU0#ET*?C0RiMasmi4-Fz6L=bHsEsf{$%$V_EQVdw## zuT&~rMdq+ynh z)(x70A|c}==8$Z#c|paK{$MzxX}#An&==~^$BpzB0EF#`>iwxb48%Sdt`$UeIBiFTc9c0`fgsy=yn#vF-Im$M5H!6N0%Fvv6_Y{)e|d`a~J-+IsQcYgcgLxE@4sQ+Pwo_!9=zAR?0xh3*DKro-*&$-fBuJF zYWnmSUv+I=ee(Nb>*t-9*5z|Ov*LjK@!ayevu{Ic7EK8<^~|k53L{sZ|McX~^FQ)W ZEK-+7a({ksZk3C>bY^%=o7;Ev#9wx~{zL!( literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_bumper.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_bumper.png new file mode 100644 index 0000000000000000000000000000000000000000..1ccabb8b0bcd66b0b5f4c0d8fd90570015a8f3a5 GIT binary patch literal 1521 zcmbVMZD`$87*0`2ZQVeWG4w~XOr+}ad2f1eV&iOYuJ)qoN_)#X>yMr$C-;UXIWakH z(y}r9V0OX~r{a(yjQvom?1wl8n{0}1Za6548;AoL7TI-vAcDgDp(po?IjZ6UNzTV} zp6C5I@4I(&NK``LK@8}`bNxt7G^Wn0l5x7sGjtt-XEkOp6{IJPQM!Oa zNGqJcnt~wEVkXB~APaI(-a<=)B=MriOF33h)QqeO8M^r}L@%^#bxeP>sYYfRQ^hz? zc|ML~F3xZ$tni|uD7+x?lEe}O8!h-4B&;88ZZYUEGD9c84)W=g5zL@DtTDvah7(@U zruCy{ph(F01O&Xu38_A!BErRa;zlzdYoep_LIC+O7@@h)gvEJc=H}F!KrMt2U=)@Sa$BhyZJA6< zSx%(;wmZIs;^_YC4nQ4Xs4*mK8CH;3K`aY-RVG;zo@}5DWI6W24JfHBNvhbd%Ka@A zxhfXG;NNhTsoE&?0P)cAKn3!FUt#EW+|&YcQAp&7Ihne)VW=fP!oW9SN!J+SBEm9rMBWM^`$tSGjn*!^<8-)x=NkvW&9vUT42pXaxQ4k;$!-ZW2I zV(A@Fhn_0?n#;~jN1#C)S0N!?JHHJ?glrsx z5Dr#Ip#Jmf@WgIDt+|E^{Egn#_pYnY7Mv7)I&AkFGHG`#=#w@Tl72Mw-5=*kFZ-aR z50>-$F1#wPe@?i)xO=p7!^KO_^aS0#ub)Uh>o|Y5J0I6*}L1e)Zlf=dP{K zPZWEO-*<89#a+j{HhtOolfJsc{kUh-m8`M*t7|7eeQ(RO{YtFV23CGs?fdbiZ@Z}O zo#zfLJ)-yS-;Y;nm+$!e(C<5I(^r3ZySh^ATRwgCuES?vp8xZ~2i{tqh&LwRnAEhr zwDfE3@@oh3XC~Qu_I&jH<1ZXL^3H{+Gap_Zf48U~+&dMV>fM$3<(8*TZrd}lJOR?C NTq=&}M~0@K{R>jT`q}^h literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick.png new file mode 100644 index 0000000000000000000000000000000000000000..a415a10a80cf81435ec69c6a282fa5658e6fc7c1 GIT binary patch literal 1609 zcmbVMZD<>19KT9y*VrkuT8qlKUL82yy(VXRw_Qt3x+TNJnufMcrF-(+lk{lr#k*^h zbRa4#Ox^4UZ9$z|#iCf9PPDN4G94)U;ABG`ZVWr6%&C(W{Gcm-n9rq2n4>E0;O?H6 z-}C$ZU!MQ}sjm2z)xKMO1VOBhM#2gFZ}i?(*W&MI?`JpSU!4_s&>@KH8oalPcyVtN zK~$g7l6`Jp>^>QqIs;TQg_yicb&tf8aAXilD%IXbZpzD;Ku0bh^Lo~BhJ`~zE~F- zGHpak3`awb<4E2gWK<9UK_qS<1x^rHo@a$1%}KIflsP|HdMK=Ct7$nA-ds|{Gld#( zT}x)!TrS7t{ETU5SYDDOmJ?V(pfQ4W3Wf{vwBfXr8N$ecwr07SX^Kgcg+s>XfyMF+=Q&jbs<48yhU{!nT!k#kA{`m7gRy)CYYmvL=?s|vKwa7X zn*a`OELPE2(H32=NH}iiFm|I9kQLERvS1-LfgE$lhNyEGo7v)dW62>K0oSyXrkO3L zs;g`=DFhgvyt7v`R5RzSzw7`C0~aY2&YGX*1e)WMT&pbNta00xQ8821(uFHfLQ)W9 z{vKK6%P4$RRN#Vt!>LeCo3;+HhnfyDh_#FiMONY_hs>;LV>xV&zo;FH$x*{`fdNr8 ztWelVM$=ST%j&ZX$3^cAEByvfDjf?MxJ}u(A64Jc z+)ofyH$}r8$<{q5$N8^zlJ%qg!`TgQ?d>X5#gCj;YHm45h0ax7d`7H4$X)2~`}Iw( zp{@VM@YeM|)P216mA1Nl#N)fBN&ne}v0cfRW@fr?IHcb;ftGyA@dhHXSo3`1nl(?n z@Z3z}vHCT$gRSRxEH+b9&-VZF&9;ZuogJNKx@SjcZ*Hz`f+V&5$1~OeW@p#uuRrkH z;l-uXQZ4b_smRwg)WacyfX8Z2NWDh_uf^f+lf--TjdK&{$S4=^F5DP$FzVsgxU}(5bnV<1Et5|_wy^(?+S`v#ftRDFR(Z`n+8GbOeed=s{{r~Y B9@qc? literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_trigger.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..a0e295e8b3732e76398534725501c6fbd7c3bfb6 GIT binary patch literal 1544 zcmbVMYlz%b6wa2VZXa8_t+vaea7|GZn)jqL$u{m|GCG6J?y{Y>+qGab$(@<7liW1P zG_#AlNd2RQwjva)E^4Jvq13vzr51&@?t-BE!}|DR(LbUV(JBgxAn46JLM^Le14(Xj zzjMCxopaB5c4X+$&XqkY34-X%Wz|vq-Vog#%kgvJT%{MkR=C;SK0&NnAKh)li?iJX z(SFU!j|F4eHrYTnt(&L->6-0gG(l|btGT){0RvEgCCgFBAK$q|0?Sm$U82TnZW@-Y z?34$$PYvabsR=_e$-ald)|!kR*f7vR&8|4UTvN!pUm35X-wX-rkYGX~2O>f+rj39! z@*t3CmNHnD1)PwiO+BF}p|~0FEYC9>$M8vtm1IGbSphUJ67zXxQ65zvX|mxjg)9ew zD>FgvmeW7CS*yppPzIgGYWlF^$eKVkHg#)sd44B2lW7Xd4wvhQjN4i z27H3%z=OLi$3&t3&`kzV)dQ%IxM~8$@)XPE*_15esJIC%?-&-1*@ZIQ(o zg^!A<2l~I^OhYar&(^U*%hpSfah(zg;=IXeR6!o5!{G>ZZcUSOjvwfb0duNCVv)3E znKH{2#pIR(Pl+kWQ4*gJs8k`5qztnl@C*LWAbeB| zLfwM{C7h`LJUR@Hn~8d^A%VG7U48GS{%pZ<(?`pA-r$dTVnGLwDG$%1;Zw?SJj*`G zsRQ}cp&yRUb0Ny%L99h$Q>c;DDhF{y-FP~RC?wdRD!NGgRM~~iJ zI^KWf;JQ6K-kM+N9#B6MW*;l{5byqW=J4tB7cO7i`BOrBqV3n|J72xB?&mL`K6dD8 m=F+qnE-Wr57XQ4%S~If@Y%K4auALZ;W_2zzq`on@_rPDX@%wN9 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/start.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/start.png new file mode 100644 index 0000000000000000000000000000000000000000..9d736719774e373b5e1be67111f1c4e393adaae5 GIT binary patch literal 1748 zcmbVNZD`zN98YzVwzINXEoGDuQ>qr1%gZJA5_cL9|Z)@G#N)SX_YB)KD?;ZZx{1Cpbo;9Auw-$T&6_+4(wfkoiv9Q=l z5IaB8GZWr~{Gy~FGXzwWg`tvZV>Ce==quSknT8&jg*n|yP#fQTPm#Kspbm>NE8BxG zuMf{TaC~kgqs&b!ah2*jNFFFjn81V{AWNoUxl$=XRd^-5_Fpp;S%G-d398?BNKVLU zau7L?jE7iSVOf^s!qJcl1Q3Pdev)T-p5Zu#kJ4;h3X2jOCTj8jU}I}dBaZ$ zD(`u=#4yETF;onPkdtG$cs$OqJj3%eM$m5A@<55U-0nI<61s|`+n$ar(q{x&RPYiM zjg-H+P*TPYi=LOun z9E{M>s36cWk=JM;28Ae41yvD*y8Wo5m)uk{8MxVH?dw8cEcq>c5hB3z_-Mc}~x94^#dTZg$_+%A~CbJX{y zTf?Yt>bLuex>igj53C%-Gm)YEJQ#m=RHa~URjKmrhB;G* zUTb-Gs_Ek5S)+M%`NY+(?aqOzGppT8&pdu@;K=`9na2AUe?8gz(+@wt^k`&0zptfl zK7HlXv47VJ<|aq1cE0C!pq|YyH!q0$hpt#VFaPpRW^?k}l|#pmz0h)bQtRyz|6Jew xU)#dx&D-CdX&*Ul(KGU6>94apHnzo+O(%{lu3LZY`rZHUq=rV4UkpsWejOaZKH&fW literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/x_button.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/x_button.png new file mode 100644 index 0000000000000000000000000000000000000000..93d6629a310a9be081e94565f523dd4e4c15af5a GIT binary patch literal 1577 zcmbVMTWB0r7@pR))S4hgjIkJ*P86D&*}3d)l0EHOH@jPRZD$SX(xjmg&z?EEJMPS! z>CD;gCPj!%t4$#aB_K$}dZC3rc)?Ju24Y2}c%e^frFebuNh?%EFi_9zjas5&2WIA6 zzVm(m<^2E5Q0Bq*bsN@k9M_&sC5G9*!++PVVc!qt%`NPAvy>n&Y-^t2z*sFy(W&VAxUN^1F)yZ|G5BB&^Ar6URj( zHR2!W8mV%d|aEjS4khz{dWvB=9u|Ek%WxFUXH-LwuaL zn2!Wx5y`U5E5ZJN4nwdXhkJQdR#iz+B(-0ZBOn+Ca*%I40@HK#JQz-FZ>X_ZR47vF z07j) zxSH0|Sk@NHYDsuBxu3bw2*|Q%FFWC2X&8H?>>`}p&&=%hy>UR?#gGy=ONiM_)lk!9 zUJV5le$$9y>!jlKTyp>?AjMIEWi2SmswgX2c>si2*5t7|N+Y_FpSTXCW>pm^eIPW@ zM6s))Lkj;5rz4Ojt_7Kgh6M{)a_oY@x8erk#3U}0W99^F+L{K^wnw3ja5@ncn3Dm+ z(1ETX1Qi5D7}ER1NG{YT=HxsQb2&7SQ$xW>K9p~sPY_i0Q`tPP|IhOey9O&JXx=c7 zUt<0p04c*`*_mjRz%IP6F)|IlmUaM3)`$sxvU=*GTlG0UiND` z5z7wDem?ic`f0f>x#;yQa2uvJ7LTFXS5hZ;9MzxE*MQ99j$f2}XO0|?$#0IXKJ(t`(+BtEI})q%zdx2V?>lmS=M%e!AEM(= zoitwhdHU)25NbR0M{xd&O-FO*+t1x`UWYe0S&Jl~+b4mm-=wwTFB-_S5ATzinH(Y5QyTj7fJt Q={NIqGLv|9@X>>R0jXpU;Q#;t literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/y_button.png b/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/y_button.png new file mode 100644 index 0000000000000000000000000000000000000000..5203598663fd875464d89a3f20c3e7da6ca456ec GIT binary patch literal 1588 zcmbVMeQ4Zd98Ou?){d%mwU&Vrvi-5?<=%Ysc6Zt0t@i4Ao2h4etnH-OT=L#Ey-VIK zdArM1nOir|DMdF*#acuMe*EFaP*HG2D+&`<-9SV^g<`=ErWPHdf=u7s7jsm_1(LiU z&-*;TkN5XGn(W!x)VQIMqNt`scf6PUTm5(KYVv*m_53#SYp}ZqU5Z+_-hb<;nb|gq zs{h6JnM2%)I4Fp3IbZB{KGx0YjG{I43h5z92oIBimqby-*CDnWsqa4#6!bWvk=%;mZ58LlR8lzIYf?_BbKxkMM_vMRxJ%C;xa>= z3>tT53tFU2Iiw#wviRTx z>1BT=;vK2TSnhtYqN6z2Z2Ozd)Tskt$Ii!wkNmXi z#uJOTFP(kqZZLpJah|Q{_wLk=DE?^Zg}tDro@x*>Ggdrb9?UJ<2@W}eQ5Xf-VblEcg)Rf zpaQ>5Uwm0xYM~Z}=4Qs9y7Z;ontn~4o_$NXd1fhcJlB%n^unyR_GIql6Ne@*p4d$N z0Y)3@55B!zNA21|+0M@sM?a8eU;J(^^L6?H?Hy0q1FJ9BO&ze?B8~Ik`+Ysp)e}E;*YLz&vnLM^ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_bumper_big.png b/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_bumper_big.png deleted file mode 100644 index 194a40818663009bbe238cf634a2099b4a3b1174..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%i#=T&Ln>}1{rUgjp4pZ`n0fAj z6|T+qmBeC%l9G}_y!+P2?M+HKnA$mWrlf0IaY2E>p1QwMd2+%Vr{&z-#Jajsp-@82 zs_(?t&i;OWiN~9JI%gepH82dw;yk0S5P#96ac{c+S4}-FbeNiP7 ROE%EC44$rjF6*2UngIHCOrih) diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_trigger_big.png b/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_trigger_big.png deleted file mode 100644 index e7fc042040538ae4e24e7e268e1a2f5d6a8e05d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmV+o0rLKdP)@*P=u3{Cj8lhKSuJ2$&X3$E(SaM?DK}jDJ3)m3z~#! zL~A)Q#x#Sq)`xXgN?{nKlv|=631bY6#AmX@(v6Y;Aih*!xq@Wa3_!v;f3WN7Y6v0U zxwRGvAvSTaUIXHsbEq`KdiJE5{MSL6($&>G`w6;XvXHT=51?jyhe(X3hJ T9Q00000NkvXXu0mjfa<_KC diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_bumper_big.png b/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_bumper_big.png deleted file mode 100644 index 6bc380eff5870c615a8746c8447b787dd8b2f69a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI11|(N{`J4k%dp%toLn>}1{rUgjp4pZ`n0fAj z6|T+qmBb{ReSCVdPQJgoI=m-BmnkwSEzND>Lm3$vp7rtj+pMMT8i~b7ZO^&Ml>Yn2 z4*`+?A0M+fuUMgR!^qdym*@0{V}d6X1l-JI=3AUG5@?8Y+kB+qw2@fNtOPwbKBbyR zKbj`4ax~xZNl>#P;6fsE&w{Kbsh=G2`*zj+wL0Xkz`2X%?%wL}8A*)#`uep(ho7IH uFIedE)=4y4@wCOQG>NSp@5C5cBp8+jq}WI9T_Fne2!p4qpUXO@geCxdVqN+G diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_trigger_big.png b/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_trigger_big.png deleted file mode 100644 index 01c9651c2bd50d6bfd73083e91e1b4677f39d863..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 276 zcmV+v0qg#WP)(DM&pb%P@kD zR7*erq@I`%ph_GOV;R*FgPolnUdsk1)qx@v7EfEYY(Y`z_eKK6I14-?iy0XB z4ude`@%$AjK*3Z`7sn8d;I$VX7VtGFaIhpCY5HFOn9tSa^YZP>z7`f6UrN?9R{Uo* zbA^K8hrNlKF~`f}&(3>taDD1i4R_le+v{_3ZDu~dDZTlt;JTR)%@V{u#zjwcxpAS? mK#za&-%lq#hUe})uu5L!KbJ?mQHU+jMg~t;KbLh*2~7ZOpI@v1 literal 1556 zcmbVMTWl0n7#>UoTp&@4cu6CZsURes`;OZ^bS>@fYBxBg*=}S-Bt1KGW_R43In$Zh z?zTit3?ZV`7v-huiwXJy8p6eZm#XoW7%3s}B3?F;;FD=Wf*Rq$Gkck!R*g(DGw1T1 z@B1(3|M!oLKD;J!PlTeVHJOoAmi*TR@5<%m`{}XD7V^8r8F|d3s8!v;yNsH9?JkPy zx?tw=eqMb9XxQSQjthvZTMj`})WBfffm#{)bO9AjJHafzH_y=T3lxaAEXEBfDl;7hjiVl*dC}Sm?keE<6xO*=qAK3Czzo?kj|@PbP~IW zj&lO534%b2a*Weq6vmL!M@xbv@uJ8}F;<8JSpkAfw;zV+xw-+esfXHXWR_q`zV85@ zuhnW?P3Ev$G3^C=xQh4jo?PgutmLP}f47Q*|p%ab4q41zE`UJ%SZOtW&~1_Dc94sKf0)36S8b zYN)ZKEtVBZcz$|{xX}*Cl4vhC?I1pjJY01(l%67H_6FWKAn79LV>gF!rIV_$j>)tX z!vaNHP z`taXyx&{pFT99~XTCj+C$1XB-7&nl_73>l@VvgL@R#lL(Js;W{%A^tuagsAl9q3UB zDT1c3F~iVVJr;d{EyzZURkWB;K%!#ElF~V!!df**W#_#9KhJM>O;Sv-a>G19i3N86 zjF=wD&UCv3#?fSZR59sh+5yyp>zrV;Adg6A+RNq*N1#PpD1U=+G;X>?^?Y)++u5~PD5me=w|=qi`QFGIKW2a1lDvFrA-47VJ?GB6 z6}fVtv3Ovk@zT?KpF7j*P$#Tc552Hblv5XlZ6^;;81pNZZ$2of-TUB|r?!L9p$~qg z=k7iokE%z1d9(4^?9~qk&&`Slk7#Q*y!-g;>Fn2&w^e5M_x$wjogaS{IXrpoyCd1i z>63l;UEH&(XIJfs6%)JPVQ-~ApX{RY-jLt75cKU#dNlR+@Wiu!0N!~7a{vGU diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/xbox_dpad.png b/src/main/resources/assets/controlify/textures/gui/buttons/xbox/xbox_dpad.png deleted file mode 100644 index b6494e2422349250590af39a59fa060819941c1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1587 zcmbVMYiJx*7@bI~wM{Hq4I&1HsgYDVJFiT4cg!}AoupZkiEDPXu@dp_ox8i&-FakY zHoF_CN)5iF_$s2M(vqrwlnS9J#TG$PNC|2Yi;-4Rf`~tgD1=a|-q{ybqhbeU=046n z=X>1mdpbU{t7YRY8!3uvi4CfwlHaxV;C(JdZQA0$P1NfLZlqn3l{ zaDZna&-1h(1p_+h1VJQkr$t^AIYHpWAj^jpNmh7?u00IVbM&+_st(lD$SlHSJg$6sar*N+hqrk3Op2HNY*5l7g=6N@*zbgS>yLqQJSUWbop;6A*^)DiV&=$ z$W_sS2mTGGLnUoF1|S|{17r|qn;C{~#7&7>dCMVk#2l%jt!YZkbUk1~6jLJ%aWa6h zt_UG1B{bA*9@9+A$}mhlxwph{cJB0{=$eya%AN_$1% zpxz7#)PG(bj@ZrlHCJ_k`>S`Az02ye4ktzL4;%f4Od1^vnWRlQq#y0ub$*ugvfpBA zZ!$FV^(*gRH%&J`u{htd{rO4$1jT)S@#Ok9wj|od&wTLl>FDf!>8$-|U*F&={`Q5` zbJss|=lQvt{PN^WM>}#X)pOh7hiBwVZ~i>+ zX>3JaE)1j}c&DTA`~Ayv$yZYce=zVKe0TQ-s%P=o;?C)~yPSY;3xxp?7_`^BcY W0~70?`7sCmwjS#rQQzx(aNi$G5EB#t diff --git a/src/main/resources/hiddb.json5 b/src/main/resources/hiddb.json5 new file mode 100644 index 0000000..865580d --- /dev/null +++ b/src/main/resources/hiddb.json5 @@ -0,0 +1,30 @@ +// THIS FILE IS PARSED BY LENIENT GSON PARSER AND IS NOT JSON5 COMPLIANT! +{ + "xbox_one": { + "vendor": 1118, // 0x45e + "friendly_name": "Xbox One Controller", + "product": [ + 767, // 0x2ff + 746, // 0x2ea + 2834, // 0xb12 + 733, // 0x2dd + 739, // 0x2e3 + 742, // 0x2e6 + 765, // 0x2fd + 721, // 0x2d1 + 649, // 0x289 + 514, // 0x202 + 645, // 0x285 + 648 // 0x288 + ] + }, + "dualshock4": { + "vendor": 1356, // 0x54c + "friendly_name": "PS4 Controller", + "product": [ + 1476, // 0x5c4 + 2508, // 0x9cc + 2976 // 0xba0 + ] + } +}