1
0
forked from Clones/Controlify

Merge remote-tracking branch 'origin/feature/drivers' into 1.19.x/dev

# Conflicts:
#	src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java
This commit is contained in:
isXander
2023-04-14 21:57:39 +01:00
16 changed files with 511 additions and 125 deletions

View File

@ -25,7 +25,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
private final String guid;
private final ControllerType type;
private final ControllerBindings<S> bindings;
protected ControllerBindings<S> bindings;
protected C config, defaultConfig;
public AbstractController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
@ -37,7 +37,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
this.joystickId = joystickId;
this.guid = GLFW.glfwGetJoystickGUID(joystickId);
if (hidInfo.path().isPresent()) {
if (hidInfo.hidDevice().isPresent()) {
this.uid = hidInfo.createControllerUID().orElseThrow();
this.type = hidInfo.type();
} else {
@ -48,8 +48,6 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
var joystickName = GLFW.glfwGetJoystickName(joystickId);
String name = type != ControllerType.UNKNOWN || joystickName == null ? type.friendlyName() : joystickName;
setName(name);
this.bindings = new ControllerBindings<>(this);
}
public String name() {

View File

@ -15,6 +15,7 @@ import dev.isxander.controlify.utils.DebugLog;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import org.hid4java.HidDevice;
import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
@ -42,7 +43,6 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
void updateState();
void clearState();
default void open() {}
default void close() {}
RumbleManager rumbleManager();
@ -78,12 +78,13 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
CrashReportCategory category = crashReport.addCategory("Controller Info");
category.setDetail("Joystick ID", joystickId);
category.setDetail("Controller identification", hidInfo.type());
category.setDetail("HID path", hidInfo.path().orElse("N/A"));
category.setDetail("HID path", hidInfo.hidDevice().map(HidDevice::getPath).orElse("N/A"));
throw new ReportedException(crashReport);
}
}
static void remove(Controller<?, ?> controller) {
controller.close();
CONTROLLERS.remove(controller.uid(), controller);
}

View File

@ -1,28 +1,28 @@
package dev.isxander.controlify.controller.gamepad;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.AbstractController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.driver.*;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import org.libsdl.SDL;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGamepadState;
import java.util.Set;
public class GamepadController extends AbstractController<GamepadState, GamepadConfig> {
private GamepadState state = GamepadState.EMPTY;
private GamepadState prevState = GamepadState.EMPTY;
private long gamepadPtr;
private boolean rumbleSupported, triggerRumbleSupported;
private final RumbleManager rumbleManager;
private boolean hasGyro;
private GamepadState.GyroState absoluteGyro = GamepadState.GyroState.ORIGIN;
private final GamepadDrivers drivers;
private final Set<Driver> uniqueDrivers;
public GamepadController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
super(joystickId, hidInfo);
if (!GLFW.glfwJoystickIsGamepad(joystickId))
@ -31,10 +31,15 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
if (!this.name.startsWith(type().friendlyName()))
setName(GLFW.glfwGetGamepadName(joystickId));
this.drivers = GamepadDrivers.forController(joystickId, hidInfo.hidDevice());
this.uniqueDrivers = drivers.getUniqueDrivers();
this.rumbleManager = new RumbleManager(this);
this.defaultConfig = new GamepadConfig();
this.config = new GamepadConfig();
this.bindings = new ControllerBindings<>(this);
}
@Override
@ -51,23 +56,16 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
public void updateState() {
prevState = state;
GamepadState.AxesState rawAxesState = GamepadState.AxesState.fromController(this);
GamepadState.AxesState axesState = rawAxesState
uniqueDrivers.forEach(Driver::update);
BasicGamepadInputDriver.BasicGamepadState basicState = drivers.basicGamepadInputDriver().getBasicGamepadState();
GamepadState.AxesState deadzoneAxesState = basicState.axes()
.leftJoystickDeadZone(config().leftStickDeadzoneX, config().leftStickDeadzoneY)
.rightJoystickDeadZone(config().rightStickDeadzoneX, config().rightStickDeadzoneY);
GamepadState.ButtonState buttonState = GamepadState.ButtonState.fromController(this);
GamepadState.GyroState gyroDelta = null;
if (this.hasGyro) {
float[] gyro = new float[3];
SDL.SDL_GameControllerGetSensorData(gamepadPtr, SDL.SDL_SENSOR_GYRO, gyro, 3);
gyroDelta = new GamepadState.GyroState(gyro[0], gyro[1], gyro[2]);
if (DebugProperties.PRINT_GYRO) Controlify.LOGGER.info("Gyro delta: " + gyroDelta);
absoluteGyro = absoluteGyro.add(gyroDelta);
}
SDL.SDL_GameControllerUpdate();
GamepadState.GyroState gyroState = drivers.gyroDriver().getGyroState();
state = new GamepadState(axesState, rawAxesState, buttonState, gyroDelta, absoluteGyro);
state = new GamepadState(deadzoneAxesState, basicState.axes(), basicState.buttons(), gyroState, absoluteGyro);
}
public GamepadState.GyroState absoluteGyroState() {
@ -75,7 +73,7 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
}
public boolean hasGyro() {
return hasGyro;
return drivers.gyroDriver().isGyroSupported();
}
@Override
@ -104,18 +102,12 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
strongMagnitude *= strengthMod;
weakMagnitude *= strengthMod;
// the duration doesn't matter because we are not updating the gamecontroller state,
// so there is never any SDL check to stop the rumble after the desired time.
if (!SDL.SDL_GameControllerRumble(gamepadPtr, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 0)) {
Controlify.LOGGER.error("Could not rumble controller " + name() + ": " + SDL.SDL_GetError());
return false;
}
return true;
return drivers.rumbleDriver().rumble(strongMagnitude, weakMagnitude);
}
@Override
public boolean canRumble() {
return rumbleSupported
return drivers.rumbleDriver().isRumbleSupported()
&& config().allowVibrations
&& ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER;
}
@ -125,27 +117,8 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
return this.rumbleManager;
}
@Override
public void open() {
if (SDL2NativesManager.isLoaded()) {
this.gamepadPtr = SDL.SDL_GameControllerOpen(joystickId);
this.rumbleSupported = SDL.SDL_GameControllerHasRumble(gamepadPtr);
this.triggerRumbleSupported = SDL.SDL_GameControllerHasRumble(gamepadPtr);
if (this.hasGyro = SDL.SDL_GameControllerHasSensor(gamepadPtr, SDL.SDL_SENSOR_GYRO)) {
SDL.SDL_GameControllerSetSensorEnabled(gamepadPtr, SDL.SDL_SENSOR_GYRO, true);
}
} else {
this.gamepadPtr = 0;
this.rumbleSupported = false;
this.hasGyro = false;
}
}
@Override
public void close() {
SDL.SDL_GameControllerClose(gamepadPtr);
this.gamepadPtr = 0;
this.rumbleSupported = false;
this.hasGyro = false;
uniqueDrivers.forEach(Driver::close);
}
}

View File

@ -138,7 +138,6 @@ public final class GamepadState implements ControllerState {
"gamepadButtons=" + gamepadButtons + ']';
}
public record AxesState(
float leftStickX, float leftStickY,
float rightStickX, float rightStickY,
@ -178,23 +177,6 @@ public final class GamepadState implements ControllerState {
ControllerUtils.deadzone(rightTrigger, deadZone)
);
}
public static AxesState fromController(GamepadController controller) {
if (controller == null)
return EMPTY;
var state = controller.getGamepadState();
var axes = state.axes();
float leftX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X);
float leftY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y);
float rightX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X);
float rightY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y);
float leftTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) + 1f) / 2f;
float rightTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER) + 1f) / 2f;
return new AxesState(leftX, leftY, rightX, rightY, leftTrigger, rightTrigger);
}
}
public record ButtonState(
@ -211,32 +193,6 @@ public final class GamepadState implements ControllerState {
false, false, false, false,
false, false
);
public static ButtonState fromController(GamepadController controller) {
if (controller == null)
return EMPTY;
var state = controller.getGamepadState();
var buttons = state.buttons();
boolean a = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_A) == GLFW.GLFW_PRESS;
boolean b = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_B) == GLFW.GLFW_PRESS;
boolean x = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_X) == GLFW.GLFW_PRESS;
boolean y = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_Y) == GLFW.GLFW_PRESS;
boolean leftBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) == GLFW.GLFW_PRESS;
boolean rightBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) == GLFW.GLFW_PRESS;
boolean back = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_BACK) == GLFW.GLFW_PRESS;
boolean start = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_START) == GLFW.GLFW_PRESS;
boolean guide = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_GUIDE) == GLFW.GLFW_PRESS;
boolean dpadUp = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) == GLFW.GLFW_PRESS;
boolean dpadDown = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) == GLFW.GLFW_PRESS;
boolean dpadLeft = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) == GLFW.GLFW_PRESS;
boolean dpadRight = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT) == GLFW.GLFW_PRESS;
boolean leftStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB) == GLFW.GLFW_PRESS;
boolean rightStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB) == GLFW.GLFW_PRESS;
return new ButtonState(a, b, x, y, leftBumper, rightBumper, back, start, guide, dpadUp, dpadDown, dpadLeft, dpadRight, leftStick, rightStick);
}
}
public record GyroState(float pitch, float yaw, float roll) {

View File

@ -1,5 +1,6 @@
package dev.isxander.controlify.controller.hid;
import com.mojang.datafixers.util.Pair;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.ControllerType;
import org.hid4java.*;
@ -11,7 +12,7 @@ public class ControllerHIDService {
private final HidServicesSpecification specification;
private HidServices services;
private final Queue<HIDIdentifierWithPath> unconsumedControllerHIDs;
private final Queue<Pair<HidDevice, HIDIdentifier>> unconsumedControllerHIDs;
private final Map<String, HidDevice> attachedDevices = new HashMap<>();
private boolean disabled = false;
// https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages#usage-page
@ -41,19 +42,19 @@ public class ControllerHIDService {
public ControllerHIDInfo fetchType() {
doScanOnThisThread();
HIDIdentifierWithPath hid = unconsumedControllerHIDs.poll();
Pair<HidDevice, HIDIdentifier> hid = unconsumedControllerHIDs.poll();
if (hid == null) {
Controlify.LOGGER.warn("No controller found via USB hardware scan! This prevents identifying controller type.");
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
}
ControllerType type = ControllerType.getTypeMap().getOrDefault(hid.identifier(), ControllerType.UNKNOWN);
ControllerType type = ControllerType.getTypeMap().getOrDefault(hid.getSecond(), ControllerType.UNKNOWN);
if (type == ControllerType.UNKNOWN)
Controlify.LOGGER.warn("Controller found via USB hardware scan, but it was not found in the controller identification database! (HID: {})", hid.identifier());
Controlify.LOGGER.warn("Controller found via USB hardware scan, but it was not found in the controller identification database! (HID: {})", hid.getSecond());
unconsumedControllerHIDs.removeIf(h -> hid.path().equals(h.path()));
unconsumedControllerHIDs.removeIf(h -> hid.getFirst().getPath().equals(h.getFirst().getPath()));
return new ControllerHIDInfo(type, Optional.of(hid.path()));
return new ControllerHIDInfo(type, Optional.of(hid.getFirst()));
}
public boolean isDisabled() {
@ -75,7 +76,7 @@ public class ControllerHIDService {
// add an unconsumed identifier that can be removed if not disconnected
HIDIdentifier identifier = new HIDIdentifier(attachedDevice.getVendorId(), attachedDevice.getProductId());
if (isController(attachedDevice))
unconsumedControllerHIDs.add(new HIDIdentifierWithPath(attachedDevice.getPath(), identifier));
unconsumedControllerHIDs.add(new Pair<>(attachedDevice, identifier));
}
}
@ -90,7 +91,7 @@ public class ControllerHIDService {
removeList.add(deviceId);
// remove device from unconsumed list
unconsumedControllerHIDs.removeIf(device -> this.attachedDevices.get(deviceId).getPath().equals(device.path()));
unconsumedControllerHIDs.removeIf(device -> this.attachedDevices.get(deviceId).getPath().equals(device.getFirst().getPath()));
}
}
@ -105,16 +106,12 @@ public class ControllerHIDService {
boolean isGenericDesktopControlOrGameControl = device.getUsagePage() == 0x1 || device.getUsagePage() == 0x5;
boolean isSelfIdentifiedController = CONTROLLER_USAGE_IDS.contains(device.getUsage());
return ControllerType.getTypeMap().containsKey(new HIDIdentifier(device.getVendorId(), device.getProductId()))
|| (isGenericDesktopControlOrGameControl && isSelfIdentifiedController);
return isControllerType || (isGenericDesktopControlOrGameControl && isSelfIdentifiedController);
}
public record ControllerHIDInfo(ControllerType type, Optional<String> path) {
public record ControllerHIDInfo(ControllerType type, Optional<HidDevice> hidDevice) {
public Optional<String> createControllerUID() {
return path.map(p -> UUID.nameUUIDFromBytes(p.getBytes())).map(UUID::toString);
return hidDevice.map(HidDevice::getPath).map(p -> UUID.nameUUIDFromBytes(p.getBytes())).map(UUID::toString);
}
}
private record HIDIdentifierWithPath(String path, HIDIdentifier identifier) {
}
}

View File

@ -5,6 +5,7 @@ import com.google.gson.JsonElement;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.AbstractController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
@ -31,6 +32,12 @@ public class SingleJoystickController extends AbstractController<JoystickState,
this.config = new JoystickConfig(this);
this.defaultConfig = new JoystickConfig(this);
this.ptrJoystick = SDL2NativesManager.isLoaded() ? SDL.SDL_JoystickOpen(joystickId) : 0;
this.rumbleSupported = SDL2NativesManager.isLoaded() && SDL.SDL_JoystickHasRumble(this.ptrJoystick);
this.rumbleManager = new RumbleManager(this);
this.bindings = new ControllerBindings<>(this);
}
@Override
@ -117,13 +124,6 @@ public class SingleJoystickController extends AbstractController<JoystickState,
return this.rumbleManager;
}
@Override
public void open() {
this.ptrJoystick = SDL2NativesManager.isLoaded() ? SDL.SDL_JoystickOpen(joystickId) : 0;
this.rumbleSupported = SDL2NativesManager.isLoaded() && SDL.SDL_JoystickHasRumble(this.ptrJoystick);
this.rumbleManager = new RumbleManager(this);
}
@Override
public void close() {
SDL.SDL_JoystickClose(ptrJoystick);