Merge pull request #13260 from JosJuice/android-gcadapter-hotplug-callback

Android: Detect GCAdapter hotplug using BroadcastReceiver
This commit is contained in:
JMC47
2025-09-28 14:03:50 -04:00
committed by GitHub
6 changed files with 342 additions and 233 deletions
+53 -45
View File
@@ -66,13 +66,7 @@ static void AddGCAdapter(libusb_device* device);
static void ResetRumbleLockNeeded();
#endif
enum class CalledFromReadThread
{
No,
Yes,
};
static void Reset(CalledFromReadThread called_from_read_thread);
static void Reset();
static void Setup();
static void ProcessInputPayload(const u8* data, std::size_t size);
static void ReadThreadFunc();
@@ -129,24 +123,22 @@ static std::atomic<int> s_controller_write_payload_size{0};
static std::thread s_read_adapter_thread;
static Common::Flag s_read_adapter_thread_running;
static Common::Flag s_read_adapter_thread_needs_joining;
static std::thread s_write_adapter_thread;
static Common::Flag s_write_adapter_thread_running;
static Common::Event s_write_happened;
static std::mutex s_read_mutex;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static std::mutex s_init_mutex;
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
static std::mutex s_read_mutex;
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
static std::mutex s_write_mutex;
#endif
static std::thread s_adapter_detect_thread;
static Common::Flag s_adapter_detect_thread_running;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static Common::Event s_hotplug_event;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static std::function<void(void)> s_detect_callback;
#if defined(__FreeBSD__) && __FreeBSD__ >= 11
@@ -180,14 +172,14 @@ static void ReadThreadFunc()
bool first_read = true;
JNIEnv* const env = IDCache::GetEnvForThread();
const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controller_payload", "[B");
const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controllerPayload", "[B");
jobject payload_object = env->GetStaticObjectField(s_adapter_class, payload_field);
auto* const java_controller_payload = reinterpret_cast<jbyteArray*>(&payload_object);
// Get function pointers
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "GetFD", "()I");
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "Input", "()I");
const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "getFd", "()I");
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "input", "()I");
const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
const bool connected = env->CallStaticBooleanMethod(s_adapter_class, openadapter_func);
@@ -279,7 +271,7 @@ static void WriteThreadFunc()
int size = 0;
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "Output", "([B)I");
const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "output", "([B)I");
#endif
while (s_write_adapter_thread_running.IsSet())
@@ -331,7 +323,7 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
{
if (s_handle != nullptr && libusb_get_device(s_handle) == dev)
Reset(CalledFromReadThread::No);
Reset();
// Reset a potential error status now that the adapter is unplugged
if (s_status == AdapterStatus::Error)
@@ -344,6 +336,25 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
return 0;
}
#endif
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
extern "C" {
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterConnected(JNIEnv* env, jclass)
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter connected");
if (!s_detected)
s_hotplug_event.Set();
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env, jclass)
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter disconnected");
if (s_detected)
Reset();
}
}
#endif
static void ScanThreadFunc()
@@ -393,15 +404,23 @@ static void ScanThreadFunc()
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jmethodID queryadapter_func =
env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
const jmethodID enable_hotplug_callback_func =
env->GetStaticMethodID(s_adapter_class, "enableHotplugCallback", "()V");
env->CallStaticVoidMethod(s_adapter_class, enable_hotplug_callback_func);
const jmethodID is_usb_device_available_func =
env->GetStaticMethodID(s_adapter_class, "isUsbDeviceAvailable", "()Z");
while (s_adapter_detect_thread_running.IsSet())
{
if (!s_detected && UseAdapter() &&
env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func))
env->CallStaticBooleanMethod(s_adapter_class, is_usb_device_available_func))
{
std::lock_guard lk(s_init_mutex);
Setup();
Common::SleepCurrentThread(1000);
}
s_hotplug_event.Wait();
}
#endif
@@ -456,7 +475,7 @@ void Init()
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_GCAdapter");
const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/GCAdapter");
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
#endif
@@ -484,9 +503,7 @@ void StopScanThread()
{
if (s_adapter_detect_thread_running.TestAndClear())
{
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
s_hotplug_event.Set();
#endif
s_adapter_detect_thread.join();
}
}
@@ -523,11 +540,8 @@ static void Setup()
s_detected = true;
// Make sure the thread isn't in the middle of shutting down while starting a new one
if (s_read_adapter_thread_needs_joining.TestAndClear() ||
s_read_adapter_thread_running.TestAndClear())
{
if (s_read_adapter_thread_running.TestAndClear())
s_read_adapter_thread.join();
}
s_read_adapter_thread_running.Set(true);
s_read_adapter_thread = std::thread(ReadThreadFunc);
@@ -691,8 +705,13 @@ void Shutdown()
if (s_libusb_context && s_libusb_context->IsValid() && s_libusb_hotplug_enabled)
libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle);
#endif
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
const jmethodID disable_hotplug_callback_func =
env->GetStaticMethodID(s_adapter_class, "disableHotplugCallback", "()V");
env->CallStaticVoidMethod(s_adapter_class, disable_hotplug_callback_func);
#endif
Reset(CalledFromReadThread::No);
Reset();
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
s_libusb_context.reset();
@@ -706,12 +725,12 @@ void Shutdown()
}
}
static void Reset(CalledFromReadThread called_from_read_thread)
static void Reset()
{
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
std::unique_lock lock(s_init_mutex, std::defer_lock);
if (!lock.try_lock())
return;
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
if (s_status != AdapterStatus::Detected)
return;
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
@@ -719,16 +738,8 @@ static void Reset(CalledFromReadThread called_from_read_thread)
return;
#endif
if (called_from_read_thread == CalledFromReadThread::No)
{
if (s_read_adapter_thread_running.TestAndClear())
s_read_adapter_thread.join();
}
else
{
s_read_adapter_thread_needs_joining.Set();
s_read_adapter_thread_running.Clear();
}
if (s_read_adapter_thread_running.TestAndClear())
s_read_adapter_thread.join();
// The read thread will close the write thread
s_port_states.fill({});
@@ -807,9 +818,6 @@ void ProcessInputPayload(const u8* data, std::size_t size)
// This can occur for a few frames on initialization.
ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", size,
data[0]);
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
Reset(CalledFromReadThread::Yes);
#endif
}
else
{