Repository navigation
Conversation
|
Retargeted to |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
Launchers that edit the controls had no way to change them during a game: the configuration is only read at startup and written back on exit. The main window now watches the config folder for a "reload-controls" file holding the [Controls] keys of the players to change, in the qt-config.ini format. Those players are saved and applied to their controllers, then the file is removed. Players using the game's own input profile are left alone, and a controller whose type and connection did not change stays connected, so the game does not show the controller applet again. The request waits while a settings window is open. Signed-off-by: tom.tourbez <188838116+tom-tour@users.noreply.github.com>
2ff5bb9 to
e49b6ee
Compare
|
|
||
| const bool players_global = Settings::values.players.UsingGlobal(); | ||
| Settings::values.players.SetGlobal(true); | ||
| for (const std::size_t p : players_in_file) { |
There was a problem hiding this comment.
Any player_N_ key in the request makes the whole player be re-read from the request file through ReadPlayerValues/ReadQtPlayerValues. Every key the request leaves out gets its default instead of keeping its current value:
connectedbecomes true only for player 0typebecomes Pro Controller- buttons, sticks and motion go back to keyboard defaults
It's then saved to qt-config.ini. For example, a request with only player_1_button_a disconnects player 2 and puts the rest of its buttons back on keyboard defaults.
Could the request be read into its own CSimpleIniA, filled from the current settings first, and then have the request's keys applied on top? The other option is to document that a request must contain every key for each player it lists, but that's easy for launchers to get wrong.
| // The file uses the same [Controls] keys as this configuration. Only the players that have | ||
| // keys in it are read, every other player is left as it is. | ||
| const std::string own_path = config_loc; | ||
| config_loc = path; |
There was a problem hiding this comment.
This points the main config at the request and then calls SetUpIni() again on qt-config.ini. If that "rb" open fails (for example, the launcher has the file open on Windows), SetUpIni falls back to "wb", which empties the file, and WriteToIni below then writes only the player keys. Reading the request into its own CSimpleIniA would avoid this and leave the main config's state alone.
| auto& game_players = Settings::values.players.GetValue(); | ||
| const auto& global_players = Settings::values.players.GetValue(true); | ||
| for (const std::size_t p : players_in_file) { | ||
| if (game_running && !game_players[p].profile_name.empty()) { |
There was a problem hiding this comment.
This reads the game's copy of the players without checking Settings::values.players.UsingGlobal(). Nothing clears that copy when a game shuts down, so after a game with its own input profile, a later game without per-game settings still sees the old profile_name. That player is then skipped, so the new controls are saved but not applied until a restart.
| game_players[p].profile_name = ""; | ||
| players_to_apply.push_back(p); | ||
| } | ||
| Settings::values.players.SetGlobal(players_global); |
There was a problem hiding this comment.
When player 0 is Handheld, ReadControlValues also switches the console to handheld mode (use_docked_mode). This path doesn't, so a request that sets player_0_type to Handheld during a docked game leaves the controller Handheld and the console docked, a combination the normal loading code and the settings dialog never allow.
| npad_type_in_settings = npad_id_type == NpadIdType::Other | ||
| ? NpadStyleIndex::Fullkey | ||
| : MapSettingsTypeToNPad(player.controller_type); | ||
| connected_in_settings = player.connected; |
There was a problem hiding this comment.
These two are only updated here. The controls dialog applies changes with DisableConfiguration() + SaveCurrentConfig() (configure_input_player.cpp:853-854), which changes both the settings and the controller, and nothing calls ReloadFromSettings afterwards during a game. Say player 1 starts as a Pro Controller and is switched to Handheld in the dialog during a game:
- If the launcher then sends
type=Pro, it matches the out-of-date value, so only the mappings reload and the controller stays Handheld whileqt-config.inisays Pro. - If the launcher sends Handheld again, it doesn't match, so the controller is disconnected and reconnected and the game can show the controller applet, which this change is meant to avoid.
Updating both fields in SaveCurrentConfig() should fix this.
| } | ||
| const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); | ||
| const auto& player = Settings::values.players.GetValue()[player_index]; | ||
| const auto style = npad_id_type == NpadIdType::Other |
There was a problem hiding this comment.
Minor: the Other ? Fullkey : MapSettingsTypeToNPad(...) line now appears three times, and the button/stick/motion/ring loops below are copied from ReloadFromSettings. Shared helpers used by both functions would stop them drifting apart.
| for (std::size_t index = 0; index < player.motions.size(); ++index) { | ||
| motion_params[index] = Common::ParamPackage(player.motions[index]); | ||
| } | ||
| ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs); |
There was a problem hiding this comment.
This path never calls ReloadColorsFromSettings(), so colours in a request are saved but the game keeps the old ones until the next full reload. Also, ringcon_analogs is reloaded here but ApplyPlayerValuesFromFile never reads it from the request, so it can't change through this path.
| // Take the request first, so the program that wrote it cannot change or remove it while it | ||
| // is read. | ||
| Common::FS::RemoveFile(ControlsReloadApplyingPath()); | ||
| if (!Common::FS::RenameFile(ControlsReloadRequestPath(), ControlsReloadApplyingPath())) { |
There was a problem hiding this comment.
A directory watch in QFileSystemWatcher fires when a file is created or renamed, not when it's written to (inotify on Linux, kqueue on macOS). If a launcher opens reload-controls and writes to it directly, this can run while the file is still empty or half-written. The file is then renamed away and only part of it is applied, and that part is saved. On Windows this rename fails while the launcher still has the file open, and nothing retries it until some other file in the config folder changes.
Could the protocol require writing a temp file and renaming it to reload-controls, documented next to ControlsReloadRequestPath()? Retrying a failed rename on a short timer would also help.
|
|
||
| void MainWindow::WatchControlsReloadRequests() { | ||
| // A request left from a previous run is stale: the program that wrote it has given up on it. | ||
| Common::FS::RemoveFile(ControlsReloadRequestPath()); |
There was a problem hiding this comment.
This means a launcher that writes the request and then starts Eglantine loses it. That's fine if it's intended, but it's worth saying so in the protocol description so launcher authors know to wait until Eglantine is running.
- Read the request into its own ini, on top of the current values of its players, so keys it leaves out keep their value and qt-config.ini is never opened again. - Record the players using the game's own input profile when the game boots, and leave them alone until it stops. - Switch to handheld mode when player 1 becomes handheld, as loading the configuration does. - Keep the controller's configured style and connection in sync when the controls window saves. - Apply colours, drop the ring-con reload, and share the mapping code with ReloadFromSettings. - Retry a request that can't be taken yet, apply one written before startup, and document how to write one. Signed-off-by: tom.tourbez <188838116+tom-tour@users.noreply.github.com>
|
Thanks for the review, I pushed a commit that should fix all the points One change on my side : a request written before startup is now applied instead of deleted. Tell me if you'd rather keep deleting it Tested again with Fire Emblem Fortune's Weave, all good |
yeah this makes sense to me. thanks for the update, I will review this soon and get back to you |
Summary
Launchers that manage the controls of Eden-based emulators (NX2 Launcher, for example) cannot change them while a game runs: the emulator reads
qt-config.inionly when it starts, and writes it again from memory when it closes, so an outside change is lost. The only way today is the emulator's own Controls windowAs discussed in #853, this adds a small request file that other programs can use:
reload-controlsin the emulator'sconfigfolder. It holds the[Controls]keys of the players to change, in the same format asqt-config.iniQFileSystemWatcheron the config folder), takes it (renamed toreload-controls.applyingso the program can't change it while it's read), applies those players to the emulated controllers, saves them inqt-config.iniand removes the fileqt-config.iniback to checkOnly the emulator writes
qt-config.ini, the other program only writes the requestDetails:
QtConfig::ApplyPlayerValuesFromFile, built on the existingReadQtControlPlayerValuesused by input profiles). The program sends every key of a player it changes. Other players and the rest of the configuration don't changeEmulatedController::ReloadMappingsFromSettings,HIDCore::ReloadInputMappings), so the game does not see a controller unplugged and doesn't show the controller applet again. The comparison uses the values loaded from the configuration, not the current state, because the game and its controller applet can connect or disconnect controllers themselves. A controller whose type or connection changed goes through the existingReloadFromSettingsType of change
Related issues
Closes #853
Testing
Tested on Windows with Fire Emblem Fortune's Weave, by writing the
reload-controlsfile during the game. The new mapping worked right away, without the controller being reconnected or the controller applet showing up, andqt-config.inikept it after closingAlso checked : a request for player 2 only (player 1 untouched), a game with its own input profile (left as it is), and a request sent while the settings window was open (applied once it closed)
Suggested steps:
config/reload-controlsfile with a[Controls]section and theplayer_0_keys ofqt-config.ini, withplayer_0_button_aset to another keyqt-config.inialready has itplayer_1_keys leaves player 1 (player_0_) as it isGames tested: Fire Emblem Fortune's Weave
Checklist
Helped by AIs : Refactoring & Code quality
Screenshots / logs
When the request is handled, the log shows:
Applying the controls sent by another programSummary by CodeRabbit