Qt: Add option to pause when controller is disconnected

This commit is contained in:
Stenzek 2024-05-15 07:42:19 +10:00 committed by Connor McLaughlin
parent b9d7b63b32
commit f084e76f36
14 changed files with 135 additions and 78 deletions

View File

@ -218,11 +218,11 @@ std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
return GSRunner::GetPlatformWindowInfo(); return GSRunner::GetPlatformWindowInfo();
} }
void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) void Host::OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name)
{ {
} }
void Host::OnInputDeviceDisconnected(const std::string_view& identifier) void Host::OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier)
{ {
} }

View File

@ -424,6 +424,7 @@ void MainWindow::connectSignals()
void MainWindow::connectVMThreadSignals(EmuThread* thread) void MainWindow::connectVMThreadSignals(EmuThread* thread)
{ {
connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::statusMessage, this, &MainWindow::onStatusMessage);
connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested); connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested);
@ -438,7 +439,6 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
connect(thread, &EmuThread::onCaptureStarted, this, &MainWindow::onCaptureStarted); connect(thread, &EmuThread::onCaptureStarted, this, &MainWindow::onCaptureStarted);
connect(thread, &EmuThread::onCaptureStopped, this, &MainWindow::onCaptureStopped); connect(thread, &EmuThread::onCaptureStopped, this, &MainWindow::onCaptureStopped);
connect(thread, &EmuThread::onAchievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested); connect(thread, &EmuThread::onAchievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
connect(thread, &EmuThread::onAchievementsLoginSucceeded, this, &MainWindow::onAchievementsLoginSucceeded);
connect(thread, &EmuThread::onAchievementsHardcoreModeChanged, this, &MainWindow::onAchievementsHardcoreModeChanged); connect(thread, &EmuThread::onAchievementsHardcoreModeChanged, this, &MainWindow::onAchievementsHardcoreModeChanged);
connect(thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered); connect(thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(thread, &EmuThread::onCreateMemoryCardOpenRequested, this, &MainWindow::onCreateMemoryCardOpenRequested); connect(thread, &EmuThread::onCreateMemoryCardOpenRequested, this, &MainWindow::onCreateMemoryCardOpenRequested);
@ -763,13 +763,6 @@ void MainWindow::onAchievementsLoginRequested(Achievements::LoginRequestReason r
dlg.exec(); dlg.exec();
} }
void MainWindow::onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points, quint32 unread_messages)
{
const QString message =
tr("RA: Logged in as %1 (%2 pts, softcore: %3 pts). %4 unread messages.").arg(display_name).arg(points).arg(sc_points).arg(unread_messages);
m_ui.statusBar->showMessage(message);
}
void MainWindow::onAchievementsHardcoreModeChanged(bool enabled) void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
{ {
// disable debugger while hardcore mode is active // disable debugger while hardcore mode is active
@ -1143,6 +1136,11 @@ bool MainWindow::confirmMessage(const QString& title, const QString& message)
return (QMessageBox::question(this, title, message) == QMessageBox::Yes); return (QMessageBox::question(this, title, message) == QMessageBox::Yes);
} }
void MainWindow::onStatusMessage(const QString& message)
{
m_ui.statusBar->showMessage(message);
}
void MainWindow::runOnUIThread(const std::function<void()>& func) void MainWindow::runOnUIThread(const std::function<void()>& func)
{ {
func(); func();

View File

@ -110,6 +110,8 @@ public Q_SLOTS:
void cancelGameListRefresh(); void cancelGameListRefresh();
void reportError(const QString& title, const QString& message); void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message);
void runOnUIThread(const std::function<void()>& func); void runOnUIThread(const std::function<void()>& func);
void requestReset(); void requestReset();
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool default_save_to_state = true); bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool default_save_to_state = true);
@ -190,7 +192,6 @@ private Q_SLOTS:
void onCaptureStopped(); void onCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason); void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
void onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points, quint32 unread_messages);
void onAchievementsHardcoreModeChanged(bool enabled); void onAchievementsHardcoreModeChanged(bool enabled);
protected: protected:

View File

@ -1107,7 +1107,14 @@ void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages) void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
{ {
emit g_emu_thread->onAchievementsLoginSucceeded(QString::fromUtf8(username), points, sc_points, unread_messages); const QString message =
qApp->translate("QtHost", "RA: Logged in as %1 (%2 pts, softcore: %3 pts). %4 unread messages.")
.arg(QString::fromUtf8(username))
.arg(points)
.arg(sc_points)
.arg(unread_messages);
emit g_emu_thread->statusMessage(message);
} }
void Host::OnAchievementsRefreshed() void Host::OnAchievementsRefreshed()
@ -1671,15 +1678,44 @@ std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
return ret; return ret;
} }
void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) void Host::OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name)
{ {
emit g_emu_thread->onInputDeviceConnected(identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()), emit g_emu_thread->onInputDeviceConnected(identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()),
device_name.empty() ? QString() : QString::fromUtf8(device_name.data(), device_name.size())); device_name.empty() ? QString() : QString::fromUtf8(device_name.data(), device_name.size()));
if (VMManager::HasValidVM() || g_emu_thread->isRunningFullscreenUI())
{
Host::AddIconOSDMessage(fmt::format("controller_connected_{}", identifier), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("QtHost", "Controller {} connected."), identifier),
Host::OSD_INFO_DURATION);
}
} }
void Host::OnInputDeviceDisconnected(const std::string_view& identifier) void Host::OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier)
{ {
emit g_emu_thread->onInputDeviceDisconnected(identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size())); emit g_emu_thread->onInputDeviceDisconnected(identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()));
if (VMManager::GetState() == VMState::Running && Host::GetBoolSettingValue("UI", "PauseOnControllerDisconnection", false) &&
InputManager::HasAnyBindingsForSource(key))
{
std::string message =
fmt::format(TRANSLATE_FS("QtHost", "System paused because controller {} was disconnected."), identifier);
Host::RunOnCPUThread([message = QString::fromStdString(message)]() {
VMManager::SetPaused(true);
// has to be done after pause, otherwise pause message takes precedence
emit g_emu_thread->statusMessage(message);
});
Host::AddIconOSDMessage(fmt::format("controller_connected_{}", identifier), ICON_FA_GAMEPAD, std::move(message),
Host::OSD_WARNING_DURATION);
}
else if (VMManager::HasValidVM() || g_emu_thread->isRunningFullscreenUI())
{
Host::AddIconOSDMessage(fmt::format("controller_connected_{}", identifier), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("QtHost", "Controller {} disconnected."), identifier),
Host::OSD_INFO_DURATION);
}
} }
void Host::SetMouseMode(bool relative_mode, bool hide_cursor) void Host::SetMouseMode(bool relative_mode, bool hide_cursor)

View File

@ -117,6 +117,7 @@ public Q_SLOTS:
Q_SIGNALS: Q_SIGNALS:
bool messageConfirmed(const QString& title, const QString& message); bool messageConfirmed(const QString& title, const QString& message);
void statusMessage(const QString& message);
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless); std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeRenderWindowRequested(qint32 width, qint32 height); void onResizeRenderWindowRequested(qint32 width, qint32 height);
@ -161,9 +162,6 @@ Q_SIGNALS:
/// Called when achievements login is requested. /// Called when achievements login is requested.
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason); void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
/// Called when achievements login succeeds. Also happens on startup.
void onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points, quint32 unread_messages);
/// Called when achievements are reloaded/refreshed (e.g. game change, login, option change). /// Called when achievements are reloaded/refreshed (e.g. game change, login, option change).
void onAchievementsRefreshed(quint32 id, const QString& game_info_string); void onAchievementsRefreshed(quint32 id, const QString& game_info_string);

View File

@ -10,7 +10,7 @@
const char* InterfaceSettingsWidget::THEME_NAMES[] = { const char* InterfaceSettingsWidget::THEME_NAMES[] = {
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"), QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"),
//: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated. //: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated.
#ifdef _WIN32 #ifdef _WIN32
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Classic Windows"), QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Classic Windows"),
#endif #endif
@ -75,6 +75,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.saveStateOnShutdown, "EmuCore", "SaveStateOnShutdown", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.saveStateOnShutdown, "EmuCore", "SaveStateOnShutdown", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnControllerDisconnection, "UI", "PauseOnControllerDisconnection", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.backupSaveStates, "EmuCore", "BackupSavestate", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.backupSaveStates, "EmuCore", "BackupSavestate", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false);
@ -147,6 +148,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
dialog->registerWidgetHelp(m_ui.pauseOnFocusLoss, tr("Pause On Focus Loss"), tr("Unchecked"), dialog->registerWidgetHelp(m_ui.pauseOnFocusLoss, tr("Pause On Focus Loss"), tr("Unchecked"),
tr("Pauses the emulator when you minimize the window or switch to another application, " tr("Pauses the emulator when you minimize the window or switch to another application, "
"and unpauses when you switch back.")); "and unpauses when you switch back."));
dialog->registerWidgetHelp(m_ui.pauseOnControllerDisconnection, tr("Pause On Controller Disconnection"),
tr("Unchecked"), tr("Pauses the emulator when a controller with bindings is disconnected."));
dialog->registerWidgetHelp(m_ui.backupSaveStates, tr("Create Save State Backups"), tr("Checked"), dialog->registerWidgetHelp(m_ui.backupSaveStates, tr("Create Save State Backups"), tr("Checked"),
//: Do not translate the ".backup" extension. //: Do not translate the ".backup" extension.
tr("Creates a backup copy of a save state if it already exists when the save is created. The backup copy has a .backup suffix.")); tr("Creates a backup copy of a save state if it already exists when the save is created. The backup copy has a .backup suffix."));
@ -164,10 +167,10 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
m_ui.discordPresence, tr("Enable Discord Presence"), tr("Unchecked"), m_ui.discordPresence, tr("Enable Discord Presence"), tr("Unchecked"),
tr("Shows the game you are currently playing as part of your profile in Discord.")); tr("Shows the game you are currently playing as part of your profile in Discord."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.doubleClickTogglesFullscreen, tr("Double-Click Toggles Fullscreen"), tr("Checked"), m_ui.doubleClickTogglesFullscreen, tr("Double-Click Toggles Fullscreen"), tr("Checked"),
tr("Allows switching in and out of fullscreen mode by double-clicking the game window.")); tr("Allows switching in and out of fullscreen mode by double-clicking the game window."));
dialog->registerWidgetHelp( dialog->registerWidgetHelp(
m_ui.disableWindowResizing, tr("Disable Window Resizing"), tr("Unchecked"), m_ui.disableWindowResizing, tr("Disable Window Resizing"), tr("Unchecked"),
tr("Prevents the main window from being resized.")); tr("Prevents the main window from being resized."));
onRenderToSeparateWindowChanged(); onRenderToSeparateWindowChanged();

View File

@ -29,28 +29,28 @@
<string>Behaviour</string> <string>Behaviour</string>
</property> </property>
<layout class="QGridLayout" name="formLayout_4"> <layout class="QGridLayout" name="formLayout_4">
<item row="3" column="1"> <item row="0" column="1">
<widget class="QCheckBox" name="pauseOnFocusLoss">
<property name="text">
<string>Pause On Focus Loss</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="inhibitScreensaver">
<property name="text">
<string>Inhibit Screensaver</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="saveStateOnShutdown"> <widget class="QCheckBox" name="saveStateOnShutdown">
<property name="text"> <property name="text">
<string>Save State On Shutdown</string> <string>Save State On Shutdown</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="1" column="0">
<widget class="QCheckBox" name="inhibitScreensaver">
<property name="text">
<string>Inhibit Screensaver</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="confirmShutdown">
<property name="text">
<string>Confirm Shutdown</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="pauseOnStart"> <widget class="QCheckBox" name="pauseOnStart">
<property name="text"> <property name="text">
<string>Pause On Start</string> <string>Pause On Start</string>
@ -58,26 +58,33 @@
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QCheckBox" name="confirmShutdown"> <widget class="QCheckBox" name="pauseOnFocusLoss">
<property name="text"> <property name="text">
<string>Confirm Shutdown</string> <string>Pause On Focus Loss</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="2" column="1">
<widget class="QCheckBox" name="backupSaveStates"> <widget class="QCheckBox" name="pauseOnControllerDisconnection">
<property name="text"> <property name="text">
<string>Create Save State Backups</string> <string>Pause On Controller Disconnection</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="3" column="1">
<widget class="QCheckBox" name="discordPresence"> <widget class="QCheckBox" name="discordPresence">
<property name="text"> <property name="text">
<string>Enable Discord Presence</string> <string>Enable Discord Presence</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QCheckBox" name="backupSaveStates">
<property name="text">
<string>Create Save State Backups</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -215,7 +222,7 @@
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>

View File

@ -3101,6 +3101,8 @@ void FullscreenUI::DrawInterfaceSettingsPage()
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_EYE, "Pause On Focus Loss"), DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_EYE, "Pause On Focus Loss"),
FSUI_CSTR("Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back."), FSUI_CSTR("Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back."),
"UI", "PauseOnFocusLoss", false); "UI", "PauseOnFocusLoss", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_GAMEPAD, "Pause On Controller Disconnection"),
FSUI_CSTR("Pauses the emulator when a controller with bindings is disconnected."), "UI", "PauseOnControllerDisconnection", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST_ALT, "Pause On Menu"), DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_LIST_ALT, "Pause On Menu"),
FSUI_CSTR("Pauses the emulator when you open the quick menu, and unpauses when you close it."), "UI", "PauseOnMenu", true); FSUI_CSTR("Pauses the emulator when you open the quick menu, and unpauses when you close it."), "UI", "PauseOnMenu", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_POWER_OFF, "Confirm Shutdown"), DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_POWER_OFF, "Confirm Shutdown"),
@ -6898,6 +6900,7 @@ TRANSLATE_NOOP("FullscreenUI", "Prevents the screen saver from activating and th
TRANSLATE_NOOP("FullscreenUI", "Shows the game you are currently playing as part of your profile on Discord."); TRANSLATE_NOOP("FullscreenUI", "Shows the game you are currently playing as part of your profile on Discord.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a game is started."); TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a game is started.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back."); TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a controller with bindings is disconnected.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you open the quick menu, and unpauses when you close it."); TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you open the quick menu, and unpauses when you close it.");
TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed."); TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.");
TRANSLATE_NOOP("FullscreenUI", "Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time."); TRANSLATE_NOOP("FullscreenUI", "Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time.");
@ -7140,10 +7143,11 @@ TRANSLATE_NOOP("FullscreenUI", "Synchronization");
TRANSLATE_NOOP("FullscreenUI", "Changes when SPU samples are generated relative to system emulation."); TRANSLATE_NOOP("FullscreenUI", "Changes when SPU samples are generated relative to system emulation.");
TRANSLATE_NOOP("FullscreenUI", "Buffer Size"); TRANSLATE_NOOP("FullscreenUI", "Buffer Size");
TRANSLATE_NOOP("FullscreenUI", "Determines the amount of audio buffered before being pulled by the host API."); TRANSLATE_NOOP("FullscreenUI", "Determines the amount of audio buffered before being pulled by the host API.");
TRANSLATE_NOOP("FullscreenUI", "Minimal Output Latency"); TRANSLATE_NOOP("FullscreenUI", "%d ms");
TRANSLATE_NOOP("FullscreenUI", "When enabled, the minimum supported output latency will be used for the host API.");
TRANSLATE_NOOP("FullscreenUI", "Output Latency"); TRANSLATE_NOOP("FullscreenUI", "Output Latency");
TRANSLATE_NOOP("FullscreenUI", "Determines how much latency there is between the audio being picked up by the host API, and played through speakers."); TRANSLATE_NOOP("FullscreenUI", "Determines how much latency there is between the audio being picked up by the host API, and played through speakers.");
TRANSLATE_NOOP("FullscreenUI", "Minimal Output Latency");
TRANSLATE_NOOP("FullscreenUI", "When enabled, the minimum supported output latency will be used for the host API.");
TRANSLATE_NOOP("FullscreenUI", "Settings and Operations"); TRANSLATE_NOOP("FullscreenUI", "Settings and Operations");
TRANSLATE_NOOP("FullscreenUI", "Creates a new memory card file or folder."); TRANSLATE_NOOP("FullscreenUI", "Creates a new memory card file or folder.");
TRANSLATE_NOOP("FullscreenUI", "Simulates a larger memory card by filtering saves only to the current game."); TRANSLATE_NOOP("FullscreenUI", "Simulates a larger memory card by filtering saves only to the current game.");
@ -7561,6 +7565,7 @@ TRANSLATE_NOOP("FullscreenUI", "Inhibit Screensaver");
TRANSLATE_NOOP("FullscreenUI", "Enable Discord Presence"); TRANSLATE_NOOP("FullscreenUI", "Enable Discord Presence");
TRANSLATE_NOOP("FullscreenUI", "Pause On Start"); TRANSLATE_NOOP("FullscreenUI", "Pause On Start");
TRANSLATE_NOOP("FullscreenUI", "Pause On Focus Loss"); TRANSLATE_NOOP("FullscreenUI", "Pause On Focus Loss");
TRANSLATE_NOOP("FullscreenUI", "Pause On Controller Disconnection");
TRANSLATE_NOOP("FullscreenUI", "Pause On Menu"); TRANSLATE_NOOP("FullscreenUI", "Pause On Menu");
TRANSLATE_NOOP("FullscreenUI", "Confirm Shutdown"); TRANSLATE_NOOP("FullscreenUI", "Confirm Shutdown");
TRANSLATE_NOOP("FullscreenUI", "Save State On Shutdown"); TRANSLATE_NOOP("FullscreenUI", "Save State On Shutdown");
@ -7683,6 +7688,5 @@ TRANSLATE_NOOP("FullscreenUI", "Game not loaded or no RetroAchievements availabl
TRANSLATE_NOOP("FullscreenUI", "Card Enabled"); TRANSLATE_NOOP("FullscreenUI", "Card Enabled");
TRANSLATE_NOOP("FullscreenUI", "Card Name"); TRANSLATE_NOOP("FullscreenUI", "Card Name");
TRANSLATE_NOOP("FullscreenUI", "Eject Card"); TRANSLATE_NOOP("FullscreenUI", "Eject Card");
TRANSLATE_NOOP("FullscreenUI", "%d ms");
// TRANSLATION-STRING-AREA-END // TRANSLATION-STRING-AREA-END
#endif #endif

View File

@ -145,7 +145,9 @@ void DInputSource::Shutdown()
{ {
while (!m_controllers.empty()) while (!m_controllers.empty())
{ {
InputManager::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1))); const u32 index = static_cast<u32>(m_controllers.size() - 1);
InputManager::OnInputDeviceDisconnected({{.source_type = InputSourceType::DInput, .source_index = index}},
GetDeviceIdentifier(index));
m_controllers.pop_back(); m_controllers.pop_back();
} }
} }
@ -245,7 +247,9 @@ void DInputSource::PollEvents()
if (hr != DI_OK) if (hr != DI_OK)
{ {
InputManager::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(i))); InputManager::OnInputDeviceDisconnected(
{{.source_type = InputSourceType::DInput, .source_index = static_cast<u32>(i)}},
GetDeviceIdentifier(static_cast<u32>(i)));
m_controllers.erase(m_controllers.begin() + i); m_controllers.erase(m_controllers.begin() + i);
continue; continue;
} }

View File

@ -1199,7 +1199,7 @@ void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis,
ImGuiManager::UpdateMousePosition(s_host_pointer_positions[0][0], s_host_pointer_positions[0][1]); ImGuiManager::UpdateMousePosition(s_host_pointer_positions[0][0], s_host_pointer_positions[0][1]);
} }
void InputManager::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) void InputManager::OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name)
{ {
if (VMManager::HasValidVM()) if (VMManager::HasValidVM())
USB::InputDeviceConnected(identifier); USB::InputDeviceConnected(identifier);
@ -1207,12 +1207,12 @@ void InputManager::OnInputDeviceConnected(const std::string_view& identifier, co
Host::OnInputDeviceConnected(identifier, device_name); Host::OnInputDeviceConnected(identifier, device_name);
} }
void InputManager::OnInputDeviceDisconnected(const std::string_view& identifier) void InputManager::OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier)
{ {
if (VMManager::HasValidVM()) if (VMManager::HasValidVM())
USB::InputDeviceDisconnected(identifier); USB::InputDeviceDisconnected(identifier);
Host::OnInputDeviceDisconnected(identifier); Host::OnInputDeviceDisconnected(key, identifier);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -289,10 +289,10 @@ namespace InputManager
void UpdateHostMouseMode(); void UpdateHostMouseMode();
/// Called when a new input device is connected. /// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name); void OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name);
/// Called when an input device is disconnected. /// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier); void OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier);
} // namespace InputManager } // namespace InputManager
namespace Host namespace Host
@ -301,10 +301,10 @@ namespace Host
std::optional<WindowInfo> GetTopLevelWindowInfo(); std::optional<WindowInfo> GetTopLevelWindowInfo();
/// Called when a new input device is connected. /// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name); void OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name);
/// Called when an input device is disconnected. /// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier); void OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier);
/// Enables relative mouse mode in the host, and/or hides the cursor. /// Enables relative mouse mode in the host, and/or hides the cursor.
void SetMouseMode(bool relative_mode, bool hide_cursor); void SetMouseMode(bool relative_mode, bool hide_cursor);

View File

@ -657,7 +657,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (!gcontroller && !joystick) if (!gcontroller && !joystick)
{ {
Console.Error("(SDLInputSource) Failed to open controller %d", index); ERROR_LOG("(SDLInputSource) Failed to open controller {}", index);
return false; return false;
} }
@ -667,7 +667,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
{ {
if (it->joystick_id == joystick_id) if (it->joystick_id == joystick_id)
{ {
Console.Error("(SDLInputSource) Controller %d, instance %d, player %d already connected, ignoring.", index, joystick_id, player_id); ERROR_LOG("(SDLInputSource) Controller {}, instance {}, player {} already connected, ignoring.", index, joystick_id, player_id);
if (gcontroller) if (gcontroller)
SDL_GameControllerClose(gcontroller); SDL_GameControllerClose(gcontroller);
else else
@ -680,8 +680,8 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end()) if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end())
{ {
const int free_player_id = GetFreePlayerId(); const int free_player_id = GetFreePlayerId();
Console.Warning("(SDLInputSource) Controller %d (joystick %d) returned player ID %d, which is invalid or in " WARNING_LOG("(SDLInputSource) Controller {} (joystick {}) returned player ID {}, which is invalid or in "
"use. Using ID %d instead.", "use. Using ID {} instead.",
index, joystick_id, player_id, free_player_id); index, joystick_id, player_id, free_player_id);
player_id = free_player_id; player_id = free_player_id;
} }
@ -690,7 +690,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (!name) if (!name)
name = "Unknown Device"; name = "Unknown Device";
Console.WriteLn("(SDLInputSource) Opened %s %d (instance id %d, player id %d): %s", is_gamecontroller ? "game controller" : "joystick", INFO_LOG("(SDLInputSource) Opened {} {} (instance id {}, player id {}): {}", is_gamecontroller ? "game controller" : "joystick",
index, joystick_id, player_id, name); index, joystick_id, player_id, name);
ControllerData cd = {}; ControllerData cd = {};
@ -717,7 +717,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
for (size_t i = 0; i < std::size(s_sdl_button_names); i++) for (size_t i = 0; i < std::size(s_sdl_button_names); i++)
mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast<SDL_GameControllerButton>(i))); mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast<SDL_GameControllerButton>(i)));
Console.WriteLn("(SDLInputSource) Controller %d has %d axes and %d buttons", player_id, num_axes, num_buttons); INFO_LOG("(SDLInputSource) Controller {} has {} axes and {} buttons", player_id, num_axes, num_buttons);
} }
else else
{ {
@ -726,14 +726,14 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
if (num_hats > 0) if (num_hats > 0)
cd.last_hat_state.resize(static_cast<size_t>(num_hats), u8(0)); cd.last_hat_state.resize(static_cast<size_t>(num_hats), u8(0));
Console.WriteLn("(SDLInputSource) Joystick %d has %d axes, %d buttons and %d hats", player_id, INFO_LOG("(SDLInputSource) Joystick {} has {} axes, {} buttons and {} hats", player_id,
SDL_JoystickNumAxes(joystick), SDL_JoystickNumButtons(joystick), num_hats); SDL_JoystickNumAxes(joystick), SDL_JoystickNumButtons(joystick), num_hats);
} }
cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0); cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0);
if (cd.use_game_controller_rumble) if (cd.use_game_controller_rumble)
{ {
Console.WriteLn("(SDLInputSource) Rumble is supported on '%s' via gamecontroller", name); INFO_LOG("(SDLInputSource) Rumble is supported on '{}' via gamecontroller", name);
} }
else else
{ {
@ -752,25 +752,25 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
} }
else else
{ {
Console.Error("(SDLInputSource) Failed to create haptic left/right effect: %s", SDL_GetError()); ERROR_LOG("(SDLInputSource) Failed to create haptic left/right effect: {}", SDL_GetError());
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0) if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0)
{ {
cd.haptic = haptic; cd.haptic = haptic;
} }
else else
{ {
Console.Error("(SDLInputSource) No haptic rumble supported: %s", SDL_GetError()); ERROR_LOG("(SDLInputSource) No haptic rumble supported: {}", SDL_GetError());
SDL_HapticClose(haptic); SDL_HapticClose(haptic);
} }
} }
} }
if (cd.haptic) if (cd.haptic)
Console.WriteLn("(SDLInputSource) Rumble is supported on '%s' via haptic", name); INFO_LOG("(SDLInputSource) Rumble is supported on '{}' via haptic", name);
} }
if (!cd.haptic && !cd.use_game_controller_rumble) if (!cd.haptic && !cd.use_game_controller_rumble)
Console.Warning("(SDLInputSource) Rumble is not supported on '%s'", name); WARNING_LOG("(SDLInputSource) Rumble is not supported on '{}'", name);
if (player_id >= 0 && static_cast<u32>(player_id) < MAX_LED_COLORS && gcontroller && SDL_GameControllerHasLED(gcontroller)) if (player_id >= 0 && static_cast<u32>(player_id) < MAX_LED_COLORS && gcontroller && SDL_GameControllerHasLED(gcontroller))
{ {
@ -779,7 +779,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
m_controllers.push_back(std::move(cd)); m_controllers.push_back(std::move(cd));
InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name); InputManager::OnInputDeviceConnected(fmt::format("SDL-{}", player_id), name);
return true; return true;
} }
@ -789,7 +789,9 @@ bool SDLInputSource::CloseDevice(int joystick_index)
if (it == m_controllers.end()) if (it == m_controllers.end())
return false; return false;
InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("SDL-%d", it->player_id)); InputManager::OnInputDeviceDisconnected(
{{.source_type = InputSourceType::SDL, .source_index = static_cast<u32>(it->player_id)}},
fmt::format("SDL-{}", it->player_id));
if (it->haptic) if (it->haptic)
SDL_HapticClose(it->haptic); SDL_HapticClose(it->haptic);

View File

@ -226,7 +226,7 @@ void XInputSource::PollEvents()
else else
{ {
if (result != ERROR_DEVICE_NOT_CONNECTED) if (result != ERROR_DEVICE_NOT_CONNECTED)
Console.Warning("XInputGetState(%u) failed: 0x%08X / 0x%08X", i, result, GetLastError()); WARNING_LOG("XInputGetState({}) failed: 0x{:08X} / 0x{:08X}", i, result, GetLastError());
if (was_connected) if (was_connected)
HandleControllerDisconnection(i); HandleControllerDisconnection(i);
@ -424,11 +424,11 @@ bool XInputSource::GetGenericBindingMapping(const std::string_view& device, Inpu
void XInputSource::HandleControllerConnection(u32 index) void XInputSource::HandleControllerConnection(u32 index)
{ {
Console.WriteLn("XInput controller %u connected.", index); INFO_LOG("XInput controller {} connected.", index);
XINPUT_CAPABILITIES caps = {}; XINPUT_CAPABILITIES caps = {};
if (m_xinput_get_capabilities(index, 0, &caps) != ERROR_SUCCESS) if (m_xinput_get_capabilities(index, 0, &caps) != ERROR_SUCCESS)
Console.Warning("Failed to get XInput capabilities for controller %u", index); WARNING_LOG("Failed to get XInput capabilities for controller {}", index);
ControllerData& cd = m_controllers[index]; ControllerData& cd = m_controllers[index];
cd.connected = true; cd.connected = true;
@ -438,13 +438,17 @@ void XInputSource::HandleControllerConnection(u32 index)
cd.last_state_scp = {}; cd.last_state_scp = {};
InputManager::OnInputDeviceConnected( InputManager::OnInputDeviceConnected(
StringUtil::StdStringFromFormat("XInput-%u", index), StringUtil::StdStringFromFormat("XInput Controller %u", index)); fmt::format("XInput-{}", index), fmt::format("XInput Controller {}", index));
} }
void XInputSource::HandleControllerDisconnection(u32 index) void XInputSource::HandleControllerDisconnection(u32 index)
{ {
Console.WriteLn("XInput controller %u disconnected.", index); INFO_LOG("XInput controller {} disconnected.", index);
InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("XInput-%u", index)); InputManager::OnInputDeviceDisconnected({{
.source_type = InputSourceType::XInput,
.source_index = index,
}},
fmt::format("XInput-{}", index));
m_controllers[index] = {}; m_controllers[index] = {};
} }
@ -485,7 +489,7 @@ void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state
{ {
const GenericInputBinding generic_key = (button < std::size(s_xinput_generic_binding_button_mapping)) ? const GenericInputBinding generic_key = (button < std::size(s_xinput_generic_binding_button_mapping)) ?
s_xinput_generic_binding_button_mapping[button] : s_xinput_generic_binding_button_mapping[button] :
GenericInputBinding::Unknown; GenericInputBinding::Unknown;
const float value = ((new_button_bits & button_mask) != 0) ? 1.0f : 0.0f; const float value = ((new_button_bits & button_mask) != 0) ? 1.0f : 0.0f;
InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::XInput, index, button), value, generic_key); InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::XInput, index, button), value, generic_key);
} }

View File

@ -64,11 +64,11 @@ std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
return std::nullopt; return std::nullopt;
} }
void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) void Host::OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name)
{ {
} }
void Host::OnInputDeviceDisconnected(const std::string_view& identifier) void Host::OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier)
{ {
} }