Skip to content

Profile switching - #930

Merged
isXander merged 6 commits into
mainfrom
feat/profile-switching
Jul 27, 2026
Merged

Profile switching#930
isXander merged 6 commits into
mainfrom
feat/profile-switching

Conversation

@isXander

@isXander isXander commented Jul 25, 2026

Copy link
Copy Markdown
Owner

What is profile switching

This PR introduced profile switching.

Since 3.0, Controlify has the concept of profiles. Profiles hold what used to be per-controller configuration, like sensitivity, vibration settings, etc. Basically, a profile holds everything that isn't part of "Global Settings".

Profiles were designed with future splitscreen in mind. Each player will get their own profile.

Since 3.0, people have missed the feature of being able to explicitly select a controller a client uses, so they can make a makeshift splitscreen setup by launching multiple clients and selecting a different controller on each. The new profile system does not allow for this, as the last-used controller is selected.

This PR allows users to create and manage multiple profiles. Each profile will have a new option, controller assignment; by default, "Automatic" (matching current switching behaviour), you will be able to set a profile to a specific controller which will disable the auto switching. This means you assign a different profile to each running instance, and they can have their own separate settings and controller. This also lays important groundwork for the proper first-party splitscreen implementation.

Implementation details

  • Because each client will manage (read and write) its own profile, each profile will need to be a separate config file

  • Data fixing and migration is required to split the legacy monolithic file into profiles

  • Each profile config will have an OS lock associated with it. This informs other running instances that the profile has already been assigned, and prevents concurrent writes.

  • On client startup, the following precedence is used for picking a profile to use:

    1. A debug option to force a specific profile
    2. A "preferred startup profile" global setting
    3. Sequential order, starting from profile 1
    4. If all profiles are locked, create a new profile
  • UI to manage, create, reset, and delete profiles

  • This fleshing out of profiles also made one previous implementation detail appear unnecessary. Resource packs can define the default config, but they can do this per-controller-type. Profiles are not linked to controllers, so their defaults should not relate to them. This should be simplified so resource packs provide a default config, but not per-controller-type.

@isXander
isXander marked this pull request as ready for review July 27, 2026 16:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces multi-profile support to Controlify, including per-profile controller assignment (automatic vs specific device), profile selection UI, and a split-config persistence model that supports multiple running instances via OS-level profile locks.

Changes:

  • Split the legacy monolithic client config into a shared config + per-profile config files, with migration and file-locking to prevent concurrent writes.
  • Add UI/UX for creating, switching, remembering, and deleting profiles; add a global “preferred startup profile” setting.
  • Simplify default profile config loading to a single layered default config resource (no longer per-controller-type), and update localization strings accordingly.

Reviewed changes

Copilot reviewed 26 out of 28 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main/resources/resourcepacks/legacy_console/assets/controlify/controllers/default_config.json Adds legacy-console-specific overrides for the unified default profile config.
src/main/resources/assets/controlify/lang/en_us.json Adds strings for profile switching UI, controller assignment UI, and new toast errors.
src/main/resources/assets/controlify/controllers/default_config.json Introduces the unified default profile config used by the new defaults pipeline.
src/main/java/dev/isxander/controlify/server/ControlifyServerConfig.java Moves server config storage to config/controlify/server.json.
src/main/java/dev/isxander/controlify/server/ControlifyServer.java Adds server config migration into the new controlify/ subdirectory.
src/main/java/dev/isxander/controlify/gui/screen/ProfileSelectionScreen.java New screen for switching/creating/deleting profiles and choosing persistence behavior.
src/main/java/dev/isxander/controlify/gui/screen/GlobalSettingsScreenFactory.java Adds “preferred startup profile” option to global settings UI.
src/main/java/dev/isxander/controlify/gui/screen/ControllerConfigScreenFactory.java Adds profile-related options (name + controller assignment) and applies controller selection on save.
src/main/java/dev/isxander/controlify/gui/screen/ControlifySettingsScreen.java Updates the main settings screen to show profile + controller state and add “Switch Profile”.
src/main/java/dev/isxander/controlify/debug/DebugProperties.java Adds debug property to force a specific profile index on startup.
src/main/java/dev/isxander/controlify/controllermanager/SDLControllerManager.java Uses active profile settings as controller settings source; uses unified defaults.
src/main/java/dev/isxander/controlify/controller/ControllerEntity.java Makes settings mutable to support switching active profile at runtime.
src/main/java/dev/isxander/controlify/Controlify.java Wires in profile switching, controller selection application, device tracking, and config lifecycle closing.
src/main/java/dev/isxander/controlify/config/settings/profile/ProfileSettings.java Adds per-profile name + controller UID assignment; switches defaults to unified config source.
src/main/java/dev/isxander/controlify/config/settings/GlobalSettings.java Adds preferredProfile and updates DTO/default behavior.
src/main/java/dev/isxander/controlify/config/settings/device/GyroCalibrationSettings.java Adjusts default handling to return fresh instances (no shared singleton).
src/main/java/dev/isxander/controlify/config/settings/device/DeviceSettings.java Tracks device metadata (name/lastSeen/controllerType) for controller assignment UX.
src/main/java/dev/isxander/controlify/config/settings/ControlifySettings.java Refactors settings storage to sorted map keyed by profile index; splits shared vs legacy DTO handling.
src/main/java/dev/isxander/controlify/config/dto/SharedConfig.java New DTO for shared (global + devices) config file.
src/main/java/dev/isxander/controlify/config/dto/profile/ProfileConfig.java Extends profile DTO with optional name + controller UID assignment.
src/main/java/dev/isxander/controlify/config/dto/profile/defaults/DefaultConfigProvider.java Simplifies defaults API to return a single default profile config.
src/main/java/dev/isxander/controlify/config/dto/profile/defaults/DefaultConfigManager.java Implements unified layered default config loading from controllers/default_config.json.
src/main/java/dev/isxander/controlify/config/dto/GlobalConfig.java Adds preferred_profile to global config codec.
src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifyTypeReferences.java Adds DFU type refs for shared/profile split configs.
src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifySchemas.java Adds schema v3 types for shared/profile configs and renames schema classes.
src/main/java/dev/isxander/controlify/config/dto/dfu/ControlifyDataFixer.java Bumps DFU version and updates fixer wiring for split-config era.
src/main/java/dev/isxander/controlify/config/dto/device/DeviceConfig.java Adds device metadata fields to device config codec.
src/main/java/dev/isxander/controlify/config/ConfigManager.java Major rewrite: shared+profile file IO, migration from legacy, locking, profile lifecycle ops, and schema validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +135 to +146
Map<String, DeviceSettings> devices = Controlify.instance().config().getSettings().deviceSettings();
List<String> controllerChoices = devices.entrySet().stream()
.sorted(Map.Entry.<String, DeviceSettings>comparingByValue(
Comparator.comparingLong(device -> device.lastSeen)
).reversed())
.map(Map.Entry::getKey)
.collect(Collectors.toCollection(ArrayList::new));
if (settings.controllerUid != null && !controllerChoices.contains(settings.controllerUid)) {
controllerChoices.add(settings.controllerUid);
}
controllerChoices.addFirst("");

Comment on lines +41 to +53
try {
Path configDirectory = PlatformMainUtil.getConfigDir();
Path controlifyDirectory = configDirectory.resolve("controlify");
Path serverConfig = controlifyDirectory.resolve("server.json");
Path legacyServerConfig = configDirectory.resolve("controlify.json");
Files.createDirectories(controlifyDirectory);
if (Files.notExists(serverConfig) && Files.exists(legacyServerConfig)) {
Files.move(legacyServerConfig, serverConfig);
CUtil.LOGGER.log("Migrated Controlify server config to {}.", serverConfig.toAbsolutePath());
}
} catch (IOException e) {
throw new RuntimeException("Failed to create Controlify server config directory", e);
}
@isXander
isXander merged commit 6c5b60e into main Jul 27, 2026
7 checks passed
@isXander
isXander deleted the feat/profile-switching branch July 27, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants