Skip to content

Commit 2c442bd

Browse files
committed
Add global option to disable automatic controller switching
The automatic controller switching behavior (where the active controller switches to whichever last provided input) makes it impossible to run multiple game instances side-by-side with different controllers. This adds a toggleable "Auto-Switch Controllers" option in global settings (default: enabled) that gates the tickInactiveController() loop. When disabled, a "Current Controller" dropdown appears allowing manual controller selection. The dropdown dynamically refreshes on controller hotplug events by rebuilding the settings screen.
1 parent 66d59a5 commit 2c442bd

6 files changed

Lines changed: 104 additions & 15 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
org.gradle.jvmargs=-Xmx4G
22
org.gradle.parallel=true
33

4-
modVersion=3.0.0-beta.2
4+
modVersion=3.0.0-beta.3
55
modId=controlify
66
modName=Controlify
77
modDescription=The most advanced controller mod for Minecraft.

src/main/java/dev/isxander/controlify/Controlify.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,16 @@ public void tick(Minecraft client) {
471471
currentController
472472
);
473473
});
474-
for (ControllerEntity controller : controllerManager.getConnectedControllers()) {
475-
if (controller.equals(getCurrentController().orElse(null))) continue;
476-
477-
wrapControllerError(
478-
() -> tickInactiveController(controller),
479-
"Ticking inactive controller",
480-
controller
481-
);
474+
if (config().getSettings().globalSettings().autoSwitchControllers) {
475+
for (ControllerEntity controller : controllerManager.getConnectedControllers()) {
476+
if (controller.equals(getCurrentController().orElse(null))) continue;
477+
478+
wrapControllerError(
479+
() -> tickInactiveController(controller),
480+
"Ticking inactive controller",
481+
controller
482+
);
483+
}
482484
}
483485

484486
// Periodically save config if dirty (e.g., from gyro calibration updates)

src/main/java/dev/isxander/controlify/config/dto/GlobalConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public record GlobalConfig(
1919
boolean alwaysAllowKeyboardMovement,
2020
List<String> keyboardMovementWhitelist,
2121
List<String> seenServers,
22-
boolean showSplitscreenAd
22+
boolean showSplitscreenAd,
23+
boolean autoSwitchControllers
2324
) {
2425
public static final Codec<GlobalConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group(
2526
Codec.list(Codec.STRING).fieldOf("virtual_mouse_screens").forGetter(GlobalConfig::virtualMouseScreens),
@@ -34,6 +35,7 @@ public record GlobalConfig(
3435
Codec.BOOL.fieldOf("keyboard_movement").forGetter(GlobalConfig::alwaysAllowKeyboardMovement),
3536
Codec.list(Codec.STRING).fieldOf("keyboard_movement_whitelist").forGetter(GlobalConfig::keyboardMovementWhitelist),
3637
Codec.list(Codec.STRING).fieldOf("seen_servers").forGetter(GlobalConfig::seenServers),
37-
Codec.BOOL.fieldOf("show_splitscreen_ad").forGetter(GlobalConfig::showSplitscreenAd)
38+
Codec.BOOL.fieldOf("show_splitscreen_ad").forGetter(GlobalConfig::showSplitscreenAd),
39+
Codec.BOOL.optionalFieldOf("auto_switch_controllers", true).forGetter(GlobalConfig::autoSwitchControllers)
3840
).apply(instance, GlobalConfig::new));
3941
}

src/main/java/dev/isxander/controlify/config/settings/GlobalSettings.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class GlobalSettings {
2929
public List<String> keyboardMovementWhitelist;
3030
public final Set<String> seenServers;
3131
public boolean showSplitscreenAd;
32+
public boolean autoSwitchControllers;
3233

3334
private static final GlobalSettings DEFAULT = new GlobalSettings();
3435

@@ -48,6 +49,7 @@ private GlobalSettings() {
4849
this.keyboardMovementWhitelist = new ArrayList<>();
4950
this.seenServers = new HashSet<>();
5051
this.showSplitscreenAd = true;
52+
this.autoSwitchControllers = true;
5153
}
5254

5355
public GlobalSettings(
@@ -63,7 +65,8 @@ public GlobalSettings(
6365
boolean alwaysKeyboardMovement,
6466
List<String> keyboardMovementWhitelist,
6567
Set<String> seenServers,
66-
boolean showSplitscreenAd
68+
boolean showSplitscreenAd,
69+
boolean autoSwitchControllers
6770
) {
6871
this.virtualMouseScreens = new HashSet<>(virtualMouseScreens);
6972
this.mixedInput = mixedInput;
@@ -78,6 +81,7 @@ public GlobalSettings(
7881
this.keyboardMovementWhitelist = new ArrayList<>(keyboardMovementWhitelist);
7982
this.seenServers = new HashSet<>(seenServers);
8083
this.showSplitscreenAd = showSplitscreenAd;
84+
this.autoSwitchControllers = autoSwitchControllers;
8185
}
8286

8387
public boolean shouldUseKeyboardMovement() {
@@ -114,7 +118,8 @@ public static GlobalSettings fromDTO(GlobalConfig dto) {
114118
dto.alwaysAllowKeyboardMovement(),
115119
List.copyOf(dto.keyboardMovementWhitelist()),
116120
Set.copyOf(dto.seenServers()),
117-
dto.showSplitscreenAd()
121+
dto.showSplitscreenAd(),
122+
dto.autoSwitchControllers()
118123
);
119124
}
120125

@@ -135,7 +140,8 @@ public GlobalConfig toDTO() {
135140
alwaysKeyboardMovement,
136141
List.copyOf(keyboardMovementWhitelist),
137142
List.copyOf(seenServers),
138-
showSplitscreenAd
143+
showSplitscreenAd,
144+
autoSwitchControllers
139145
);
140146
}
141147
}

src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dev.isxander.controlify.Controlify;
44
import dev.isxander.controlify.api.ControlifyApi;
5+
import dev.isxander.controlify.api.event.ControlifyEvents;
56
import dev.isxander.controlify.config.settings.GlobalSettings;
67
import dev.isxander.controlify.controller.ControllerEntity;
78
import dev.isxander.controlify.driver.steamdeck.SteamDeckUtil;
@@ -21,16 +22,27 @@
2122
import net.minecraft.network.chat.Component;
2223
import net.minecraft.resources.Identifier;
2324

25+
import java.util.ArrayList;
26+
import java.util.List;
2427
import java.util.concurrent.atomic.AtomicReference;
2528

2629
public class GlobalSettingsScreenFactory {
2730
public static Screen createGlobalSettingsScreen(Screen parent) {
2831
var globalSettings = Controlify.instance().config().getSettings().globalSettings();
2932
AtomicReference<ListOption<String>> whitelist = new AtomicReference<>();
33+
AtomicReference<Option<String>> controllerSelector = new AtomicReference<>();
34+
35+
List<String> controllerUids = new ArrayList<>();
36+
controllerUids.add("");
37+
Controlify.instance().getControllerManager().ifPresent(cm -> {
38+
for (ControllerEntity c : cm.getConnectedControllers()) {
39+
controllerUids.add(c.uid());
40+
}
41+
});
3042

3143
boolean is12106OrLater = /*? if >=1.21.6 {*/ true /*?} else {*/ /*false *//*?}*/;;
3244

33-
return YetAnotherConfigLib.createBuilder()
45+
Screen result = YetAnotherConfigLib.createBuilder()
3446
.title(Component.translatable("controlify.gui.global_settings.title"))
3547
.save(() -> Controlify.instance().config().saveSafely())
3648
.category(ConfigCategory.createBuilder()
@@ -153,6 +165,56 @@ public static Screen createGlobalSettingsScreen(Screen parent) {
153165
.binding(GlobalSettings.defaults().mixedInput, () -> globalSettings.mixedInput, v -> globalSettings.mixedInput = v)
154166
.controller(TickBoxControllerBuilder::create)
155167
.build())
168+
.option(Option.<Boolean>createBuilder()
169+
.name(Component.translatable("controlify.gui.auto_switch_controllers"))
170+
.description(OptionDescription.createBuilder()
171+
.text(Component.translatable("controlify.gui.auto_switch_controllers.tooltip"))
172+
.build())
173+
.binding(GlobalSettings.defaults().autoSwitchControllers, () -> globalSettings.autoSwitchControllers, v -> globalSettings.autoSwitchControllers = v)
174+
.controller(TickBoxControllerBuilder::create)
175+
.addListener((opt, event) -> {
176+
var selector = controllerSelector.get();
177+
if (selector != null) selector.setAvailable(!opt.pendingValue());
178+
})
179+
.build())
180+
.option(Util.make(() -> {
181+
var opt = Option.<String>createBuilder()
182+
.name(Component.translatable("controlify.gui.current_controller"))
183+
.description(OptionDescription.createBuilder()
184+
.text(Component.translatable("controlify.gui.current_controller.tooltip"))
185+
.build())
186+
.binding(
187+
"",
188+
() -> Controlify.instance().getCurrentController().map(ControllerEntity::uid).orElse(""),
189+
uid -> {
190+
if (uid.isEmpty()) {
191+
Controlify.instance().setCurrentController(null, true);
192+
} else {
193+
Controlify.instance().getControllerManager().ifPresent(cm ->
194+
cm.getConnectedControllers().stream()
195+
.filter(c -> c.uid().equals(uid))
196+
.findFirst()
197+
.ifPresent(c -> Controlify.instance().setCurrentController(c, true)));
198+
}
199+
}
200+
)
201+
.controller(o -> CyclingListControllerBuilder.create(o)
202+
.values(controllerUids)
203+
.formatValue(uid -> {
204+
if (uid.isEmpty()) return Component.translatable("controlify.gui.carousel.entry.keyboard_mouse");
205+
return Controlify.instance().getControllerManager()
206+
.map(cm -> cm.getConnectedControllers().stream()
207+
.filter(c -> c.uid().equals(uid))
208+
.findFirst()
209+
.map(c -> (Component) Component.literal(c.name()))
210+
.orElse(Component.literal(uid)))
211+
.orElse(Component.literal(uid));
212+
}))
213+
.available(!globalSettings.autoSwitchControllers)
214+
.build();
215+
controllerSelector.set(opt);
216+
return opt;
217+
}))
156218
.optionIf(SteamDeckUtil.IS_STEAM_DECK, Option.<Boolean>createBuilder()
157219
.name(Component.translatable("controlify.gui.use_enhanced_steam_deck_driver"))
158220
.description(OptionDescription.createBuilder()
@@ -182,6 +244,19 @@ public static Screen createGlobalSettingsScreen(Screen parent) {
182244
.build())
183245
.build())
184246
.build().generateScreen(parent);
247+
248+
ControlifyEvents.CONTROLLER_CONNECTED.register(event -> {
249+
if (Minecraft.getInstance().screen == result) {
250+
Minecraft.getInstance().setScreen(createGlobalSettingsScreen(parent));
251+
}
252+
});
253+
ControlifyEvents.CONTROLLER_DISCONNECTED.register(event -> {
254+
if (Minecraft.getInstance().screen == result) {
255+
Minecraft.getInstance().setScreen(createGlobalSettingsScreen(parent));
256+
}
257+
});
258+
259+
return result;
185260
}
186261

187262
private static Identifier screenshot(String filename) {

src/main/resources/assets/controlify/lang/en_us.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
"controlify.gui.open_issue_tracker": "Open Issue Tracker",
5353
"controlify.gui.use_enhanced_steam_deck_driver": "Use Enhanced Steam Deck Driver",
5454
"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.",
55+
"controlify.gui.auto_switch_controllers": "Auto-Switch Controllers",
56+
"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.",
57+
"controlify.gui.current_controller": "Current Controller",
58+
"controlify.gui.current_controller.tooltip": "Select which controller to use. This option is only available when auto-switching is disabled.",
5559
"controlify.gui.copy_debug_dump": "Copy Debug Dump To Clipboard",
5660
"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.",
5761

0 commit comments

Comments
 (0)