Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/main/java/dev/isxander/controlify/Controlify.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@
import dev.isxander.controlify.config.settings.device.DeviceSettings;
import dev.isxander.controlify.config.settings.profile.ProfileSettings;
import dev.isxander.controlify.controller.*;
import dev.isxander.controlify.controller.dualsense.BuiltinTriggerEffects;
import dev.isxander.controlify.controller.dualsense.TriggerEffectManager;
import dev.isxander.controlify.controller.id.ControllerTypeManager;
import dev.isxander.controlify.controller.input.ControllerState;
import dev.isxander.controlify.controller.input.ControllerStateView;
import dev.isxander.controlify.controller.input.InputComponent;
import dev.isxander.controlify.controller.rumble.RumbleComponent;
import dev.isxander.controlify.controllermanager.ControllerManager;
import dev.isxander.controlify.controllermanager.SDLControllerManager;
import dev.isxander.controlify.driver.dualsense.DualsenseTriggerEffect;
import dev.isxander.controlify.driver.sdl.SDLNativesLoader;
import dev.isxander.controlify.driver.steamdeck.SteamDeckMode;
import dev.isxander.controlify.driver.steamdeck.SteamDeckUtil;
Expand Down Expand Up @@ -62,6 +65,7 @@
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
Expand Down Expand Up @@ -94,6 +98,8 @@ public class Controlify implements ControlifyApi {
private ControllerTypeManager controllerTypeManager;
private KeyboardLayoutManager keyboardLayoutManager;

private TriggerEffectManager triggerEffectManager;

private Set<BindContext> thisTickContexts;

private ConfigManager config;
Expand Down Expand Up @@ -242,10 +248,12 @@ public void initializeControlify() {
CUtil.LOGGER.log("Initializing Controlify...");
this.minecraft = Minecraft.getInstance();

config().loadOrDefault();

this.inGameInputHandler = null; // set when the current controller changes
this.virtualMouseHandler = new VirtualMouseHandler();

config().loadOrDefault();
this.triggerEffectManager = new TriggerEffectManager(this.minecraft);
BuiltinTriggerEffects.register();

ControlifyEvents.CONTROLLER_CONNECTED.register(event -> this.onControllerAdded(
event.controller(), event.hotplugged()));
Expand Down Expand Up @@ -520,6 +528,9 @@ private void tickActiveController(ControllerEntity controller, boolean outOfFocu
rumbleManager.ifPresent(RumbleManager::tick);
}

boolean controllerInputSuppressed = outOfFocus || currentInputMode() == InputMode.KEYBOARD_MOUSE;
triggerEffectManager.applyTriggerEffects(controller, controllerInputSuppressed);

if (state.isGivingInput()) {
minecraft.getFramerateLimitTracker().onInputReceived();

Expand Down Expand Up @@ -754,6 +765,10 @@ public KeyboardLayoutManager keyboardLayoutManager() {
return keyboardLayoutManager;
}

public TriggerEffectManager triggerEffectManager() {
return triggerEffectManager;
}

public Set<BindContext> thisTickBindContexts() {
return this.thisTickContexts;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
* This should be called every time a button is initialised, like in {@link Screen#init()}
*/
public final class ButtonGuideApi {

private ButtonGuideApi() {
}

/**
* Makes the button render the image of the binding specified.
* This does not invoke the button press on binding trigger, only renders the guide.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2026 isXander
* This file is part of Controlify.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
package dev.isxander.controlify.api.triggereffect;

import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.controller.dualsense.TriggerEffectHolder;
import dev.isxander.controlify.driver.dualsense.DualsenseTriggerEffect;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.world.item.Item;
import org.jetbrains.annotations.NotNull;

import java.util.function.Function;

public final class TriggerEffectApi {

private TriggerEffectApi() {
}

public static <T> void registerUseItemEffect(DataComponentType<T> componentType, Function<? super T, @NotNull DualsenseTriggerEffect> effectFunction) {
Controlify.instance().triggerEffectManager().registerUseItemComponentEffect(componentType, effectFunction);
}

public static void registerUseItemEffect(DataComponentType<?> componentType, @NotNull DualsenseTriggerEffect effect) {
registerUseItemEffect(componentType, _ -> effect);
}

public static <T> void registerSwingItemEffect(DataComponentType<T> componentType, Function<? super T, @NotNull DualsenseTriggerEffect> effectFunction) {
Controlify.instance().triggerEffectManager().registerSwingItemComponentEffect(componentType, effectFunction);
}

public static void registerSwingItemEffect(DataComponentType<?> componentType, @NotNull DualsenseTriggerEffect effect) {
registerSwingItemEffect(componentType, _ -> effect);
}

public static void registerUseItemEffect(Item item, @NotNull DualsenseTriggerEffect effect) {
((TriggerEffectHolder) item).controlify$assignUseTriggerEffect(effect);
}

public static void registerSwingItemEffect(Item item, @NotNull DualsenseTriggerEffect effect) {
((TriggerEffectHolder) item).controlify$assignSwingTriggerEffect(effect);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import dev.isxander.controlify.config.settings.profile.ProfileSettings;

public final class ControlifyDataFixer {
public static final int CURRENT_VERSION = 5;
public static final int CURRENT_VERSION = 6;

private static final DataFixer FIXER = createFixer();

Expand All @@ -28,14 +28,15 @@ private static DataFixer createFixer() {
var v1 = builder.addSchema(1, ControlifySchemas.V1::new);
var v2 = builder.addSchema(2, ControlifySchemas.V2::new);
var v3 = builder.addSchema(3, ControlifySchemas.V3::new);
var v6 = builder.addSchema(6, ControlifySchemas.V6::new);

builder.addFixer(new TheHolyMigrationFix(
v1,
GlobalSettings.defaults(),
ProfileSettings.createDefault()
));
var globalDefaults = GlobalSettings.defaults();
var profileDefaults = ProfileSettings.createDefault();

builder.addFixer(new TheHolyMigrationFix(v1, globalDefaults, profileDefaults));
builder.addFixer(new AnalogueMovementWhitelistFix(v2));
builder.addFixer(new HorizontalLookInvertFix(v2));
builder.addFixer(new DualsenseConfigFix(v6, profileDefaults));

return builder.build().fixer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,10 @@ public void registerTypes(Schema schema, Map<String, Supplier<TypeTemplate>> ent
);
}
}

public static class V6 extends Schema {
public V6(int versionKey, Schema parent) {
super(versionKey, parent);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2026 isXander
* This file is part of Controlify.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
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;
import dev.isxander.controlify.config.settings.profile.ProfileSettings;

public final class DualsenseConfigFix extends DataFix {
private final ProfileSettings profileDefaults;

public DualsenseConfigFix(Schema outputSchema, ProfileSettings profileDefaults) {
super(outputSchema, true);
this.profileDefaults = profileDefaults;
}

@Override
protected TypeRewriteRule makeRule() {
var profileType = getInputSchema().getType(ControlifyTypeReferences.PROFILE_CONFIG);

return fixTypeEverywhereTyped(
"Controlify: add DualSense profile config",
profileType,
typed -> typed.update(DSL.remainderFinder(), this::rewriteProfile)
);
}

private <T> Dynamic<T> rewriteProfile(Dynamic<T> root) {
Dynamic<T> dualsense = root.get("dualsense").orElseEmptyMap();
if (dualsense.get("trigger_effects").result().isEmpty()) {
dualsense = dualsense.set("trigger_effects", root.createBoolean(profileDefaults.dualsense.triggerEffects));
}
return root.set("dualsense", dualsense);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2026 isXander
* This file is part of Controlify.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
package dev.isxander.controlify.config.dto.profile;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;

public record DualsenseConfig(
boolean triggerEffects
) {
public static final Codec<DualsenseConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.BOOL.fieldOf("trigger_effects").forGetter(DualsenseConfig::triggerEffects)
).apply(instance, DualsenseConfig::new));
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public record ProfileConfig(
RumbleConfig rumble,
HDHapticConfig hdHaptic,
GyroConfig gyro,
BluetoothDeviceConfig bluetoothDevice
BluetoothDeviceConfig bluetoothDevice,
DualsenseConfig dualsense
) {
public static final Codec<ProfileConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.optionalFieldOf("name").forGetter(ProfileConfig::name),
Expand All @@ -29,6 +30,7 @@ public record ProfileConfig(
RumbleConfig.CODEC.fieldOf("rumble").forGetter(ProfileConfig::rumble),
HDHapticConfig.CODEC.fieldOf("hd_haptic").forGetter(ProfileConfig::hdHaptic),
GyroConfig.CODEC.fieldOf("gyro").forGetter(ProfileConfig::gyro),
BluetoothDeviceConfig.CODEC.fieldOf("bluetooth_device").forGetter(ProfileConfig::bluetoothDevice)
BluetoothDeviceConfig.CODEC.fieldOf("bluetooth_device").forGetter(ProfileConfig::bluetoothDevice),
DualsenseConfig.CODEC.fieldOf("dualsense").forGetter(ProfileConfig::dualsense)
).apply(instance, ProfileConfig::new));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2026 isXander
* This file is part of Controlify.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
package dev.isxander.controlify.config.settings.profile;

import dev.isxander.controlify.config.dto.profile.DualsenseConfig;

public class DualsenseSettings {
public boolean triggerEffects;

public DualsenseSettings(boolean triggerEffects) {
this.triggerEffects = triggerEffects;
}

public static DualsenseSettings fromDTO(DualsenseConfig dto) {
return new DualsenseSettings(dto.triggerEffects());
}

public DualsenseConfig toDTO() {
return new DualsenseConfig(triggerEffects);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ProfileSettings {
public final HDHapticSettings hdHaptic;
public final GyroSettings gyro;
public final BluetoothDeviceSettings bluetoothDevice;
public final DualsenseSettings dualsense;

public ProfileSettings(
@Nullable String name,
Expand All @@ -30,7 +31,8 @@ public ProfileSettings(
RumbleSettings rumble,
HDHapticSettings hdHaptic,
GyroSettings gyro,
BluetoothDeviceSettings bluetoothDevice
BluetoothDeviceSettings bluetoothDevice,
DualsenseSettings dualsense
) {
this.name = name;
this.controllerUid = controllerUid;
Expand All @@ -40,6 +42,7 @@ public ProfileSettings(
this.hdHaptic = hdHaptic;
this.gyro = gyro;
this.bluetoothDevice = bluetoothDevice;
this.dualsense = dualsense;
}

public static ProfileSettings fromDTO(ProfileConfig dto) {
Expand All @@ -51,7 +54,8 @@ public static ProfileSettings fromDTO(ProfileConfig dto) {
RumbleSettings.fromDTO(dto.rumble()),
HDHapticSettings.fromDTO(dto.hdHaptic()),
GyroSettings.fromDTO(dto.gyro()),
BluetoothDeviceSettings.fromDTO(dto.bluetoothDevice())
BluetoothDeviceSettings.fromDTO(dto.bluetoothDevice()),
DualsenseSettings.fromDTO(dto.dualsense())
);
}

Expand All @@ -64,7 +68,8 @@ public ProfileConfig toDTO() {
rumble.toDTO(),
hdHaptic.toDTO(),
gyro.toDTO(),
bluetoothDevice.toDTO()
bluetoothDevice.toDTO(),
dualsense.toDTO()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import dev.isxander.controlify.config.settings.profile.ProfileSettings;
import dev.isxander.controlify.controller.battery.BatteryLevelComponent;
import dev.isxander.controlify.controller.dualsense.DualSenseComponent;
import dev.isxander.controlify.controller.dualsense.DualsenseComponent;
import dev.isxander.controlify.controller.haptic.HDHapticComponent;
import dev.isxander.controlify.controller.info.ControllerInfo;
import dev.isxander.controlify.controller.info.DriverNameComponent;
Expand Down Expand Up @@ -133,8 +133,8 @@ public Optional<HDHapticComponent> hdHaptics() {
}

@Contract(pure = true)
public Optional<DualSenseComponent> dualSense() {
return this.getComponent(DualSenseComponent.ID);
public Optional<DualsenseComponent> dualSense() {
return this.getComponent(DualsenseComponent.ID);
}

@Contract(pure = true)
Expand Down
Loading
Loading