1
0
forked from Clones/Controlify

Custom natives path (closes #114)

This commit is contained in:
isXander
2023-10-25 20:42:52 +01:00
parent 9427520013
commit 3ec8daa125
9 changed files with 142 additions and 38 deletions

View File

@ -56,6 +56,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;
import static io.github.libsdl4j.api.gamecontroller.SdlGamecontroller.SDL_GameControllerAddMappingsFromRW;
import static io.github.libsdl4j.api.gamecontroller.SdlGamecontroller.SDL_GameControllerNumMappings;
import static io.github.libsdl4j.api.rwops.SdlRWops.SDL_RWFromConstMem;
public class Controlify implements ControlifyApi {
@ -426,6 +427,8 @@ public class Controlify implements ControlifyApi {
* @param resource the already located `gamecontrollerdb.txt` resource
*/
private void loadGamepadMappings(Resource resource) {
Log.LOGGER.debug("Loading gamepad mappings...");
try (InputStream is = resource.open()) {
byte[] bytes = ByteStreams.toByteArray(is);

View File

@ -19,11 +19,11 @@ public class GlobalSettings {
AbstractContainerScreen.class
);
@SerializedName("keyboardMovement")
public boolean alwaysKeyboardMovement = false;
@SerializedName("keyboardMovement") public boolean alwaysKeyboardMovement = false;
public List<String> keyboardMovementWhitelist = new ArrayList<>();
public boolean outOfFocusInput = false;
public boolean loadVibrationNatives = false;
public String customVibrationNativesPath = "";
public boolean vibrationOnboarded = false;
public ReachAroundMode reachAround = ReachAroundMode.OFF;
public boolean allowServerRumble = true;

View File

@ -5,6 +5,10 @@ public interface RumbleDriver extends Driver {
boolean isRumbleSupported();
boolean rumbleTrigger(float left, float right);
boolean isTriggerRumbleSupported();
String getRumbleDetails();
RumbleDriver UNSUPPORTED = new RumbleDriver() {
@ -22,6 +26,16 @@ public interface RumbleDriver extends Driver {
return false;
}
@Override
public boolean rumbleTrigger(float left, float right) {
return false;
}
@Override
public boolean isTriggerRumbleSupported() {
return false;
}
@Override
public String getRumbleDetails() {
return "Unsupported";

View File

@ -8,11 +8,12 @@ import static io.github.libsdl4j.api.joystick.SdlJoystick.*;
public class SDL2JoystickDriver implements RumbleDriver {
private final SDL_Joystick ptrJoystick;
private final boolean isRumbleSupported;
private final boolean isRumbleSupported, isTriggerRumbleSupported;
public SDL2JoystickDriver(int jid) {
this.ptrJoystick = SDL_JoystickOpen(jid);
this.isRumbleSupported = SDL_JoystickHasRumble(ptrJoystick);
this.isTriggerRumbleSupported = SDL_JoystickHasRumbleTriggers(ptrJoystick);
}
@Override
@ -23,21 +24,36 @@ public class SDL2JoystickDriver implements RumbleDriver {
@Override
public boolean rumble(float strongMagnitude, float weakMagnitude) {
// duration of 0 is infinite
if (SDL_JoystickRumble(ptrJoystick, (short)(strongMagnitude * 65535.0F), (short)(weakMagnitude * 65535.0F), 0) != 0) {
if (SDL_JoystickRumble(ptrJoystick, (short)(strongMagnitude * 0xFFFF), (short)(weakMagnitude * 0xFFFF), 0) != 0) {
Log.LOGGER.error("Could not rumble controller: " + SDL_GetError());
return false;
}
return true;
}
@Override
public boolean rumbleTrigger(float left, float right) {
// duration of 0 is infinite
if (SDL_JoystickRumbleTriggers(ptrJoystick, (short)(left * 0xFFFF), (short)(right * 0xFFFF), 0) != 0) {
Log.LOGGER.error("Could not rumble controller trigger: " + SDL_GetError());
return false;
}
return true;
}
@Override
public boolean isRumbleSupported() {
return isRumbleSupported;
}
@Override
public boolean isTriggerRumbleSupported() {
return isTriggerRumbleSupported;
}
@Override
public String getRumbleDetails() {
return "SDL2joy supported=" + isRumbleSupported();
return "SDL2joy supported=" + isRumbleSupported() + " trigger=" + isTriggerRumbleSupported();
}
@Override

View File

@ -1,8 +1,8 @@
package dev.isxander.controlify.driver;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.config.ControlifyConfig;
import dev.isxander.controlify.gui.screen.DownloadingSDLScreen;
import dev.isxander.controlify.utils.DebugLog;
import dev.isxander.controlify.utils.Log;
import dev.isxander.controlify.utils.TrackingBodySubscriber;
import dev.isxander.controlify.utils.TrackingConsumer;
@ -10,22 +10,12 @@ import io.github.libsdl4j.jna.SdlNativeLibraryLoader;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import org.apache.commons.lang3.Validate;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.nio.file.*;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
@ -46,7 +36,6 @@ public class SDL2NativesManager {
new Target(Util.OS.OSX, true, true), new NativeFileInfo("darwin-aarch64", "macos-aarch64", "dylib")
);
private static final String NATIVE_LIBRARY_URL = "https://maven.isxander.dev/releases/dev/isxander/libsdl4j-natives/%s/".formatted(SDL2_VERSION);
private static final Path NATIVES_PATH = FabricLoader.getInstance().getGameDir().resolve("controlify-natives");
private static boolean loaded = false;
private static boolean attemptedLoad = false;
@ -65,7 +54,7 @@ public class SDL2NativesManager {
attemptedLoad = true;
Path localLibraryPath = NATIVES_PATH.resolve(Target.CURRENT.getArtifactName());
Path localLibraryPath = getNativesFolderPath().resolve(Target.CURRENT.getArtifactName());
if (Files.exists(localLibraryPath)) {
boolean success = loadAndStart(localLibraryPath);
@ -117,7 +106,7 @@ public class SDL2NativesManager {
}
private static CompletableFuture<Boolean> downloadAndStart(Path localLibraryPath) {
return downloadLibrary()
return downloadLibrary(localLibraryPath)
.thenCompose(success -> {
if (!success) {
return CompletableFuture.completedFuture(false);
@ -128,9 +117,7 @@ public class SDL2NativesManager {
.thenCompose(success -> Minecraft.getInstance().submit(() -> success));
}
private static CompletableFuture<Boolean> downloadLibrary() {
Path path = NATIVES_PATH.resolve(Target.CURRENT.getArtifactName());
private static CompletableFuture<Boolean> downloadLibrary(Path path) {
try {
Files.deleteIfExists(path);
Files.createDirectories(path.getParent());
@ -191,6 +178,22 @@ public class SDL2NativesManager {
return attemptedLoad;
}
private static Path getNativesFolderPath() {
Path nativesFolderPath = FabricLoader.getInstance().getGameDir();
ControlifyConfig config = Controlify.instance().config();
String customPath = config.globalSettings().customVibrationNativesPath;
if (!customPath.isEmpty()) {
try {
nativesFolderPath = Path.of(customPath);
} catch (InvalidPathException e) {
Log.LOGGER.error("Invalid custom SDL2 native library path. Using default and resetting custom path.", e);
config.globalSettings().customVibrationNativesPath = "";
config.save();
}
}
return nativesFolderPath.resolve("controlify-natives");
}
public record Target(Util.OS os, boolean is64Bit, boolean isARM) {
public static final Target CURRENT = Util.make(() -> {
Util.OS os = Util.getPlatform();

View File

@ -21,7 +21,7 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
private final SDL_GameController ptrGamepad;
private BasicGamepadState state = BasicGamepadState.EMPTY;
private GamepadState.GyroState gyroDelta = new GamepadState.GyroState(0, 0, 0);
private final boolean isGyroSupported, isRumbleSupported;
private final boolean isGyroSupported, isRumbleSupported, isTriggerRumbleSupported;
private final String guid;
public SDL2GamepadDriver(int jid) {
@ -29,6 +29,7 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
this.guid = SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(ptrGamepad)).toString();
this.isGyroSupported = SDL_GameControllerHasSensor(ptrGamepad, SDL_SENSOR_GYRO);
this.isRumbleSupported = SDL_GameControllerHasRumble(ptrGamepad);
this.isTriggerRumbleSupported = SDL_GameControllerHasRumbleTriggers(ptrGamepad);
if (this.isGyroSupported()) {
SDL_GameControllerSetSensorEnabled(ptrGamepad, SDL_SENSOR_GYRO, true);
@ -94,14 +95,28 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
@Override
public boolean rumble(float strongMagnitude, float weakMagnitude) {
if (!isRumbleSupported()) return false;
// duration of 0 is infinite
if (SDL_GameControllerRumble(ptrGamepad, (short)(strongMagnitude * 65535.0F), (short)(weakMagnitude * 65535.0F), 0) != 0) {
if (SDL_GameControllerRumble(ptrGamepad, (short)(strongMagnitude * 0xFFFF), (short)(weakMagnitude * 0xFFFF), 0) != 0) {
Log.LOGGER.error("Could not rumble controller: " + SDL_GetError());
return false;
}
return true;
}
@Override
public boolean rumbleTrigger(float left, float right) {
if (!isTriggerRumbleSupported()) return false;
// duration of 0 is infinite
if (SDL_GameControllerRumbleTriggers(ptrGamepad, (short)(left * 0xFFFF), (short)(right * 0xFFFF), 0) != 0) {
Log.LOGGER.error("Could not rumble controller trigger: " + SDL_GetError());
return false;
}
return true;
}
@Override
public GamepadState.GyroStateC getGyroState() {
return gyroDelta;
@ -132,6 +147,11 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
return isRumbleSupported;
}
@Override
public boolean isTriggerRumbleSupported() {
return isTriggerRumbleSupported;
}
@Override
public String getGUID() {
return guid;
@ -149,7 +169,7 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
@Override
public String getRumbleDetails() {
return "SDL2gp supported=" + isRumbleSupported();
return "SDL2gp supported=" + isRumbleSupported() + " trigger=" + isTriggerRumbleSupported();
}
@Override

View File

@ -0,0 +1,26 @@
package dev.isxander.controlify.gui.controllers;
import dev.isxander.yacl3.api.Option;
import dev.isxander.yacl3.api.controller.ValueFormatter;
import dev.isxander.yacl3.gui.controllers.string.StringController;
import net.minecraft.network.chat.Component;
public class FormattableStringController extends StringController {
private final ValueFormatter<String> formatter;
/**
* Constructs a string controller
*
* @param option bound option
* @param formatter the formatter to use
*/
public FormattableStringController(Option<String> option, ValueFormatter<String> formatter) {
super(option);
this.formatter = formatter;
}
@Override
public Component formatValue() {
return formatter.format(getString());
}
}

View File

@ -3,6 +3,7 @@ package dev.isxander.controlify.gui.screen;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.config.GlobalSettings;
import dev.isxander.controlify.gui.controllers.FormattableStringController;
import dev.isxander.controlify.reacharound.ReachAroundMode;
import dev.isxander.controlify.server.ServerPolicies;
import dev.isxander.controlify.server.ServerPolicy;
@ -27,20 +28,36 @@ public class GlobalSettingsScreenFactory {
.title(Component.translatable("controlify.gui.global_settings.title"))
.category(ConfigCategory.createBuilder()
.name(Component.translatable("controlify.gui.global_settings.title"))
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.load_vibration_natives"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.load_vibration_natives.tooltip"))
.text(Component.translatable("controlify.gui.load_vibration_natives.tooltip.warning").withStyle(ChatFormatting.RED))
.build())
.binding(true, () -> globalSettings.loadVibrationNatives, v -> globalSettings.loadVibrationNatives = v)
.controller(opt -> BooleanControllerBuilder.create(opt).yesNoFormatter())
.flag(OptionFlag.GAME_RESTART)
.build())
.option(ButtonOption.createBuilder()
.name(Component.translatable("controlify.gui.open_issue_tracker"))
.action((screen, button) -> Util.getPlatform().openUri("https://github.com/isxander/controlify/issues"))
.build())
.group(OptionGroup.createBuilder()
.name(Component.translatable("controlify.gui.natives"))
.option(Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.load_vibration_natives"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.load_vibration_natives.tooltip"))
.text(Component.translatable("controlify.gui.load_vibration_natives.tooltip.warning").withStyle(ChatFormatting.RED))
.build())
.binding(true, () -> globalSettings.loadVibrationNatives, v -> globalSettings.loadVibrationNatives = v)
.controller(opt -> BooleanControllerBuilder.create(opt).yesNoFormatter())
.flag(OptionFlag.GAME_RESTART)
.build())
.option(Option.<String>createBuilder()
.name(Component.translatable("controlify.gui.custom_natives_path"))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.custom_natives_path.tooltip"))
.text(Component.translatable("controlify.gui.custom_natives_path.tooltip.warning").withStyle(ChatFormatting.RED))
.build())
.binding("", () -> globalSettings.customVibrationNativesPath, v -> globalSettings.customVibrationNativesPath = v)
.customController(opt -> new FormattableStringController(opt, s -> {
if (s.isEmpty())
return Component.translatable("controlify.gui.custom_natives_path.none");
return Component.literal(s);
}))
.build())
.build())
.group(OptionGroup.createBuilder()
.name(Component.translatable("controlify.gui.server_options"))
.option(Option.<ReachAroundMode>createBuilder()