Merge pull request #4090 from leoetlino/hidapi

WiimoteReal: Add a hidapi IO implementation
This commit is contained in:
shuffle2
2016-10-03 03:20:17 -07:00
committed by GitHub
92 changed files with 11364 additions and 454 deletions

View File

@ -263,6 +263,9 @@ elseif(UNIX)
set(SRCS ${SRCS} HW/WiimoteReal/IOAndroid.cpp)
endif()
endif()
if(HIDAPI_FOUND)
set(SRCS ${SRCS} HW/WiimoteReal/IOhidapi.cpp)
endif(HIDAPI_FOUND)
if(PORTAUDIO_FOUND)
set(LIBS ${LIBS} portaudio)

View File

@ -7,6 +7,7 @@
#ifdef ANDROID
#include <jni.h>
#include "Common/StringUtil.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
namespace WiimoteReal
@ -16,7 +17,7 @@ class WiimoteAndroid final : public Wiimote
public:
WiimoteAndroid(int index);
~WiimoteAndroid() override;
std::string GetId() const override { return "Android " + StringFromInt(m_mayflash_index); }
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;

View File

@ -14,14 +14,6 @@
namespace WiimoteReal
{
// This is used to store the Bluetooth address of connected Wiimotes,
// so we can ignore Wiimotes that are already connected.
static std::vector<std::string> s_known_addrs;
static bool IsNewWiimote(const std::string& addr)
{
return std::find(s_known_addrs.begin(), s_known_addrs.end(), addr) == s_known_addrs.end();
}
WiimoteScannerLinux::WiimoteScannerLinux() : m_device_id(-1), m_device_sock(-1)
{
// Get the id of the first Bluetooth device.
@ -90,7 +82,7 @@ void WiimoteScannerLinux::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wi
}
NOTICE_LOG(WIIMOTE, "device name %s", name);
if (!IsValidBluetoothName(name))
if (!IsValidDeviceName(name))
continue;
char bdaddr_str[18] = {};
@ -100,7 +92,6 @@ void WiimoteScannerLinux::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wi
continue;
// Found a new device
s_known_addrs.push_back(bdaddr_str);
Wiimote* wm = new WiimoteLinux(scan_infos[i].bdaddr);
if (IsBalanceBoardName(name))
{
@ -180,10 +171,6 @@ void WiimoteLinux::DisconnectInternal()
m_cmd_sock = -1;
m_int_sock = -1;
char bdaddr_str[18] = {};
ba2str(&m_bdaddr, bdaddr_str);
s_known_addrs.erase(std::remove(s_known_addrs.begin(), s_known_addrs.end(), bdaddr_str),
s_known_addrs.end());
}
bool WiimoteLinux::IsConnected() const

View File

@ -16,6 +16,12 @@ class WiimoteLinux final : public Wiimote
public:
WiimoteLinux(bdaddr_t bdaddr);
~WiimoteLinux() override;
std::string GetId() const override
{
char bdaddr_str[18] = {};
ba2str(&m_bdaddr, bdaddr_str);
return bdaddr_str;
}
protected:
bool ConnectInternal() override;

View File

@ -25,13 +25,9 @@
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "Core/HW/WiimoteReal/IOWin.h"
//#define AUTHENTICATE_WIIMOTES
#define SHARE_WRITE_WIIMOTES
// Create func_t function pointer type and declare a nullptr-initialized static variable of that
// type named "pfunc".
#define DYN_FUNC_DECLARE(func) \
@ -64,11 +60,6 @@ static bool s_loaded_ok = false;
std::unordered_map<BTH_ADDR, std::time_t> g_connect_times;
#ifdef SHARE_WRITE_WIIMOTES
std::unordered_set<std::basic_string<TCHAR>> g_connected_wiimotes;
std::mutex g_connected_wiimotes_lock;
#endif
#define DYN_FUNC_UNLOAD(func) p##func = nullptr;
// Attempt to load the function from the given module handle.
@ -344,6 +335,61 @@ static WinWriteMethod GetInitialWriteMethod(bool IsUsingToshibaStack)
WWM_WRITE_FILE_ACTUAL_REPORT_SIZE);
}
static int WriteToHandle(HANDLE& dev_handle, WinWriteMethod& method, const u8* buf, size_t size)
{
OVERLAPPED hid_overlap_write = OVERLAPPED();
hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
DWORD written = 0;
IOWrite(dev_handle, hid_overlap_write, method, buf, size, &written);
CloseHandle(hid_overlap_write.hEvent);
return written;
}
static int ReadFromHandle(HANDLE& dev_handle, u8* buf)
{
OVERLAPPED hid_overlap_read = OVERLAPPED();
hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
const int read = IORead(dev_handle, hid_overlap_read, buf, 1);
CloseHandle(hid_overlap_read.hEvent);
return read;
}
static bool IsWiimote(const std::basic_string<TCHAR>& device_path, WinWriteMethod& method)
{
HANDLE dev_handle = CreateFile(device_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr);
if (dev_handle == INVALID_HANDLE_VALUE)
return false;
u8 buf[MAX_PAYLOAD];
u8 const req_status_report[] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_REQUEST_STATUS, 0};
int invalid_report_count = 0;
int rc = WriteToHandle(dev_handle, method, req_status_report, sizeof(req_status_report));
while (rc > 0)
{
rc = ReadFromHandle(dev_handle, buf);
if (rc <= 0)
break;
switch (buf[1])
{
case WM_STATUS_REPORT:
return true;
default:
WARN_LOG(WIIMOTE, "IsWiimote(): Received unexpected report %02x", buf[1]);
invalid_report_count++;
// If we receive over 15 invalid reports, then this is probably not a Wiimote.
if (invalid_report_count > 15)
return false;
}
}
return false;
}
// Find and connect Wiimotes.
// Does not replace already found Wiimotes even if they are disconnected.
// wm is an array of max_wiimotes Wiimotes
@ -389,27 +435,21 @@ void WiimoteScannerWindows::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
&device_info_data))
{
std::basic_string<TCHAR> device_path(detail_data->DevicePath);
bool real_wiimote = false;
bool is_bb = false;
bool IsUsingToshibaStack = CheckForToshibaStack(device_info_data.DevInst);
WinWriteMethod write_method = GetInitialWriteMethod(IsUsingToshibaStack);
CheckDeviceType(device_path, write_method, real_wiimote, is_bb);
if (real_wiimote)
if (!IsNewWiimote(UTF16ToUTF8(device_path)) || !IsWiimote(device_path, write_method))
{
Wiimote* wm = new WiimoteWindows(device_path, write_method);
if (is_bb)
{
found_board = wm;
}
else
{
found_wiimotes.push_back(wm);
}
free(detail_data);
continue;
}
auto* wiimote = new WiimoteWindows(device_path, write_method);
if (wiimote->IsBalanceBoard())
found_board = wiimote;
else
found_wiimotes.push_back(wiimote);
}
free(detail_data);
@ -418,173 +458,6 @@ void WiimoteScannerWindows::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
SetupDiDestroyDeviceInfoList(device_info);
}
int CheckDeviceType_Write(HANDLE& dev_handle, WinWriteMethod& write_method, const u8* buf,
size_t size, int attempts)
{
OVERLAPPED hid_overlap_write = OVERLAPPED();
hid_overlap_write.hEvent = CreateEvent(nullptr, true, false, nullptr);
DWORD written = 0;
for (; attempts > 0; --attempts)
{
if (IOWrite(dev_handle, hid_overlap_write, write_method, buf, size, &written))
break;
}
CloseHandle(hid_overlap_write.hEvent);
return written;
}
int CheckDeviceType_Read(HANDLE& dev_handle, u8* buf, int attempts)
{
OVERLAPPED hid_overlap_read = OVERLAPPED();
hid_overlap_read.hEvent = CreateEvent(nullptr, true, false, nullptr);
int read = 0;
for (; attempts > 0; --attempts)
{
read = IORead(dev_handle, hid_overlap_read, buf, 1);
if (read > 0)
break;
}
CloseHandle(hid_overlap_read.hEvent);
return read;
}
// A convoluted way of checking if a device is a Wii Balance Board and if it is a connectible
// Wiimote.
// Because nothing on Windows should be easy.
// (We can't seem to easily identify the Bluetooth device an HID device belongs to...)
void WiimoteScannerWindows::CheckDeviceType(std::basic_string<TCHAR>& devicepath,
WinWriteMethod& write_method, bool& real_wiimote,
bool& is_bb)
{
real_wiimote = false;
is_bb = false;
#ifdef SHARE_WRITE_WIIMOTES
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
if (g_connected_wiimotes.count(devicepath) != 0)
return;
#endif
HANDLE dev_handle = CreateFile(devicepath.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr);
if (dev_handle == INVALID_HANDLE_VALUE)
return;
// enable to only check for official nintendo wiimotes/bb's
bool check_vidpid = false;
HIDD_ATTRIBUTES attrib;
attrib.Size = sizeof(attrib);
if (!check_vidpid || (pHidD_GetAttributes(dev_handle, &attrib) && (attrib.VendorID == 0x057e) &&
(attrib.ProductID == 0x0306)))
{
// max_cycles insures we are never stuck here due to bad coding...
int max_cycles = 20;
u8 buf[MAX_PAYLOAD] = {0};
u8 const req_status_report[] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_REQUEST_STATUS, 0};
// The new way to initialize the extension is by writing 0x55 to 0x(4)A400F0, then writing 0x00
// to 0x(4)A400FB
// 52 16 04 A4 00 F0 01 55
// 52 16 04 A4 00 FB 01 00
u8 const disable_enc_pt1_report[MAX_PAYLOAD] = {
WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xf0, 0x01, 0x55};
u8 const disable_enc_pt2_report[MAX_PAYLOAD] = {
WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xfb, 0x01, 0x00};
CheckDeviceType_Write(dev_handle, write_method, disable_enc_pt1_report,
sizeof(disable_enc_pt1_report), 1);
CheckDeviceType_Write(dev_handle, write_method, disable_enc_pt2_report,
sizeof(disable_enc_pt2_report), 1);
int rc = CheckDeviceType_Write(dev_handle, write_method, req_status_report,
sizeof(req_status_report), 1);
while (rc > 0 && --max_cycles > 0)
{
if ((rc = CheckDeviceType_Read(dev_handle, buf, 1)) <= 0)
{
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Read failed...");
break;
}
switch (buf[1])
{
case WM_STATUS_REPORT:
{
real_wiimote = true;
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Status Report");
wm_status_report* wsr = (wm_status_report*)&buf[2];
if (wsr->extension)
{
// Wiimote with extension, we ask it what kind.
u8 read_ext[MAX_PAYLOAD] = {0};
read_ext[0] = WM_SET_REPORT | WM_BT_OUTPUT;
read_ext[1] = WM_READ_DATA;
// Extension type register.
*(u32*)&read_ext[2] = Common::swap32(0x4a400fa);
// Size.
*(u16*)&read_ext[6] = Common::swap16(6);
rc = CheckDeviceType_Write(dev_handle, write_method, read_ext, 8, 1);
}
else
{
// Normal Wiimote, exit while and be happy.
rc = -1;
}
break;
}
case WM_ACK_DATA:
{
real_wiimote = true;
// wm_acknowledge * wm = (wm_acknowledge*)&buf[2];
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Ack Error: %X ReportID: %X", wm->errorID,
// wm->reportID);
break;
}
case WM_READ_DATA_REPLY:
{
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: Got Data Reply");
wm_read_data_reply* wrdr = (wm_read_data_reply*)&buf[2];
// Check if it has returned what we asked.
if (Common::swap16(wrdr->address) == 0x00fa)
{
real_wiimote = true;
// 0x020420A40000ULL means balance board.
u64 ext_type = (*(u64*)&wrdr->data[0]);
// DEBUG_LOG(WIIMOTE,
// "CheckDeviceType: GOT EXT TYPE %llX",
// ext_type);
is_bb = (ext_type == 0x020420A40000ULL);
}
else
{
ERROR_LOG(WIIMOTE, "CheckDeviceType: GOT UNREQUESTED ADDRESS %X",
Common::swap16(wrdr->address));
}
// force end
rc = -1;
break;
}
default:
{
// We let read try again incase there is another packet waiting.
// DEBUG_LOG(WIIMOTE, "CheckDeviceType: GOT UNKNOWN REPLY: %X", buf[1]);
break;
}
}
}
}
CloseHandle(dev_handle);
}
bool WiimoteScannerWindows::IsReady() const
{
if (!s_loaded_ok)
@ -615,21 +488,12 @@ bool WiimoteScannerWindows::IsReady() const
bool WiimoteWindows::ConnectInternal()
{
if (IsConnected())
return false;
return true;
#ifdef SHARE_WRITE_WIIMOTES
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
if (g_connected_wiimotes.count(m_devicepath) != 0)
if (!IsNewWiimote(UTF16ToUTF8(m_devicepath)))
return false;
auto const open_flags = FILE_SHARE_READ | FILE_SHARE_WRITE;
#else
// Having no FILE_SHARE_WRITE disallows us from connecting to the same Wiimote twice.
// (And disallows using Wiimotes in use by other programs)
// This is what "WiiYourself" does.
// Apparently this doesn't work for everyone. It might be their fault.
auto const open_flags = FILE_SHARE_READ;
#endif
m_dev_handle = CreateFile(m_devicepath.c_str(), GENERIC_READ | GENERIC_WRITE, open_flags, nullptr,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
@ -665,18 +529,15 @@ bool WiimoteWindows::ConnectInternal()
}
#endif
// TODO: thread isn't started here now, do this elsewhere
// This isn't as drastic as it sounds, since the process in which the threads
// reside is normal priority. Needed for keeping audio reports at a decent rate
/*
if (!SetThreadPriority(m_wiimote_thread.native_handle(), THREAD_PRIORITY_TIME_CRITICAL))
{
ERROR_LOG(WIIMOTE, "Failed to set Wiimote thread priority");
}
*/
#ifdef SHARE_WRITE_WIIMOTES
g_connected_wiimotes.insert(m_devicepath);
#endif
// TODO: thread isn't started here now, do this elsewhere
// This isn't as drastic as it sounds, since the process in which the threads
// reside is normal priority. Needed for keeping audio reports at a decent rate
/*
if (!SetThreadPriority(m_wiimote_thread.native_handle(), THREAD_PRIORITY_TIME_CRITICAL))
{
ERROR_LOG(WIIMOTE, "Failed to set Wiimote thread priority");
}
*/
return true;
}
@ -688,11 +549,6 @@ void WiimoteWindows::DisconnectInternal()
CloseHandle(m_dev_handle);
m_dev_handle = nullptr;
#ifdef SHARE_WRITE_WIIMOTES
std::lock_guard<std::mutex> lk(g_connected_wiimotes_lock);
g_connected_wiimotes.erase(m_devicepath);
#endif
}
WiimoteWindows::WiimoteWindows(const std::basic_string<TCHAR>& path,
@ -983,7 +839,7 @@ void ProcessWiimotes(bool new_scan, T& callback)
DEBUG_LOG(WIIMOTE, "Authenticated %i connected %i remembered %i ", btdi.fAuthenticated,
btdi.fConnected, btdi.fRemembered);
if (IsValidBluetoothName(UTF16ToUTF8(btdi.szName)))
if (IsValidDeviceName(UTF16ToUTF8(btdi.szName)))
{
callback(hRadio, radioInfo, btdi);
}

View File

@ -7,6 +7,7 @@
#ifdef _WIN32
#include <windows.h>
#include "Common/StringUtil.h"
#include "Core/HW/WiimoteEmu/WiimoteHid.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
@ -25,7 +26,7 @@ class WiimoteWindows final : public Wiimote
public:
WiimoteWindows(const std::basic_string<TCHAR>& path, WinWriteMethod initial_write_method);
~WiimoteWindows() override;
std::string GetId() const override { return UTF16ToUTF8(m_devicepath); }
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;
@ -50,10 +51,6 @@ public:
bool IsReady() const override;
void FindWiimotes(std::vector<Wiimote*>&, Wiimote*&) override;
void Update() override;
private:
void CheckDeviceType(std::basic_string<TCHAR>& devicepath, WinWriteMethod& write_method,
bool& real_wiimote, bool& is_bb);
};
}

View File

@ -18,16 +18,6 @@ public:
void FindWiimotes(std::vector<Wiimote*>&, Wiimote*&) override;
void Update() override{}; // not needed
};
class WiimoteScannerDarwinHID final : public WiimoteScannerBackend
{
public:
WiimoteScannerDarwinHID() = default;
~WiimoteScannerDarwinHID() override = default;
bool IsReady() const override;
void FindWiimotes(std::vector<Wiimote*>&, Wiimote*&) override;
void Update() override{}; // not needed
};
}
#else
@ -35,6 +25,5 @@ public:
namespace WiimoteReal
{
using WiimoteScannerDarwin = WiimoteScannerDummy;
using WiimoteScannerDarwinHID = WiimoteScannerDummy;
}
#endif

View File

@ -66,7 +66,7 @@ void WiimoteScannerDarwin::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
for (int i = 0; i < found_devices; i++)
{
IOBluetoothDevice* dev = [en nextObject];
if (!IsValidBluetoothName([[dev name] UTF8String]))
if (!IsValidDeviceName([[dev name] UTF8String]))
continue;
Wiimote* wm = new WiimoteDarwin([dev retain]);
@ -92,55 +92,6 @@ bool WiimoteScannerDarwin::IsReady() const
return true;
}
void WiimoteScannerDarwinHID::FindWiimotes(std::vector<Wiimote*>& found_wiimotes,
Wiimote*& found_board)
{
found_board = nullptr;
IOHIDManagerRef hid = IOHIDManagerCreate(NULL, kIOHIDOptionsTypeNone);
bool hidFailed = CFGetTypeID(hid) != IOHIDManagerGetTypeID();
if (hidFailed)
{
CFRelease(hid);
WARN_LOG(WIIMOTE, "No HID manager");
return;
}
NSArray* criteria = @[
@{ @kIOHIDVendorIDKey : @0x057e,
@kIOHIDProductIDKey : @0x0306 },
@{ @kIOHIDVendorIDKey : @0x057e,
@kIOHIDProductIDKey : @0x0330 },
];
IOHIDManagerSetDeviceMatchingMultiple(hid, (CFArrayRef)criteria);
if (IOHIDManagerOpen(hid, kIOHIDOptionsTypeNone) != kIOReturnSuccess)
WARN_LOG(WIIMOTE, "Failed to open HID Manager");
CFSetRef devices = IOHIDManagerCopyDevices(hid);
if (devices)
{
int found_devices = CFSetGetCount(devices);
if (found_devices)
{
NOTICE_LOG(WIIMOTE, "Found %i HID devices", found_devices);
IOHIDDeviceRef values[found_devices];
CFSetGetValues(devices, reinterpret_cast<const void**>(&values));
for (int i = 0; i < found_devices; i++)
{
Wiimote* wm = new WiimoteDarwinHid(values[i]);
found_wiimotes.push_back(wm);
}
}
}
CFRelease(hid);
}
bool WiimoteScannerDarwinHID::IsReady() const
{
// TODO: only return true when !hidFailed
return true;
}
WiimoteDarwin::WiimoteDarwin(IOBluetoothDevice* device) : m_btd(device)
{
m_inputlen = 0;
@ -296,117 +247,6 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
ERROR_LOG(WIIMOTE, "Could not release power management assertion: %08x", ret);
}
}
WiimoteDarwinHid::WiimoteDarwinHid(IOHIDDeviceRef device) : m_device(device)
{
CFRetain(m_device);
m_connected = false;
m_report_buffer = Report(MAX_PAYLOAD);
}
WiimoteDarwinHid::~WiimoteDarwinHid()
{
Shutdown();
CFRelease(m_device);
}
bool WiimoteDarwinHid::ConnectInternal()
{
IOReturn ret = IOHIDDeviceOpen(m_device, kIOHIDOptionsTypeNone);
m_connected = ret == kIOReturnSuccess;
if (m_connected)
{
IOHIDDeviceScheduleWithRunLoop(m_device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDDeviceRegisterInputReportCallback(m_device, m_report_buffer.data() + 1, MAX_PAYLOAD - 1,
&WiimoteDarwinHid::ReportCallback, this);
IOHIDDeviceRegisterRemovalCallback(m_device, &WiimoteDarwinHid::RemoveCallback, this);
NOTICE_LOG(WIIMOTE, "Connected to Wiimote %i", m_index + 1);
}
else
{
ERROR_LOG(WIIMOTE, "Could not open IOHID Wiimote: %08x", ret);
}
return m_connected;
}
void WiimoteDarwinHid::DisconnectInternal()
{
IOHIDDeviceUnscheduleFromRunLoop(m_device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOWakeup();
IOReturn ret = IOHIDDeviceClose(m_device, kIOHIDOptionsTypeNone);
if (ret != kIOReturnSuccess)
ERROR_LOG(WIIMOTE, "Error closing IOHID Wiimote: %08x", ret);
if (!IsConnected())
return;
NOTICE_LOG(WIIMOTE, "Disconnecting Wiimote %i", m_index + 1);
m_buffered_reports.Clear();
m_connected = false;
}
bool WiimoteDarwinHid::IsConnected() const
{
return m_connected;
}
void WiimoteDarwinHid::IOWakeup()
{
m_interrupted.store(true);
CFRunLoopStop(CFRunLoopGetCurrent());
}
int WiimoteDarwinHid::IORead(u8* buf)
{
Report rpt;
m_interrupted.store(false);
while (m_buffered_reports.Empty() && !m_interrupted.load())
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
if (m_buffered_reports.Pop(rpt))
{
memcpy(buf, rpt.data(), rpt.size());
return rpt.size();
}
return -1;
}
int WiimoteDarwinHid::IOWrite(u8 const* buf, size_t len)
{
IOReturn ret = IOHIDDeviceSetReport(m_device, kIOHIDReportTypeOutput, buf[1], buf + 1, len - 1);
if (ret != kIOReturnSuccess)
{
ERROR_LOG(WIIMOTE, "Error writing to Wiimote: %08x", ret);
return 0;
}
return len;
}
void WiimoteDarwinHid::QueueBufferReport(int length)
{
Report rpt(m_report_buffer);
rpt[0] = 0xa1;
rpt.resize(length + 1);
m_buffered_reports.Push(std::move(rpt));
}
void WiimoteDarwinHid::ReportCallback(void* context, IOReturn result, void*, IOHIDReportType type,
u32 report_id, u8* report, CFIndex report_length)
{
WiimoteDarwinHid* wm = static_cast<WiimoteDarwinHid*>(context);
report[0] = report_id;
wm->QueueBufferReport(report_length);
}
void WiimoteDarwinHid::RemoveCallback(void* context, IOReturn result, void*)
{
WiimoteDarwinHid* wm = static_cast<WiimoteDarwinHid*>(context);
wm->DisconnectInternal();
}
} // namespace
@implementation SearchBT

View File

@ -4,8 +4,6 @@
#pragma once
#include "Core/HW/WiimoteReal/WiimoteReal.h"
// Work around an Apple bug: for some reason, IOBluetooth.h errors on
// inclusion in Mavericks, but only in Objective-C++ C++11 mode. I filed
// this as <rdar://15312520>; in the meantime...
@ -13,10 +11,11 @@
#undef NS_ENUM_AVAILABLE
#define NS_ENUM_AVAILABLE(...)
// end hack
#include <IOBluetooth/IOBluetooth.h>
#include <IOKit/hid/IOHIDManager.h>
#import <IOBluetooth/IOBluetooth.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include "Core/HW/WiimoteReal/WiimoteReal.h"
namespace WiimoteReal
{
class WiimoteDarwin final : public Wiimote
@ -24,7 +23,7 @@ class WiimoteDarwin final : public Wiimote
public:
WiimoteDarwin(IOBluetoothDevice* device);
~WiimoteDarwin() override;
std::string GetId() const override { return [[m_btd addressString] UTF8String]; }
// These are not protected/private because ConnectBT needs them.
void DisconnectInternal() override;
IOBluetoothDevice* m_btd;
@ -47,31 +46,4 @@ private:
CFRunLoopRef m_wiimote_thread_run_loop;
IOPMAssertionID m_pm_assertion;
};
class WiimoteDarwinHid final : public Wiimote
{
public:
WiimoteDarwinHid(IOHIDDeviceRef device);
~WiimoteDarwinHid() override;
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;
bool IsConnected() const override;
void IOWakeup() override;
int IORead(u8* buf) override;
int IOWrite(u8 const* buf, size_t len) override;
private:
static void ReportCallback(void* context, IOReturn result, void* sender, IOHIDReportType type,
u32 reportID, u8* report, CFIndex reportLength);
static void RemoveCallback(void* context, IOReturn result, void* sender);
void QueueBufferReport(int length);
IOHIDDeviceRef m_device;
bool m_connected;
std::atomic<bool> m_interrupted;
Report m_report_buffer;
Common::FifoQueue<Report> m_buffered_reports;
};
} // namespace WiimoteReal

View File

@ -0,0 +1,146 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include "Common/Assert.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/HW/WiimoteEmu/WiimoteHid.h"
#include "Core/HW/WiimoteReal/IOhidapi.h"
static bool IsDeviceUsable(const std::string& device_path)
{
hid_device* handle = hid_open_path(device_path.c_str());
if (handle == nullptr)
{
ERROR_LOG(WIIMOTE, "Could not connect to Wiimote at \"%s\". "
"Do you have permission to access the device?",
device_path.c_str());
return false;
}
// Some third-party adapters (DolphinBar) always expose all four Wiimotes as HIDs
// even when they are not connected, which causes an endless error loop when we try to use them.
// Try to write a report to the device to see if this Wiimote is really usable.
static const u8 report[] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_REQUEST_STATUS, 0};
const int result = hid_write(handle, report, sizeof(report));
// The DolphinBar uses EPIPE to signal the absence of a Wiimote connected to this HID.
if (result == -1 && errno != EPIPE)
ERROR_LOG(WIIMOTE, "Couldn't write to Wiimote at \"%s\".", device_path.c_str());
hid_close(handle);
return result != -1;
}
namespace WiimoteReal
{
WiimoteScannerHidapi::WiimoteScannerHidapi()
{
int ret = hid_init();
_assert_msg_(WIIMOTE, ret == 0, "Couldn't initialise hidapi.");
}
WiimoteScannerHidapi::~WiimoteScannerHidapi()
{
if (hid_exit() == -1)
ERROR_LOG(WIIMOTE, "Failed to clean up hidapi.");
}
bool WiimoteScannerHidapi::IsReady() const
{
return true;
}
void WiimoteScannerHidapi::FindWiimotes(std::vector<Wiimote*>& wiimotes, Wiimote*& board)
{
hid_device_info* list = hid_enumerate(0x0, 0x0);
for (hid_device_info* device = list; device; device = device->next)
{
const std::string name = device->product_string ? UTF16ToUTF8(device->product_string) : "";
const bool is_wiimote =
IsValidDeviceName(name) || (device->vendor_id == 0x057e &&
(device->product_id == 0x0306 || device->product_id == 0x0330));
if (!is_wiimote || !IsNewWiimote(device->path) || !IsDeviceUsable(device->path))
continue;
auto* wiimote = new WiimoteHidapi(device->path);
const bool is_balance_board = IsBalanceBoardName(name) || wiimote->IsBalanceBoard();
if (is_balance_board)
board = wiimote;
else
wiimotes.push_back(wiimote);
NOTICE_LOG(WIIMOTE, "Found %s at %s: %ls %ls (%04hx:%04hx)",
is_balance_board ? "balance board" : "Wiimote", device->path,
device->manufacturer_string, device->product_string, device->vendor_id,
device->product_id);
}
hid_free_enumeration(list);
}
WiimoteHidapi::WiimoteHidapi(const std::string& device_path) : m_device_path(device_path)
{
}
WiimoteHidapi::~WiimoteHidapi()
{
Shutdown();
}
bool WiimoteHidapi::ConnectInternal()
{
if (m_handle != nullptr)
return true;
m_handle = hid_open_path(m_device_path.c_str());
if (m_handle == nullptr)
{
ERROR_LOG(WIIMOTE, "Could not connect to Wiimote at \"%s\". "
"Do you have permission to access the device?",
m_device_path.c_str());
}
return m_handle != nullptr;
}
void WiimoteHidapi::DisconnectInternal()
{
hid_close(m_handle);
m_handle = nullptr;
}
bool WiimoteHidapi::IsConnected() const
{
return m_handle != nullptr;
}
int WiimoteHidapi::IORead(u8* buf)
{
int timeout = 200; // ms
int result = hid_read_timeout(m_handle, buf + 1, MAX_PAYLOAD - 1, timeout);
// TODO: If and once we use hidapi across plaforms, change our internal API to clean up this mess.
if (result == -1)
{
ERROR_LOG(WIIMOTE, "Failed to read from %s.", m_device_path.c_str());
return 0; // error
}
if (result == 0)
{
return -1; // didn't read packet
}
buf[0] = WM_SET_REPORT | WM_BT_INPUT;
return result + 1; // number of bytes read
}
int WiimoteHidapi::IOWrite(const u8* buf, size_t len)
{
_dbg_assert_(WIIMOTE, buf[0] == (WM_SET_REPORT | WM_BT_OUTPUT));
int result = hid_write(m_handle, buf + 1, len - 1);
if (result == -1)
{
ERROR_LOG(WIIMOTE, "Failed to write to %s.", m_device_path.c_str());
return 0;
}
return (result == 0) ? 1 : result;
}
}; // WiimoteReal

View File

@ -0,0 +1,50 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#ifdef HAVE_HIDAPI
#include <hidapi.h>
#include "Core/HW/WiimoteReal/WiimoteReal.h"
namespace WiimoteReal
{
class WiimoteHidapi final : public Wiimote
{
public:
explicit WiimoteHidapi(const std::string& device_path);
~WiimoteHidapi() override;
std::string GetId() const override { return m_device_path; }
protected:
bool ConnectInternal() override;
void DisconnectInternal() override;
bool IsConnected() const override;
void IOWakeup() override {}
int IORead(u8* buf) override;
int IOWrite(const u8* buf, size_t len) override;
private:
std::string m_device_path;
hid_device* m_handle = nullptr;
};
class WiimoteScannerHidapi final : public WiimoteScannerBackend
{
public:
WiimoteScannerHidapi();
~WiimoteScannerHidapi();
bool IsReady() const override;
void FindWiimotes(std::vector<Wiimote*>&, Wiimote*&) override;
void Update() override{}; // not needed for hidapi
};
}
#else
#include "Core/HW/WiimoteReal/IODummy.h"
namespace WiimoteReal
{
using WiimoteScannerHidapi = WiimoteScannerDummy;
}
#endif

View File

@ -5,6 +5,7 @@
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <unordered_set>
#include "Core/HW/WiimoteReal/WiimoteReal.h"
@ -21,6 +22,7 @@
#include "Core/HW/WiimoteReal/IOLinux.h"
#include "Core/HW/WiimoteReal/IOWin.h"
#include "Core/HW/WiimoteReal/IOdarwin.h"
#include "Core/HW/WiimoteReal/IOhidapi.h"
#include "Core/Host.h"
#include "InputCommon/InputConfig.h"
@ -36,6 +38,11 @@ void HandleWiimoteDisconnect(int index);
static bool g_real_wiimotes_initialized = false;
// This is used to store connected Wiimotes' IDs, so we don't connect
// more than once to the same device.
static std::unordered_set<std::string> s_known_ids;
static std::mutex s_known_ids_mutex;
std::mutex g_wiimotes_mutex;
Wiimote* g_wiimotes[MAX_BBMOTES];
@ -217,10 +224,11 @@ void Wiimote::Read()
}
}
void Wiimote::Write()
bool Wiimote::Write()
{
// nothing written, but this is not an error
if (m_write_reports.Empty())
return;
return true;
Report const& rpt = m_write_reports.Front();
@ -230,12 +238,79 @@ void Wiimote::Write()
Socket.send((char*)rpt.data(), rpt.size(), sf::IpAddress::LocalHost,
SConfig::GetInstance().iBBDumpPort);
}
IOWrite(rpt.data(), rpt.size());
int ret = IOWrite(rpt.data(), rpt.size());
m_write_reports.Pop();
if (!m_write_reports.Empty())
IOWakeup();
return ret != 0;
}
bool Wiimote::IsBalanceBoard()
{
if (!ConnectInternal())
return false;
// Initialise the extension by writing 0x55 to 0xa400f0, then writing 0x00 to 0xa400fb.
static const u8 init_extension_rpt1[MAX_PAYLOAD] = {
WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xf0, 0x01, 0x55};
static const u8 init_extension_rpt2[MAX_PAYLOAD] = {
WM_SET_REPORT | WM_BT_OUTPUT, WM_WRITE_DATA, 0x04, 0xa4, 0x00, 0xfb, 0x01, 0x00};
static const u8 status_report[] = {WM_SET_REPORT | WM_BT_OUTPUT, WM_REQUEST_STATUS, 0};
if (!IOWrite(init_extension_rpt1, sizeof(init_extension_rpt1)) ||
!IOWrite(init_extension_rpt2, sizeof(init_extension_rpt2)))
{
ERROR_LOG(WIIMOTE, "IsBalanceBoard(): Failed to initialise extension.");
return false;
}
int ret = IOWrite(status_report, sizeof(status_report));
u8 buf[MAX_PAYLOAD];
while (ret != 0)
{
ret = IORead(buf);
if (ret == -1)
continue;
switch (buf[1])
{
case WM_STATUS_REPORT:
{
const auto* status = reinterpret_cast<wm_status_report*>(&buf[2]);
// A Balance Board has a Balance Board extension.
if (!status->extension)
return false;
// Read two bytes from 0xa400fe to identify the extension.
static const u8 identify_ext_rpt[] = {
WM_SET_REPORT | WM_BT_OUTPUT, WM_READ_DATA, 0x04, 0xa4, 0x00, 0xfe, 0x02, 0x00};
ret = IOWrite(identify_ext_rpt, sizeof(identify_ext_rpt));
break;
}
case WM_READ_DATA_REPLY:
{
const auto* reply = reinterpret_cast<wm_read_data_reply*>(&buf[2]);
if (Common::swap16(reply->address) != 0x00fe)
{
ERROR_LOG(WIIMOTE, "IsBalanceBoard(): Received unexpected data reply for address %X",
Common::swap16(reply->address));
return false;
}
// A Balance Board ext can be identified by checking for 0x0402.
return reply->data[0] == 0x04 && reply->data[1] == 0x02;
}
case WM_ACK_DATA:
{
const auto* ack = reinterpret_cast<wm_acknowledge*>(&buf[2]);
if (ack->reportID == WM_READ_DATA && ack->errorID != 0x00)
{
WARN_LOG(WIIMOTE, "Failed to read from 0xa400fe, assuming Wiimote is not a Balance Board.");
return false;
}
}
}
}
return false;
}
static bool IsDataReport(const Report& rpt)
@ -499,6 +574,8 @@ void WiimoteScanner::ThreadFunc()
Wiimote* found_board = nullptr;
backend->FindWiimotes(found_wiimotes, found_board);
{
if (!g_real_wiimotes_initialized)
continue;
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::for_each(found_wiimotes.begin(), found_wiimotes.end(), TryToConnectWiimote);
if (found_board)
@ -574,7 +651,11 @@ void Wiimote::ThreadFunc()
m_index + 1);
break;
}
Write();
if (!Write())
{
ERROR_LOG(WIIMOTE, "Wiimote::Write failed. Disconnecting Wiimote %d.", m_index + 1);
break;
}
Read();
}
@ -612,11 +693,12 @@ void Initialize(::Wiimote::InitializeMode init_mode)
{
if (!g_real_wiimotes_initialized)
{
s_known_ids.clear();
g_wiimote_scanner.AddScannerBackend(std::make_unique<WiimoteScannerLinux>());
g_wiimote_scanner.AddScannerBackend(std::make_unique<WiimoteScannerAndroid>());
g_wiimote_scanner.AddScannerBackend(std::make_unique<WiimoteScannerWindows>());
g_wiimote_scanner.AddScannerBackend(std::make_unique<WiimoteScannerDarwin>());
g_wiimote_scanner.AddScannerBackend(std::make_unique<WiimoteScannerDarwinHID>());
g_wiimote_scanner.AddScannerBackend(std::make_unique<WiimoteScannerHidapi>());
g_wiimote_scanner.StartThread();
}
@ -656,6 +738,7 @@ void Stop()
// called when the Dolphin app exits
void Shutdown()
{
g_real_wiimotes_initialized = false;
g_wiimote_scanner.StopThread();
NOTICE_LOG(WIIMOTE, "WiimoteReal::Shutdown");
@ -714,6 +797,8 @@ static bool TryToConnectWiimoteToSlot(Wiimote* wm, unsigned int i)
NOTICE_LOG(WIIMOTE, "Connected to Wiimote %i.", i + 1);
g_wiimotes[i] = wm;
Host_ConnectWiimote(i, true);
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.insert(wm->GetId());
}
return true;
}
@ -748,6 +833,8 @@ void HandleWiimoteDisconnect(int index)
std::swap(wm, g_wiimotes[index]);
if (wm)
{
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
s_known_ids.erase(wm->GetId());
delete wm;
NOTICE_LOG(WIIMOTE, "Disconnected Wiimote %i.", index + 1);
}
@ -810,7 +897,7 @@ void StateChange(EMUSTATE_CHANGE newState)
// TODO: disable/enable auto reporting, maybe
}
bool IsValidBluetoothName(const std::string& name)
bool IsValidDeviceName(const std::string& name)
{
return "Nintendo RVL-CNT-01" == name || "Nintendo RVL-CNT-01-TR" == name ||
IsBalanceBoardName(name);
@ -821,4 +908,11 @@ bool IsBalanceBoardName(const std::string& name)
return "Nintendo RVL-WBC-01" == name;
}
// This is called from the scanner backends (currently on the scanner thread).
bool IsNewWiimote(const std::string& identifier)
{
std::lock_guard<std::mutex> lk(s_known_ids_mutex);
return s_known_ids.count(identifier) == 0;
}
}; // end of namespace

View File

@ -31,6 +31,8 @@ public:
// This needs to be called in derived destructors!
void Shutdown();
virtual std::string GetId() const = 0;
void ControlChannel(const u16 channel, const void* const data, const u32 size);
void InterruptChannel(const u16 channel, const void* const data, const u32 size);
void Update();
@ -39,7 +41,9 @@ public:
const Report& ProcessReadQueue();
void Read();
void Write();
bool Write();
bool IsBalanceBoard();
void StartThread();
void StopThread();
@ -160,8 +164,9 @@ void ConnectOnInput(int _WiimoteNumber);
void StateChange(EMUSTATE_CHANGE newState);
void ChangeWiimoteSource(unsigned int index, int source);
bool IsValidBluetoothName(const std::string& name);
bool IsValidDeviceName(const std::string& name);
bool IsBalanceBoardName(const std::string& name);
bool IsNewWiimote(const std::string& identifier);
#ifdef ANDROID
void InitAdapterClass();

View File

@ -82,6 +82,8 @@ std::string wxStringTranslator(const char*);
CFrame* main_frame = nullptr;
static std::mutex s_init_mutex;
bool DolphinApp::Initialize(int& c, wxChar** v)
{
#if defined HAVE_X11 && HAVE_X11
@ -94,6 +96,7 @@ bool DolphinApp::Initialize(int& c, wxChar** v)
bool DolphinApp::OnInit()
{
std::lock_guard<std::mutex> lk(s_init_mutex);
if (!wxApp::OnInit())
return false;
@ -548,6 +551,7 @@ bool Host_RendererIsFullscreen()
void Host_ConnectWiimote(int wm_idx, bool connect)
{
std::lock_guard<std::mutex> lk(s_init_mutex);
if (connect)
{
wxCommandEvent event(wxEVT_HOST_COMMAND, IDM_FORCE_CONNECT_WIIMOTE1 + wm_idx);