diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java index e0816e408bb8..9ced73b403ff 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java @@ -8,8 +8,8 @@ import org.dolphinemu.dolphinemu.utils.ActivityTracker; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; -import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; -import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; +import org.dolphinemu.dolphinemu.utils.GCAdapter; +import org.dolphinemu.dolphinemu.utils.WiimoteAdapter; import org.dolphinemu.dolphinemu.utils.VolleyUtil; public class DolphinApplication extends Application @@ -25,8 +25,8 @@ public void onCreate() VolleyUtil.init(getApplicationContext()); System.loadLibrary("main"); - Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); - Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); + GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); + WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); if (DirectoryInitialization.shouldStart(getApplicationContext())) DirectoryInitialization.start(getApplicationContext()); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java new file mode 100644 index 000000000000..12ed20fec7f1 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.utils; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbConfiguration; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.os.Build; +import android.widget.Toast; + +import androidx.annotation.Keep; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import org.dolphinemu.dolphinemu.BuildConfig; +import org.dolphinemu.dolphinemu.DolphinApplication; +import org.dolphinemu.dolphinemu.R; + +import java.util.HashMap; +import java.util.Map; + +public class GCAdapter +{ + public static UsbManager manager; + + @Keep + static byte[] controllerPayload = new byte[37]; + + static UsbDeviceConnection usbConnection; + static UsbInterface usbInterface; + static UsbEndpoint usbIn; + static UsbEndpoint usbOut; + + private static final String ACTION_GC_ADAPTER_PERMISSION_GRANTED = + BuildConfig.APPLICATION_ID + ".GC_ADAPTER_PERMISSION_GRANTED"; + + private static final Object hotplugCallbackLock = new Object(); + private static boolean hotplugCallbackEnabled = false; + private static UsbDevice adapterDevice = null; + private static BroadcastReceiver hotplugBroadcastReceiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context context, Intent intent) + { + onUsbDevicesChanged(); + } + }; + + private static void requestPermission() + { + HashMap devices = manager.getDeviceList(); + for (Map.Entry pair : devices.entrySet()) + { + UsbDevice dev = pair.getValue(); + if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) + { + if (!manager.hasPermission(dev)) + { + Context context = DolphinApplication.getAppContext(); + + int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + PendingIntent.FLAG_IMMUTABLE : 0; + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, + new Intent(ACTION_GC_ADAPTER_PERMISSION_GRANTED), flags); + + manager.requestPermission(dev, pendingIntent); + } + } + } + } + + public static void shutdown() + { + usbConnection.close(); + } + + @Keep + public static int getFd() + { + return usbConnection.getFileDescriptor(); + } + + @Keep + public static boolean isUsbDeviceAvailable() + { + synchronized (hotplugCallbackLock) + { + return adapterDevice != null; + } + } + + @Nullable + private static UsbDevice queryAdapter() + { + HashMap devices = manager.getDeviceList(); + for (Map.Entry pair : devices.entrySet()) + { + UsbDevice dev = pair.getValue(); + if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) + { + if (manager.hasPermission(dev)) + return dev; + else + requestPermission(); + } + } + return null; + } + + public static void initAdapter() + { + byte[] init = {0x13}; + usbConnection.bulkTransfer(usbOut, init, init.length, 0); + } + + @Keep + public static int input() + { + return usbConnection.bulkTransfer(usbIn, controllerPayload, controllerPayload.length, 16); + } + + @Keep + public static int output(byte[] rumble) + { + return usbConnection.bulkTransfer(usbOut, rumble, 5, 16); + } + + @Keep + public static boolean openAdapter() + { + UsbDevice dev; + synchronized (hotplugCallbackLock) + { + dev = adapterDevice; + } + + if (dev != null) + { + usbConnection = manager.openDevice(dev); + + Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount()); + Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount()); + + if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0) + { + UsbConfiguration conf = dev.getConfiguration(0); + usbInterface = conf.getInterface(0); + usbConnection.claimInterface(usbInterface, true); + + Log.info("GCAdapter: Number of endpoints: " + usbInterface.getEndpointCount()); + + if (usbInterface.getEndpointCount() == 2) + { + for (int i = 0; i < usbInterface.getEndpointCount(); ++i) + if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) + usbIn = usbInterface.getEndpoint(i); + else + usbOut = usbInterface.getEndpoint(i); + + initAdapter(); + return true; + } + else + { + usbConnection.releaseInterface(usbInterface); + } + } + + Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter, + Toast.LENGTH_LONG).show(); + usbConnection.close(); + } + return false; + } + + @Keep + public static void enableHotplugCallback() + { + synchronized (hotplugCallbackLock) + { + if (hotplugCallbackEnabled) + { + throw new IllegalStateException("enableHotplugCallback was called when already enabled"); + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + filter.addAction(ACTION_GC_ADAPTER_PERMISSION_GRANTED); + + ContextCompat.registerReceiver(DolphinApplication.getAppContext(), hotplugBroadcastReceiver, + filter, ContextCompat.RECEIVER_EXPORTED); + + hotplugCallbackEnabled = true; + + onUsbDevicesChanged(); + } + } + + @Keep + public static void disableHotplugCallback() + { + synchronized (hotplugCallbackLock) + { + if (hotplugCallbackEnabled) + { + DolphinApplication.getAppContext().unregisterReceiver(hotplugBroadcastReceiver); + hotplugCallbackEnabled = false; + adapterDevice = null; + } + } + } + + public static void onUsbDevicesChanged() + { + synchronized (hotplugCallbackLock) + { + if (adapterDevice != null) + { + boolean adapterStillConnected = manager.getDeviceList().entrySet().stream() + .anyMatch(pair -> pair.getValue().getDeviceId() == adapterDevice.getDeviceId()); + + if (!adapterStillConnected) + { + adapterDevice = null; + onAdapterDisconnected(); + } + } + + if (adapterDevice == null) + { + UsbDevice newAdapter = queryAdapter(); + if (newAdapter != null) + { + adapterDevice = newAdapter; + onAdapterConnected(); + } + } + } + } + + private static native void onAdapterConnected(); + + private static native void onAdapterDisconnected(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_GCAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_GCAdapter.java deleted file mode 100644 index 3fcd58afacb1..000000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_GCAdapter.java +++ /dev/null @@ -1,158 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.hardware.usb.UsbConfiguration; -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.os.Build; -import android.widget.Toast; - -import androidx.annotation.Keep; - -import org.dolphinemu.dolphinemu.DolphinApplication; -import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.services.USBPermService; - -import java.util.HashMap; -import java.util.Map; - -public class Java_GCAdapter -{ - public static UsbManager manager; - - @Keep - static byte[] controller_payload = new byte[37]; - - static UsbDeviceConnection usb_con; - static UsbInterface usb_intf; - static UsbEndpoint usb_in; - static UsbEndpoint usb_out; - - private static void RequestPermission() - { - HashMap devices = manager.getDeviceList(); - for (Map.Entry pair : devices.entrySet()) - { - UsbDevice dev = pair.getValue(); - if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) - { - if (!manager.hasPermission(dev)) - { - Context context = DolphinApplication.getAppContext(); - Intent intent = new Intent(context, USBPermService.class); - - int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? - PendingIntent.FLAG_IMMUTABLE : 0; - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, flags); - - manager.requestPermission(dev, pendingIntent); - } - } - } - } - - public static void Shutdown() - { - usb_con.close(); - } - - @Keep - public static int GetFD() - { - return usb_con.getFileDescriptor(); - } - - @Keep - public static boolean QueryAdapter() - { - HashMap devices = manager.getDeviceList(); - for (Map.Entry pair : devices.entrySet()) - { - UsbDevice dev = pair.getValue(); - if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) - { - if (manager.hasPermission(dev)) - return true; - else - RequestPermission(); - } - } - return false; - } - - public static void InitAdapter() - { - byte[] init = {0x13}; - usb_con.bulkTransfer(usb_out, init, init.length, 0); - } - - @Keep - public static int Input() - { - return usb_con.bulkTransfer(usb_in, controller_payload, controller_payload.length, 16); - } - - @Keep - public static int Output(byte[] rumble) - { - return usb_con.bulkTransfer(usb_out, rumble, 5, 16); - } - - @Keep - public static boolean OpenAdapter() - { - HashMap devices = manager.getDeviceList(); - for (Map.Entry pair : devices.entrySet()) - { - UsbDevice dev = pair.getValue(); - if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) - { - if (manager.hasPermission(dev)) - { - usb_con = manager.openDevice(dev); - - Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount()); - Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount()); - - if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0) - { - UsbConfiguration conf = dev.getConfiguration(0); - usb_intf = conf.getInterface(0); - usb_con.claimInterface(usb_intf, true); - - Log.info("GCAdapter: Number of endpoints: " + usb_intf.getEndpointCount()); - - if (usb_intf.getEndpointCount() == 2) - { - for (int i = 0; i < usb_intf.getEndpointCount(); ++i) - if (usb_intf.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) - usb_in = usb_intf.getEndpoint(i); - else - usb_out = usb_intf.getEndpoint(i); - - InitAdapter(); - return true; - } - else - { - usb_con.releaseInterface(usb_intf); - } - } - - Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter, - Toast.LENGTH_LONG).show(); - usb_con.close(); - } - } - } - return false; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiimoteAdapter.java similarity index 80% rename from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java rename to Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiimoteAdapter.java index 469375614f65..98a7d5190045 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiimoteAdapter.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -public class Java_WiimoteAdapter +public class WiimoteAdapter { final static int MAX_PAYLOAD = 23; final static int MAX_WIIMOTES = 4; @@ -31,14 +31,14 @@ public class Java_WiimoteAdapter final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306; public static UsbManager manager; - static UsbDeviceConnection usb_con; - static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES]; - static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES]; + static UsbDeviceConnection usbConnection; + static UsbInterface[] usbInterface = new UsbInterface[MAX_WIIMOTES]; + static UsbEndpoint[] usbIn = new UsbEndpoint[MAX_WIIMOTES]; @Keep - public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD]; + public static byte[][] wiimotePayload = new byte[MAX_WIIMOTES][MAX_PAYLOAD]; - private static void RequestPermission() + private static void requestPermission() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -65,7 +65,7 @@ private static void RequestPermission() } @Keep - public static boolean QueryAdapter() + public static boolean queryAdapter() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -77,20 +77,20 @@ public static boolean QueryAdapter() if (manager.hasPermission(dev)) return true; else - RequestPermission(); + requestPermission(); } } return false; } @Keep - public static int Input(int index) + public static int input(int index) { - return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT); + return usbConnection.bulkTransfer(usbIn[index], wiimotePayload[index], MAX_PAYLOAD, TIMEOUT); } @Keep - public static int Output(int index, byte[] buf, int size) + public static int output(int index, byte[] buf, int size) { byte report_number = buf[0]; @@ -105,7 +105,7 @@ public static int Output(int index, byte[] buf, int size) final int HID_SET_REPORT = 0x9; final int HID_OUTPUT = (2 << 8); - int write = usb_con.controlTransfer( + int write = usbConnection.controlTransfer( LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HID_SET_REPORT, HID_OUTPUT | report_number, @@ -120,10 +120,10 @@ public static int Output(int index, byte[] buf, int size) } @Keep - public static boolean OpenAdapter() + public static boolean openAdapter() { // If the adapter is already open. Don't attempt to do it again - if (usb_con != null && usb_con.getFileDescriptor() != -1) + if (usbConnection != null && usbConnection.getFileDescriptor() != -1) return true; HashMap devices = manager.getDeviceList(); @@ -135,7 +135,7 @@ public static boolean OpenAdapter() { if (manager.hasPermission(dev)) { - usb_con = manager.openDevice(dev); + usbConnection = manager.openDevice(dev); UsbConfiguration conf = dev.getConfiguration(0); Log.info("Number of configurations: " + dev.getConfigurationCount()); @@ -149,20 +149,20 @@ public static boolean OpenAdapter() for (int i = 0; i < MAX_WIIMOTES; ++i) { // One interface per Wii Remote - usb_intf[i] = dev.getInterface(i); - usb_con.claimInterface(usb_intf[i], true); + usbInterface[i] = dev.getInterface(i); + usbConnection.claimInterface(usbInterface[i], true); // One endpoint per Wii Remote. Input only // Output reports go through the control channel. - usb_in[i] = usb_intf[i].getEndpoint(0); - Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount()); + usbIn[i] = usbInterface[i].getEndpoint(0); + Log.info("Interface " + i + " endpoint count:" + usbInterface[i].getEndpointCount()); } return true; } else { // XXX: Message that the device was found, but it needs to be unplugged and plugged back in? - usb_con.close(); + usbConnection.close(); } } } diff --git a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp index 10234ceef7a7..507cacf00676 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp +++ b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp @@ -30,8 +30,8 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector& found_wiimotes, JNIEnv* env = IDCache::GetEnvForThread(); - jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z"); - jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z"); + jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z"); + jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z"); if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) && env->CallStaticBooleanMethod(s_adapter_class, openadapter_func)) @@ -55,15 +55,15 @@ bool WiimoteAndroid::ConnectInternal() { m_env = IDCache::GetEnvForThread(); - jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B"); + jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimotePayload", "[[B"); jobjectArray payload_object = reinterpret_cast(m_env->GetStaticObjectField(s_adapter_class, payload_field)); m_java_wiimote_payload = (jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index); // Get function pointers - m_input_func = m_env->GetStaticMethodID(s_adapter_class, "Input", "(I)I"); - m_output_func = m_env->GetStaticMethodID(s_adapter_class, "Output", "(I[BI)I"); + m_input_func = m_env->GetStaticMethodID(s_adapter_class, "input", "(I)I"); + m_output_func = m_env->GetStaticMethodID(s_adapter_class, "output", "(I[BI)I"); is_connected = true; @@ -110,7 +110,7 @@ int WiimoteAndroid::IOWrite(u8 const* buf, size_t len) void InitAdapterClass() { JNIEnv* env = IDCache::GetEnvForThread(); - jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter"); + jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/WiimoteAdapter"); s_adapter_class = reinterpret_cast(env->NewGlobalRef(adapter_class)); } } // namespace WiimoteReal diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index bf7b1f136cf1..f883e0d3a957 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -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 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 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(&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(env->NewGlobalRef(adapter_class)); #endif @@ -523,11 +542,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 +707,13 @@ void Shutdown() if (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 +727,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 +740,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 +820,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 {