From 792cb6219533bd9ce5154da9a87f6eabddfaa977 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 31 Dec 2021 16:14:15 +0100 Subject: [PATCH] ControllerInterface/Android: Implement device population --- Source/Core/InputCommon/CMakeLists.txt | 3 + .../ControllerInterface/Android/Android.cpp | 603 +++++++++++++++++- .../ControllerInterface/Android/Android.h | 6 +- .../ControllerInterface.cpp | 4 +- 4 files changed, 612 insertions(+), 4 deletions(-) diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index bb3b1b5872..597a90291f 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -146,6 +146,9 @@ elseif(ANDROID) ControllerInterface/Touch/InputOverrider.cpp ControllerInterface/Touch/InputOverrider.h ) + target_link_libraries(inputcommon PRIVATE + androidcommon + ) endif() if(NOT ANDROID) 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 +#include +#include +#include + +#include + +#include +#include +#include + +#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 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 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(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 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( + 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(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(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(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(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(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( + 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.