From 98c229ef79e1fa8465cea7f31bbaa39c7a722f4f Mon Sep 17 00:00:00 2001 From: visarhoxha Date: Fri, 18 Sep 2026 20:29:36 +0200 Subject: android: add global hotkeys --- .../dolphinemu/features/input/model/Hotkeys.kt | 11 + .../model/controlleremu/EmulatedController.kt | 6 + .../dolphinemu/features/settings/ui/MenuTag.kt | 4 +- .../features/settings/ui/SettingsActivity.kt | 5 + .../settings/ui/SettingsFragmentPresenter.kt | 33 +++ Source/Android/app/src/main/res/values/strings.xml | 1 + Source/Android/jni/CMakeLists.txt | 2 + Source/Android/jni/HotkeyScheduler.cpp | 298 +++++++++++++++++++++ Source/Android/jni/HotkeyScheduler.h | 14 + Source/Android/jni/Input/EmulatedController.cpp | 34 +++ Source/Android/jni/MainAndroid.cpp | 5 + 11 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/Hotkeys.kt create mode 100644 Source/Android/jni/HotkeyScheduler.cpp create mode 100644 Source/Android/jni/HotkeyScheduler.h (limited to 'Source/Android') 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 @@ -55,6 +55,12 @@ class EmulatedController private constructor(private val pointer: Long) : Contro @JvmStatic external fun getWiimote(controllerIndex: Int): EmulatedController + @JvmStatic + external fun getHotkeys(): EmulatedController + + @JvmStatic + external fun getSupportedHotkeyIndices(): IntArray + @JvmStatic external fun getWiimoteAttachment( controllerIndex: 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/SettingsActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt index 19c08b9a05..6a99d1365c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt @@ -27,6 +27,7 @@ import org.dolphinemu.dolphinemu.NativeLibrary import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.databinding.ActivitySettingsBinding import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface +import org.dolphinemu.dolphinemu.features.input.model.Hotkeys import org.dolphinemu.dolphinemu.features.settings.model.Settings import org.dolphinemu.dolphinemu.features.settings.ui.SettingsFragment.Companion.newInstance import org.dolphinemu.dolphinemu.ui.main.MainPresenter @@ -180,6 +181,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide override fun onStart() { super.onStart() presenter!!.onStart() + + Hotkeys.setEnabled(false) } override fun onResume() { @@ -199,6 +202,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide override fun onStop() { super.onStop() presenter!!.onStop(isFinishing) + + Hotkeys.setEnabled(true) } override fun onDestroy() { 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 210a34b23f..0bc651fe3c 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) { + 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, 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 2204f4380f..af963e0e78 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 @@ Graphics Settings GameCube Input Wii Input + Hotkeys General 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..bb360d1e2a --- /dev/null +++ b/Source/Android/jni/HotkeyScheduler.cpp @@ -0,0 +1,298 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "jni/HotkeyScheduler.h" + +#include +#include +#include +#include + +#include + +#include "AudioCommon/AudioCommon.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.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/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; + +int s_state_slot = 1; + +bool IsHotkey(int id, bool held = false) +{ + return HotkeyManagerEmu::IsPressed(id, held); +} + +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 SetStateSlot(int slot) +{ + s_state_slot = slot; + Core::DisplayMessage( + fmt::format("Selected slot {} - {}", slot, State::GetInfoStringOfSlot(slot, false)), 2500); +} + +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)) + SetStateSlot(i + 1); + } + + if (IsHotkey(HK_SAVE_STATE_SLOT_SELECTED)) + State::Save(system, s_state_slot); + + if (IsHotkey(HK_LOAD_STATE_SLOT_SELECTED)) + State::Load(system, s_state_slot); + + if (IsHotkey(HK_INCREMENT_SELECTED_STATE_SLOT)) + SetStateSlot(s_state_slot + 1 > static_cast(State::NUM_STATES) ? 1 : s_state_slot + 1); + + if (IsHotkey(HK_DECREMENT_SELECTED_STATE_SLOT)) + SetStateSlot(s_state_slot - 1 < 1 ? static_cast(State::NUM_STATES) : s_state_slot - 1); + + 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"); + + while (!s_stop_requested.IsSet()) + { + Common::SleepCurrentThread(POLL_INTERVAL_MS); + + ControllerInterface::SetCurrentInputChannel(ciface::InputChannel::Host); + g_controller_interface.UpdateInput(); + + if (!HotkeyManagerEmu::IsEnabled()) + continue; + + Core::System& system = Core::System::GetInstance(); + if (Core::GetState(system) == Core::State::Stopping) + continue; + + 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); + + HandleGeneralHotkeys(); + HandleEmulationSpeedHotkeys(); + HandleVolumeHotkeys(system); + HandleGraphicsHotkeys(); + HandleStateHotkeys(system); + } +} +} // namespace + +void Start() +{ + if (s_thread.joinable()) + return; + + HotkeyManagerEmu::Enable(true); + s_state_slot = 1; + s_stop_requested.Clear(); + s_thread = std::thread(Run); +} + +void Stop() +{ + if (!s_thread.joinable()) + return; + + s_stop_requested.Set(); + s_thread.join(); + + Core::SetIsThrottlerTempDisabled(false); +} + +const std::vector& GetSupportedHotkeys() +{ + static const std::vector supported = [] { + std::vector 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); +} +} diff --git a/Source/Android/jni/HotkeyScheduler.h b/Source/Android/jni/HotkeyScheduler.h new file mode 100644 index 0000000000..c904139d6b --- /dev/null +++ b/Source/Android/jni/HotkeyScheduler.h @@ -0,0 +1,14 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +namespace HotkeyScheduler { + void Start(); + + void Stop(); + + const std::vector &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 +#include + #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" @@ -191,6 +195,36 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_EmulatedContro return EmulatedControllerToJava(env, Wiimote::GetConfig()->GetController(controller_index)); } +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(HotkeyManagerEmu::GetConfig()->GetController(0)); + const std::vector& supported = HotkeyScheduler::GetSupportedHotkeys(); + + std::vector 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(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 bbf65230f2..3cdd97f5ee 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -66,6 +66,7 @@ #include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" +#include "jni/HotkeyScheduler.h" namespace { @@ -578,12 +579,16 @@ static void Run(JNIEnv* env, std::unique_ptr&& 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()); -- cgit v1.2.3 From f0ef80016182ef0a79fd8b100b35ed906f954044 Mon Sep 17 00:00:00 2001 From: visarhoxha Date: Thu, 24 Sep 2026 00:30:54 +0200 Subject: Android: Avoid constant hotkey polling to save battery Only poll for hotkeys while EmulationActivity is resumed and at least one supported hotkey is mapped. Otherwise the scheduler thread waits on an event instead of polling. --- .../dolphinemu/activities/EmulationActivity.kt | 8 +++ .../features/settings/ui/SettingsActivity.kt | 5 -- Source/Android/jni/HotkeyScheduler.cpp | 84 +++++++++++++++++++--- Source/Android/jni/HotkeyScheduler.h | 11 +-- 4 files changed, 90 insertions(+), 18 deletions(-) (limited to 'Source/Android') 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 23ffe96cd6..0678c97724 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/settings/ui/SettingsActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt index 6a99d1365c..19c08b9a05 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt @@ -27,7 +27,6 @@ import org.dolphinemu.dolphinemu.NativeLibrary import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.databinding.ActivitySettingsBinding import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface -import org.dolphinemu.dolphinemu.features.input.model.Hotkeys import org.dolphinemu.dolphinemu.features.settings.model.Settings import org.dolphinemu.dolphinemu.features.settings.ui.SettingsFragment.Companion.newInstance import org.dolphinemu.dolphinemu.ui.main.MainPresenter @@ -181,8 +180,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide override fun onStart() { super.onStart() presenter!!.onStart() - - Hotkeys.setEnabled(false) } override fun onResume() { @@ -202,8 +199,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView, ThemeProvide override fun onStop() { super.onStop() presenter!!.onStop(isFinishing) - - Hotkeys.setEnabled(true) } override fun onDestroy() { diff --git a/Source/Android/jni/HotkeyScheduler.cpp b/Source/Android/jni/HotkeyScheduler.cpp index bb360d1e2a..85d981f755 100644 --- a/Source/Android/jni/HotkeyScheduler.cpp +++ b/Source/Android/jni/HotkeyScheduler.cpp @@ -3,6 +3,7 @@ #include "jni/HotkeyScheduler.h" +#include #include #include #include @@ -13,6 +14,7 @@ #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" @@ -22,6 +24,8 @@ #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" @@ -34,6 +38,8 @@ 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; int s_state_slot = 1; @@ -42,6 +48,43 @@ 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(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(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( @@ -196,19 +239,24 @@ void Run() { Common::SetCurrentThreadName("HotkeyScheduler"); + bool was_polling = false; + while (!s_stop_requested.IsSet()) { - Common::SleepCurrentThread(POLL_INTERVAL_MS); + if (!ShouldPoll()) + { + was_polling = false; + s_wakeup.Wait(); + continue; + } - ControllerInterface::SetCurrentInputChannel(ciface::InputChannel::Host); - g_controller_interface.UpdateInput(); + s_wakeup.WaitFor(std::chrono::milliseconds(POLL_INTERVAL_MS)); - if (!HotkeyManagerEmu::IsEnabled()) - continue; + if (s_stop_requested.IsSet()) + break; - Core::System& system = Core::System::GetInstance(); - if (Core::GetState(system) == Core::State::Stopping) - continue; + ControllerInterface::SetCurrentInputChannel(ciface::InputChannel::Host); + g_controller_interface.UpdateInput(); ControlReference::SetInputGate(true); @@ -217,6 +265,15 @@ void Run() HotkeyManagerEmu::GetStatus(false); HotkeyManagerEmu::GetStatus(true); + if (!was_polling) + { + ConsumePendingPresses(); + was_polling = true; + continue; + } + + Core::System& system = Core::System::GetInstance(); + HandleGeneralHotkeys(); HandleEmulationSpeedHotkeys(); HandleVolumeHotkeys(system); @@ -231,9 +288,10 @@ void Start() if (s_thread.joinable()) return; - HotkeyManagerEmu::Enable(true); s_state_slot = 1; + s_any_hotkey_set.Set(IsAnyHotkeySet()); s_stop_requested.Clear(); + s_wakeup.Reset(); s_thread = std::thread(Run); } @@ -243,11 +301,18 @@ void Stop() 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& GetSupportedHotkeys() { static const std::vector supported = [] { @@ -294,5 +359,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_input_model_Hotke 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 index c904139d6b..d98d637991 100644 --- a/Source/Android/jni/HotkeyScheduler.h +++ b/Source/Android/jni/HotkeyScheduler.h @@ -5,10 +5,13 @@ #include -namespace HotkeyScheduler { - void Start(); +namespace HotkeyScheduler +{ +void Start(); - void Stop(); +void Stop(); - const std::vector &GetSupportedHotkeys(); +void RefreshActiveHotkeys(); + +const std::vector& GetSupportedHotkeys(); } // namespace HotkeyScheduler -- cgit v1.2.3 From 9e6659a2d8c890d60be9a3609e4e55d3a75a1216 Mon Sep 17 00:00:00 2001 From: visarhoxha Date: Thu, 24 Sep 2026 20:13:08 +0200 Subject: State: Move logic of selected state to Core/State --- Source/Android/jni/HotkeyScheduler.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'Source/Android') diff --git a/Source/Android/jni/HotkeyScheduler.cpp b/Source/Android/jni/HotkeyScheduler.cpp index 85d981f755..e4e44415b6 100644 --- a/Source/Android/jni/HotkeyScheduler.cpp +++ b/Source/Android/jni/HotkeyScheduler.cpp @@ -41,8 +41,6 @@ Common::Flag s_stop_requested; Common::Flag s_any_hotkey_set; Common::Event s_wakeup; -int s_state_slot = 1; - bool IsHotkey(int id, bool held = false) { return HotkeyManagerEmu::IsPressed(id, held); @@ -117,13 +115,6 @@ void ShowInternalResolutionOSD(int new_efb_scale) } } -void SetStateSlot(int slot) -{ - s_state_slot = slot; - Core::DisplayMessage( - fmt::format("Selected slot {} - {}", slot, State::GetInfoStringOfSlot(slot, false)), 2500); -} - void HandleGeneralHotkeys() { // TODO: add support for more general hotkeys @@ -210,20 +201,20 @@ void HandleStateHotkeys(Core::System& system) State::LoadLastSaved(system, i + 1); if (IsHotkey(HK_SELECT_STATE_SLOT_1 + i)) - SetStateSlot(i + 1); + State::SelectSlot(i + 1); } if (IsHotkey(HK_SAVE_STATE_SLOT_SELECTED)) - State::Save(system, s_state_slot); + State::SaveSelected(system); if (IsHotkey(HK_LOAD_STATE_SLOT_SELECTED)) - State::Load(system, s_state_slot); + State::LoadSelected(system); if (IsHotkey(HK_INCREMENT_SELECTED_STATE_SLOT)) - SetStateSlot(s_state_slot + 1 > static_cast(State::NUM_STATES) ? 1 : s_state_slot + 1); + State::SelectSlot(State::GetNextSlot()); if (IsHotkey(HK_DECREMENT_SELECTED_STATE_SLOT)) - SetStateSlot(s_state_slot - 1 < 1 ? static_cast(State::NUM_STATES) : s_state_slot - 1); + State::SelectSlot(State::GetPreviousSlot()); if (IsHotkey(HK_SAVE_FIRST_STATE)) State::SaveFirstSaved(system); @@ -288,7 +279,7 @@ void Start() if (s_thread.joinable()) return; - s_state_slot = 1; + State::SetSelectedSlot(1); s_any_hotkey_set.Set(IsAnyHotkeySet()); s_stop_requested.Clear(); s_wakeup.Reset(); -- cgit v1.2.3