summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorvisarhoxha <vhoxha08@gmail.com>2026-09-18 20:29:36 +0200
committervisarhoxha <vhoxha08@gmail.com>2026-09-18 20:29:36 +0200
commit98c229ef79e1fa8465cea7f31bbaa39c7a722f4f (patch)
tree53d0374bbf6c64c09bbc5048124c33e1d81c7740 /Source
parentc185d27ede09771fe93a3b520c576f646f937ed9 (diff)
android: add global hotkeys
Diffstat (limited to 'Source')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/Hotkeys.kt11
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt4
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsActivity.kt5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt33
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml1
-rw-r--r--Source/Android/jni/CMakeLists.txt2
-rw-r--r--Source/Android/jni/HotkeyScheduler.cpp298
-rw-r--r--Source/Android/jni/HotkeyScheduler.h14
-rw-r--r--Source/Android/jni/Input/EmulatedController.cpp34
-rw-r--r--Source/Android/jni/MainAndroid.cpp5
11 files changed, 412 insertions, 1 deletions
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/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<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 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 @@
<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..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 <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/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<int>(State::NUM_STATES) ? 1 : s_state_slot + 1);
+
+ if (IsHotkey(HK_DECREMENT_SELECTED_STATE_SLOT))
+ SetStateSlot(s_state_slot - 1 < 1 ? static_cast<int>(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<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);
+}
+}
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 <vector>
+
+namespace HotkeyScheduler {
+ void Start();
+
+ void Stop();
+
+ 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 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<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());