1
0
forked from Clones/Controlify

Mixed input mode for Steam Deck

This commit is contained in:
isXander
2023-06-16 17:26:42 +01:00
parent 320c9d3d83
commit c5c7a3775f
18 changed files with 82 additions and 47 deletions

View File

@ -3,6 +3,7 @@ package dev.isxander.controlify;
import com.mojang.blaze3d.Blaze3D; import com.mojang.blaze3d.Blaze3D;
import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint; import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
import dev.isxander.controlify.driver.SteamDeckDriver;
import dev.isxander.controlify.gui.controllers.ControllerBindHandler; import dev.isxander.controlify.gui.controllers.ControllerBindHandler;
import dev.isxander.controlify.gui.screen.ControllerCarouselScreen; import dev.isxander.controlify.gui.screen.ControllerCarouselScreen;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
@ -72,6 +73,8 @@ public class Controlify implements ControlifyApi {
private int consecutiveInputSwitches = 0; private int consecutiveInputSwitches = 0;
private double lastInputSwitchTime = 0; private double lastInputSwitchTime = 0;
private int showMouseTicks = 0;
private @Nullable Controller<?, ?> switchableController = null; private @Nullable Controller<?, ?> switchableController = null;
private double askSwitchTime = 0; private double askSwitchTime = 0;
private ToastUtils.ControlifyToast askSwitchToast = null; private ToastUtils.ControlifyToast askSwitchToast = null;
@ -278,6 +281,15 @@ public class Controlify implements ControlifyApi {
} }
} }
if (minecraft.mouseHandler.isMouseGrabbed())
showMouseTicks = 0;
if (currentInputMode() == InputMode.MIXED && showMouseTicks > 0) {
showMouseTicks--;
if (showMouseTicks == 0) {
hideMouse(true, false);
}
}
LowBatteryNotifier.tick(); LowBatteryNotifier.tick();
getCurrentController().ifPresent(currentController -> { getCurrentController().ifPresent(currentController -> {
@ -300,7 +312,11 @@ public class Controlify implements ControlifyApi {
} }
if (state.hasAnyInput()) { if (state.hasAnyInput()) {
this.setInputMode(InputMode.CONTROLLER); // use MIXED input mode to support Steam Input for things like gyro mouse and touchpads
// this is only temporary until the Steam Deck driver is finished
boolean isSteamDeck = controller.hidInfo().map(info -> info.hidDevice().map(device -> SteamDeckDriver.isSteamDeck(device.getVendorId(), device.getProductId())).orElse(false)).orElse(false);
this.setInputMode(isSteamDeck ? InputMode.MIXED : InputMode.CONTROLLER);
} }
if (consecutiveInputSwitches > 100) { if (consecutiveInputSwitches > 100) {
@ -483,7 +499,7 @@ public class Controlify implements ControlifyApi {
this.currentInputMode = currentInputMode; this.currentInputMode = currentInputMode;
if (!minecraft.mouseHandler.isMouseGrabbed()) if (!minecraft.mouseHandler.isMouseGrabbed())
hideMouse(currentInputMode == InputMode.CONTROLLER, true); hideMouse(currentInputMode.isController(), true);
if (minecraft.screen != null) { if (minecraft.screen != null) {
ScreenProcessorProvider.provide(minecraft.screen).onInputModeChanged(currentInputMode); ScreenProcessorProvider.provide(minecraft.screen).onInputModeChanged(currentInputMode);
} }
@ -501,7 +517,7 @@ public class Controlify implements ControlifyApi {
} }
lastInputSwitchTime = Blaze3D.getTime(); lastInputSwitchTime = Blaze3D.getTime();
if (currentInputMode == InputMode.CONTROLLER) if (this.currentInputMode == InputMode.CONTROLLER)
getCurrentController().ifPresent(Controller::clearState); getCurrentController().ifPresent(Controller::clearState);
ControlifyEvents.INPUT_MODE_CHANGED.invoker().onInputModeChanged(currentInputMode); ControlifyEvents.INPUT_MODE_CHANGED.invoker().onInputModeChanged(currentInputMode);
@ -527,6 +543,13 @@ public class Controlify implements ControlifyApi {
} }
} }
public void showCursorTemporarily() {
if (currentInputMode() == InputMode.MIXED && !minecraft.mouseHandler.isMouseGrabbed()) {
hideMouse(false, false);
showMouseTicks = 20 * 3;
}
}
public static Controlify instance() { public static Controlify instance() {
if (instance == null) instance = new Controlify(); if (instance == null) instance = new Controlify();
return instance; return instance;

View File

@ -2,5 +2,14 @@ package dev.isxander.controlify;
public enum InputMode { public enum InputMode {
KEYBOARD_MOUSE, KEYBOARD_MOUSE,
CONTROLLER; CONTROLLER,
MIXED;
public boolean isKeyboardMouse() {
return this != CONTROLLER;
}
public boolean isController() {
return this != KEYBOARD_MOUSE;
}
} }

View File

@ -50,7 +50,7 @@ public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, Gy
} }
// TODO: Fix Steam Deck driver // TODO: Fix Steam Deck driver
if (hid.isPresent() && SteamDeckDriver.isSteamDeck(hid.get()) && false) { if (hid.isPresent() && SteamDeckDriver.isSteamDeck(hid.get().getVendorId(), hid.get().getProductId()) && false) {
gyroDriver = new SteamDeckDriver(hid.get()); gyroDriver = new SteamDeckDriver(hid.get());
} }

View File

@ -1,6 +1,7 @@
package dev.isxander.controlify.driver; package dev.isxander.controlify.driver;
import dev.isxander.controlify.controller.gamepad.GamepadState; import dev.isxander.controlify.controller.gamepad.GamepadState;
import dev.isxander.controlify.controller.hid.HIDIdentifier;
import dev.isxander.controlify.utils.Log; import dev.isxander.controlify.utils.Log;
import org.hid4java.HidDevice; import org.hid4java.HidDevice;
@ -243,7 +244,7 @@ public class SteamDeckDriver implements GyroDriver, BasicGamepadInputDriver {
} }
} }
public static boolean isSteamDeck(HidDevice hid) { public static boolean isSteamDeck(int vendorId, int productId) {
return hid.getVendorId() == 0x28DE && hid.getProductId() == 0x1205; return vendorId == 0x28DE && productId == 0x1205;
} }
} }

View File

@ -44,7 +44,7 @@ public class InGameInputHandler {
ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> { ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> {
if (minecraft.player != null) { if (minecraft.player != null) {
minecraft.player.input = mode == InputMode.CONTROLLER minecraft.player.input = mode.isController()
? new ControllerPlayerMovement(controller, minecraft.player) ? new ControllerPlayerMovement(controller, minecraft.player)
: new KeyboardInput(minecraft.options); : new KeyboardInput(minecraft.options);
} }

View File

@ -35,7 +35,7 @@ public class ClientPacketListenerMixin {
} }
private void overrideInput(LocalPlayer player) { private void overrideInput(LocalPlayer player) {
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER && player != null) if (Controlify.instance().currentInputMode().isController() && player != null)
player.input = new ControllerPlayerMovement(Controlify.instance().currentController(), player); player.input = new ControllerPlayerMovement(Controlify.instance().currentController(), player);
} }
} }

View File

@ -18,7 +18,7 @@ public class KeyboardHandlerMixin {
// m_unngxkoe is lambda for GLFW keypress hook - do it outside of the `keyPress` method due to fake inputs // m_unngxkoe is lambda for GLFW keypress hook - do it outside of the `keyPress` method due to fake inputs
@Inject(method = "method_22678", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;keyPress(JIIII)V")) @Inject(method = "method_22678", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;keyPress(JIIII)V"))
private void onKeyboardInput(long window, int i, int j, int k, int m, CallbackInfo ci) { private void onKeyboardInput(long window, int i, int j, int k, int m, CallbackInfo ci) {
if (window == minecraft.getWindow().getWindow()) if (window == minecraft.getWindow().getWindow() && Controlify.instance().currentInputMode() != InputMode.MIXED)
Controlify.instance().setInputMode(InputMode.KEYBOARD_MOUSE); Controlify.instance().setInputMode(InputMode.KEYBOARD_MOUSE);
} }
} }

View File

@ -16,25 +16,32 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public class MouseHandlerMixin { public class MouseHandlerMixin {
@Shadow @Final private Minecraft minecraft; @Shadow @Final private Minecraft minecraft;
// m_sljgmtqm is lambda for GLFW mouse click hook - do it outside of the `onPress` method due to fake inputs // method_22686 is lambda for GLFW mouse click hook - do it outside of the `onPress` method due to fake inputs
@Inject(method = "method_22686", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onPress(JIII)V")) @Inject(method = "method_22686", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onPress(JIII)V"))
private void onMouseClickInput(long window, int button, int action, int modifiers, CallbackInfo ci) { private void onMouseClickInput(long window, int button, int action, int modifiers, CallbackInfo ci) {
if (window == minecraft.getWindow().getWindow()) onMouse(window);
Controlify.instance().setInputMode(InputMode.KEYBOARD_MOUSE);
} }
// m_swhlgdws is lambda for GLFW mouse move hook - do it outside of the `onMove` method due to fake inputs // method_22689 is lambda for GLFW mouse move hook - do it outside of the `onMove` method due to fake inputs
@Inject(method = "method_22689", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onMove(JDD)V")) @Inject(method = "method_22689", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onMove(JDD)V"))
private void onMouseMoveInput(long window, double x, double y, CallbackInfo ci) { private void onMouseMoveInput(long window, double x, double y, CallbackInfo ci) {
if (window == minecraft.getWindow().getWindow()) onMouse(window);
Controlify.instance().setInputMode(InputMode.KEYBOARD_MOUSE);
} }
// m_qoshpwkl is lambda for GLFW mouse scroll hook - do it outside of the `onScroll` method due to fake inputs // method_22687 is lambda for GLFW mouse scroll hook - do it outside of the `onScroll` method due to fake inputs
@Inject(method = "method_22687", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onScroll(JDD)V")) @Inject(method = "method_22687", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;onScroll(JDD)V"))
private void onMouseScrollInput(long window, double scrollDeltaX, double scrollDeltaY, CallbackInfo ci) { private void onMouseScrollInput(long window, double scrollDeltaX, double scrollDeltaY, CallbackInfo ci) {
if (window == minecraft.getWindow().getWindow()) onMouse(window);
Controlify.instance().setInputMode(InputMode.KEYBOARD_MOUSE); }
private void onMouse(long window) {
if (window == minecraft.getWindow().getWindow()) {
if (Controlify.instance().currentInputMode() != InputMode.MIXED) {
Controlify.instance().setInputMode(InputMode.KEYBOARD_MOUSE);
} else {
Controlify.instance().showCursorTemporarily();
}
}
} }
/** /**
@ -42,7 +49,7 @@ public class MouseHandlerMixin {
*/ */
@Inject(method = "releaseMouse", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/InputConstants;grabOrReleaseMouse(JIDD)V")) @Inject(method = "releaseMouse", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/InputConstants;grabOrReleaseMouse(JIDD)V"))
private void moveMouseIfNecessary(CallbackInfo ci) { private void moveMouseIfNecessary(CallbackInfo ci) {
if (ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER) { if (ControlifyApi.get().currentInputMode().isController()) {
Controlify.instance().hideMouse(true, true); Controlify.instance().hideMouse(true, true);
} }
} }

View File

@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.At;
public class LocalPlayerMixin { public class LocalPlayerMixin {
@ModifyExpressionValue(method = "sendPosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;")) @ModifyExpressionValue(method = "sendPosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/OptionInstance;get()Ljava/lang/Object;"))
private Object shouldUseAutoJump(Object keyboardAutoJump) { private Object shouldUseAutoJump(Object keyboardAutoJump) {
if (ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER) { if (ControlifyApi.get().currentInputMode().isController()) {
return ControlifyApi.get().getCurrentController() return ControlifyApi.get().getCurrentController()
.map(controller -> controller.config().autoJump) .map(controller -> controller.config().autoJump)
.orElse(false); .orElse(false);

View File

@ -3,33 +3,28 @@ package dev.isxander.controlify.mixins.feature.fixes.boatfix;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import dev.isxander.controlify.Controlify; import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.fixes.boatfix.AnalogBoatInput; import dev.isxander.controlify.fixes.boatfix.AnalogBoatInput;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.entity.vehicle.Boat;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import java.util.Optional;
@Mixin(LocalPlayer.class) @Mixin(LocalPlayer.class)
public class LocalPlayerMixin { public class LocalPlayerMixin {
@Shadow public Input input;
@WrapOperation(method = "rideTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/vehicle/Boat;setInput(ZZZZ)V")) @WrapOperation(method = "rideTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/vehicle/Boat;setInput(ZZZZ)V"))
private void useAnalogInput(Boat boat, boolean pressingLeft, boolean pressingRight, boolean pressingForward, boolean pressingBack, Operation<Void> original) { private void useAnalogInput(Boat boat, boolean pressingLeft, boolean pressingRight, boolean pressingForward, boolean pressingBack, Operation<Void> original) {
if (ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER && !Controlify.instance().config().globalSettings().keyboardMovement) { if (ControlifyApi.get().currentInputMode().isController() && !Controlify.instance().config().globalSettings().keyboardMovement) {
Optional<Controller<?, ?>> controllerOpt = ControlifyApi.get().getCurrentController(); ((AnalogBoatInput) boat).setAnalogInput(
if (controllerOpt.isPresent()) { input.forwardImpulse,
var controller = controllerOpt.get(); -input.leftImpulse
);
((AnalogBoatInput) boat).setAnalogInput( return;
controller.bindings().WALK_FORWARD.state() - controller.bindings().WALK_BACKWARD.state(),
controller.bindings().WALK_RIGHT.state() - controller.bindings().WALK_LEFT.state()
);
return;
}
} }
original.call(boat, pressingLeft, pressingRight, pressingForward, pressingBack); original.call(boat, pressingLeft, pressingRight, pressingForward, pressingBack);

View File

@ -30,7 +30,7 @@ public class ClientPacketListenerMixin {
} }
private void initButtonGuide() { private void initButtonGuide() {
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER && minecraft.player != null) if (Controlify.instance().currentInputMode().isController() && minecraft.player != null)
Controlify.instance().inGameButtonGuide = new InGameButtonGuide(Controlify.instance().getCurrentController().orElseThrow(), minecraft.player); Controlify.instance().inGameButtonGuide = new InGameButtonGuide(Controlify.instance().getCurrentController().orElseThrow(), minecraft.player);
} }
} }

View File

@ -66,7 +66,7 @@ public abstract class AbstractButtonMixin extends AbstractWidgetMixin implements
private boolean shouldRender() { private boolean shouldRender() {
return renderData != null return renderData != null
&& this.isActive() && this.isActive()
&& Controlify.instance().currentInputMode() == InputMode.CONTROLLER && Controlify.instance().currentInputMode().isController()
&& Controlify.instance().getCurrentController().map(c -> c.config().showScreenGuide).orElse(false) && Controlify.instance().getCurrentController().map(c -> c.config().showScreenGuide).orElse(false)
&& !renderData.binding().apply(Controlify.instance().currentController().bindings()).isUnbound() && !renderData.binding().apply(Controlify.instance().currentController().bindings()).isUnbound()
&& renderData.renderPredicate().shouldDisplay((AbstractButton) (Object) this); && renderData.renderPredicate().shouldDisplay((AbstractButton) (Object) this);

View File

@ -25,7 +25,7 @@ public class TabNavigationBarMixin {
@Inject(method = "render", at = @At("RETURN")) @Inject(method = "render", at = @At("RETURN"))
private void renderControllerButtonOverlay(GuiGraphics graphics, int mouseX, int mouseY, float delta, CallbackInfo ci) { private void renderControllerButtonOverlay(GuiGraphics graphics, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER) { if (Controlify.instance().currentInputMode().isController()) {
Controlify.instance().getCurrentController().ifPresent(c -> { Controlify.instance().getCurrentController().ifPresent(c -> {
if (c.config().showScreenGuide) { if (c.config().showScreenGuide) {
this.renderControllerButtonOverlay(graphics, c); this.renderControllerButtonOverlay(graphics, c);

View File

@ -11,6 +11,6 @@ import org.spongepowered.asm.mixin.injection.At;
public class AbstractSelectionListMixin { public class AbstractSelectionListMixin {
@ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/InputType;isKeyboard()Z")) @ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/InputType;isKeyboard()Z"))
private boolean shouldEnsureEntryVisible(boolean keyboard) { private boolean shouldEnsureEntryVisible(boolean keyboard) {
return keyboard || Controlify.instance().currentInputMode() == InputMode.CONTROLLER; return keyboard || Controlify.instance().currentInputMode().isController();
} }
} }

View File

@ -29,7 +29,7 @@ public class AbstractSliderButtonMixin implements ComponentProcessorProvider {
@ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getLastInputType()Lnet/minecraft/client/InputType;")) @ModifyExpressionValue(method = "setFocused", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getLastInputType()Lnet/minecraft/client/InputType;"))
private InputType shouldChangeValue(InputType type) { private InputType shouldChangeValue(InputType type) {
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER) if (Controlify.instance().currentInputMode().isController())
return InputType.NONE; // none doesn't pass condition return InputType.NONE; // none doesn't pass condition
return type; return type;
} }

View File

@ -199,7 +199,7 @@ public class ScreenProcessor<T extends Screen> {
} }
protected void setInitialFocus() { protected void setInitialFocus() {
if (screen.getFocused() == null && Controlify.instance().currentInputMode() == InputMode.CONTROLLER && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) { if (screen.getFocused() == null && Controlify.instance().currentInputMode().isController() && !Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
var accessor = (ScreenAccessor) screen; var accessor = (ScreenAccessor) screen;
ComponentPath path = screen.nextFocusPath(accessor.invokeCreateArrowEvent(ScreenDirection.DOWN)); ComponentPath path = screen.nextFocusPath(accessor.invokeCreateArrowEvent(ScreenDirection.DOWN));
if (path != null) { if (path != null) {

View File

@ -184,14 +184,14 @@ public class AbstractContainerScreenProcessor<T extends AbstractContainerScreen<
AnchorPoint.BOTTOM_RIGHT AnchorPoint.BOTTOM_RIGHT
); );
if (ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER) { if (ControlifyApi.get().currentInputMode().isController()) {
setRenderGuide(true); setRenderGuide(true);
} }
} }
@Override @Override
public void onInputModeChanged(InputMode mode) { public void onInputModeChanged(InputMode mode) {
setRenderGuide(mode == InputMode.CONTROLLER); setRenderGuide(mode.isController());
} }
private void setRenderGuide(boolean render) { private void setRenderGuide(boolean render) {

View File

@ -183,7 +183,7 @@ public class VirtualMouseHandler {
} else { } else {
disableVirtualMouse(); disableVirtualMouse();
} }
if (Controlify.instance().currentInputMode() == InputMode.CONTROLLER) if (Controlify.instance().currentInputMode().isController())
GLFW.glfwSetInputMode(minecraft.getWindow().getWindow(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_HIDDEN); GLFW.glfwSetInputMode(minecraft.getWindow().getWindow(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_HIDDEN);
} else if (virtualMouseEnabled) { } else if (virtualMouseEnabled) {
disableVirtualMouse(); disableVirtualMouse();
@ -193,7 +193,7 @@ public class VirtualMouseHandler {
} }
public void onInputModeChanged(InputMode mode) { public void onInputModeChanged(InputMode mode) {
if (mode == InputMode.CONTROLLER) { if (mode.isController()) {
if (requiresVirtualMouse()) { if (requiresVirtualMouse()) {
enableVirtualMouse(); enableVirtualMouse();
} }
@ -269,7 +269,7 @@ public class VirtualMouseHandler {
} }
public boolean requiresVirtualMouse() { public boolean requiresVirtualMouse() {
var isController = Controlify.instance().currentInputMode() == InputMode.CONTROLLER; var isController = Controlify.instance().currentInputMode().isController();
var hasScreen = minecraft.screen != null; var hasScreen = minecraft.screen != null;
if (isController && hasScreen) { if (isController && hasScreen) {