summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerInterface
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2021-12-31 16:14:15 +0100
committerJosJuice <josjuice@gmail.com>2023-03-03 22:28:23 +0100
commit792cb6219533bd9ce5154da9a87f6eabddfaa977 (patch)
tree3dfb844880b0abeb5971496dbd87cdee46f88539 /Source/Core/InputCommon/ControllerInterface
parent68ebb5c33e49aec1b7a49f1b1de94ef2291d319c (diff)
ControllerInterface/Android: Implement device population
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface')
-rw-r--r--Source/Core/InputCommon/ControllerInterface/Android/Android.cpp603
-rw-r--r--Source/Core/InputCommon/ControllerInterface/Android/Android.h6
-rw-r--r--Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp4
3 files changed, 609 insertions, 4 deletions
diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp
index d59dc03588..681641c2e3 100644
--- a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp
+++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp
@@ -1,13 +1,614 @@
-// Copyright 2013 Dolphin Emulator Project
+// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "InputCommon/ControllerInterface/Android/Android.h"
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include <fmt/format.h>
+
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <jni.h>
+
+#include "Common/Logging/Log.h"
+
#include "InputCommon/ControllerInterface/ControllerInterface.h"
+#include "jni/AndroidCommon/AndroidCommon.h"
+#include "jni/AndroidCommon/IDCache.h"
+
+namespace
+{
+jclass s_list_class;
+jmethodID s_list_get;
+jmethodID s_list_size;
+
+jclass s_input_device_class;
+jmethodID s_input_device_get_device_ids;
+jmethodID s_input_device_get_device;
+jmethodID s_input_device_get_controller_number;
+jmethodID s_input_device_get_motion_ranges;
+jmethodID s_input_device_get_name;
+jmethodID s_input_device_get_sources;
+jmethodID s_input_device_has_keys;
+
+jclass s_motion_range_class;
+jmethodID s_motion_range_get_axis;
+jmethodID s_motion_range_get_max;
+jmethodID s_motion_range_get_min;
+jmethodID s_motion_range_get_source;
+
+jintArray s_keycodes_array;
+
+constexpr int MAX_KEYCODE = AKEYCODE_PROFILE_SWITCH; // Up to date as of SDK 31
+
+const std::array<std::string_view, MAX_KEYCODE + 1> KEYCODE_NAMES = {
+ "Unknown",
+ "Soft Left",
+ "Soft Right",
+ "Home",
+ "Back",
+ "Call",
+ "End Call",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "Star",
+ "Pound",
+ "Up",
+ "Down",
+ "Left",
+ "Right",
+ "Center",
+ "Volume Up",
+ "Volume Down",
+ "Power",
+ "Camera",
+ "Clear",
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "N",
+ "O",
+ "P",
+ "Q",
+ "R",
+ "S",
+ "T",
+ "U",
+ "V",
+ "W",
+ "X",
+ "Y",
+ "Z",
+ "Comma",
+ "Period",
+ "Left Alt",
+ "Right Alt",
+ "Left Shift",
+ "Right Shift",
+ "Tab",
+ "Space",
+ "Sym",
+ "Explorer",
+ "Envelope",
+ "Enter",
+ "Backspace",
+ "Grave",
+ "Minus",
+ "Equals",
+ "Left Bracket",
+ "Right Bracket",
+ "Backslash",
+ "Semicolon",
+ "Apostrophe",
+ "Slash",
+ "At",
+ "Num",
+ "Headset Hook",
+ "Focus",
+ "Plus",
+ "Menu",
+ "Notification",
+ "Search",
+ "Play Pause",
+ "Stop",
+ "Next",
+ "Previous",
+ "Rewind",
+ "Fast Forward",
+ "Mute",
+ "Page Up",
+ "Page Down",
+ "Emoji",
+ "Switch Charset",
+ "Button A",
+ "Button B",
+ "Button C",
+ "Button X",
+ "Button Y",
+ "Button Z",
+ "Button L1",
+ "Button R1",
+ "Button L2",
+ "Button R2",
+ "Button L3",
+ "Button R3",
+ "Start",
+ "Select",
+ "Mode",
+ "Escape",
+ "Delete",
+ "Left Ctrl",
+ "Right Ctrl",
+ "Caps Lock",
+ "Scroll Lock",
+ "Left Meta",
+ "Right Meta",
+ "Fn",
+ "PrtSc SysRq",
+ "Pause Break",
+ "Move Home",
+ "Move End",
+ "Insert",
+ "Forward",
+ "Play",
+ "Pause",
+ "Close",
+ "Eject",
+ "Record",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+ "Num Lock",
+ "Numpad 0",
+ "Numpad 1",
+ "Numpad 2",
+ "Numpad 3",
+ "Numpad 4",
+ "Numpad 5",
+ "Numpad 6",
+ "Numpad 7",
+ "Numpad 8",
+ "Numpad 9",
+ "Numpad Divide",
+ "Numpad Multiply",
+ "Numpad Subtract",
+ "Numpad Add",
+ "Numpad Dot",
+ "Numpad Comma",
+ "Numpad Enter",
+ "Numpad Equals",
+ "Numpad Left Paren",
+ "Numpad Right Paren",
+ "Volume Mute",
+ "Info",
+ "Channel Up",
+ "Channel Down",
+ "Zoom In",
+ "Zoom Out",
+ "TV",
+ "Window",
+ "Guide",
+ "DVR",
+ "Bookmark",
+ "Captions",
+ "Settings",
+ "TV Power",
+ "TV Input",
+ "STB Power",
+ "STB Input",
+ "AVR Power",
+ "AVR Input",
+ "Prog Red",
+ "Prog Green",
+ "Prog Yellow",
+ "Prog Blue",
+ "App Switch",
+ "Button 1",
+ "Button 2",
+ "Button 3",
+ "Button 4",
+ "Button 5",
+ "Button 6",
+ "Button 7",
+ "Button 8",
+ "Button 9",
+ "Button 10",
+ "Button 11",
+ "Button 12",
+ "Button 13",
+ "Button 14",
+ "Button 15",
+ "Button 16",
+ "Language Switch",
+ "Manner Mode",
+ "3D Mode",
+ "Contacts",
+ "Calendar",
+ "Music",
+ "Calculator",
+ "Zenkaku Hankaku",
+ "Eisu",
+ "Henkan",
+ "Muhenkan",
+ "Katakana Hiragana",
+ "Yen",
+ "Ro",
+ "Kana",
+ "Assist",
+ "Brightness Down",
+ "Brightness Up",
+ "Audio Track",
+ "Sleep",
+ "Wakeup",
+ "Pairing",
+ "Top Menu",
+ "11",
+ "12",
+ "Last Channel",
+ "Data Service",
+ "Voice Assist",
+ "Radio Service",
+ "Teletext",
+ "Number Entry",
+ "Terrestrial Analog",
+ "Terrestrial Digital",
+ "Satellite",
+ "Satellite BS",
+ "Satellite CS",
+ "Satellite Service",
+ "Network",
+ "Antenna Cable",
+ "Input HDMI 1",
+ "Input HDMI 2",
+ "Input HDMI 3",
+ "Input HDMI 4",
+ "Input Composite 1",
+ "Input Composite 2",
+ "Input Component 1",
+ "Input Component 2",
+ "Input VGA 1",
+ "Audio Description",
+ "Audio Description Mix Up",
+ "Audio Description Mix Down",
+ "Zoom Mode",
+ "Contents Menu",
+ "Media Context Menu",
+ "Timer Programming",
+ "Help",
+ "Navigate Previous",
+ "Navigate Next",
+ "Navigate In",
+ "Navigate Out",
+ "Stem Primary",
+ "Stem 1",
+ "Stem 2",
+ "Stem 3",
+ "Up Left",
+ "Down Left",
+ "Up Right",
+ "Down Right",
+ "Skip Forward",
+ "Skip Backward",
+ "Step Forward",
+ "Step Backward",
+ "Soft Sleep",
+ "Cut",
+ "Copy",
+ "Paste",
+ "System Navigation Up",
+ "System Navigation Down",
+ "System Navigation Left",
+ "System Navigation Right",
+ "All Apps",
+ "Refresh",
+ "Thumbs Up",
+ "Thumbs Down",
+ "Profile Switch",
+};
+
+std::string ConstructKeyName(int keycode)
+{
+ return std::string(KEYCODE_NAMES[keycode]);
+}
+
+std::string ConstructAxisNamePrefix(int source)
+{
+ // A device is allowed to have two axes with the same axis ID but different source IDs,
+ // so we have to make sure to include the source in the axis name.
+
+ static const std::unordered_map<int, std::string> source_names{
+ {AINPUT_SOURCE_KEYBOARD, "Keyboard"},
+ {AINPUT_SOURCE_DPAD, "Dpad"},
+ {AINPUT_SOURCE_GAMEPAD, "Gamepad"},
+ {AINPUT_SOURCE_TOUCHSCREEN, "Touch"},
+ {AINPUT_SOURCE_MOUSE, "Cursor"},
+ {AINPUT_SOURCE_STYLUS, "Stylus"},
+ {AINPUT_SOURCE_BLUETOOTH_STYLUS, "BTStylus"},
+ {AINPUT_SOURCE_TRACKBALL, "Trackball"},
+ {AINPUT_SOURCE_MOUSE_RELATIVE, "Mouse"},
+ {AINPUT_SOURCE_TOUCHPAD, "Touchpad"},
+ {AINPUT_SOURCE_TOUCH_NAVIGATION, "Touchnav"},
+ {AINPUT_SOURCE_JOYSTICK, "Axis"}, // The typical source for all axes on a gamepad
+ {AINPUT_SOURCE_HDMI, "HDMI"},
+ {AINPUT_SOURCE_SENSOR, "Sensor"},
+ {AINPUT_SOURCE_ROTARY_ENCODER, "Rotary"},
+ };
+
+ const auto it = source_names.find(source);
+ if (it != source_names.end())
+ return fmt::format("{} ", it->second);
+ else
+ return fmt::format("Axis {:08x}/", source);
+}
+
+std::string ConstructAxisName(int source, int axis, bool negative)
+{
+ const char sign = negative ? '-' : '+';
+ return fmt::format("{}{}{}", ConstructAxisNamePrefix(source), axis, sign);
+}
+
+} // namespace
+
namespace ciface::Android
{
+class AndroidKey final : public Core::Device::Input
+{
+public:
+ explicit AndroidKey(int keycode) : m_name(ConstructKeyName(keycode))
+ {
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Created {}", m_name);
+ }
+
+ std::string GetName() const override { return m_name; }
+
+ ControlState GetState() const override
+ {
+ return 0; // TODO
+ }
+
+private:
+ std::string m_name;
+};
+
+class AndroidAxis final : public Core::Device::Input
+{
+public:
+ AndroidAxis(int source, int axis, bool negative)
+ : m_name(ConstructAxisName(source, axis, negative))
+ {
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Created {}", m_name);
+ }
+
+ std::string GetName() const override { return m_name; };
+
+ ControlState GetState() const override
+ {
+ return 0; // TODO
+ };
+
+private:
+ std::string m_name;
+};
+
+class AndroidDevice final : public Core::Device
+{
+public:
+ AndroidDevice(JNIEnv* env, jobject input_device)
+ : m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
+ m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number)
+ {
+ jstring j_name =
+ reinterpret_cast<jstring>(env->CallObjectMethod(input_device, s_input_device_get_name));
+ m_name = GetJString(env, j_name);
+ env->DeleteLocalRef(j_name);
+
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Sources for {}: {:08x}", GetQualifiedName(), m_source);
+
+ AddKeys(env, input_device);
+ AddAxes(env, input_device);
+ }
+
+ std::string GetName() const override { return m_name; }
+
+ std::string GetSource() const override { return "Android"; }
+
+ std::optional<int> GetPreferredId() const override
+ {
+ return m_controller_number != 0 ? std::make_optional(m_controller_number) : std::nullopt;
+ }
+
+ int GetSortPriority() const override
+ {
+ // If m_controller_number is non-zero, Android considers this to be a gamepad
+ if (m_controller_number != 0)
+ return 0;
+
+ if ((m_source & AINPUT_SOURCE_KEYBOARD) != 0)
+ return -1;
+
+ if ((m_source & (AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_MOUSE_RELATIVE)) != 0)
+ return -2;
+
+ return -3;
+ }
+
+private:
+ void AddKeys(JNIEnv* env, jobject input_device)
+ {
+ jbooleanArray keys_array = reinterpret_cast<jbooleanArray>(
+ env->CallObjectMethod(input_device, s_input_device_has_keys, s_keycodes_array));
+ jboolean* keys = env->GetBooleanArrayElements(keys_array, nullptr);
+ jsize keys_count = env->GetArrayLength(keys_array);
+ for (jsize i = 0; i < keys_count; ++i)
+ {
+ // These specific keys never get delivered to applications,
+ // so there's no point in letting users try to map them
+ if (i == AKEYCODE_HOME || i == AKEYCODE_ASSIST || i == AKEYCODE_VOICE_ASSIST)
+ continue;
+
+ if (keys[i])
+ AddInput(new AndroidKey(i));
+ }
+ env->ReleaseBooleanArrayElements(keys_array, keys, JNI_ABORT);
+ env->DeleteLocalRef(keys_array);
+ }
+
+ void AddAxes(JNIEnv* env, jobject input_device)
+ {
+ jobject motion_ranges_list =
+ env->CallObjectMethod(input_device, s_input_device_get_motion_ranges);
+ jint motion_ranges_count = env->CallIntMethod(motion_ranges_list, s_list_size);
+ for (jint i = 0; i < motion_ranges_count; ++i)
+ {
+ jobject motion_range = env->CallObjectMethod(motion_ranges_list, s_list_get, i);
+
+ jint source = env->CallIntMethod(motion_range, s_motion_range_get_source);
+ jint axis = env->CallIntMethod(motion_range, s_motion_range_get_axis);
+ jfloat min = env->CallFloatMethod(motion_range, s_motion_range_get_min);
+ jfloat max = env->CallFloatMethod(motion_range, s_motion_range_get_max);
+
+ env->DeleteLocalRef(motion_range);
+
+ AndroidAxis* positive = nullptr;
+ AndroidAxis* negative = nullptr;
+ if (max > 0)
+ positive = new AndroidAxis(source, axis, false);
+ if (min < 0)
+ negative = new AndroidAxis(source, axis, true);
+
+ if (positive && negative)
+ AddAnalogInputs(positive, negative);
+ else if (positive || negative)
+ AddInput(positive ? positive : negative);
+ }
+ env->DeleteLocalRef(motion_ranges_list);
+ }
+
+ const int m_source;
+ const int m_controller_number;
+ std::string m_name;
+};
+
+// Creates an array that contains every possible keycode
+static jintArray CreateKeyCodesArray(JNIEnv* env)
+{
+ jintArray keycodes_array = env->NewIntArray(MAX_KEYCODE + 1);
+
+ int* keycodes = env->GetIntArrayElements(keycodes_array, nullptr);
+ for (int i = 0; i <= MAX_KEYCODE; ++i)
+ keycodes[i] = i;
+ env->ReleaseIntArrayElements(keycodes_array, keycodes, 0);
+
+ return keycodes_array;
+}
+
+void Init()
+{
+ JNIEnv* env = IDCache::GetEnvForThread();
+
+ const jclass list_class = env->FindClass("java/util/List");
+ s_list_class = reinterpret_cast<jclass>(env->NewGlobalRef(list_class));
+ s_list_get = env->GetMethodID(s_list_class, "get", "(I)Ljava/lang/Object;");
+ s_list_size = env->GetMethodID(s_list_class, "size", "()I");
+ env->DeleteLocalRef(list_class);
+
+ const jclass input_device_class = env->FindClass("android/view/InputDevice");
+ s_input_device_class = reinterpret_cast<jclass>(env->NewGlobalRef(input_device_class));
+ s_input_device_get_device_ids =
+ env->GetStaticMethodID(s_input_device_class, "getDeviceIds", "()[I");
+ s_input_device_get_device =
+ env->GetStaticMethodID(s_input_device_class, "getDevice", "(I)Landroid/view/InputDevice;");
+ s_input_device_get_controller_number =
+ env->GetMethodID(s_input_device_class, "getControllerNumber", "()I");
+ s_input_device_get_motion_ranges =
+ env->GetMethodID(s_input_device_class, "getMotionRanges", "()Ljava/util/List;");
+ s_input_device_get_name =
+ env->GetMethodID(s_input_device_class, "getName", "()Ljava/lang/String;");
+ s_input_device_get_sources = env->GetMethodID(s_input_device_class, "getSources", "()I");
+ s_input_device_has_keys = env->GetMethodID(s_input_device_class, "hasKeys", "([I)[Z");
+ env->DeleteLocalRef(input_device_class);
+
+ const jclass motion_range_class = env->FindClass("android/view/InputDevice$MotionRange");
+ s_motion_range_class = reinterpret_cast<jclass>(env->NewGlobalRef(motion_range_class));
+ s_motion_range_get_axis = env->GetMethodID(s_motion_range_class, "getAxis", "()I");
+ s_motion_range_get_max = env->GetMethodID(s_motion_range_class, "getMax", "()F");
+ s_motion_range_get_min = env->GetMethodID(s_motion_range_class, "getMin", "()F");
+ s_motion_range_get_source = env->GetMethodID(s_motion_range_class, "getSource", "()I");
+ env->DeleteLocalRef(motion_range_class);
+
+ jintArray keycodes_array = CreateKeyCodesArray(env);
+ s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
+ env->DeleteLocalRef(keycodes_array);
+}
+
+void Shutdown()
+{
+ JNIEnv* env = IDCache::GetEnvForThread();
+
+ env->DeleteGlobalRef(s_input_device_class);
+ env->DeleteGlobalRef(s_motion_range_class);
+ env->DeleteGlobalRef(s_keycodes_array);
+}
+
+static void AddDevice(JNIEnv* env, int device_id)
+{
+ jobject input_device =
+ env->CallStaticObjectMethod(s_input_device_class, s_input_device_get_device, device_id);
+
+ auto device = std::make_shared<AndroidDevice>(env, input_device);
+
+ env->DeleteLocalRef(input_device);
+
+ if (!device->Inputs().empty() || !device->Outputs().empty())
+ g_controller_interface.AddDevice(std::move(device));
+}
+
void PopulateDevices()
{
+ INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");
+
+ JNIEnv* env = IDCache::GetEnvForThread();
+
+ jintArray device_ids_array = reinterpret_cast<jintArray>(
+ env->CallStaticObjectMethod(s_input_device_class, s_input_device_get_device_ids));
+ int* device_ids = env->GetIntArrayElements(device_ids_array, nullptr);
+ jsize device_ids_count = env->GetArrayLength(device_ids_array);
+ for (jsize i = 0; i < device_ids_count; ++i)
+ AddDevice(env, device_ids[i]);
+ env->ReleaseIntArrayElements(device_ids_array, device_ids, JNI_ABORT);
+ env->DeleteLocalRef(device_ids_array);
}
+
} // namespace ciface::Android
diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.h b/Source/Core/InputCommon/ControllerInterface/Android/Android.h
index 9f57f5ba2e..8b8478d7ac 100644
--- a/Source/Core/InputCommon/ControllerInterface/Android/Android.h
+++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.h
@@ -1,9 +1,13 @@
-// Copyright 2008 Dolphin Emulator Project
+// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace ciface::Android
{
+void Init();
+void Shutdown();
+
void PopulateDevices();
+
} // namespace ciface::Android
diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp
index 6a6affd478..8f58525f0c 100644
--- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp
+++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp
@@ -67,7 +67,7 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
m_input_backends.emplace_back(ciface::SDL::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_ANDROID
-// nothing needed
+ ciface::Android::Init();
#endif
#ifdef CIFACE_USE_EVDEV
m_input_backends.emplace_back(ciface::evdev::CreateInputBackend(this));
@@ -237,7 +237,7 @@ void ControllerInterface::Shutdown()
ciface::Quartz::DeInit();
#endif
#ifdef CIFACE_USE_ANDROID
-// nothing needed
+ ciface::Android::Shutdown();
#endif
// Empty the container of input backends to deconstruct and deinitialize them.