diff options
| author | JosJuice <josjuice@gmail.com> | 2026-10-31 16:17:25 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-10-31 16:17:25 +0100 |
| commit | c4856ead6420cceb48744815d79d6be59febb5ff (patch) | |
| tree | bc662fe345ab37ebc861b1d013da411d06688729 /Source/Android | |
| parent | 78a8ab40c3b28a6d477c9c6b6260dc3993cfe491 (diff) | |
| parent | 9e6659a2d8c890d60be9a3609e4e55d3a75a1216 (diff) | |
Merge pull request #14878 from vhoxha08/add-global-hotkeys-android
Android: Add global hotkeys
Diffstat (limited to 'Source/Android')
11 files changed, 475 insertions, 1 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt index 2761c4b4e9..0145887b78 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt @@ -42,6 +42,7 @@ import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlotAdapter import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener +import org.dolphinemu.dolphinemu.features.input.model.Hotkeys import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting import org.dolphinemu.dolphinemu.utils.ContentHandler import org.dolphinemu.dolphinemu.features.settings.model.IntSetting @@ -319,6 +320,13 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { } DolphinSensorEventListener.setDeviceRotation(windowManager.defaultDisplay.rotation) + + Hotkeys.setEnabled(true) + } + + override fun onPause() { + Hotkeys.setEnabled(false) + super.onPause() } override fun onStop() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/Hotkeys.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/Hotkeys.kt new file mode 100644 index 0000000000..7f0100238b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/Hotkeys.kt @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +object Hotkeys { + /** + * Stops or resumes the native hotkey scheduler acting on presses. + */ + @JvmStatic + external fun setEnabled(enabled: Boolean) +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt index e0938b975d..00c06cb896 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt @@ -56,6 +56,12 @@ class EmulatedController private constructor(private val pointer: Long) : Contro external fun getWiimote(controllerIndex: Int): EmulatedController @JvmStatic + external fun getHotkeys(): EmulatedController + + @JvmStatic + external fun getSupportedHotkeyIndices(): IntArray + + @JvmStatic external fun getWiimoteAttachment( controllerIndex: Int, attachmentIndex: Int diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt index 74556b07d9..3a6089144f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt @@ -26,6 +26,7 @@ enum class MenuTag { STATISTICS("statistics"), ADVANCED_GRAPHICS("advanced_graphics"), GCPAD_TYPE("gc_pad_type"), + HOTKEYS("hotkeys"), WIIMOTE("wiimote"), WIIMOTE_EXTENSION("wiimote_extension"), GCPAD_1("gcpad", 0), @@ -77,7 +78,8 @@ enum class MenuTag { val correspondingEmulatedController: EmulatedController get() = if (isGCPadMenu) EmulatedController.getGcPad(subType) else if (isWiimoteMenu) EmulatedController.getWiimote( subType - ) else throw UnsupportedOperationException() + ) else if (this == HOTKEYS) EmulatedController.getHotkeys() + else throw UnsupportedOperationException() val isSerialPort1Menu: Boolean get() = this == CONFIG_SERIALPORT1 diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 8a46a698ce..e3b47b3786 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -303,6 +303,7 @@ class SettingsFragmentPresenter( ) MenuTag.GCPAD_TYPE -> addGcPadSettings(sl) + MenuTag.HOTKEYS -> addHotkeySettings(sl) MenuTag.WIIMOTE -> addWiimoteSettings(sl) MenuTag.ENHANCEMENTS -> addEnhanceSettings(sl) MenuTag.COLOR_CORRECTION -> addColorCorrectionSettings(sl) @@ -354,6 +355,11 @@ class SettingsFragmentPresenter( sl.add(SubmenuSetting(context, R.string.wiimote_settings, MenuTag.WIIMOTE)) } + // Hotkeys live in Hotkeys.ini and are global, so there is nothing to edit per game. + if (gameId.isNullOrEmpty()) { + sl.add(SubmenuSetting(context, R.string.hotkey_settings, MenuTag.HOTKEYS)) + } + sl.add(HeaderSetting(context, R.string.setting_clear_info, 0)) } @@ -2503,6 +2509,33 @@ class SettingsFragmentPresenter( ) } + /** + * Lists a subset of the hotkeys the native HotkeyScheduler dispatches. + */ + private fun addHotkeySettings(sl: ArrayList<SettingsItem>) { + val hotkeys = EmulatedController.getHotkeys() + + addControllerMetaSettings(sl, hotkeys) + + // Flattened {group index, control index} pairs. Each control group gets a header the first + // time one of its hotkeys shows up. + val indices = EmulatedController.getSupportedHotkeyIndices() + var currentGroupIndex = -1 + var i = 0 + while (i < indices.size) { + val groupIndex = indices[i] + val group = hotkeys.getGroup(groupIndex) + + if (groupIndex != currentGroupIndex) { + sl.add(HeaderSetting(group.getUiName(), "")) + currentGroupIndex = groupIndex + } + + sl.add(InputMappingControlSetting(group.getControl(indices[i + 1]), hotkeys)) + i += 2 + } + } + private fun addGcPadSubSettings( sl: ArrayList<SettingsItem>, gcPadNumber: Int, gcPadType: Int ) { diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 69d8631e7e..6abf81e1cc 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -71,6 +71,7 @@ <string name="graphics_settings">Graphics Settings</string> <string name="gcpad_settings">GameCube Input</string> <string name="wiimote_settings">Wii Input</string> + <string name="hotkey_settings">Hotkeys</string> <!-- General Preference Fragment --> <string name="general_submenu">General</string> diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index a3a741a433..a7ee0b6b3a 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(main SHARED GameList/GameFile.h GameList/GameFileCache.cpp GpuDriver.cpp + HotkeyScheduler.cpp + HotkeyScheduler.h InfinityConfig.cpp Input/Control.cpp Input/Control.h diff --git a/Source/Android/jni/HotkeyScheduler.cpp b/Source/Android/jni/HotkeyScheduler.cpp new file mode 100644 index 0000000000..e4e44415b6 --- /dev/null +++ b/Source/Android/jni/HotkeyScheduler.cpp @@ -0,0 +1,355 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "jni/HotkeyScheduler.h" + +#include <chrono> +#include <cmath> +#include <jni.h> +#include <thread> +#include <vector> + +#include <fmt/format.h> + +#include "AudioCommon/AudioCommon.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/Event.h" +#include "Common/Flag.h" +#include "Common/Thread.h" +#include "Core/Config/GraphicsSettings.h" +#include "Core/Config/MainSettings.h" +#include "Core/Core.h" +#include "Core/HotkeyManager.h" +#include "Core/State.h" +#include "Core/System.h" +#include "InputCommon/ControlReference/ControlReference.h" +#include "InputCommon/ControllerEmu/Control/Control.h" +#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/VideoConfig.h" + +namespace HotkeyScheduler +{ +namespace +{ +constexpr int POLL_INTERVAL_MS = 5; + +std::thread s_thread; +Common::Flag s_stop_requested; +Common::Flag s_any_hotkey_set; +Common::Event s_wakeup; + +bool IsHotkey(int id, bool held = false) +{ + return HotkeyManagerEmu::IsPressed(id, held); +} + +bool IsAnyHotkeySet() +{ + InputConfig* const config = HotkeyManagerEmu::GetConfig(); + if (config == nullptr || config->ControllersNeedToBeCreated()) + return false; + + auto* const hotkeys = static_cast<HotkeyManager*>(config->GetController(0)); + + for (const int hotkey : GetSupportedHotkeys()) + { + const int group = hotkeys->FindGroupByID(hotkey); + const int index = hotkeys->GetIndexForGroup(group, hotkey); + const auto* const control_group = + HotkeyManagerEmu::GetHotkeyGroup(static_cast<HotkeyGroup>(group)); + + if (!control_group->controls[index]->control_ref->GetExpression().empty()) + return true; + } + + return false; +} + +bool ShouldPoll() +{ + if (!s_any_hotkey_set.IsSet() || !HotkeyManagerEmu::IsEnabled()) + return false; + + return Core::GetState(Core::System::GetInstance()) != Core::State::Stopping; +} + +// Buttons already held when polling resumes must not count as new presses +void ConsumePendingPresses() +{ + for (const int hotkey : GetSupportedHotkeys()) + IsHotkey(hotkey); +} + +void ShowVolumeOSD() +{ + OSD::AddMessage( + fmt::format("Volume: {}", Config::Get(Config::MAIN_AUDIO_MUTED) ? + "Muted" : + fmt::format("{}%", Config::Get(Config::MAIN_AUDIO_VOLUME)))); +} + +void ShowEmulationSpeedOSD() +{ + const float emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED); + OSD::AddMessage(emulation_speed <= 0 ? + "Speed Limit: Unlimited" : + fmt::format("Speed Limit: {}%", std::lround(emulation_speed * 100.f))); +} + +void ShowInternalResolutionOSD(int new_efb_scale) +{ + switch (new_efb_scale) + { + case EFB_SCALE_AUTO_INTEGRAL: + OSD::AddMessage("Internal Resolution: Auto (integral)"); + break; + case 1: + OSD::AddMessage("Internal Resolution: Native"); + break; + default: + OSD::AddMessage(fmt::format("Internal Resolution: {}x", new_efb_scale)); + break; + } +} + +void HandleGeneralHotkeys() +{ + // TODO: add support for more general hotkeys + if (IsHotkey(HK_SCREENSHOT)) + Core::SaveScreenShot(); +} + +void HandleEmulationSpeedHotkeys() +{ + if (IsHotkey(HK_TOGGLE_THROTTLE)) + Core::SetIsThrottlerTempDisabled(!Core::GetIsThrottlerTempDisabled()); + + if (IsHotkey(HK_DECREASE_EMULATION_SPEED)) + { + auto speed = Config::Get(Config::MAIN_EMULATION_SPEED) - 0.1; + if (speed > 0) + { + speed = (speed >= 0.95 && speed <= 1.05) ? 1.0 : speed; + Config::SetCurrent(Config::MAIN_EMULATION_SPEED, speed); + } + ShowEmulationSpeedOSD(); + } + + if (IsHotkey(HK_INCREASE_EMULATION_SPEED)) + { + auto speed = Config::Get(Config::MAIN_EMULATION_SPEED) + 0.1; + speed = (speed >= 0.95 && speed <= 1.05) ? 1.0 : speed; + Config::SetCurrent(Config::MAIN_EMULATION_SPEED, speed); + ShowEmulationSpeedOSD(); + } +} + +void HandleVolumeHotkeys(Core::System& system) +{ + if (IsHotkey(HK_VOLUME_DOWN)) + { + AudioCommon::DecreaseVolume(system, 3); + ShowVolumeOSD(); + } + + if (IsHotkey(HK_VOLUME_UP)) + { + AudioCommon::IncreaseVolume(system, 3); + ShowVolumeOSD(); + } + + if (IsHotkey(HK_VOLUME_TOGGLE_MUTE)) + { + AudioCommon::ToggleMuteVolume(system); + ShowVolumeOSD(); + } +} + +void HandleGraphicsHotkeys() +{ + const auto efb_scale = Config::Get(Config::GFX_EFB_SCALE); + + if (IsHotkey(HK_INCREASE_IR)) + { + Config::SetCurrent(Config::GFX_EFB_SCALE, efb_scale + 1); + ShowInternalResolutionOSD(efb_scale + 1); + } + if (IsHotkey(HK_DECREASE_IR)) + { + if (efb_scale > EFB_SCALE_AUTO_INTEGRAL) + { + Config::SetCurrent(Config::GFX_EFB_SCALE, efb_scale - 1); + ShowInternalResolutionOSD(efb_scale - 1); + } + } +} + +void HandleStateHotkeys(Core::System& system) +{ + for (u32 i = 0; i < State::NUM_STATES; ++i) + { + if (IsHotkey(HK_LOAD_STATE_SLOT_1 + i)) + State::Load(system, i + 1); + + if (IsHotkey(HK_SAVE_STATE_SLOT_1 + i)) + State::Save(system, i + 1); + + if (IsHotkey(HK_LOAD_LAST_STATE_1 + i)) + State::LoadLastSaved(system, i + 1); + + if (IsHotkey(HK_SELECT_STATE_SLOT_1 + i)) + State::SelectSlot(i + 1); + } + + if (IsHotkey(HK_SAVE_STATE_SLOT_SELECTED)) + State::SaveSelected(system); + + if (IsHotkey(HK_LOAD_STATE_SLOT_SELECTED)) + State::LoadSelected(system); + + if (IsHotkey(HK_INCREMENT_SELECTED_STATE_SLOT)) + State::SelectSlot(State::GetNextSlot()); + + if (IsHotkey(HK_DECREMENT_SELECTED_STATE_SLOT)) + State::SelectSlot(State::GetPreviousSlot()); + + if (IsHotkey(HK_SAVE_FIRST_STATE)) + State::SaveFirstSaved(system); + + if (IsHotkey(HK_UNDO_LOAD_STATE)) + State::UndoLoadState(system); + + if (IsHotkey(HK_UNDO_SAVE_STATE)) + State::UndoSaveState(system); +} + +void Run() +{ + Common::SetCurrentThreadName("HotkeyScheduler"); + + bool was_polling = false; + + while (!s_stop_requested.IsSet()) + { + if (!ShouldPoll()) + { + was_polling = false; + s_wakeup.Wait(); + continue; + } + + s_wakeup.WaitFor(std::chrono::milliseconds(POLL_INTERVAL_MS)); + + if (s_stop_requested.IsSet()) + break; + + ControllerInterface::SetCurrentInputChannel(ciface::InputChannel::Host); + g_controller_interface.UpdateInput(); + + ControlReference::SetInputGate(true); + + // These two cover disjoint sets of groups, see HotkeyManager::GetInput. + // Skipping either one leaves half the groups reading as unpressed. + HotkeyManagerEmu::GetStatus(false); + HotkeyManagerEmu::GetStatus(true); + + if (!was_polling) + { + ConsumePendingPresses(); + was_polling = true; + continue; + } + + Core::System& system = Core::System::GetInstance(); + + HandleGeneralHotkeys(); + HandleEmulationSpeedHotkeys(); + HandleVolumeHotkeys(system); + HandleGraphicsHotkeys(); + HandleStateHotkeys(system); + } +} +} // namespace + +void Start() +{ + if (s_thread.joinable()) + return; + + State::SetSelectedSlot(1); + s_any_hotkey_set.Set(IsAnyHotkeySet()); + s_stop_requested.Clear(); + s_wakeup.Reset(); + s_thread = std::thread(Run); +} + +void Stop() +{ + if (!s_thread.joinable()) + return; + + s_stop_requested.Set(); + s_wakeup.Set(); + s_thread.join(); + + Core::SetIsThrottlerTempDisabled(false); +} + +void RefreshActiveHotkeys() +{ + s_any_hotkey_set.Set(IsAnyHotkeySet()); + s_wakeup.Set(); +} + +const std::vector<int>& GetSupportedHotkeys() +{ + static const std::vector<int> supported = [] { + std::vector<int> hotkeys{ + // General - only the one entry, see HandleGeneralHotkeys + HK_SCREENSHOT, + // Emulation Speed + HK_TOGGLE_THROTTLE, + HK_DECREASE_EMULATION_SPEED, + HK_INCREASE_EMULATION_SPEED, + // Volume + HK_VOLUME_DOWN, + HK_VOLUME_UP, + HK_VOLUME_TOGGLE_MUTE, + // Internal Resolution + HK_INCREASE_IR, + HK_DECREASE_IR, + }; + + hotkeys.insert(hotkeys.end(), + {HK_SAVE_STATE_SLOT_SELECTED, HK_LOAD_STATE_SLOT_SELECTED, + HK_INCREMENT_SELECTED_STATE_SLOT, HK_DECREMENT_SELECTED_STATE_SLOT, + HK_SAVE_FIRST_STATE, HK_UNDO_LOAD_STATE, HK_UNDO_SAVE_STATE}); + + for (u32 i = 0; i < State::NUM_STATES; ++i) + hotkeys.push_back(HK_SAVE_STATE_SLOT_1 + i); + for (u32 i = 0; i < State::NUM_STATES; ++i) + hotkeys.push_back(HK_LOAD_STATE_SLOT_1 + i); + for (u32 i = 0; i < State::NUM_STATES; ++i) + hotkeys.push_back(HK_SELECT_STATE_SLOT_1 + i); + for (u32 i = 0; i < State::NUM_STATES; ++i) + hotkeys.push_back(HK_LOAD_LAST_STATE_1 + i); + + return hotkeys; + }(); + + return supported; +} +} // namespace HotkeyScheduler + +extern "C" { + +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_input_model_Hotkeys_setEnabled( + JNIEnv*, jclass, jboolean enabled) +{ + HotkeyManagerEmu::Enable(enabled == JNI_TRUE); + HotkeyScheduler::RefreshActiveHotkeys(); +} +} diff --git a/Source/Android/jni/HotkeyScheduler.h b/Source/Android/jni/HotkeyScheduler.h new file mode 100644 index 0000000000..d98d637991 --- /dev/null +++ b/Source/Android/jni/HotkeyScheduler.h @@ -0,0 +1,17 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <vector> + +namespace HotkeyScheduler +{ +void Start(); + +void Stop(); + +void RefreshActiveHotkeys(); + +const std::vector<int>& GetSupportedHotkeys(); +} // namespace HotkeyScheduler diff --git a/Source/Android/jni/Input/EmulatedController.cpp b/Source/Android/jni/Input/EmulatedController.cpp index 495a376f69..341e35d168 100644 --- a/Source/Android/jni/Input/EmulatedController.cpp +++ b/Source/Android/jni/Input/EmulatedController.cpp @@ -3,18 +3,22 @@ #include <jni.h> +#include <vector> + #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Core/HW/GCKeyboard.h" #include "Core/HW/GCPad.h" #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" +#include "Core/HotkeyManager.h" #include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/InputConfig.h" #include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" +#include "jni/HotkeyScheduler.h" #include "jni/Input/Control.h" #include "jni/Input/ControlGroup.h" #include "jni/Input/ControlReference.h" @@ -192,6 +196,36 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedContro } JNIEXPORT jobject JNICALL +Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getHotkeys( + JNIEnv* env, jclass) +{ + return EmulatedControllerToJava(env, HotkeyManagerEmu::GetConfig()->GetController(0)); +} + +JNIEXPORT jintArray JNICALL +Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getSupportedHotkeyIndices( + JNIEnv* env, jclass) +{ + auto* const hotkeys = + static_cast<HotkeyManager*>(HotkeyManagerEmu::GetConfig()->GetController(0)); + const std::vector<int>& supported = HotkeyScheduler::GetSupportedHotkeys(); + + std::vector<jint> indices; + indices.reserve(supported.size() * 2); + for (const int hotkey : supported) + { + const int group = hotkeys->FindGroupByID(hotkey); + indices.push_back(group); + indices.push_back(hotkeys->GetIndexForGroup(group, hotkey)); + } + + const jsize size = static_cast<jsize>(indices.size()); + jintArray result = env->NewIntArray(size); + env->SetIntArrayRegion(result, 0, size, indices.data()); + return result; +} + +JNIEXPORT jobject JNICALL Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedController_getWiimoteAttachment( JNIEnv* env, jclass, jint controller_index, jint attachment_index) { diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 4806c9a2ff..1cd20f0818 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -67,6 +67,7 @@ #include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" +#include "jni/HotkeyScheduler.h" namespace { @@ -584,12 +585,16 @@ static void Run(JNIEnv* env, std::unique_ptr<BootParameters>&& boot, bool riivol s_need_nonblocking_alert_msg = false; surface_guard.unlock(); + HotkeyScheduler::Start(); + while (Core::IsRunning(Core::System::GetInstance())) { s_update_main_frame_event.Wait(); Core::HostDispatchJobs(Core::System::GetInstance()); } + HotkeyScheduler::Stop(); + s_game_metadata_is_valid = false; Core::Shutdown(Core::System::GetInstance()); |
