forked from Clones/Controlify
➕ Battery level warning + update SDL with macOS ARM support
This commit is contained in:
@ -0,0 +1,21 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
|
||||
public enum BatteryLevel {
|
||||
EMPTY, LOW, MEDIUM, FULL, MAX,
|
||||
WIRED, UNKNOWN;
|
||||
|
||||
public MutableComponent getFriendlyName() {
|
||||
return Component.translatable("controlify.battery_level." + name().toLowerCase()).withStyle(
|
||||
switch (this) {
|
||||
case EMPTY, LOW -> ChatFormatting.RED;
|
||||
case MEDIUM -> ChatFormatting.YELLOW;
|
||||
case FULL, MAX -> ChatFormatting.GREEN;
|
||||
default -> ChatFormatting.WHITE;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -2,24 +2,11 @@ package dev.isxander.controlify.controller;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.rumble.RumbleCapable;
|
||||
import dev.isxander.controlify.rumble.RumbleManager;
|
||||
import dev.isxander.controlify.rumble.RumbleSource;
|
||||
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;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface Controller<S extends ControllerState, C extends ControllerConfig> {
|
||||
@ -48,6 +35,10 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
|
||||
RumbleManager rumbleManager();
|
||||
boolean supportsRumble();
|
||||
|
||||
default BatteryLevel batteryLevel() {
|
||||
return BatteryLevel.UNKNOWN;
|
||||
}
|
||||
|
||||
Optional<ControllerHIDService.ControllerHIDInfo> hidInfo();
|
||||
|
||||
default boolean canBeUsed() {
|
||||
|
@ -17,6 +17,10 @@ public record ControllerType(String friendlyName, String mappingId, String theme
|
||||
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
||||
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "controllers/controller_identification.json5");
|
||||
|
||||
public static ControllerType getTypeForHID(HIDIdentifier hid) {
|
||||
return getTypeMap().getOrDefault(hid, ControllerType.UNKNOWN);
|
||||
}
|
||||
|
||||
public static void ensureTypeMapFilled() {
|
||||
if (typeMap != null) return;
|
||||
|
||||
|
@ -2,6 +2,7 @@ package dev.isxander.controlify.controller.gamepad;
|
||||
|
||||
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.driver.*;
|
||||
import dev.isxander.controlify.rumble.RumbleManager;
|
||||
@ -103,6 +104,11 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
|
||||
return this.rumbleManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatteryLevel batteryLevel() {
|
||||
return drivers.batteryDriver().getBatteryLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
uniqueDrivers.forEach(Driver::close);
|
||||
|
@ -3,6 +3,9 @@ package dev.isxander.controlify.controller.hid;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.hid4java.*;
|
||||
|
||||
import java.util.*;
|
||||
@ -15,6 +18,7 @@ public class ControllerHIDService {
|
||||
private final Queue<Pair<HidDevice, HIDIdentifier>> unconsumedControllerHIDs;
|
||||
private final Map<String, HidDevice> attachedDevices = new HashMap<>();
|
||||
private boolean disabled = false;
|
||||
private boolean firstFetch = true;
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages#usage-page
|
||||
private static final Set<Integer> CONTROLLER_USAGE_IDS = Set.of(
|
||||
0x04, // Joystick
|
||||
@ -39,7 +43,29 @@ public class ControllerHIDService {
|
||||
}
|
||||
}
|
||||
|
||||
public ControllerHIDInfo fetchType() {
|
||||
public ControllerHIDInfo fetchType(int jid) {
|
||||
if (firstFetch) {
|
||||
firstFetch = false;
|
||||
if (isDisabled() && !SDL2NativesManager.isLoaded()) {
|
||||
if (Controlify.instance().controllerHIDService().isDisabled() && !SDL2NativesManager.isLoaded()) {
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.error.hid"),
|
||||
Component.translatable("controlify.error.hid.desc"),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
|
||||
}
|
||||
@ -52,7 +78,7 @@ public class ControllerHIDService {
|
||||
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
|
||||
}
|
||||
|
||||
ControllerType type = ControllerType.getTypeMap().getOrDefault(hid.getSecond(), ControllerType.UNKNOWN);
|
||||
ControllerType type = ControllerType.getTypeForHID(hid.getSecond());
|
||||
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.getSecond());
|
||||
|
||||
|
@ -6,6 +6,7 @@ import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.Util;
|
||||
import org.libsdl.SDL;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
@ -13,6 +14,7 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.libsdl.SDL_Hints.*;
|
||||
@ -20,10 +22,11 @@ import static org.libsdl.SDL_Hints.*;
|
||||
public class SDL2NativesManager {
|
||||
private static final String SDL2_VERSION = "<SDL2_VERSION>";
|
||||
private static final Map<Target, String> NATIVE_LIBRARIES = Map.of(
|
||||
new Target(Util.OS.WINDOWS, true), "windows64.dll",
|
||||
new Target(Util.OS.WINDOWS, false), "window32.dll",
|
||||
new Target(Util.OS.LINUX, true), "linux64.so"
|
||||
//new Target(Util.OS.OSX, true), "mac64.dylib"
|
||||
new Target(Util.OS.WINDOWS, true, false), "windows64.dll",
|
||||
new Target(Util.OS.WINDOWS, false, false), "window32.dll",
|
||||
new Target(Util.OS.LINUX, true, false), "linux64.so",
|
||||
new Target(Util.OS.OSX, true, false), "macosx64.dylib",
|
||||
new Target(Util.OS.OSX, true, true), "macosxarm64.dylib"
|
||||
);
|
||||
private static final String NATIVE_LIBRARY_URL = "https://maven.isxander.dev/releases/dev/isxander/sdl2-jni-natives/%s/".formatted(SDL2_VERSION);
|
||||
|
||||
@ -41,6 +44,16 @@ public class SDL2NativesManager {
|
||||
|
||||
Path localLibraryPath = Target.CURRENT.getLocalNativePath();
|
||||
if (Files.notExists(localLibraryPath)) {
|
||||
if (Files.exists(localLibraryPath.getParent())) {
|
||||
try(var walk = Files.walk(localLibraryPath.getParent())) {
|
||||
walk.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEachOrdered(File::delete);
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to delete old SDL2 native library", e);
|
||||
}
|
||||
}
|
||||
|
||||
Controlify.LOGGER.info("Downloading SDL2 native library: " + Target.CURRENT.getArtifactName());
|
||||
downloadLibrary(localLibraryPath);
|
||||
}
|
||||
@ -110,12 +123,13 @@ public class SDL2NativesManager {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private record Target(Util.OS os, boolean is64Bit) {
|
||||
private record Target(Util.OS os, boolean is64Bit, boolean isARM) {
|
||||
public static final Target CURRENT = Util.make(() -> {
|
||||
Util.OS os = Util.getPlatform();
|
||||
boolean is64bit = System.getProperty("os.arch").contains("64");
|
||||
boolean isARM = System.getProperty("os.arch").contains("arm");
|
||||
|
||||
return new Target(os, is64bit);
|
||||
return new Target(os, is64bit, isARM);
|
||||
});
|
||||
|
||||
public boolean hasNativeLibrary() {
|
||||
|
Reference in New Issue
Block a user