full IOKit support for smash adapter

This commit is contained in:
Jack Andersen 2015-04-23 14:24:15 -10:00
parent 92e7c3fb04
commit 4377109346
13 changed files with 627 additions and 71 deletions

View File

@ -1,20 +1,35 @@
#ifndef CDEVICEBASE #ifndef CDEVICEBASE
#define CDEVICEBASE #define CDEVICEBASE
class CDeviceToken; #include <stdint.h>
class IHIDDevice; #include <stdlib.h>
class CDeviceBase class CDeviceBase
{ {
CDeviceToken* m_token; friend class CDeviceToken;
IHIDDevice* m_hidDev; friend class CHIDDeviceIOKit;
friend CDeviceToken; friend class CHIDDeviceUdev;
friend class CHIDDeviceWin32;
class CDeviceToken* m_token;
class IHIDDevice* m_hidDev;
void _deviceDisconnected(); void _deviceDisconnected();
public: public:
CDeviceBase(CDeviceToken* token, IHIDDevice* hidDev); CDeviceBase(CDeviceToken* token);
virtual ~CDeviceBase(); virtual ~CDeviceBase();
void closeDevice(); void closeDevice();
virtual void deviceDisconnected()=0; virtual void deviceDisconnected()=0;
/* Low-Level API */
bool sendInterruptTransfer(uint8_t pipe, const uint8_t* data, size_t length);
size_t receiveInterruptTransfer(uint8_t pipe, uint8_t* data, size_t length);
virtual void transferCycle() {};
/* High-Level API */
bool sendReport(const uint8_t* data, size_t length);
virtual size_t receiveReport(uint8_t* data, size_t length) {};
}; };
#endif // CDEVICEBASE #endif // CDEVICEBASE

View File

@ -33,9 +33,9 @@ private:
/* Friend methods for platform-listener to find/insert/remove /* Friend methods for platform-listener to find/insert/remove
* tokens with type-filtering */ * tokens with type-filtering */
inline bool _hasToken(TDeviceHandle handle) inline bool _hasToken(const std::string& path)
{ {
auto preCheck = m_tokens.find(handle); auto preCheck = m_tokens.find(path);
if (preCheck != m_tokens.end()) if (preCheck != m_tokens.end())
return true; return true;
return false; return false;
@ -44,14 +44,14 @@ private:
{ {
if (BooDeviceMatchToken(token, m_types)) { if (BooDeviceMatchToken(token, m_types)) {
m_tokensLock.lock(); m_tokensLock.lock();
TInsertedDeviceToken inseredTok = m_tokens.insert(std::make_pair(token.getDeviceHandle(), std::move(token))); TInsertedDeviceToken inseredTok = m_tokens.insert(std::make_pair(token.getDevicePath(), std::move(token)));
m_tokensLock.unlock(); m_tokensLock.unlock();
deviceConnected(inseredTok.first->second); deviceConnected(inseredTok.first->second);
} }
} }
inline void _removeToken(TDeviceHandle handle) inline void _removeToken(const std::string& path)
{ {
auto preCheck = m_tokens.find(handle); auto preCheck = m_tokens.find(path);
if (preCheck != m_tokens.end()) if (preCheck != m_tokens.end())
{ {
CDeviceToken& tok = preCheck->second; CDeviceToken& tok = preCheck->second;

View File

@ -5,20 +5,13 @@
#include "CDeviceBase.hpp" #include "CDeviceBase.hpp"
#include "DeviceClasses.hpp" #include "DeviceClasses.hpp"
#if __APPLE__
#include <IOKit/hid/IOHIDLib.h>
typedef IOHIDDeviceRef TDeviceHandle;
#elif _WIN32
#elif __linux__
#endif
class CDeviceToken class CDeviceToken
{ {
unsigned m_vendorId; unsigned m_vendorId;
unsigned m_productId; unsigned m_productId;
std::string m_vendorName; std::string m_vendorName;
std::string m_productName; std::string m_productName;
TDeviceHandle m_devHandle; std::string m_devPath;
friend class CDeviceBase; friend class CDeviceBase;
CDeviceBase* m_connectedDev; CDeviceBase* m_connectedDev;
@ -34,8 +27,8 @@ class CDeviceToken
public: public:
CDeviceToken(const CDeviceToken&) = delete; CDeviceToken(const CDeviceToken&) = delete;
CDeviceToken(CDeviceToken&&) = default; CDeviceToken(CDeviceToken&&) = default;
inline CDeviceToken(unsigned vid, unsigned pid, const char* vname, const char* pname, TDeviceHandle handle) inline CDeviceToken(unsigned vid, unsigned pid, const char* vname, const char* pname, const char* path)
: m_vendorId(vid), m_productId(pid), m_devHandle(handle), m_connectedDev(NULL) : m_vendorId(vid), m_productId(pid), m_devPath(path), m_connectedDev(NULL)
{ {
if (vname) if (vname)
m_vendorName = vname; m_vendorName = vname;
@ -47,19 +40,19 @@ public:
inline unsigned getProductId() const {return m_productId;} inline unsigned getProductId() const {return m_productId;}
inline const std::string& getVendorName() const {return m_vendorName;} inline const std::string& getVendorName() const {return m_vendorName;}
inline const std::string& getProductName() const {return m_productName;} inline const std::string& getProductName() const {return m_productName;}
inline TDeviceHandle getDeviceHandle() const {return m_devHandle;} inline const std::string& getDevicePath() const {return m_devPath;}
inline bool isDeviceOpen() const {return m_connectedDev;} inline bool isDeviceOpen() const {return m_connectedDev;}
inline CDeviceBase* openAndGetDevice() inline CDeviceBase* openAndGetDevice()
{ {
if (!m_connectedDev) if (!m_connectedDev)
m_connectedDev = BooDeviceNew(this); m_connectedDev = BooDeviceNew(*this);
return m_connectedDev; return m_connectedDev;
} }
inline bool operator ==(const CDeviceToken& rhs) const inline bool operator ==(const CDeviceToken& rhs) const
{return m_devHandle == rhs.m_devHandle;} {return m_devPath == rhs.m_devPath;}
inline bool operator <(const CDeviceToken& rhs) const inline bool operator <(const CDeviceToken& rhs) const
{return m_devHandle < rhs.m_devHandle;} {return m_devPath < rhs.m_devPath;}
}; };
#endif // CDEVICETOKEN #endif // CDEVICETOKEN

View File

@ -1,14 +1,63 @@
#ifndef CDOLPHINSMASHADAPTER_HPP #ifndef CDOLPHINSMASHADAPTER_HPP
#define CDOLPHINSMASHADAPTER_HPP #define CDOLPHINSMASHADAPTER_HPP
#include <stdint.h>
#include "CDeviceBase.hpp" #include "CDeviceBase.hpp"
struct IDolphinSmashAdapterCallback
{
enum EDolphinControllerType
{
DOL_TYPE_NONE = 0,
DOL_TYPE_NORMAL = 0x10,
DOL_TYPE_WAVEBIRD = 0x20,
DOL_TYPE_RUMBLE = 0xF0
};
enum EDolphinControllerButtons
{
DOL_START = 1<<0,
DOL_Z = 1<<1,
DOL_L = 1<<2,
DOL_R = 1<<3,
DOL_A = 1<<8,
DOL_B = 1<<9,
DOL_X = 1<<10,
DOL_Y = 1<<11,
DOL_LEFT = 1<<12,
DOL_RIGHT = 1<<13,
DOL_DOWN = 1<<14,
DOL_UP = 1<<15
};
struct SDolphinControllerState
{
uint8_t m_leftStick[2];
uint8_t m_rightStick[2];
uint8_t m_analogTriggers[2];
uint16_t m_btns;
};
virtual void controllerConnected(unsigned idx, EDolphinControllerType type) {}
virtual void controllerDisconnected(unsigned idx, EDolphinControllerType type) {}
virtual void controllerUpdate(unsigned idx, EDolphinControllerType type,
const SDolphinControllerState& state) {}
};
class CDolphinSmashAdapter final : public CDeviceBase class CDolphinSmashAdapter final : public CDeviceBase
{ {
IDolphinSmashAdapterCallback* m_callback;
uint8_t m_knownControllers;
uint8_t m_rumbleRequest;
uint8_t m_rumbleState;
bool m_didHandshake;
void deviceDisconnected(); void deviceDisconnected();
void transferCycle();
public: public:
CDolphinSmashAdapter(CDeviceToken* token, IHIDDevice* hidDev); CDolphinSmashAdapter(CDeviceToken* token);
~CDolphinSmashAdapter(); ~CDolphinSmashAdapter();
inline void setCallback(IDolphinSmashAdapterCallback* cb)
{m_callback = cb; m_knownControllers = 0;}
inline void startRumble(unsigned idx) {if (idx >= 4) return; m_rumbleRequest |= 1<<idx;}
inline void stopRumble(unsigned idx) {if (idx >= 4) return; m_rumbleRequest &= ~(1<<idx);}
}; };
#endif // CDOLPHINSMASHADAPTER_HPP #endif // CDOLPHINSMASHADAPTER_HPP

View File

@ -22,6 +22,6 @@ enum EDeviceMask
}; };
bool BooDeviceMatchToken(const CDeviceToken& token, EDeviceMask mask); bool BooDeviceMatchToken(const CDeviceToken& token, EDeviceMask mask);
CDeviceBase* BooDeviceNew(CDeviceToken* token); CDeviceBase* BooDeviceNew(CDeviceToken& token);
#endif // CDEVICECLASSES_HPP #endif // CDEVICECLASSES_HPP

View File

@ -2,10 +2,9 @@
#include "inputdev/CDeviceToken.hpp" #include "inputdev/CDeviceToken.hpp"
#include "IHIDDevice.hpp" #include "IHIDDevice.hpp"
CDeviceBase::CDeviceBase(CDeviceToken* token, IHIDDevice* hidDev) CDeviceBase::CDeviceBase(CDeviceToken* token)
: m_token(token), m_hidDev(hidDev) : m_token(token), m_hidDev(NULL)
{ {
hidDev->_setDeviceImp(this);
} }
CDeviceBase::~CDeviceBase() CDeviceBase::~CDeviceBase()
@ -25,11 +24,31 @@ void CDeviceBase::_deviceDisconnected()
} }
} }
void CDeviceBase::closeDevice() void CDeviceBase::closeDevice()
{ {
if (m_token) if (m_token)
m_token->_deviceClose(); m_token->_deviceClose();
} }
bool CDeviceBase::sendInterruptTransfer(uint8_t pipe, const uint8_t* data, size_t length)
{
if (m_hidDev)
return m_hidDev->_sendInterruptTransfer(pipe, data, length);
return false;
}
size_t CDeviceBase::receiveInterruptTransfer(uint8_t pipe, uint8_t* data, size_t length)
{
if (m_hidDev)
return m_hidDev->_receiveInterruptTransfer(pipe, data, length);
return false;
}
bool CDeviceBase::sendReport(const uint8_t* data, size_t length)
{
if (m_hidDev)
return m_hidDev->_sendReport(data, length);
return false;
}

View File

@ -1,17 +1,143 @@
#include "inputdev/CDolphinSmashAdapter.hpp" #include "inputdev/CDolphinSmashAdapter.hpp"
#include <stdio.h> #include <stdio.h>
#include <string.h>
CDolphinSmashAdapter::CDolphinSmashAdapter(CDeviceToken* token, IHIDDevice* hidDev) /* Reference: https://github.com/ToadKing/wii-u-gc-adapter/blob/master/wii-u-gc-adapter.c
: CDeviceBase(token, hidDev) */
static const bool BUTTON_MASK[] =
{ {
true,
true,
true,
true,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true
};
CDolphinSmashAdapter::CDolphinSmashAdapter(CDeviceToken* token)
: CDeviceBase(token),
m_callback(NULL),
m_knownControllers(0),
m_rumbleRequest(0),
m_rumbleState(0),
m_didHandshake(false)
{
} }
CDolphinSmashAdapter::~CDolphinSmashAdapter() CDolphinSmashAdapter::~CDolphinSmashAdapter()
{ {
} }
static const uint8_t HANDSHAKE_PAYLOAD[] = {0x13};
static inline IDolphinSmashAdapterCallback::EDolphinControllerType
parseType(unsigned char status)
{
unsigned char type = status & (IDolphinSmashAdapterCallback::DOL_TYPE_NORMAL |
IDolphinSmashAdapterCallback::DOL_TYPE_WAVEBIRD);
switch (type)
{
case IDolphinSmashAdapterCallback::DOL_TYPE_NORMAL:
case IDolphinSmashAdapterCallback::DOL_TYPE_WAVEBIRD:
return (IDolphinSmashAdapterCallback::EDolphinControllerType)type;
default:
return IDolphinSmashAdapterCallback::DOL_TYPE_NONE;
}
}
static inline IDolphinSmashAdapterCallback::EDolphinControllerType
parseState(IDolphinSmashAdapterCallback::SDolphinControllerState* stateOut, uint8_t* payload)
{
memset(stateOut, 0, sizeof(IDolphinSmashAdapterCallback::SDolphinControllerState));
unsigned char status = payload[0];
IDolphinSmashAdapterCallback::EDolphinControllerType type = parseType(status);
IDolphinSmashAdapterCallback::EDolphinControllerType extra =
((status & 0x04) != 0) ? IDolphinSmashAdapterCallback::DOL_TYPE_RUMBLE :
IDolphinSmashAdapterCallback::DOL_TYPE_NONE;
stateOut->m_btns = (uint16_t)payload[1] << 8 | (uint16_t)payload[2];
stateOut->m_leftStick[0] = payload[3];
stateOut->m_leftStick[1] = payload[4] ^ 0xFF;
stateOut->m_rightStick[0] = payload[5];
stateOut->m_rightStick[1] = payload[6] ^ 0xFF;
stateOut->m_analogTriggers[0] = payload[7];
stateOut->m_analogTriggers[1] = payload[8];
return static_cast<IDolphinSmashAdapterCallback::EDolphinControllerType>(type|extra);
}
void CDolphinSmashAdapter::transferCycle()
{
if (!m_didHandshake)
{
if (!sendInterruptTransfer(0, HANDSHAKE_PAYLOAD, sizeof(HANDSHAKE_PAYLOAD)))
return;
//printf("SENT HANDSHAKE %d\n", res);
m_didHandshake = true;
}
else
{
uint8_t payload[37];
size_t recvSz = receiveInterruptTransfer(0, payload, sizeof(payload));
if (recvSz != 37 || payload[0] != 0x21)
return;
//printf("RECEIVED DATA %zu %02X\n", recvSz, payload[0]);
if (!m_callback)
return;
/* Parse controller states */
uint8_t* controller = &payload[1];
bool rumbleMask[4] = {false};
for (int i=0 ; i<4 ; i++, controller += 9)
{
IDolphinSmashAdapterCallback::SDolphinControllerState state;
IDolphinSmashAdapterCallback::EDolphinControllerType type = parseState(&state, controller);
if (type && !(m_knownControllers & 1<<i))
{
m_knownControllers |= 1<<i;
m_callback->controllerConnected(i, type);
}
else if (!type && (m_knownControllers & 1<<i))
{
m_knownControllers &= ~(1<<i);
m_callback->controllerDisconnected(i, type);
}
m_callback->controllerUpdate(i, type, state);
rumbleMask[i] = type & IDolphinSmashAdapterCallback::DOL_TYPE_RUMBLE;
}
/* Send rumble message (if needed) */
uint8_t rumbleReq = m_rumbleRequest;
if (rumbleReq != m_rumbleState)
{
uint8_t rumbleMessage[5] = {0x11};
for (int i=0 ; i<4 ; ++i)
{
if (rumbleReq & 1<<i && rumbleMask[i])
rumbleMessage[i+1] = 1;
else
rumbleMessage[i+1] = 0;
}
sendInterruptTransfer(0, rumbleMessage, sizeof(rumbleMessage));
m_rumbleState = rumbleReq;
}
}
};
void CDolphinSmashAdapter::deviceDisconnected() void CDolphinSmashAdapter::deviceDisconnected()
{ {

View File

@ -2,19 +2,162 @@
#include "inputdev/CDeviceToken.hpp" #include "inputdev/CDeviceToken.hpp"
#include "inputdev/CDeviceBase.hpp" #include "inputdev/CDeviceBase.hpp"
#include <IOKit/hid/IOHIDLib.h> #include <IOKit/hid/IOHIDLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <thread> #include <thread>
#define MAX_REPORT_SIZE 65536 #define MAX_REPORT_SIZE 65536
class CHIDDeviceIOKit final : public IHIDDevice class CHIDDeviceIOKit final : public IHIDDevice
{ {
CDeviceToken* m_token; CDeviceToken& m_token;
IOHIDDeviceRef m_dev; CDeviceBase& m_devImp;
IOUSBInterfaceInterface** m_usbIntf = NULL;
uint8_t m_usbIntfInPipe = 0;
uint8_t m_usbIntfOutPipe = 0;
bool m_runningTransferLoop = false;
const std::string& m_devPath;
std::mutex m_initMutex; std::mutex m_initMutex;
std::condition_variable m_initCond; std::condition_variable m_initCond;
std::thread* m_thread; std::thread* m_thread;
CFRunLoopRef m_runLoop; CFRunLoopRef m_runLoop = NULL;
CDeviceBase* m_devImp;
bool _sendInterruptTransfer(uint8_t pipe, const uint8_t* data, size_t length)
{
if (m_usbIntf)
{
IOReturn res = (*m_usbIntf)->WritePipe(m_usbIntf, m_usbIntfOutPipe, (void*)data, length);
return res == kIOReturnSuccess;
}
return false;
}
size_t _receiveInterruptTransfer(uint8_t pipe, uint8_t* data, size_t length)
{
if (m_usbIntf)
{
UInt32 readSize = length;
IOReturn res = (*m_usbIntf)->ReadPipe(m_usbIntf, m_usbIntfInPipe, data, &readSize);
if (res != kIOReturnSuccess)
return 0;
return readSize;
}
return 0;
}
static void _threadProcLL(CHIDDeviceIOKit* device)
{
char thrName[128];
snprintf(thrName, 128, "%s Transfer Thread", device->m_token.getProductName().c_str());
pthread_setname_np(thrName);
std::unique_lock<std::mutex> lk(device->m_initMutex);
/* Get the HID element's parent (USB interrupt transfer-interface) */
io_iterator_t devIter;
io_registry_entry_t devEntry = IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.c_str());
IORegistryEntryGetChildIterator(devEntry, kIOServicePlane, &devIter);
io_object_t obj, interfaceEntry = 0;
while ((obj = IOIteratorNext(devIter)))
{
if (IOObjectConformsTo(obj, kIOUSBInterfaceClassName))
interfaceEntry = obj;
else
IOObjectRelease(obj);
}
if (!interfaceEntry)
{
throw std::runtime_error("unable to find device interface");
lk.unlock();
device->m_initCond.notify_one();
return;
}
/* IOKit Plugin COM interface (WTF Apple???) */
IOCFPlugInInterface **iodev;
SInt32 score;
IOReturn err;
err = IOCreatePlugInInterfaceForService(interfaceEntry,
kIOUSBInterfaceUserClientTypeID,
kIOCFPlugInInterfaceID,
&iodev,
&score);
IOObjectRelease(interfaceEntry);
if (err)
{
throw std::runtime_error("unable to obtain IOKit plugin service");
lk.unlock();
device->m_initCond.notify_one();
return;
}
/* USB interface function-pointer table */
IOUSBInterfaceInterface** intf = NULL;
err = (*iodev)->QueryInterface(iodev,
CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
(LPVOID*)&intf);
if (err)
{
throw std::runtime_error("unable to query IOKit USB interface");
lk.unlock();
device->m_initCond.notify_one();
IODestroyPlugInInterface(iodev);
return;
}
/* Obtain exclusive lock on device */
device->m_usbIntf = intf;
err = (*intf)->USBInterfaceOpen(intf);
if (err != kIOReturnSuccess)
{
if (err == kIOReturnExclusiveAccess)
throw std::runtime_error("unable to open IOKit USB interface; someone else using it");
else
throw std::runtime_error("unable to open IOKit USB interface");
lk.unlock();
device->m_initCond.notify_one();
(*intf)->Release(intf);
IODestroyPlugInInterface(iodev);
return;
}
/* Determine pipe indices for interrupt I/O */
UInt8 numEndpoints = 0;
err = (*intf)->GetNumEndpoints(intf, &numEndpoints);
for (int i=1 ; i<numEndpoints+1 ; ++i)
{
UInt8 dir, num, tType, interval;
UInt16 mPacketSz;
err = (*intf)->GetPipeProperties(intf, i, &dir, &num, &tType, &mPacketSz, &interval);
if (tType == kUSBInterrupt)
{
if (dir == kUSBIn)
device->m_usbIntfInPipe = num;
else if (dir == kUSBOut)
device->m_usbIntfOutPipe = num;
}
}
/* Return control to main thread */
device->m_runningTransferLoop = true;
lk.unlock();
device->m_initCond.notify_one();
/* Start transfer loop */
while (device->m_runningTransferLoop)
{
device->m_devImp.transferCycle();
}
/* Cleanup */
(*intf)->USBInterfaceClose(intf);
(*intf)->Release(intf);
IODestroyPlugInInterface(iodev);
device->m_usbIntf = NULL;
}
static void _inputReport(CHIDDeviceIOKit* device, static void _inputReport(CHIDDeviceIOKit* device,
IOReturn result, IOReturn result,
@ -32,10 +175,11 @@ class CHIDDeviceIOKit final : public IHIDDevice
{ {
device->_deviceDisconnected(); device->_deviceDisconnected();
} }
static void _threadProc(CHIDDeviceIOKit* device)
static void _threadProcHL(CHIDDeviceIOKit* device)
{ {
char thrName[128]; char thrName[128];
snprintf(thrName, 128, "%s Device Thread", device->m_token->getProductName().c_str()); snprintf(thrName, 128, "%s HID Thread", device->m_token.getProductName().c_str());
pthread_setname_np(thrName); pthread_setname_np(thrName);
__block std::unique_lock<std::mutex> lk(device->m_initMutex); __block std::unique_lock<std::mutex> lk(device->m_initMutex);
device->m_runLoop = CFRunLoopGetCurrent(); device->m_runLoop = CFRunLoopGetCurrent();
@ -47,14 +191,17 @@ class CHIDDeviceIOKit final : public IHIDDevice
}), kCFRunLoopCommonModes); }), kCFRunLoopCommonModes);
uint8_t* inputBuf = new uint8_t[MAX_REPORT_SIZE]; uint8_t* inputBuf = new uint8_t[MAX_REPORT_SIZE];
IOHIDDeviceRegisterInputReportCallback(device->m_dev, inputBuf, MAX_REPORT_SIZE, io_registry_entry_t devServ = IORegistryEntryFromPath(kIOMasterPortDefault, device->m_devPath.c_str());
IOHIDDeviceRef dev = IOHIDDeviceCreate(kCFAllocatorDefault, devServ);
IOHIDDeviceRegisterInputReportCallback(dev, inputBuf, MAX_REPORT_SIZE,
(IOHIDReportCallback)_inputReport, device); (IOHIDReportCallback)_inputReport, device);
IOHIDDeviceRegisterRemovalCallback(device->m_dev, (IOHIDCallback)_disconnect, device); IOHIDDeviceRegisterRemovalCallback(dev, (IOHIDCallback)_disconnect, device);
IOHIDDeviceScheduleWithRunLoop(device->m_dev, device->m_runLoop, kCFRunLoopDefaultMode); IOHIDDeviceScheduleWithRunLoop(dev, device->m_runLoop, kCFRunLoopDefaultMode);
IOHIDDeviceOpen(device->m_dev, kIOHIDOptionsTypeNone); IOHIDDeviceOpen(dev, kIOHIDOptionsTypeNone);
CFRunLoopRun(); CFRunLoopRun();
if (device->m_runLoop) if (device->m_runLoop)
IOHIDDeviceClose(device->m_dev, kIOHIDOptionsTypeNone); IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
CFRelease(dev);
} }
void _deviceDisconnected() void _deviceDisconnected()
@ -63,21 +210,31 @@ class CHIDDeviceIOKit final : public IHIDDevice
m_runLoop = NULL; m_runLoop = NULL;
if (rl) if (rl)
CFRunLoopStop(rl); CFRunLoopStop(rl);
m_runningTransferLoop = false;
} }
void _setDeviceImp(CDeviceBase* dev) bool _sendReport(const uint8_t* data, size_t length)
{ {
m_devImp = dev; if (m_runLoop)
{
}
return false;
} }
public: public:
CHIDDeviceIOKit(CDeviceToken* token) CHIDDeviceIOKit(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel)
: m_token(token), : m_token(token),
m_dev(token->getDeviceHandle()) m_devImp(devImp),
m_devPath(token.getDevicePath())
{ {
devImp.m_hidDev = this;
std::unique_lock<std::mutex> lk(m_initMutex); std::unique_lock<std::mutex> lk(m_initMutex);
m_thread = new std::thread(_threadProc, this); if (lowLevel)
m_thread = new std::thread(_threadProcLL, this);
else
m_thread = new std::thread(_threadProcHL, this);
m_initCond.wait(lk); m_initCond.wait(lk);
} }
@ -85,6 +242,7 @@ public:
{ {
if (m_runLoop) if (m_runLoop)
CFRunLoopStop(m_runLoop); CFRunLoopStop(m_runLoop);
m_runningTransferLoop = false;
m_thread->detach(); m_thread->detach();
delete m_thread; delete m_thread;
} }
@ -92,7 +250,7 @@ public:
}; };
IHIDDevice* IHIDDeviceNew(CDeviceToken* token) IHIDDevice* IHIDDeviceNew(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel)
{ {
return new CHIDDeviceIOKit(token); return new CHIDDeviceIOKit(token, devImp, lowLevel);
} }

View File

@ -2,6 +2,51 @@
#include "inputdev/CDeviceFinder.hpp" #include "inputdev/CDeviceFinder.hpp"
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDLib.h> #include <IOKit/hid/IOHIDLib.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/IOCFPlugIn.h>
/* Reference: http://oroboro.com/usb-serial-number-osx/
*/
static bool getUSBStringDescriptor(IOUSBDeviceInterface182** usbDevice, UInt8 idx, char* out)
{
UInt16 buffer[128];
// wow... we're actually forced to make hard coded bus requests. Its like
// hard disk programming in the 80's!
IOUSBDevRequest request;
request.bmRequestType = USBmakebmRequestType(kUSBIn,
kUSBStandard,
kUSBDevice);
request.bRequest = kUSBRqGetDescriptor;
request.wValue = (kUSBStringDesc << 8) | idx;
request.wIndex = 0x409; // english
request.wLength = sizeof(buffer);
request.pData = buffer;
kern_return_t err = (*usbDevice)->DeviceRequest(usbDevice, &request);
if (err != 0)
{
// the request failed... fairly uncommon for the USB disk driver, but not
// so uncommon for other devices. This can also be less reliable if your
// disk is mounted through an external USB hub. At this level we actually
// have to worry about hardware issues like this.
return false;
}
// we're mallocing this string just as an example. But you probably will want
// to do something smarter, like pre-allocated buffers in the info class, or
// use a string class.
unsigned count = (request.wLenDone - 1) / 2;
unsigned i;
for (i=0 ; i<count ; ++i)
out[i] = buffer[i+1];
out[i] = '\0';
return true;
}
class CHIDListenerIOKit final : public IHIDListener class CHIDListenerIOKit final : public IHIDListener
{ {
@ -9,6 +54,7 @@ class CHIDListenerIOKit final : public IHIDListener
CFRunLoopRef m_listenerRunLoop; CFRunLoopRef m_listenerRunLoop;
IOHIDManagerRef m_hidManager; IOHIDManagerRef m_hidManager;
IONotificationPortRef m_llPort;
bool m_scanningEnabled; bool m_scanningEnabled;
static void deviceConnected(CHIDListenerIOKit* listener, static void deviceConnected(CHIDListenerIOKit* listener,
@ -18,7 +64,10 @@ class CHIDListenerIOKit final : public IHIDListener
{ {
if (!listener->m_scanningEnabled) if (!listener->m_scanningEnabled)
return; return;
if (listener->m_finder._hasToken(device)) io_string_t devPath;
if (IORegistryEntryGetPath(IOHIDDeviceGetService(device), kIOServicePlane, devPath) != 0)
return;
if (listener->m_finder._hasToken(devPath))
return; return;
CFIndex vid, pid; CFIndex vid, pid;
CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberCFIndexType, &vid); CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberCFIndexType, &vid);
@ -28,7 +77,7 @@ class CHIDListenerIOKit final : public IHIDListener
listener->m_finder._insertToken(CDeviceToken(vid, pid, listener->m_finder._insertToken(CDeviceToken(vid, pid,
CFStringGetCStringPtr(manuf, kCFStringEncodingUTF8), CFStringGetCStringPtr(manuf, kCFStringEncodingUTF8),
CFStringGetCStringPtr(product, kCFStringEncodingUTF8), CFStringGetCStringPtr(product, kCFStringEncodingUTF8),
device)); devPath));
} }
static void deviceDisconnected(CHIDListenerIOKit* listener, static void deviceDisconnected(CHIDListenerIOKit* listener,
@ -44,12 +93,18 @@ class CHIDListenerIOKit final : public IHIDListener
CFRunLoopWakeUp(listener->m_listenerRunLoop); CFRunLoopWakeUp(listener->m_listenerRunLoop);
return; return;
} }
listener->m_finder._removeToken(device); io_string_t devPath;
if (IORegistryEntryGetPath(IOHIDDeviceGetService(device), kIOServicePlane, devPath) != 0)
return;
listener->m_finder._removeToken(devPath);
} }
static void applyDevice(IOHIDDeviceRef device, CHIDListenerIOKit* listener) static void applyDevice(IOHIDDeviceRef device, CHIDListenerIOKit* listener)
{ {
if (listener->m_finder._hasToken(device)) io_string_t devPath;
if (IORegistryEntryGetPath(IOHIDDeviceGetService(device), kIOServicePlane, devPath) != 0)
return;
if (listener->m_finder._hasToken(devPath))
return; return;
CFIndex vid, pid; CFIndex vid, pid;
CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberCFIndexType, &vid); CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberCFIndexType, &vid);
@ -59,7 +114,84 @@ class CHIDListenerIOKit final : public IHIDListener
listener->m_finder._insertToken(CDeviceToken(vid, pid, listener->m_finder._insertToken(CDeviceToken(vid, pid,
CFStringGetCStringPtr(manuf, kCFStringEncodingUTF8), CFStringGetCStringPtr(manuf, kCFStringEncodingUTF8),
CFStringGetCStringPtr(product, kCFStringEncodingUTF8), CFStringGetCStringPtr(product, kCFStringEncodingUTF8),
device)); devPath));
}
static void devicesConnectedLL(CHIDListenerIOKit* listener,
io_iterator_t iterator)
{
io_object_t obj;
while ((obj = IOIteratorNext(iterator)))
{
io_string_t devPath;
if (IORegistryEntryGetPath(obj, kIOServicePlane, devPath) != 0)
continue;
if (!listener->m_scanningEnabled ||
listener->m_finder._hasToken(devPath))
{
IOObjectRelease(obj);
continue;
}
IOCFPlugInInterface** devServ;
SInt32 score;
IOReturn err;
err = IOCreatePlugInInterfaceForService(obj, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &devServ, &score);
if (err != kIOReturnSuccess)
throw std::runtime_error("unable to open IOKit plugin interface");
IOUSBDeviceInterface182 **dev;
err = (*devServ)->QueryInterface(devServ,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182),
(LPVOID*)&dev);
if (err != kIOReturnSuccess)
throw std::runtime_error("unable to open IOKit device interface");
UInt16 vid, pid;
(*dev)->GetDeviceVendor(dev, &vid);
(*dev)->GetDeviceProduct(dev, &pid);
UInt8 vstridx, pstridx;
(*dev)->USBGetManufacturerStringIndex(dev, &vstridx);
(*dev)->USBGetProductStringIndex(dev, &pstridx);
char vstr[128] = {0};
char pstr[128] = {0};
getUSBStringDescriptor(dev, vstridx, vstr);
getUSBStringDescriptor(dev, pstridx, pstr);
listener->m_finder._insertToken(CDeviceToken(vid, pid, vstr, pstr, devPath));
//printf("ADDED %08X %s\n", obj, devPath);
(*dev)->Release(dev);
IODestroyPlugInInterface(devServ);
IOObjectRelease(obj);
}
}
static void devicesDisconnectedLL(CHIDListenerIOKit* listener,
io_iterator_t iterator)
{
if (CFRunLoopGetCurrent() != listener->m_listenerRunLoop)
{
CFRunLoopPerformBlock(listener->m_listenerRunLoop, kCFRunLoopDefaultMode, ^{
devicesDisconnectedLL(listener, iterator);
});
CFRunLoopWakeUp(listener->m_listenerRunLoop);
return;
}
io_object_t obj;
while ((obj = IOIteratorNext(iterator)))
{
io_string_t devPath;
if (IORegistryEntryGetPath(obj, kIOServicePlane, devPath) != 0)
continue;
listener->m_finder._removeToken(devPath);
//printf("REMOVED %08X %s\n", obj, devPath);
IOObjectRelease(obj);
}
} }
public: public:
@ -78,9 +210,38 @@ public:
if (ret != kIOReturnSuccess) if (ret != kIOReturnSuccess)
throw std::runtime_error("error establishing IOHIDManager"); throw std::runtime_error("error establishing IOHIDManager");
/* Initial Device Add */ /* Initial HID Device Add */
m_scanningEnabled = true; m_scanningEnabled = true;
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
/* Register Low-Level Matcher */
m_llPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopAddSource(m_listenerRunLoop, IONotificationPortGetRunLoopSource(m_llPort), kCFRunLoopDefaultMode);
CFMutableDictionaryRef matchDict = IOServiceMatching(kIOUSBDeviceClassName);
CFIndex nintendoVid = VID_NINTENDO;
CFIndex smashPid = PID_SMASH_ADAPTER;
CFNumberRef nintendoVidNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &nintendoVid);
CFNumberRef smashPidNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &smashPid);
CFDictionaryAddValue(matchDict, CFSTR(kUSBVendorID), nintendoVidNum);
CFDictionaryAddValue(matchDict, CFSTR(kUSBProductID), smashPidNum);
CFRelease(nintendoVidNum);
CFRelease(smashPidNum);
CFRetain(matchDict);
io_iterator_t initialIt;
kern_return_t llRet =
IOServiceAddMatchingNotification(m_llPort, kIOMatchedNotification, matchDict,
(IOServiceMatchingCallback)devicesConnectedLL, this, &initialIt);
if (llRet == 0)
devicesConnectedLL(this, initialIt);
llRet =
IOServiceAddMatchingNotification(m_llPort, kIOTerminatedNotification, matchDict,
(IOServiceMatchingCallback)devicesDisconnectedLL, this, &initialIt);
if (llRet == 0)
devicesDisconnectedLL(this, initialIt);
m_scanningEnabled = false; m_scanningEnabled = false;
} }
@ -90,6 +251,8 @@ public:
IOHIDManagerUnscheduleFromRunLoop(m_hidManager, m_listenerRunLoop, kCFRunLoopDefaultMode); IOHIDManagerUnscheduleFromRunLoop(m_hidManager, m_listenerRunLoop, kCFRunLoopDefaultMode);
IOHIDManagerClose(m_hidManager, kIOHIDManagerOptionNone); IOHIDManagerClose(m_hidManager, kIOHIDManagerOptionNone);
CFRelease(m_hidManager); CFRelease(m_hidManager);
CFRunLoopRemoveSource(m_listenerRunLoop, IONotificationPortGetRunLoopSource(m_llPort), kCFRunLoopDefaultMode);
IONotificationPortDestroy(m_llPort);
} }
/* Automatic device scanning */ /* Automatic device scanning */

View File

@ -10,17 +10,29 @@ bool BooDeviceMatchToken(const CDeviceToken& token, EDeviceMask mask)
return false; return false;
} }
IHIDDevice* IHIDDeviceNew(CDeviceToken* token); IHIDDevice* IHIDDeviceNew(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel);
CDeviceBase* BooDeviceNew(CDeviceToken* token) CDeviceBase* BooDeviceNew(CDeviceToken& token)
{ {
IHIDDevice* newDev = IHIDDeviceNew(token);
CDeviceBase* retval = NULL;
bool lowLevel = false;
if (token.getVendorId() == VID_NINTENDO && token.getProductId() == PID_SMASH_ADAPTER)
{
retval = new CDolphinSmashAdapter(&token);
lowLevel = true;
}
if (!retval)
return NULL;
IHIDDevice* newDev = IHIDDeviceNew(token, *retval, lowLevel);
if (!newDev) if (!newDev)
{
delete retval;
return NULL; return NULL;
}
if (token->getVendorId() == VID_NINTENDO && token->getProductId() == PID_SMASH_ADAPTER) return retval;
return new CDolphinSmashAdapter(token, newDev);
else
delete newDev;
return NULL;
} }

View File

@ -7,8 +7,10 @@ class CDeviceBase;
class IHIDDevice class IHIDDevice
{ {
friend CDeviceBase; friend CDeviceBase;
virtual void _setDeviceImp(CDeviceBase* dev)=0;
virtual void _deviceDisconnected()=0; virtual void _deviceDisconnected()=0;
virtual bool _sendInterruptTransfer(uint8_t pipe, const uint8_t* data, size_t length)=0;
virtual size_t _receiveInterruptTransfer(uint8_t pipe, uint8_t* data, size_t length)=0;
virtual bool _sendReport(const uint8_t* data, size_t length)=0;
public: public:
inline virtual ~IHIDDevice() {}; inline virtual ~IHIDDevice() {};
}; };

View File

@ -1,10 +1,10 @@
#ifndef IHIDLISTENER_HPP #ifndef IHIDLISTENER_HPP
#define IHIDLISTENER_HPP #define IHIDLISTENER_HPP
#include <map> #include <unordered_map>
#include <mutex> #include <mutex>
#include "CDeviceToken.hpp" #include "CDeviceToken.hpp"
typedef std::map<TDeviceHandle, CDeviceToken> TDeviceTokens; typedef std::unordered_map<std::string, CDeviceToken> TDeviceTokens;
typedef std::pair<TDeviceTokens::iterator, bool> TInsertedDeviceToken; typedef std::pair<TDeviceTokens::iterator, bool> TInsertedDeviceToken;
class CDeviceFinder; class CDeviceFinder;

View File

@ -3,9 +3,27 @@
#include <stdio.h> #include <stdio.h>
#include <boo.hpp> #include <boo.hpp>
class CDolphinSmashAdapterCallback : public IDolphinSmashAdapterCallback
{
void controllerConnected(unsigned idx, EDolphinControllerType type)
{
printf("CONTROLLER %u CONNECTED\n", idx);
}
void controllerDisconnected(unsigned idx, EDolphinControllerType type)
{
printf("CONTROLLER %u DISCONNECTED\n", idx);
}
void controllerUpdate(unsigned idx, EDolphinControllerType type,
const SDolphinControllerState& state)
{
printf("CONTROLLER %u UPDATE %d %d\n", idx, state.m_leftStick[0], state.m_leftStick[1]);
}
};
class CTestDeviceFinder : public CDeviceFinder class CTestDeviceFinder : public CDeviceFinder
{ {
CDolphinSmashAdapter* smashAdapter = NULL; CDolphinSmashAdapter* smashAdapter = NULL;
CDolphinSmashAdapterCallback m_cb;
public: public:
CTestDeviceFinder() CTestDeviceFinder()
: CDeviceFinder(DEV_DOL_SMASH_ADAPTER) : CDeviceFinder(DEV_DOL_SMASH_ADAPTER)
@ -13,6 +31,7 @@ public:
void deviceConnected(CDeviceToken& tok) void deviceConnected(CDeviceToken& tok)
{ {
smashAdapter = dynamic_cast<CDolphinSmashAdapter*>(tok.openAndGetDevice()); smashAdapter = dynamic_cast<CDolphinSmashAdapter*>(tok.openAndGetDevice());
smashAdapter->setCallback(&m_cb);
} }
void deviceDisconnected(CDeviceToken&, CDeviceBase* device) void deviceDisconnected(CDeviceToken&, CDeviceBase* device)
{ {