Heyy everyone!
Continuing my work on the input system overhaul, this PR is mainly focused around three new features :
- Dynamic Input Swap
- Device Profile Linking
- Player Device Assignement
What are these new features and why are they bundled in the same PR you might ask?
- **Dynamic Input Swap**
Is a new feature that, when enabled, allows the user to switch between multiple input devices, without having to return to the settings menu. This feature can be enabled at the bottom of the Input Settings menu, next to the "global input" checkbox. A nifty new tooltip has been added for clarity.
- **Device Profile linking** :
While working on Dynamic Input Swap, one issue arose, which was that Ryujinx would use that device’s Default profile, without the user being able to specify a specific profile to use on that specific device. This feature fixes that, by allowing the user to link a profile as the device’s Default. This means that this profile will be auto loaded when selecting that same device, both when connecting it to the emulator, and when using Dynamic Input Swap. This feature can be found next to the Device Profiles dropdown, and includes a nifty new tooltip. By default, the "default" profile is linked to any device. There also is a small icon to the right of the profile name’s Right to visualise which profile is linked.
- **Player Device Assignement** :
Another issue that arose while working on Dynamic Input Swap was that enabling the feature would render multi player configurations unusable. This new feature addresses this issue by adding a new menu, which allows the user to assign different Input Devices to different players. Inside this new menu, you also get the device’s linked profile and the list of which players are already assigned to which input device, below and to the right of the input device name, respectively. You also get an option to allow mapping the same input device to different players.
### Nerd Zone
This PR adds a new player-level input routing layer on top of the existing `InputConfig` system.
Previously, each player effectively had one active input config, tied to one device. Given Dynamic Input Swap needs more state than that, this PR introduces a persisted `PlayerInputAssignment` model, which stores the player index, whether Dynamic Input Swap is enabled or not, the list of assigned input devices and an optional profile name bound to each assigned device.
The new assignment data lives coexists with the existing input configs instead of replacing them, which should keep the old single-device behavior intact when Dynamic Input Swap is disabled, while allowing dynamic players to own multiple devices.
New configl types include:
- `AssignedInputDevice`
- `AssignedInputDeviceType`
- `PlayerInputAssignment`
- `PlayerInputAssignmentHelper`
- `PlayerInputDeviceAssignmentItem`
`PlayerInputAssignmentHelper` normalizes assignments, deduplicates device entries, preserves a primary device, and compares assignments for dirty-checking.
On the runtime side, `NpadManager` now passes both the normal `InputConfig` and the player assignment to `NpadController`. When Dynamic Input Swap is disabled, Ryujinx switches to the old behavior : one player config opens one device. However, when it's enabled, `NpadController` opens every assigned keyboard/controller device it can resolve, tracks their state snapshots, and promotes the active source based on recent input.
Dynamic swap currently follows a “last meaningful input wins” model (annotated in the code) :
So if the keyboard produces new input, it becomes active; if an assigned controller produces new input, that controller becomes active; if the active device stops producing input and another assigned device is held/active, the active source can fall back; and if no device has produced input yet, the initial active source is chosen from the selected config and available assigned devices.
Per-device profile binding is handled by storing the profile name on the assigned device entry. When a device is selected or resolved through Dynamic Input Swap, Ryujinx tries to load that bound profile for the matching device type. If the profile is missing, invalid, or explicitly `Default`, it falls back to the generated default config for that device type.
The new restore-to-defaults behavior deliberately bypasses linked profiles. This means pressing the reset button loads the real generated Default profile for the currently selected device, not the profile linked to that device.
The input settings UI now dirty-checks both the currently edited input config and the player input assignment state, meaning that assigning/unassigning devices, changing Dynamic Input Swap, or changing profile bindings correctly marks the page as modified -> in continuation of my previous efforts in #13 to clean up its behaviour.
The Assigned Devices dialog is backed by the currently available keyboard/controller device list. It also checks other players’ persisted assignments so the UI can show which players already use a device. If duplicate assignment is disabled, devices already assigned to another dynamic-swap player are disabled for the current player.
If no explicit player assignments exist yet, Ryujinx synthesizes a default assignment from the existing input config. Dynamic swap is disabled by default until the user enables it.
⚠️⚠️⚠️⚠️⚠️Caution : this PR is still a WIP; and while all features described above have been fully implemented, various issues still remain, notably inside the Player assignement menu, that are getting worked on. Additionally, little bug testing has been done across the emulator, so it is not guaranteed that this build will be bug free.
Signed by : 🦫
With the very generous help and support from @neo 🤗
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/125
This PR refactors keyboard handling to use physical key mappings for all gameplay input, ensuring controls remain consistent across different OS keyboard layouts.
I'd like to give out an ENORMOUS thank you to @Neo for his very generous help on getting MacOS caps lock behaviour working, but as well for taking the time for extensive testing, planning and discussions, and finally for writing this PR message :) Keep being awsome pal 👊
### New Features :
* **New**: Gameplay input now uses physical key positions instead of OS layouts, ensuring the same physical key triggers the same action across keyboard layouts.
* Key rebinding stores physical keys and config compatibility is preserved, with physical keys now the primary gameplay‑binding format.
* Physical‑key model is now consistent across platforms, including updated SDL/headless behavior.
* **Added**: New Input setting "Reset keybinds to default", with a new confirmation dialog appears when changes are being overwritten.
* **Fractured**: Keyboard‑related locales to the newly created `KeyboardLayout.json`.
* New input device settings/actions use clearer labels and tooltips.
* UI Key Labels (such as Left Shift and Right Shift) are more accurate and standardized, with clearer symbols, consistent naming, dynamic learning of printable labels from real key events, and persistence across restarts.
### Improvements :
* **Reduced**: Incorrect key labels by using observed host symbols instead of language assumptions.
* **Reduced**: Stuck/stale keys by using binary pressed‑key tracking, fixing rebinding/gameplay paths, better held‑key recovery after focus changes, and clearing keyboard state when Ryujinx/settings windows lose focus.
* **Improved**: Device handling → refreshing no longer clears the selector, disconnect fallback is consistent, reconnect restores controllers automatically, and the UI avoids invalid/empty device states.
* **Improved**: Async input‑assignment callbacks are now guarded when switching views/devices, preventing stale callbacks from hitting detached views.
* **Adjusted**: Input visualiser to be more robust when switching sources or handling controller disconnect/reconnect, without needing to reopen settings.
* **Improved**: Modification (changes to input controls) tracking
* Rebinding to the same value, reverting to original config, restoring defaults without differences, or reloading equivalent profiles no longer leaves Player marked as modified.
* **Reduced**: Keyboard LED noise in logs and added optional UI keyboard‑state/rebinding diagnostics.
### Fixes :
* **Special Keys**:
* AltGr and other special keys behave correctly, including proper Ctrl+Alt → AltRight handling and more consistent normalization of special/synthetic keys.
* Caps Lock is now reliably bindable on all platforms (Windows/Linux register every press; macOS every other).
* **Fixed**: Certain cases where keyboard input broke after pointer interactions
### Current Limitations
These are planned on being fixed/improved upon in future PRs:
* Hotkeys still use semantic (Key) mappings.
* Software keyboard / text input still uses the semantic path
* Printable key labels may fall back to defaults until observed from host input.
* Full semantic/physical split currently implemented only in the Avalonia driver.
Co-authored-by: _Neo_ <ursamajorjanus2819@gmail.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/13
When switching between players' gamepads while saving settings, then
returning to the previous player, the settings show the default settings
instead of the actual settings applied