diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index 7bf2f0f14..5b6f9d133 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -335,12 +335,18 @@ public void discoverControllers() { CUtil.LOGGER.log("No controllers found."); } - // if no controller is currently selected, pick the first one + // if no controller is currently selected, try to restore preference or pick the first one if (this.getCurrentController().isEmpty()) { - Optional firstController = controllerManager.getConnectedControllers() - .stream() - .findAny(); - this.setCurrentController(firstController.orElse(null), false); + String preferredUid = config().getSettings().globalSettings().preferredControllerUid; + Optional preferred = Optional.empty(); + if (!config().getSettings().globalSettings().autoSwitchControllers && !preferredUid.isEmpty()) { + preferred = controllerManager.getConnectedControllers().stream() + .filter(c -> c.uid().equals(preferredUid)) + .findFirst(); + } + this.setCurrentController(preferred.orElseGet(() -> + controllerManager.getConnectedControllers().stream().findAny().orElse(null) + ), false); } config().saveIfDirty(); @@ -398,6 +404,13 @@ private void onControllerAdded(ControllerEntity controller, boolean hotplugged) config().saveIfDirty(); } + if (hotplugged && !config().getSettings().globalSettings().autoSwitchControllers) { + String preferredUid = config().getSettings().globalSettings().preferredControllerUid; + if (controller.uid().equals(preferredUid)) { + this.setCurrentController(controller, true); + } + } + setupWizards.add(wizard); } @@ -546,7 +559,8 @@ private void tickInactiveController(ControllerEntity controller) { boolean thisControllerGivingInput = state.isGivingInput(); boolean activeControllerGivingInput = getCurrentController().map(c -> c.input().orElseThrow().stateNow().isGivingInput()).orElse(false); - if (thisControllerGivingInput && !activeControllerGivingInput) { + if (config().getSettings().globalSettings().autoSwitchControllers + && thisControllerGivingInput && !activeControllerGivingInput) { this.setCurrentController(controller, true); } } diff --git a/src/main/java/dev/isxander/controlify/config/dto/GlobalConfig.java b/src/main/java/dev/isxander/controlify/config/dto/GlobalConfig.java index 49edb6d8f..84e05eac7 100644 --- a/src/main/java/dev/isxander/controlify/config/dto/GlobalConfig.java +++ b/src/main/java/dev/isxander/controlify/config/dto/GlobalConfig.java @@ -19,7 +19,9 @@ public record GlobalConfig( boolean alwaysAllowKeyboardMovement, List keyboardMovementWhitelist, List seenServers, - boolean showSplitscreenAd + boolean showSplitscreenAd, + boolean autoSwitchControllers, + String preferredControllerUid ) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.list(Codec.STRING).fieldOf("virtual_mouse_screens").forGetter(GlobalConfig::virtualMouseScreens), @@ -34,6 +36,8 @@ public record GlobalConfig( Codec.BOOL.fieldOf("keyboard_movement").forGetter(GlobalConfig::alwaysAllowKeyboardMovement), Codec.list(Codec.STRING).fieldOf("keyboard_movement_whitelist").forGetter(GlobalConfig::keyboardMovementWhitelist), Codec.list(Codec.STRING).fieldOf("seen_servers").forGetter(GlobalConfig::seenServers), - Codec.BOOL.fieldOf("show_splitscreen_ad").forGetter(GlobalConfig::showSplitscreenAd) + Codec.BOOL.fieldOf("show_splitscreen_ad").forGetter(GlobalConfig::showSplitscreenAd), + Codec.BOOL.fieldOf("auto_switch_controllers").forGetter(GlobalConfig::autoSwitchControllers), + Codec.STRING.fieldOf("preferred_controller_uid").forGetter(GlobalConfig::preferredControllerUid) ).apply(instance, GlobalConfig::new)); } diff --git a/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifyDataFixer.java b/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifyDataFixer.java index 6916f86f7..9844cba6c 100644 --- a/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifyDataFixer.java +++ b/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifyDataFixer.java @@ -2,13 +2,14 @@ import com.mojang.datafixers.DataFixer; import com.mojang.datafixers.DataFixerBuilder; +import dev.isxander.controlify.config.dto.dfu.fixes.AddAutoSwitchControllersFix; import dev.isxander.controlify.config.dto.dfu.fixes.TheHolyMigrationFix; import dev.isxander.controlify.config.settings.GlobalSettings; import dev.isxander.controlify.config.settings.profile.ProfileSettings; import dev.isxander.controlify.utils.CUtil; public final class ControlifyDataFixer { - public static final int CURRENT_VERSION = 1; + public static final int CURRENT_VERSION = 2; private static final DataFixer FIXER = createFixer(); @@ -28,6 +29,9 @@ private static DataFixer createFixer() { ProfileSettings.createDefault(CUtil.rl("generic")) )); + var v2 = builder.addSchema(2, ControlifySchemas.SchemaV2::new); + builder.addFixer(new AddAutoSwitchControllersFix(v2)); + return builder.build().fixer(); } diff --git a/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifySchemas.java b/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifySchemas.java index f9c4d7c64..87a1c56e9 100644 --- a/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifySchemas.java +++ b/src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifySchemas.java @@ -47,4 +47,10 @@ public SchemaV1(int versionKey, Schema parent) { super(versionKey, parent); } } + + public static class SchemaV2 extends ControlifySchema { + public SchemaV2(int versionKey, Schema parent) { + super(versionKey, parent); + } + } } diff --git a/src/main/java/dev/isxander/controlify/config/dto/dfu/fixes/AddAutoSwitchControllersFix.java b/src/main/java/dev/isxander/controlify/config/dto/dfu/fixes/AddAutoSwitchControllersFix.java new file mode 100644 index 000000000..107bf664a --- /dev/null +++ b/src/main/java/dev/isxander/controlify/config/dto/dfu/fixes/AddAutoSwitchControllersFix.java @@ -0,0 +1,41 @@ +package dev.isxander.controlify.config.dto.dfu.fixes; + +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.DataFix; +import com.mojang.datafixers.TypeRewriteRule; +import com.mojang.datafixers.schemas.Schema; +import com.mojang.serialization.Dynamic; +import dev.isxander.controlify.config.dto.dfu.ControlifyTypeReferences; + +public final class AddAutoSwitchControllersFix extends DataFix { + public AddAutoSwitchControllersFix(Schema outputSchema) { + super(outputSchema, false); + } + + @Override + protected TypeRewriteRule makeRule() { + var type = getInputSchema().getType(ControlifyTypeReferences.USER_STATE); + + return fixTypeEverywhereTyped( + "Controlify: add auto_switch_controllers and preferred_controller_uid defaults", + type, + typed -> typed.update( + DSL.remainderFinder(), + this::rewrite + ) + ); + } + + private Dynamic rewrite(Dynamic root) { + Dynamic global = root.get("global").orElseEmptyMap(); + + if (global.get("auto_switch_controllers").result().isEmpty()) { + global = global.set("auto_switch_controllers", root.createBoolean(true)); + } + if (global.get("preferred_controller_uid").result().isEmpty()) { + global = global.set("preferred_controller_uid", root.createString("")); + } + + return root.set("global", global); + } +} diff --git a/src/main/java/dev/isxander/controlify/config/settings/GlobalSettings.java b/src/main/java/dev/isxander/controlify/config/settings/GlobalSettings.java index d1ac96473..668f920b3 100644 --- a/src/main/java/dev/isxander/controlify/config/settings/GlobalSettings.java +++ b/src/main/java/dev/isxander/controlify/config/settings/GlobalSettings.java @@ -29,6 +29,8 @@ public class GlobalSettings { public List keyboardMovementWhitelist; public final Set seenServers; public boolean showSplitscreenAd; + public boolean autoSwitchControllers; + public String preferredControllerUid; private static final GlobalSettings DEFAULT = new GlobalSettings(); @@ -48,6 +50,8 @@ private GlobalSettings() { this.keyboardMovementWhitelist = new ArrayList<>(); this.seenServers = new HashSet<>(); this.showSplitscreenAd = true; + this.autoSwitchControllers = true; + this.preferredControllerUid = ""; } public GlobalSettings( @@ -63,7 +67,9 @@ public GlobalSettings( boolean alwaysKeyboardMovement, List keyboardMovementWhitelist, Set seenServers, - boolean showSplitscreenAd + boolean showSplitscreenAd, + boolean autoSwitchControllers, + String preferredControllerUid ) { this.virtualMouseScreens = new HashSet<>(virtualMouseScreens); this.mixedInput = mixedInput; @@ -78,6 +84,8 @@ public GlobalSettings( this.keyboardMovementWhitelist = new ArrayList<>(keyboardMovementWhitelist); this.seenServers = new HashSet<>(seenServers); this.showSplitscreenAd = showSplitscreenAd; + this.autoSwitchControllers = autoSwitchControllers; + this.preferredControllerUid = preferredControllerUid; } public boolean shouldUseKeyboardMovement() { @@ -114,7 +122,9 @@ public static GlobalSettings fromDTO(GlobalConfig dto) { dto.alwaysAllowKeyboardMovement(), List.copyOf(dto.keyboardMovementWhitelist()), Set.copyOf(dto.seenServers()), - dto.showSplitscreenAd() + dto.showSplitscreenAd(), + dto.autoSwitchControllers(), + dto.preferredControllerUid() ); } @@ -135,7 +145,9 @@ public GlobalConfig toDTO() { alwaysKeyboardMovement, List.copyOf(keyboardMovementWhitelist), List.copyOf(seenServers), - showSplitscreenAd + showSplitscreenAd, + autoSwitchControllers, + preferredControllerUid ); } } diff --git a/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java b/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java index c76d1b462..cd25c736f 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java @@ -21,12 +21,23 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; public class GlobalSettingsScreenFactory { public static Screen createGlobalSettingsScreen(Screen parent) { var globalSettings = Controlify.instance().config().getSettings().globalSettings(); AtomicReference> whitelist = new AtomicReference<>(); + AtomicReference> controllerSelector = new AtomicReference<>(); + + List controllerUids = new ArrayList<>(); + controllerUids.add(""); + Controlify.instance().getControllerManager().ifPresent(cm -> { + for (ControllerEntity c : cm.getConnectedControllers()) { + controllerUids.add(c.uid()); + } + }); boolean is12106OrLater = /*? if >=1.21.6 {*/ true /*?} else {*/ /*false *//*?}*/;; @@ -153,6 +164,57 @@ public static Screen createGlobalSettingsScreen(Screen parent) { .binding(GlobalSettings.defaults().mixedInput, () -> globalSettings.mixedInput, v -> globalSettings.mixedInput = v) .controller(TickBoxControllerBuilder::create) .build()) + .option(Option.createBuilder() + .name(Component.translatable("controlify.gui.auto_switch_controllers")) + .description(OptionDescription.createBuilder() + .text(Component.translatable("controlify.gui.auto_switch_controllers.tooltip")) + .build()) + .binding(GlobalSettings.defaults().autoSwitchControllers, () -> globalSettings.autoSwitchControllers, v -> globalSettings.autoSwitchControllers = v) + .controller(TickBoxControllerBuilder::create) + .addListener((opt, event) -> { + var selector = controllerSelector.get(); + if (selector != null) selector.setAvailable(!opt.pendingValue()); + }) + .build()) + .option(Util.make(() -> { + var opt = Option.createBuilder() + .name(Component.translatable("controlify.gui.current_controller")) + .description(OptionDescription.createBuilder() + .text(Component.translatable("controlify.gui.current_controller.tooltip")) + .build()) + .binding( + "", + () -> Controlify.instance().getCurrentController().map(ControllerEntity::uid).orElse(""), + uid -> { + if (uid.isEmpty()) { + Controlify.instance().setCurrentController(null, true); + } else { + Controlify.instance().getControllerManager().ifPresent(cm -> + cm.getConnectedControllers().stream() + .filter(c -> c.uid().equals(uid)) + .findFirst() + .ifPresent(c -> Controlify.instance().setCurrentController(c, true))); + } + globalSettings.preferredControllerUid = uid; + } + ) + .controller(o -> CyclingListControllerBuilder.create(o) + .values(controllerUids) + .formatValue(uid -> { + if (uid.isEmpty()) return Component.translatable("controlify.gui.carousel.entry.keyboard_mouse"); + return Controlify.instance().getControllerManager() + .map(cm -> cm.getConnectedControllers().stream() + .filter(c -> c.uid().equals(uid)) + .findFirst() + .map(c -> (Component) Component.literal(c.name())) + .orElse(Component.literal(uid))) + .orElse(Component.literal(uid)); + })) + .available(!globalSettings.autoSwitchControllers) + .build(); + controllerSelector.set(opt); + return opt; + })) .optionIf(SteamDeckUtil.IS_STEAM_DECK, Option.createBuilder() .name(Component.translatable("controlify.gui.use_enhanced_steam_deck_driver")) .description(OptionDescription.createBuilder() diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index c4e8997fa..669a407ab 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -52,6 +52,10 @@ "controlify.gui.open_issue_tracker": "Open Issue Tracker", "controlify.gui.use_enhanced_steam_deck_driver": "Use Enhanced Steam Deck Driver", "controlify.gui.use_enhanced_steam_deck_driver.tooltip": "If enabled, Controlify will use an enhanced driver for the Steam Deck to improve compatibility and performance. This is experimental and may have bugs. This driver requires Decky Loader to be installed on your Deck.", + "controlify.gui.auto_switch_controllers": "Auto-Switch Controllers", + "controlify.gui.auto_switch_controllers.tooltip": "Automatically switches the active controller when input is detected from a different controller. Disable this if you are running multiple game instances with different controllers.", + "controlify.gui.current_controller": "Current Controller", + "controlify.gui.current_controller.tooltip": "Select which controller to use. This option is only available when auto-switching is disabled.", "controlify.gui.copy_debug_dump": "Copy Debug Dump To Clipboard", "controlify.gui.copy_debug_dump.tooltip": "Copies a Controlify debug dump to your clipboard. You may be asked to use this if you are seeking support.",