mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
Merge pull request #13260 from JosJuice/android-gcadapter-hotplug-callback
Android: Detect GCAdapter hotplug using BroadcastReceiver
This commit is contained in:
commit
e1c7734ee4
@ -9,8 +9,8 @@ import android.hardware.usb.UsbManager;
|
|||||||
|
|
||||||
import org.dolphinemu.dolphinemu.utils.ActivityTracker;
|
import org.dolphinemu.dolphinemu.utils.ActivityTracker;
|
||||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
||||||
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
|
import org.dolphinemu.dolphinemu.utils.GCAdapter;
|
||||||
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
|
import org.dolphinemu.dolphinemu.utils.WiimoteAdapter;
|
||||||
import org.dolphinemu.dolphinemu.utils.VolleyUtil;
|
import org.dolphinemu.dolphinemu.utils.VolleyUtil;
|
||||||
|
|
||||||
public class DolphinApplication extends Application
|
public class DolphinApplication extends Application
|
||||||
@ -28,8 +28,8 @@ public class DolphinApplication extends Application
|
|||||||
VolleyUtil.init(getApplicationContext());
|
VolleyUtil.init(getApplicationContext());
|
||||||
System.loadLibrary("main");
|
System.loadLibrary("main");
|
||||||
|
|
||||||
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
|
|
||||||
if (DirectoryInitialization.shouldStart(getApplicationContext()))
|
if (DirectoryInitialization.shouldStart(getApplicationContext()))
|
||||||
DirectoryInitialization.start(getApplicationContext());
|
DirectoryInitialization.start(getApplicationContext());
|
||||||
|
|||||||
@ -0,0 +1,259 @@
|
|||||||
|
// 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<String, UsbDevice> devices = manager.getDeviceList();
|
||||||
|
for (Map.Entry<String, UsbDevice> 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<String, UsbDevice> devices = manager.getDeviceList();
|
||||||
|
for (Map.Entry<String, UsbDevice> 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)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
usbConnection = manager.openDevice(dev);
|
||||||
|
if (usbConnection == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
@ -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<String, UsbDevice> devices = manager.getDeviceList();
|
|
||||||
for (Map.Entry<String, UsbDevice> 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<String, UsbDevice> devices = manager.getDeviceList();
|
|
||||||
for (Map.Entry<String, UsbDevice> 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<String, UsbDevice> devices = manager.getDeviceList();
|
|
||||||
for (Map.Entry<String, UsbDevice> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -22,7 +22,7 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Java_WiimoteAdapter
|
public class WiimoteAdapter
|
||||||
{
|
{
|
||||||
final static int MAX_PAYLOAD = 23;
|
final static int MAX_PAYLOAD = 23;
|
||||||
final static int MAX_WIIMOTES = 4;
|
final static int MAX_WIIMOTES = 4;
|
||||||
@ -31,14 +31,14 @@ public class Java_WiimoteAdapter
|
|||||||
final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306;
|
final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306;
|
||||||
public static UsbManager manager;
|
public static UsbManager manager;
|
||||||
|
|
||||||
static UsbDeviceConnection usb_con;
|
static UsbDeviceConnection usbConnection;
|
||||||
static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES];
|
static UsbInterface[] usbInterface = new UsbInterface[MAX_WIIMOTES];
|
||||||
static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES];
|
static UsbEndpoint[] usbIn = new UsbEndpoint[MAX_WIIMOTES];
|
||||||
|
|
||||||
@Keep
|
@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<String, UsbDevice> devices = manager.getDeviceList();
|
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||||
@ -65,7 +65,7 @@ public class Java_WiimoteAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
public static boolean QueryAdapter()
|
public static boolean queryAdapter()
|
||||||
{
|
{
|
||||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||||
@ -77,20 +77,20 @@ public class Java_WiimoteAdapter
|
|||||||
if (manager.hasPermission(dev))
|
if (manager.hasPermission(dev))
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
RequestPermission();
|
requestPermission();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@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
|
@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];
|
byte report_number = buf[0];
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ public class Java_WiimoteAdapter
|
|||||||
final int HID_SET_REPORT = 0x9;
|
final int HID_SET_REPORT = 0x9;
|
||||||
final int HID_OUTPUT = (2 << 8);
|
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,
|
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||||
HID_SET_REPORT,
|
HID_SET_REPORT,
|
||||||
HID_OUTPUT | report_number,
|
HID_OUTPUT | report_number,
|
||||||
@ -120,10 +120,10 @@ public class Java_WiimoteAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
public static boolean OpenAdapter()
|
public static boolean openAdapter()
|
||||||
{
|
{
|
||||||
// If the adapter is already open. Don't attempt to do it again
|
// 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;
|
return true;
|
||||||
|
|
||||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||||
@ -135,7 +135,7 @@ public class Java_WiimoteAdapter
|
|||||||
{
|
{
|
||||||
if (manager.hasPermission(dev))
|
if (manager.hasPermission(dev))
|
||||||
{
|
{
|
||||||
usb_con = manager.openDevice(dev);
|
usbConnection = manager.openDevice(dev);
|
||||||
UsbConfiguration conf = dev.getConfiguration(0);
|
UsbConfiguration conf = dev.getConfiguration(0);
|
||||||
|
|
||||||
Log.info("Number of configurations: " + dev.getConfigurationCount());
|
Log.info("Number of configurations: " + dev.getConfigurationCount());
|
||||||
@ -149,20 +149,20 @@ public class Java_WiimoteAdapter
|
|||||||
for (int i = 0; i < MAX_WIIMOTES; ++i)
|
for (int i = 0; i < MAX_WIIMOTES; ++i)
|
||||||
{
|
{
|
||||||
// One interface per Wii Remote
|
// One interface per Wii Remote
|
||||||
usb_intf[i] = dev.getInterface(i);
|
usbInterface[i] = dev.getInterface(i);
|
||||||
usb_con.claimInterface(usb_intf[i], true);
|
usbConnection.claimInterface(usbInterface[i], true);
|
||||||
|
|
||||||
// One endpoint per Wii Remote. Input only
|
// One endpoint per Wii Remote. Input only
|
||||||
// Output reports go through the control channel.
|
// Output reports go through the control channel.
|
||||||
usb_in[i] = usb_intf[i].getEndpoint(0);
|
usbIn[i] = usbInterface[i].getEndpoint(0);
|
||||||
Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount());
|
Log.info("Interface " + i + " endpoint count:" + usbInterface[i].getEndpointCount());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// XXX: Message that the device was found, but it needs to be unplugged and plugged back in?
|
// XXX: Message that the device was found, but it needs to be unplugged and plugged back in?
|
||||||
usb_con.close();
|
usbConnection.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,8 +30,8 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
|
|||||||
|
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
|
||||||
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
|
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
|
||||||
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
|
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z");
|
||||||
|
|
||||||
if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) &&
|
if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) &&
|
||||||
env->CallStaticBooleanMethod(s_adapter_class, openadapter_func))
|
env->CallStaticBooleanMethod(s_adapter_class, openadapter_func))
|
||||||
@ -55,15 +55,15 @@ bool WiimoteAndroid::ConnectInternal()
|
|||||||
{
|
{
|
||||||
m_env = IDCache::GetEnvForThread();
|
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 =
|
jobjectArray payload_object =
|
||||||
reinterpret_cast<jobjectArray>(m_env->GetStaticObjectField(s_adapter_class, payload_field));
|
reinterpret_cast<jobjectArray>(m_env->GetStaticObjectField(s_adapter_class, payload_field));
|
||||||
m_java_wiimote_payload =
|
m_java_wiimote_payload =
|
||||||
(jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index);
|
(jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index);
|
||||||
|
|
||||||
// Get function pointers
|
// Get function pointers
|
||||||
m_input_func = m_env->GetStaticMethodID(s_adapter_class, "Input", "(I)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");
|
m_output_func = m_env->GetStaticMethodID(s_adapter_class, "output", "(I[BI)I");
|
||||||
|
|
||||||
is_connected = true;
|
is_connected = true;
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ int WiimoteAndroid::IOWrite(u8 const* buf, size_t len)
|
|||||||
void InitAdapterClass()
|
void InitAdapterClass()
|
||||||
{
|
{
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
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<jclass>(env->NewGlobalRef(adapter_class));
|
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
|
||||||
}
|
}
|
||||||
} // namespace WiimoteReal
|
} // namespace WiimoteReal
|
||||||
|
|||||||
@ -66,13 +66,7 @@ static void AddGCAdapter(libusb_device* device);
|
|||||||
static void ResetRumbleLockNeeded();
|
static void ResetRumbleLockNeeded();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum class CalledFromReadThread
|
static void Reset();
|
||||||
{
|
|
||||||
No,
|
|
||||||
Yes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void Reset(CalledFromReadThread called_from_read_thread);
|
|
||||||
static void Setup();
|
static void Setup();
|
||||||
static void ProcessInputPayload(const u8* data, std::size_t size);
|
static void ProcessInputPayload(const u8* data, std::size_t size);
|
||||||
static void ReadThreadFunc();
|
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 std::thread s_read_adapter_thread;
|
||||||
static Common::Flag s_read_adapter_thread_running;
|
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 std::thread s_write_adapter_thread;
|
||||||
static Common::Flag s_write_adapter_thread_running;
|
static Common::Flag s_write_adapter_thread_running;
|
||||||
static Common::Event s_write_happened;
|
static Common::Event s_write_happened;
|
||||||
|
|
||||||
static std::mutex s_read_mutex;
|
|
||||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
|
||||||
static std::mutex s_init_mutex;
|
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;
|
static std::mutex s_write_mutex;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static std::thread s_adapter_detect_thread;
|
static std::thread s_adapter_detect_thread;
|
||||||
static Common::Flag s_adapter_detect_thread_running;
|
static Common::Flag s_adapter_detect_thread_running;
|
||||||
|
|
||||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
|
||||||
static Common::Event s_hotplug_event;
|
static Common::Event s_hotplug_event;
|
||||||
|
|
||||||
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
static std::function<void(void)> s_detect_callback;
|
static std::function<void(void)> s_detect_callback;
|
||||||
|
|
||||||
#if defined(__FreeBSD__) && __FreeBSD__ >= 11
|
#if defined(__FreeBSD__) && __FreeBSD__ >= 11
|
||||||
@ -180,14 +172,14 @@ static void ReadThreadFunc()
|
|||||||
bool first_read = true;
|
bool first_read = true;
|
||||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
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);
|
jobject payload_object = env->GetStaticObjectField(s_adapter_class, payload_field);
|
||||||
auto* const java_controller_payload = reinterpret_cast<jbyteArray*>(&payload_object);
|
auto* const java_controller_payload = reinterpret_cast<jbyteArray*>(&payload_object);
|
||||||
|
|
||||||
// Get function pointers
|
// Get function pointers
|
||||||
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "GetFD", "()I");
|
const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "getFd", "()I");
|
||||||
const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "Input", "()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 openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z");
|
||||||
|
|
||||||
const bool connected = env->CallStaticBooleanMethod(s_adapter_class, openadapter_func);
|
const bool connected = env->CallStaticBooleanMethod(s_adapter_class, openadapter_func);
|
||||||
|
|
||||||
@ -279,7 +271,7 @@ static void WriteThreadFunc()
|
|||||||
int size = 0;
|
int size = 0;
|
||||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
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
|
#endif
|
||||||
|
|
||||||
while (s_write_adapter_thread_running.IsSet())
|
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)
|
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
|
||||||
{
|
{
|
||||||
if (s_handle != nullptr && libusb_get_device(s_handle) == dev)
|
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
|
// Reset a potential error status now that the adapter is unplugged
|
||||||
if (s_status == AdapterStatus::Error)
|
if (s_status == AdapterStatus::Error)
|
||||||
@ -344,6 +336,25 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#endif
|
||||||
|
|
||||||
static void ScanThreadFunc()
|
static void ScanThreadFunc()
|
||||||
@ -393,15 +404,23 @@ static void ScanThreadFunc()
|
|||||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||||
|
|
||||||
const jmethodID queryadapter_func =
|
const jmethodID enable_hotplug_callback_func =
|
||||||
env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
|
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())
|
while (s_adapter_detect_thread_running.IsSet())
|
||||||
{
|
{
|
||||||
if (!s_detected && UseAdapter() &&
|
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();
|
Setup();
|
||||||
Common::SleepCurrentThread(1000);
|
}
|
||||||
|
|
||||||
|
s_hotplug_event.Wait();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -456,7 +475,7 @@ void Init()
|
|||||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
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));
|
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -484,9 +503,7 @@ void StopScanThread()
|
|||||||
{
|
{
|
||||||
if (s_adapter_detect_thread_running.TestAndClear())
|
if (s_adapter_detect_thread_running.TestAndClear())
|
||||||
{
|
{
|
||||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
|
||||||
s_hotplug_event.Set();
|
s_hotplug_event.Set();
|
||||||
#endif
|
|
||||||
s_adapter_detect_thread.join();
|
s_adapter_detect_thread.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -523,11 +540,8 @@ static void Setup()
|
|||||||
s_detected = true;
|
s_detected = true;
|
||||||
|
|
||||||
// Make sure the thread isn't in the middle of shutting down while starting a new one
|
// 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() ||
|
if (s_read_adapter_thread_running.TestAndClear())
|
||||||
s_read_adapter_thread_running.TestAndClear())
|
|
||||||
{
|
|
||||||
s_read_adapter_thread.join();
|
s_read_adapter_thread.join();
|
||||||
}
|
|
||||||
|
|
||||||
s_read_adapter_thread_running.Set(true);
|
s_read_adapter_thread_running.Set(true);
|
||||||
s_read_adapter_thread = std::thread(ReadThreadFunc);
|
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)
|
if (s_libusb_context && s_libusb_context->IsValid() && s_libusb_hotplug_enabled)
|
||||||
libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle);
|
libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle);
|
||||||
#endif
|
#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
|
#endif
|
||||||
Reset(CalledFromReadThread::No);
|
Reset();
|
||||||
|
|
||||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
s_libusb_context.reset();
|
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);
|
std::unique_lock lock(s_init_mutex, std::defer_lock);
|
||||||
if (!lock.try_lock())
|
if (!lock.try_lock())
|
||||||
return;
|
return;
|
||||||
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
if (s_status != AdapterStatus::Detected)
|
if (s_status != AdapterStatus::Detected)
|
||||||
return;
|
return;
|
||||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||||
@ -719,16 +738,8 @@ static void Reset(CalledFromReadThread called_from_read_thread)
|
|||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (called_from_read_thread == CalledFromReadThread::No)
|
|
||||||
{
|
|
||||||
if (s_read_adapter_thread_running.TestAndClear())
|
if (s_read_adapter_thread_running.TestAndClear())
|
||||||
s_read_adapter_thread.join();
|
s_read_adapter_thread.join();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s_read_adapter_thread_needs_joining.Set();
|
|
||||||
s_read_adapter_thread_running.Clear();
|
|
||||||
}
|
|
||||||
// The read thread will close the write thread
|
// The read thread will close the write thread
|
||||||
|
|
||||||
s_port_states.fill({});
|
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.
|
// This can occur for a few frames on initialization.
|
||||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", size,
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", size,
|
||||||
data[0]);
|
data[0]);
|
||||||
#if GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
|
||||||
Reset(CalledFromReadThread::Yes);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user