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()

View File

@ -7,11 +7,16 @@
"controlify.gui.carousel.art_credit": "Controller art by %s.",
"controlify.gui.global_settings.title": "Global Settings",
"controlify.gui.server_options": "Server Options",
"controlify.gui.miscellaneous": "Miscellaneous",
"controlify.gui.natives": "Natives",
"controlify.gui.load_vibration_natives": "Load Natives",
"controlify.gui.load_vibration_natives.tooltip": "If enabled, Controlify will download and load native libraries on launch to enable support for enhanced features such as vibration and gyro. The download process only happens once and only downloads for your specific OS. Disabling this will not delete the natives, it just won't load them.",
"controlify.gui.load_vibration_natives.tooltip.warning": "You must enable vibration support per-controller as well as this setting.",
"controlify.gui.custom_natives_path": "Custom Natives Path",
"controlify.gui.custom_natives_path.tooltip": "Specify a custom folder where Controlify will save and load it's native libraries. This is an absolute path and is not relative to .minecraft. If you enter an invalid directory, this will be reset. Leave blank for default.",
"controlify.gui.custom_natives_path.tooltip.warning": "This is an advanced setting. Don't touch it if you don't know what you're doing!",
"controlify.gui.custom_natives_path.none": "Not set",
"controlify.gui.server_options": "Server Options",
"controlify.gui.miscellaneous": "Miscellaneous",
"controlify.gui.reach_around": "Block Reach Around",
"controlify.gui.reach_around.tooltip": "If enabled, you can interact with the block you are standing on in the direction you are looking.",
"controlify.gui.reach_around.tooltip.parity": "This is parity with bedrock edition where you can also do this.",