Merge pull request #3593 from Sonicadvance1/Android_Mayflash_adapter
[Android] Implement support for real Wiimotes with the DolphinBar
This commit is contained in:
commit
35d7c2bc78
|
@ -379,6 +379,11 @@ public final class NativeLibrary
|
|||
/** Native EGL functions not exposed by Java bindings **/
|
||||
public static native void eglBindAPI(int api);
|
||||
|
||||
/**
|
||||
* Provides a way to refresh the connections on Wiimotes
|
||||
*/
|
||||
public static native void RefreshWiimotes();
|
||||
|
||||
/**
|
||||
* The methods C++ uses to find references to Java classes and methods
|
||||
* are really expensive. Rather than calling them every time we want to
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.dolphinemu.dolphinemu.fragments.SaveStateFragment;
|
|||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||
import org.dolphinemu.dolphinemu.utils.Animations;
|
||||
import org.dolphinemu.dolphinemu.utils.Java_GCAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -120,6 +121,7 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
super.onCreate(savedInstanceState);
|
||||
|
||||
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||
|
||||
// Picasso will take a while to load these big-ass screenshots. So don't run
|
||||
// the animation until we say so.
|
||||
|
@ -392,6 +394,10 @@ public final class EmulationActivity extends AppCompatActivity
|
|||
return;
|
||||
}
|
||||
|
||||
case R.id.menu_refresh_wiimotes:
|
||||
NativeLibrary.RefreshWiimotes();
|
||||
return;
|
||||
|
||||
// Screenshot capturing
|
||||
case R.id.menu_emulation_screenshot:
|
||||
NativeLibrary.SaveScreenShot();
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.hardware.usb.UsbConfiguration;
|
||||
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 org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.services.USBPermService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class Java_WiimoteAdapter
|
||||
{
|
||||
final static int MAX_PAYLOAD = 23;
|
||||
final static int MAX_WIIMOTES = 4;
|
||||
final static int TIMEOUT = 200;
|
||||
final static short NINTENDO_VENDOR_ID = 0x057e;
|
||||
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];
|
||||
|
||||
public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD];
|
||||
|
||||
private static void RequestPermission()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = (UsbDevice) pair.getValue();
|
||||
if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID)
|
||||
{
|
||||
if (!manager.hasPermission(dev))
|
||||
{
|
||||
Log.warning("Requesting permission for Wiimote adapter");
|
||||
Intent intent = new Intent();
|
||||
PendingIntent pend_intent;
|
||||
intent.setClass(NativeLibrary.sEmulationActivity, USBPermService.class);
|
||||
pend_intent = PendingIntent.getService(NativeLibrary.sEmulationActivity, 0, intent, 0);
|
||||
manager.requestPermission(dev, pend_intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean QueryAdapter()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = (UsbDevice) pair.getValue();
|
||||
if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID)
|
||||
{
|
||||
if (manager.hasPermission(dev))
|
||||
return true;
|
||||
else
|
||||
RequestPermission();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int Input(int index)
|
||||
{
|
||||
return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT);
|
||||
}
|
||||
|
||||
public static int Output(int index, byte[] buf, int size)
|
||||
{
|
||||
byte report_number = buf[0];
|
||||
|
||||
// Remove the report number from the buffer
|
||||
buf = Arrays.copyOfRange(buf, 1, buf.length);
|
||||
size--;
|
||||
|
||||
final int LIBUSB_REQUEST_TYPE_CLASS = (1 << 5);
|
||||
final int LIBUSB_RECIPIENT_INTERFACE = 0x1;
|
||||
final int LIBUSB_ENDPOINT_OUT = 0;
|
||||
|
||||
final int HID_SET_REPORT = 0x9;
|
||||
final int HID_OUTPUT = (2 << 8);
|
||||
|
||||
int write = usb_con.controlTransfer(
|
||||
LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
|
||||
HID_SET_REPORT,
|
||||
HID_OUTPUT | report_number,
|
||||
index,
|
||||
buf, size,
|
||||
1000);
|
||||
|
||||
if (write < 0)
|
||||
return -1;
|
||||
|
||||
return write + 1;
|
||||
}
|
||||
|
||||
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)
|
||||
return true;
|
||||
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = (UsbDevice) pair.getValue();
|
||||
if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID)
|
||||
{
|
||||
if (manager.hasPermission(dev))
|
||||
{
|
||||
usb_con = manager.openDevice(dev);
|
||||
UsbConfiguration conf = dev.getConfiguration(0);
|
||||
|
||||
Log.info("Number of configurations: " + dev.getConfigurationCount());
|
||||
Log.info("Number of Interfaces: " + dev.getInterfaceCount());
|
||||
Log.info("Number of Interfaces from conf: " + conf.getInterfaceCount());
|
||||
|
||||
// Sometimes the interface count is returned as zero.
|
||||
// Means the device needs to be unplugged and plugged back in again
|
||||
if (dev.getInterfaceCount() > 0)
|
||||
{
|
||||
for (int i = 0; i < MAX_WIIMOTES; ++i)
|
||||
{
|
||||
// One interface per Wiimote
|
||||
usb_intf[i] = dev.getInterface(i);
|
||||
usb_con.claimInterface(usb_intf[i], true);
|
||||
|
||||
// One endpoint per Wiimote. 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());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX: Message that the device was found, but it needs to be unplugged and plugged back in?
|
||||
usb_con.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -59,6 +59,11 @@
|
|||
android:text="@string/settings"
|
||||
style="@style/InGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_refresh_wiimotes"
|
||||
android:text="@string/emulation_refresh_wiimotes"
|
||||
style="@style/InGameMenuOption"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/menu_exit"
|
||||
android:text="@string/overlay_exit_emulation"
|
||||
|
|
|
@ -346,6 +346,7 @@
|
|||
<string name="emulation_toggle_input">Toggle Touch Controls</string>
|
||||
<string name="emulation_quicksave">Quick Save</string>
|
||||
<string name="emulation_quickload">Quick Load</string>
|
||||
<string name="emulation_refresh_wiimotes">Refresh Wiimotes</string>
|
||||
|
||||
<!-- GC Adapter Menu-->
|
||||
<string name="gc_adapter_rumble">Enable Vibration</string>
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "Core/Host.h"
|
||||
#include "Core/State.h"
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/PowerPC/Profiler.h"
|
||||
|
@ -632,6 +633,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
|
|||
Renderer::s_ChangedSurface.Wait();
|
||||
}
|
||||
}
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv *env, jobject obj)
|
||||
{
|
||||
WiimoteReal::Refresh();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *env, jobject obj)
|
||||
{
|
||||
|
@ -646,6 +651,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *
|
|||
UICommon::SetUserDirectory(g_set_userpath);
|
||||
UICommon::Init();
|
||||
|
||||
WiimoteReal::InitAdapterClass();
|
||||
|
||||
// No use running the loop when booting fails
|
||||
if ( BootManager::BootCore( g_filename.c_str() ) )
|
||||
{
|
||||
|
|
|
@ -256,6 +256,8 @@ elseif(UNIX)
|
|||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND BLUEZ_FOUND)
|
||||
set(SRCS ${SRCS} HW/WiimoteReal/IONix.cpp)
|
||||
set(LIBS ${LIBS} bluetooth)
|
||||
elseif(ANDROID)
|
||||
set(SRCS ${SRCS} HW/WiimoteReal/IOAndroid.cpp)
|
||||
else()
|
||||
set(SRCS ${SRCS} HW/WiimoteReal/IODummy.cpp)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
|
||||
// Global java_vm class
|
||||
extern JavaVM* g_java_vm;
|
||||
|
||||
namespace WiimoteReal
|
||||
{
|
||||
|
||||
// Java classes
|
||||
static jclass s_adapter_class;
|
||||
|
||||
class WiimoteAndroid final : public Wiimote
|
||||
{
|
||||
public:
|
||||
WiimoteAndroid(int index);
|
||||
~WiimoteAndroid() override;
|
||||
|
||||
protected:
|
||||
bool ConnectInternal() override;
|
||||
void DisconnectInternal() override;
|
||||
bool IsConnected() const override;
|
||||
void IOWakeup() {}
|
||||
int IORead(u8* buf) override;
|
||||
int IOWrite(u8 const* buf, size_t len) override;
|
||||
|
||||
private:
|
||||
int m_mayflash_index;
|
||||
bool is_connected = true;
|
||||
|
||||
JNIEnv* m_env;
|
||||
|
||||
jmethodID m_input_func;
|
||||
jmethodID m_output_func;
|
||||
|
||||
jbyteArray m_java_wiimote_payload;
|
||||
};
|
||||
|
||||
WiimoteScanner::WiimoteScanner()
|
||||
{}
|
||||
|
||||
WiimoteScanner::~WiimoteScanner()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::Update()
|
||||
{}
|
||||
|
||||
void WiimoteScanner::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wiimote*& found_board)
|
||||
{
|
||||
found_wiimotes.clear();
|
||||
found_board = nullptr;
|
||||
|
||||
NOTICE_LOG(WIIMOTE, "Finding Wiimotes");
|
||||
|
||||
JNIEnv* env;
|
||||
int get_env_status = g_java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
||||
|
||||
if (get_env_status == JNI_EDETACHED)
|
||||
g_java_vm->AttachCurrentThread(&env, nullptr);
|
||||
|
||||
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))
|
||||
{
|
||||
for (int i = 0; i < MAX_WIIMOTES; ++i)
|
||||
found_wiimotes.emplace_back(new WiimoteAndroid(i));
|
||||
}
|
||||
|
||||
if (get_env_status == JNI_EDETACHED)
|
||||
g_java_vm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
bool WiimoteScanner::IsReady() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
WiimoteAndroid::WiimoteAndroid(int index)
|
||||
: Wiimote(), m_mayflash_index(index)
|
||||
{
|
||||
}
|
||||
|
||||
WiimoteAndroid::~WiimoteAndroid()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
// Connect to a Wiimote with a known address.
|
||||
bool WiimoteAndroid::ConnectInternal()
|
||||
{
|
||||
g_java_vm->AttachCurrentThread(&m_env, nullptr);
|
||||
|
||||
jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B");
|
||||
jobjectArray payload_object = reinterpret_cast<jobjectArray>(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");
|
||||
|
||||
is_connected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WiimoteAndroid::DisconnectInternal()
|
||||
{
|
||||
g_java_vm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
bool WiimoteAndroid::IsConnected() const
|
||||
{
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
// positive = read packet
|
||||
// negative = didn't read packet
|
||||
// zero = error
|
||||
int WiimoteAndroid::IORead(u8* buf)
|
||||
{
|
||||
int read_size = m_env->CallStaticIntMethod(s_adapter_class, m_input_func, m_mayflash_index);
|
||||
jbyte* java_data = m_env->GetByteArrayElements(m_java_wiimote_payload, nullptr);
|
||||
memcpy(buf + 1, java_data, std::min(MAX_PAYLOAD - 1, read_size));
|
||||
buf[0] = 0xA1;
|
||||
m_env->ReleaseByteArrayElements(m_java_wiimote_payload, java_data, 0);
|
||||
return read_size <= 0 ? read_size : read_size + 1;
|
||||
}
|
||||
|
||||
|
||||
int WiimoteAndroid::IOWrite(u8 const* buf, size_t len)
|
||||
{
|
||||
jbyteArray output_array = m_env->NewByteArray(len);
|
||||
jbyte* output = m_env->GetByteArrayElements(output_array, nullptr);
|
||||
memcpy(output, buf, len);
|
||||
m_env->ReleaseByteArrayElements(output_array, output, 0);
|
||||
int written = m_env->CallStaticIntMethod(s_adapter_class, m_output_func, m_mayflash_index, output_array, len);
|
||||
m_env->DeleteLocalRef(output_array);
|
||||
return written;
|
||||
}
|
||||
|
||||
void InitAdapterClass()
|
||||
{
|
||||
JNIEnv* env;
|
||||
g_java_vm->AttachCurrentThread(&env, nullptr);
|
||||
|
||||
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter");
|
||||
s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class));
|
||||
}
|
||||
|
||||
}
|
|
@ -161,4 +161,8 @@ void ChangeWiimoteSource(unsigned int index, int source);
|
|||
bool IsValidBluetoothName(const std::string& name);
|
||||
bool IsBalanceBoardName(const std::string& name);
|
||||
|
||||
#ifdef ANDROID
|
||||
void InitAdapterClass();
|
||||
#endif
|
||||
|
||||
} // WiimoteReal
|
||||
|
|
Loading…
Reference in New Issue