forked from Clones/Controlify
➕ Battery level warning + update SDL with macOS ARM support
This commit is contained in:
@ -22,6 +22,7 @@ import dev.isxander.controlify.sound.ControlifySounds;
|
||||
import dev.isxander.controlify.utils.DebugLog;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import dev.isxander.controlify.wireless.LowBatteryNotifier;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
|
||||
@ -57,7 +58,7 @@ public class Controlify implements ControlifyApi {
|
||||
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
||||
private ControllerHIDService controllerHIDService;
|
||||
|
||||
private CompletableFuture<Boolean> vibrationOnboardingFuture = null;
|
||||
private CompletableFuture<Boolean> nativeOnboardingFuture = null;
|
||||
|
||||
private final ControlifyConfig config = new ControlifyConfig(this);
|
||||
|
||||
@ -77,13 +78,13 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
var controllersConnected = IntStream.range(0, GLFW.GLFW_JOYSTICK_LAST + 1).anyMatch(GLFW::glfwJoystickPresent);
|
||||
if (controllersConnected) {
|
||||
askVibrationNatives().whenComplete((loaded, th) -> discoverControllers());
|
||||
askNatives().whenComplete((loaded, th) -> discoverControllers());
|
||||
}
|
||||
|
||||
// listen for new controllers
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
try {
|
||||
this.askVibrationNatives().whenComplete((loaded, th) -> {
|
||||
this.askNatives().whenComplete((loaded, th) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
this.onControllerHotplugged(jid);
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
@ -96,25 +97,25 @@ public class Controlify implements ControlifyApi {
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> askVibrationNatives() {
|
||||
if (vibrationOnboardingFuture != null) return vibrationOnboardingFuture;
|
||||
private CompletableFuture<Boolean> askNatives() {
|
||||
if (nativeOnboardingFuture != null) return nativeOnboardingFuture;
|
||||
|
||||
if (config().globalSettings().vibrationOnboarded) {
|
||||
return CompletableFuture.completedFuture(config().globalSettings().loadVibrationNatives);
|
||||
}
|
||||
|
||||
vibrationOnboardingFuture = new CompletableFuture<>();
|
||||
nativeOnboardingFuture = new CompletableFuture<>();
|
||||
|
||||
minecraft.setScreen(new VibrationOnboardingScreen(
|
||||
minecraft.setScreen(new SDLOnboardingScreen(
|
||||
minecraft.screen,
|
||||
answer -> {
|
||||
if (answer)
|
||||
SDL2NativesManager.initialise();
|
||||
vibrationOnboardingFuture.complete(answer);
|
||||
nativeOnboardingFuture.complete(answer);
|
||||
}
|
||||
));
|
||||
|
||||
return vibrationOnboardingFuture;
|
||||
return nativeOnboardingFuture;
|
||||
}
|
||||
|
||||
private void discoverControllers() {
|
||||
@ -229,6 +230,8 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
}
|
||||
|
||||
LowBatteryNotifier.tick();
|
||||
|
||||
getCurrentController().ifPresent(currentController -> {
|
||||
wrapControllerError(
|
||||
() -> tickController(currentController, outOfFocus),
|
||||
|
@ -19,4 +19,5 @@ public class GlobalSettings {
|
||||
public boolean vibrationOnboarded = false;
|
||||
public ReachAroundMode reachAround = ReachAroundMode.OFF;
|
||||
public boolean uiSounds = false;
|
||||
public boolean notifyLowBattery = true;
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ import com.terraformersmc.modmenu.api.ModMenuApi;
|
||||
public class ModMenuIntegration implements ModMenuApi {
|
||||
@Override
|
||||
public ConfigScreenFactory<?> getModConfigScreenFactory() {
|
||||
return YACLHelper::generateConfigScreen;
|
||||
return YACLHelper::openConfigScreen;
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,18 @@ public class YACLHelper {
|
||||
private static final Function<Float, Component> percentFormatter = v -> Component.literal(String.format("%.0f%%", v*100));
|
||||
private static final Function<Float, Component> percentOrOffFormatter = v -> v == 0 ? CommonComponents.OPTION_OFF : percentFormatter.apply(v);
|
||||
|
||||
public static Screen generateConfigScreen(Screen parent) {
|
||||
public static Screen openConfigScreen(Screen parent) {
|
||||
Screen configScreen = generateConfigScreen(parent);
|
||||
if (!Controlify.instance().config().globalSettings().vibrationOnboarded)
|
||||
configScreen = new SDLOnboardingScreen(configScreen, yes -> {
|
||||
if (yes) {
|
||||
SDL2NativesManager.initialise();
|
||||
}
|
||||
});
|
||||
return configScreen;
|
||||
}
|
||||
|
||||
private static Screen generateConfigScreen(Screen parent) {
|
||||
var controlify = Controlify.instance();
|
||||
|
||||
var yacl = YetAnotherConfigLib.createBuilder()
|
||||
@ -86,6 +97,12 @@ public class YACLHelper {
|
||||
.binding(GlobalSettings.DEFAULT.uiSounds, () -> globalSettings.uiSounds, v -> globalSettings.uiSounds = v)
|
||||
.controller(TickBoxController::new)
|
||||
.build())
|
||||
.option(Option.createBuilder(boolean.class)
|
||||
.name(Component.translatable("controlify.gui.notify_low_battery"))
|
||||
.tooltip(Component.translatable("controlify.gui.notify_low_battery.tooltip"))
|
||||
.binding(GlobalSettings.DEFAULT.notifyLowBattery, () -> globalSettings.notifyLowBattery, v -> globalSettings.notifyLowBattery = v)
|
||||
.controller(TickBoxController::new)
|
||||
.build())
|
||||
.option(Option.createBuilder(boolean.class)
|
||||
.name(Component.translatable("controlify.gui.out_of_focus_input"))
|
||||
.tooltip(Component.translatable("controlify.gui.out_of_focus_input.tooltip"))
|
||||
@ -126,6 +143,10 @@ public class YACLHelper {
|
||||
|
||||
category.name(Component.literal(controller.name()));
|
||||
|
||||
if (controller.batteryLevel() != BatteryLevel.UNKNOWN) {
|
||||
category.option(LabelOption.create(Component.translatable("controlify.gui.battery_level", controller.batteryLevel().getFriendlyName())));
|
||||
}
|
||||
|
||||
var config = controller.config();
|
||||
var def = controller.defaultConfig();
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -0,0 +1,25 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import dev.isxander.controlify.controller.BatteryLevel;
|
||||
|
||||
public interface BatteryDriver extends Driver {
|
||||
BatteryLevel getBatteryLevel();
|
||||
|
||||
String getBatteryDriverDetails();
|
||||
|
||||
BatteryDriver UNSUPPORTED = new BatteryDriver() {
|
||||
@Override
|
||||
public void update() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatteryLevel getBatteryLevel() {
|
||||
return BatteryLevel.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBatteryDriverDetails() {
|
||||
return "Unsupported";
|
||||
}
|
||||
};
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
@ -7,19 +8,20 @@ import org.hid4java.HidDevice;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, GyroDriver gyroDriver, RumbleDriver rumbleDriver) {
|
||||
public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, GyroDriver gyroDriver, RumbleDriver rumbleDriver, BatteryDriver batteryDriver) {
|
||||
public Set<Driver> getUniqueDrivers() {
|
||||
Set<Driver> drivers = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
drivers.addAll(List.of(basicGamepadInputDriver, gyroDriver, rumbleDriver));
|
||||
Set<Driver> drivers = Sets.newIdentityHashSet();
|
||||
drivers.addAll(List.of(basicGamepadInputDriver, gyroDriver, rumbleDriver, batteryDriver));
|
||||
return drivers;
|
||||
}
|
||||
|
||||
public void printDrivers() {
|
||||
if (DebugProperties.PRINT_DRIVER) {
|
||||
Controlify.LOGGER.info("Drivers in use: Basic Input = '{}', Gyro = '{}', Rumble = '{}'",
|
||||
Controlify.LOGGER.info("Drivers in use: Basic Input = '{}', Gyro = '{}', Rumble = '{}', Battery = '{}'",
|
||||
basicGamepadInputDriver.getBasicGamepadDetails(),
|
||||
gyroDriver.getGyroDetails(),
|
||||
rumbleDriver.getRumbleDetails()
|
||||
rumbleDriver.getRumbleDetails(),
|
||||
batteryDriver.getBatteryDriverDetails()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -28,11 +30,13 @@ public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, Gy
|
||||
BasicGamepadInputDriver basicGamepadInputDriver = new GLFWGamepadDriver(jid);
|
||||
GyroDriver gyroDriver = GyroDriver.UNSUPPORTED;
|
||||
RumbleDriver rumbleDriver = RumbleDriver.UNSUPPORTED;
|
||||
BatteryDriver batteryDriver = BatteryDriver.UNSUPPORTED;
|
||||
|
||||
if (SDL2NativesManager.isLoaded()) {
|
||||
SDL2GamepadDriver sdl2Driver = new SDL2GamepadDriver(jid);
|
||||
gyroDriver = sdl2Driver;
|
||||
rumbleDriver = sdl2Driver;
|
||||
batteryDriver = sdl2Driver;
|
||||
}
|
||||
|
||||
// broken
|
||||
@ -40,6 +44,6 @@ public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, Gy
|
||||
gyroDriver = new SteamDeckDriver(hid.get());
|
||||
}
|
||||
|
||||
return new GamepadDrivers(basicGamepadInputDriver, gyroDriver, rumbleDriver);
|
||||
return new GamepadDrivers(basicGamepadInputDriver, gyroDriver, rumbleDriver, batteryDriver);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.BatteryLevel;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import org.libsdl.SDL;
|
||||
|
||||
public class SDL2GamepadDriver implements GyroDriver, RumbleDriver {
|
||||
public class SDL2GamepadDriver implements GyroDriver, RumbleDriver, BatteryDriver {
|
||||
private final long ptrGamepad;
|
||||
private GamepadState.GyroState gyroDelta;
|
||||
private final boolean isGyroSupported, isRumbleSupported;
|
||||
@ -42,6 +43,20 @@ public class SDL2GamepadDriver implements GyroDriver, RumbleDriver {
|
||||
return gyroDelta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatteryLevel getBatteryLevel() {
|
||||
return switch (SDL.SDL_JoystickCurrentPowerLevel(ptrGamepad)) {
|
||||
case SDL.SDL_JOYSTICK_POWER_UNKNOWN -> BatteryLevel.UNKNOWN;
|
||||
case SDL.SDL_JOYSTICK_POWER_EMPTY -> BatteryLevel.EMPTY;
|
||||
case SDL.SDL_JOYSTICK_POWER_LOW -> BatteryLevel.LOW;
|
||||
case SDL.SDL_JOYSTICK_POWER_MEDIUM -> BatteryLevel.MEDIUM;
|
||||
case SDL.SDL_JOYSTICK_POWER_FULL -> BatteryLevel.FULL;
|
||||
case SDL.SDL_JOYSTICK_POWER_WIRED -> BatteryLevel.WIRED;
|
||||
case SDL.SDL_JOYSTICK_POWER_MAX -> BatteryLevel.MAX;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + SDL.SDL_JoystickCurrentPowerLevel(ptrGamepad));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGyroSupported() {
|
||||
return isGyroSupported;
|
||||
@ -66,4 +81,9 @@ public class SDL2GamepadDriver implements GyroDriver, RumbleDriver {
|
||||
public String getRumbleDetails() {
|
||||
return "SDL2gp supported=" + isRumbleSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBatteryDriverDetails() {
|
||||
return "SDL2gp";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.ConfirmScreen;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class SDLOnboardingScreen extends ConfirmScreen {
|
||||
public SDLOnboardingScreen(Screen lastScreen, BooleanConsumer onAnswered) {
|
||||
super(
|
||||
yes -> {
|
||||
Controlify.instance().config().globalSettings().loadVibrationNatives = yes;
|
||||
Controlify.instance().config().globalSettings().vibrationOnboarded = true;
|
||||
Controlify.instance().config().save();
|
||||
Minecraft.getInstance().setScreen(lastScreen);
|
||||
onAnswered.accept(yes);
|
||||
},
|
||||
Component.translatable("controlify.sdl2_onboarding.title").withStyle(ChatFormatting.BOLD),
|
||||
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));
|
||||
// }
|
||||
|
||||
message.append("\n\n").append(Component.translatable("controlify.sdl2_onboarding.question"));
|
||||
|
||||
return message;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.ConfirmScreen;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class VibrationOnboardingScreen extends ConfirmScreen {
|
||||
public VibrationOnboardingScreen(Screen lastScreen, BooleanConsumer onAnswered) {
|
||||
super(
|
||||
yes -> {
|
||||
Controlify.instance().config().globalSettings().loadVibrationNatives = yes;
|
||||
Controlify.instance().config().globalSettings().vibrationOnboarded = true;
|
||||
Controlify.instance().config().save();
|
||||
Minecraft.getInstance().setScreen(lastScreen);
|
||||
onAnswered.accept(yes);
|
||||
},
|
||||
Component.translatable("controlify.vibration_onboarding.title").withStyle(ChatFormatting.BOLD),
|
||||
Component.translatable("controlify.vibration_onboarding.message")
|
||||
);
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ public class ControlsScreenMixin extends OptionsSubScreen {
|
||||
|
||||
@Inject(method = "init", at = @At("RETURN"))
|
||||
private void addControllerSettings(CallbackInfo ci, @Local(ordinal = 0) int leftX, @Local(ordinal = 1) int rightX, @Local(ordinal = 2) int currentY) {
|
||||
addRenderableWidget(Button.builder(Component.translatable("controlify.gui.button"), btn -> minecraft.setScreen(YACLHelper.generateConfigScreen(this)))
|
||||
addRenderableWidget(Button.builder(Component.translatable("controlify.gui.button"), btn -> minecraft.setScreen(YACLHelper.openConfigScreen(this)))
|
||||
.pos(leftX, currentY)
|
||||
.width(150)
|
||||
.build());
|
||||
|
@ -0,0 +1,50 @@
|
||||
package dev.isxander.controlify.wireless;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.ControllerManager;
|
||||
import dev.isxander.controlify.controller.BatteryLevel;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LowBatteryNotifier {
|
||||
private static final Map<String, BatteryLevel> previousBatteryLevels = new HashMap<>();
|
||||
private static int interval;
|
||||
|
||||
public static void tick() {
|
||||
if (interval > 0) {
|
||||
interval--;
|
||||
return;
|
||||
}
|
||||
interval = 20 * 60; // 1 minute
|
||||
|
||||
if (!Controlify.instance().config().globalSettings().notifyLowBattery)
|
||||
return;
|
||||
|
||||
for (Controller<?, ?> controller : ControllerManager.getConnectedControllers()) {
|
||||
BatteryLevel batteryLevel = controller.batteryLevel();
|
||||
if (batteryLevel == BatteryLevel.UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String uid = controller.uid();
|
||||
if (previousBatteryLevels.containsKey(uid)) {
|
||||
BatteryLevel previousBatteryLevel = previousBatteryLevels.get(uid);
|
||||
if (batteryLevel.ordinal() < previousBatteryLevel.ordinal()) {
|
||||
if (batteryLevel == BatteryLevel.LOW) {
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.low_battery.title"),
|
||||
Component.translatable("controlify.toast.low_battery.message", controller.name()),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousBatteryLevels.put(uid, batteryLevel);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user