WiimoteReal: Add a hidapi IO implementation

Based on ca0c2efe7ab19c85449d52dd1bf4beec8603bbfe. Credits go to flacs.
However, unlike the original commit, hidapi does not completely replace
the current implementations, so we can still connect Wiimotes with 1+2
(without pairing).

Also, it is only used on Linux and OS X for now. This removes the
advantage of having only one implementation but there is no other
choice: using hidapi on Windows is currently impossible because
hid_write() is implemented in a way that won't work with Wiimotes.

Additionally:
* We now check for the device name in addition to the PID/VID so we can
  support the Balance Board and maybe third-party Wiimotes too. This
  doesn't achieve anything with the DolphinBar but it does with hidraw.
* Added a check to not connect to the same device more than once.
This commit is contained in:
Léo Lam 2016-07-24 17:20:40 +02:00
parent 84467d2ff1
commit 843b030eda
16 changed files with 257 additions and 210 deletions

View File

@ -835,6 +835,33 @@ else()
endif()
list(APPEND LIBS ${ICONV_LIBRARIES})
if(NOT ANDROID)
include(FindHIDAPI OPTIONAL)
find_package(HIDAPI)
if(HIDAPI_FOUND)
message("Using shared ${HIDAPI_LIBRARIES} ${HIDAPI_VERSION}")
include_directories(${HIDAPI_INCLUDE_DIRS})
list(APPEND LIBS ${HIDAPI_LIBRARIES})
else()
include_directories(Externals/hidapi/hidapi)
if(APPLE)
message("Using static hidapi from Externals")
add_subdirectory(Externals/hidapi/mac)
list(APPEND LIBS hidapi)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message("Using static hidapi-hidraw from Externals")
add_subdirectory(Externals/hidapi/linux)
list(APPEND LIBS hidapi-hidraw udev)
else()
message("Using static hidapi-libusb from Externals")
add_subdirectory(Externals/hidapi/libusb)
list(APPEND LIBS hidapi-libusb)
endif()
endif()
set(HIDAPI_FOUND 1)
add_definitions(-DHAVE_HIDAPI=1)
endif()
find_library(OPENSLES_LIBRARIES NAMES OpenSLES)
find_path(OPENSLES_INCLUDE_DIR NAMES SLES/OpenSLES.h)

View File

@ -0,0 +1,9 @@
find_path(HIDAPI_INCLUDE_DIR NAMES hidapi.h PATH_SUFFIXES hidapi)
find_library(HIDAPI_LIBRARY NAMES hidapi hidapi-hidraw hidapi-libusb)
set(HIDAPI_LIBRARIES ${HIDAPI_LIBRARY})
set(HIDAPI_INCLUDE_DIRS ${HIDAPI_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HIDAPI DEFAULT_MSG HIDAPI_LIBRARY HIDAPI_INCLUDE_DIR)
mark_as_advanced(HIDAPI_INCLUDE_DIR HIDAPI_LIBRARY)

View File

@ -1,5 +1,6 @@
#GameCube Controller Adapter
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0337", TAG+="uaccess"
#Mayflash DolphinBar
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0306", TAG+="uaccess"
#Wiimotes or DolphinBar
SUBSYSTEM=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0306", TAG+="uaccess"
SUBSYSTEM=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="0330", TAG+="uaccess"

View File

@ -0,0 +1 @@
add_library(hidapi-libusb hid.c)

1
Externals/hidapi/linux/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_library(hidapi-hidraw hid.c)

1
Externals/hidapi/mac/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
add_library(hidapi hid.c)

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

@ -90,7 +90,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] = {};

View File

@ -983,7 +983,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

@ -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
@ -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,152 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <unordered_set>
#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"
// This is used to store connected Wiimotes' paths, so we don't connect
// more than once to the same device.
static std::unordered_set<std::string> s_known_paths;
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()
{
s_known_paths.clear();
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 || s_known_paths.count(device->path) != 0 || !IsDeviceUsable(device->path))
continue;
const bool is_balance_board = IsBalanceBoardName(name);
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);
Wiimote* wiimote = new WiimoteHidapi(device->path);
if (is_balance_board)
board = wiimote;
else
wiimotes.push_back(wiimote);
}
hid_free_enumeration(list);
}
WiimoteHidapi::WiimoteHidapi(const std::string& device_path) : m_device_path(device_path)
{
s_known_paths.insert(m_device_path);
}
WiimoteHidapi::~WiimoteHidapi()
{
Shutdown();
s_known_paths.erase(m_device_path);
}
bool WiimoteHidapi::ConnectInternal()
{
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());
s_known_paths.erase(m_device_path);
}
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;
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

@ -21,6 +21,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"
@ -616,7 +617,7 @@ void Initialize(::Wiimote::InitializeMode init_mode)
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();
}
@ -810,7 +811,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);

View File

@ -160,7 +160,7 @@ 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);
#ifdef ANDROID