1
0
forked from Clones/Controlify

Implement SDL controller identification when hid4java is unavailable (macOS ARM)

This commit is contained in:
isXander
2023-07-10 18:01:55 +01:00
parent 23d65cb89d
commit 45e859bdb1
19 changed files with 141 additions and 43 deletions

View File

@ -20,7 +20,7 @@ quilt_json5 = "1.0.3"
sodium = "mc1.20-0.4.10"
iris = "1.6.4+1.20"
immediately_fast = "1.1.15+1.20.1"
sdl2_jni = "2.26.5-18"
sdl2_jni = "2.26.5-24"
[libraries]
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }

View File

@ -3,7 +3,6 @@ package dev.isxander.controlify;
import com.mojang.blaze3d.Blaze3D;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
import dev.isxander.controlify.driver.SteamDeckDriver;
import dev.isxander.controlify.gui.controllers.ControllerBindHandler;
import dev.isxander.controlify.gui.screen.ControllerCarouselScreen;
import dev.isxander.controlify.controller.Controller;
@ -16,7 +15,7 @@ import dev.isxander.controlify.reacharound.ReachAroundHandler;
import dev.isxander.controlify.reacharound.ReachAroundMode;
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
import dev.isxander.controlify.config.ControlifyConfig;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.api.event.ControlifyEvents;
import dev.isxander.controlify.gui.guide.InGameButtonGuide;
import dev.isxander.controlify.ingame.InGameInputHandler;

View File

@ -3,10 +3,11 @@ package dev.isxander.controlify;
import com.google.common.collect.ImmutableList;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.CompoundJoystickController;
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.hid.HIDDevice;
import dev.isxander.controlify.utils.DebugLog;
import dev.isxander.controlify.utils.Log;
import net.minecraft.CrashReport;
@ -54,7 +55,7 @@ public final class ControllerManager {
CrashReportCategory category = crashReport.addCategory("Controller Info");
category.setDetail("Joystick ID", joystickId);
category.setDetail("Controller identification", hidInfo.type());
category.setDetail("HID path", hidInfo.hidDevice().map(HidDevice::getPath).orElse("N/A"));
category.setDetail("HID path", hidInfo.hidDevice().map(HIDDevice::path).orElse("N/A"));
category.setDetail("HID service status", Controlify.instance().controllerHIDService().isDisabled() ? "Disabled" : "Enabled");
category.setDetail("GLFW name", Optional.ofNullable(GLFW.glfwGetJoystickName(joystickId)).orElse("N/A"));
throw new ReportedException(crashReport);

View File

@ -6,7 +6,7 @@ import com.google.gson.JsonElement;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.rumble.RumbleCapable;
import dev.isxander.controlify.utils.Log;
import org.apache.commons.lang3.SerializationUtils;

View File

@ -3,7 +3,7 @@ package dev.isxander.controlify.controller;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.rumble.RumbleCapable;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;

View File

@ -1,7 +1,7 @@
package dev.isxander.controlify.controller;
import com.google.common.collect.ImmutableMap;
import dev.isxander.controlify.controller.hid.HIDIdentifier;
import dev.isxander.controlify.hid.HIDIdentifier;
import dev.isxander.controlify.utils.Log;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;

View File

@ -4,7 +4,7 @@ import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.AbstractController;
import dev.isxander.controlify.controller.BatteryLevel;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.driver.*;
import dev.isxander.controlify.rumble.RumbleManager;
@ -44,7 +44,7 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
this.defaultConfig = new GamepadConfig();
this.config = new GamepadConfig();
if (hidInfo.hidDevice().map(hid -> SteamDeckDriver.isSteamDeck(hid.getVendorId(), hid.getProductId())).orElse(false)) {
if (hidInfo.hidDevice().map(hid -> SteamDeckDriver.isSteamDeck(hid.vendorID(), hid.productID())).orElse(false)) {
this.defaultConfig.mixedInput = true;
this.config.mixedInput = true;
}

View File

@ -5,7 +5,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.ControllerType;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
import dev.isxander.controlify.rumble.RumbleCapable;

View File

@ -4,7 +4,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.AbstractController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;

View File

@ -130,7 +130,7 @@ public class SDL2NativesManager {
return initialised;
}
private record Target(Util.OS os, boolean is64Bit, boolean isARM) {
public record Target(Util.OS os, boolean is64Bit, boolean isARM) {
public static final Target CURRENT = Util.make(() -> {
Util.OS os = Util.getPlatform();
@ -155,5 +155,9 @@ public class SDL2NativesManager {
.resolve("controlify-natives")
.resolve(getArtifactName());
}
public boolean isMacArm() {
return os == Util.OS.OSX && isARM;
}
}
}

View File

@ -3,8 +3,8 @@ package dev.isxander.controlify.driver;
import com.google.common.collect.Sets;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.hid.HIDDevice;
import dev.isxander.controlify.utils.Log;
import org.hid4java.HidDevice;
import java.util.*;
@ -28,7 +28,7 @@ public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, Gy
}
}
public static GamepadDrivers forController(int jid, Optional<HidDevice> hid) {
public static GamepadDrivers forController(int jid, Optional<HIDDevice> hid) {
GLFWGamepadDriver glfwDriver = new GLFWGamepadDriver(jid);
BasicGamepadInputDriver basicGamepadInputDriver = glfwDriver;
@ -50,7 +50,7 @@ public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, Gy
}
// TODO: Fix Steam Deck driver
if (hid.isPresent() && SteamDeckDriver.isSteamDeck(hid.get().getVendorId(), hid.get().getProductId()) && false) {
if (hid.isPresent() && hid.get().supportsCommunication() && SteamDeckDriver.isSteamDeck(hid.get().vendorID(), hid.get().productID()) && false) {
gyroDriver = new SteamDeckDriver(hid.get());
}

View File

@ -1,9 +1,8 @@
package dev.isxander.controlify.driver;
import dev.isxander.controlify.controller.gamepad.GamepadState;
import dev.isxander.controlify.controller.hid.HIDIdentifier;
import dev.isxander.controlify.hid.HIDDevice;
import dev.isxander.controlify.utils.Log;
import org.hid4java.HidDevice;
import java.util.Arrays;
@ -12,16 +11,15 @@ public class SteamDeckDriver implements GyroDriver, BasicGamepadInputDriver {
private static final int cByteposInput = 4; // Position in the raw hid data where HID data byte is
private static final byte[] startMarker = new byte[] { 0x01, 0x00, 0x09, 0x40 }; // Beginning of every Steam deck HID frame
private final HidDevice hidDevice;
private final HIDDevice hidDevice;
private int interval = 0;
private GamepadState.GyroState gyroDelta = new GamepadState.GyroState();
private BasicGamepadState basicGamepadState = new BasicGamepadState(GamepadState.AxesState.EMPTY, GamepadState.ButtonState.EMPTY);
public SteamDeckDriver(HidDevice hidDevice) {
public SteamDeckDriver(HIDDevice hidDevice) {
this.hidDevice = hidDevice;
this.hidDevice.open();
this.hidDevice.setNonBlocking(true);
}
@Override
@ -50,7 +48,7 @@ public class SteamDeckDriver implements GyroDriver, BasicGamepadInputDriver {
}
private void keepAlive() {
hidDevice.sendFeatureReport(new byte[0], (byte) 8);
//hidDevice.sendFeatureReport(new byte[0], (byte) 8);
}
private void readFrame(Frame frame) {

View File

@ -514,7 +514,7 @@ public class ControllerConfigScreenFactory {
return opt;
}));
} else {
boolean isSteamDeck = gamepad != null && gamepad.hidInfo().map(hid -> hid.hidDevice().map(d -> SteamDeckDriver.isSteamDeck(d.getVendorId(), d.getProductId())).orElse(false)).orElse(false);
boolean isSteamDeck = gamepad != null && gamepad.hidInfo().map(hid -> hid.hidDevice().map(d -> SteamDeckDriver.isSteamDeck(d.vendorID(), d.productID())).orElse(false)).orElse(false);
gyroGroup.option(LabelOption.create(Component.translatable(!isSteamDeck ? "controlify.gui.group.gyro.no_gyro.tooltip" : "controlify.gui.group.gyro.no_gyro_steamdeck.tooltip").withStyle(ChatFormatting.RED)));
}

View File

@ -1,6 +1,7 @@
package dev.isxander.controlify.gui.screen;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
@ -25,9 +26,9 @@ public class SDLOnboardingScreen extends ConfirmScreen {
Util.make(() -> {
var message = Component.translatable("controlify.sdl2_onboarding.message");
// if (Util.getPlatform() == Util.OS.OSX) {
// message.append("\n").append(Component.translatable("controlify.sdl2_onboarding.message_mac").withStyle(ChatFormatting.RED));
// }
if (SDL2NativesManager.Target.CURRENT.isMacArm()) {
message.append("\n").append(Component.translatable("controlify.sdl2_onboarding.message_mac").withStyle(ChatFormatting.RED));
}
message.append("\n\n").append(Component.translatable("controlify.sdl2_onboarding.question"));

View File

@ -1,4 +1,4 @@
package dev.isxander.controlify.controller.hid;
package dev.isxander.controlify.hid;
import com.mojang.datafixers.util.Pair;
import dev.isxander.controlify.Controlify;
@ -8,6 +8,8 @@ import dev.isxander.controlify.utils.Log;
import dev.isxander.controlify.utils.ToastUtils;
import net.minecraft.network.chat.Component;
import org.hid4java.*;
import org.libsdl.SDL;
import org.lwjgl.glfw.GLFW;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
@ -58,16 +60,18 @@ public class ControllerHIDService {
}
}
// if (SDL2NativesManager.isLoaded()) {
// int vid = SDL.SDL_JoystickGetDeviceVendor(jid);
// int pid = SDL.SDL_JoystickGetDeviceProduct(jid);
//
// if (vid != 0 && pid != 0) {
// return new ControllerHIDInfo(ControllerType.getTypeForHID(new HIDIdentifier(vid, pid)), Optional.empty());
// }
// }
if (disabled) {
if (SDL2NativesManager.isLoaded()) {
int vid = SDL.SDL_JoystickGetDeviceVendor(jid);
int pid = SDL.SDL_JoystickGetDeviceProduct(jid);
String path = GLFW.glfwGetJoystickGUID(jid);
if (vid != 0 && pid != 0) {
Log.LOGGER.info("Using SDL to identify controller type.");
return new ControllerHIDInfo(ControllerType.getTypeForHID(new HIDIdentifier(vid, pid)), Optional.of(new HIDDevice.IDOnly(vid, pid, path)));
}
}
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
}
@ -85,7 +89,7 @@ public class ControllerHIDService {
unconsumedControllerHIDs.removeIf(h -> hid.getFirst().getPath().equals(h.getFirst().getPath()));
return new ControllerHIDInfo(type, Optional.of(hid.getFirst()));
return new ControllerHIDInfo(type, Optional.of(new HIDDevice.Hid4Java(hid.getFirst())));
}
public boolean isDisabled() {
@ -133,7 +137,7 @@ public class ControllerHIDService {
}
public void unconsumeController(ControllerHIDInfo hid) {
hid.hidDevice.ifPresent(device -> attachedDevices.remove(device.getPath()));
hid.hidDevice.ifPresent(device -> attachedDevices.remove(device.path()));
}
private boolean isController(HidDevice device) {
@ -144,9 +148,9 @@ public class ControllerHIDService {
return isControllerType || (isGenericDesktopControlOrGameControl && isSelfIdentifiedController);
}
public record ControllerHIDInfo(ControllerType type, Optional<HidDevice> hidDevice) {
public record ControllerHIDInfo(ControllerType type, Optional<HIDDevice> hidDevice) {
public Optional<String> createControllerUID() {
return hidDevice.map(HidDevice::getPath).map(p -> UUID.nameUUIDFromBytes(p.getBytes())).map(UUID::toString);
return hidDevice.map(HIDDevice::path).map(p -> UUID.nameUUIDFromBytes(p.getBytes())).map(UUID::toString);
}
}
}

View File

@ -0,0 +1,91 @@
package dev.isxander.controlify.hid;
public sealed interface HIDDevice permits HIDDevice.Hid4Java, HIDDevice.IDOnly {
int vendorID();
int productID();
String path();
boolean supportsCommunication();
void open();
void close();
int read(byte[] buffer);
int write(byte[] buffer, int packetLength, byte reportId);
final class Hid4Java implements HIDDevice {
private final org.hid4java.HidDevice hidDevice;
public Hid4Java(org.hid4java.HidDevice hidDevice) {
this.hidDevice = hidDevice;
}
@Override
public int vendorID() {
return hidDevice.getVendorId();
}
@Override
public int productID() {
return hidDevice.getProductId();
}
@Override
public String path() {
return hidDevice.getPath();
}
@Override
public boolean supportsCommunication() {
return true;
}
@Override
public void open() {
hidDevice.open();
hidDevice.setNonBlocking(true);
}
@Override
public void close() {
hidDevice.close();
}
@Override
public int read(byte[] buffer) {
return hidDevice.read(buffer);
}
@Override
public int write(byte[] buffer, int packetLength, byte reportId) {
return hidDevice.write(buffer, packetLength, reportId);
}
}
record IDOnly(int vendorID, int productID, String path) implements HIDDevice {
@Override
public boolean supportsCommunication() {
return false;
}
@Override
public void open() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
throw new UnsupportedOperationException();
}
@Override
public int read(byte[] buffer) {
throw new UnsupportedOperationException();
}
@Override
public int write(byte[] buffer, int packetLength, byte reportId) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -1,4 +1,4 @@
package dev.isxander.controlify.controller.hid;
package dev.isxander.controlify.hid;
public record HIDIdentifier(int vendorId, int productId) {
}

View File

@ -166,7 +166,7 @@
"controlify.sdl2_onboarding.title": "Controlify Native Library",
"controlify.sdl2_onboarding.message": "Many features in Controlify require an extra library that needs to be downloaded for your system. If you do not download this library, you will lose access to many features such as: controller vibration, gyroscope control, better controller identification. This is a seamless process and will only take a few seconds. If you choose no, you may change your mind later in Controlify settings, but you won't have access to these features in the meantime.",
"controlify.sdl2_onboarding.message_mac": "Because you are on macOS, this library is required for any sort of controller identification. Without it, all controllers will be unidentified. This will severely impact on user experience if you choose not to download them.",
"controlify.sdl2_onboarding.message_mac": "Because you are on macOS ARM, this library is required for any sort of controller identification. Without it, all controllers will be unidentified. This will severely impact on user experience if you choose not to download them.",
"controlify.sdl2_onboarding.question": "Would you like to download them?",
"controlify.controller_theme.default": "Default",

View File

@ -5,7 +5,7 @@ import com.google.gson.JsonElement;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.ControllerType;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.JoystickConfig;
import dev.isxander.controlify.controller.joystick.JoystickController;
import dev.isxander.controlify.controller.joystick.JoystickState;