mirror of https://github.com/AxioDL/boo.git
full IOKit support for smash adapter
This commit is contained in:
parent
92e7c3fb04
commit
4377109346
|
@ -1,20 +1,35 @@
|
|||
#ifndef CDEVICEBASE
|
||||
#define CDEVICEBASE
|
||||
|
||||
class CDeviceToken;
|
||||
class IHIDDevice;
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
class CDeviceBase
|
||||
{
|
||||
CDeviceToken* m_token;
|
||||
IHIDDevice* m_hidDev;
|
||||
friend CDeviceToken;
|
||||
friend class CDeviceToken;
|
||||
friend class CHIDDeviceIOKit;
|
||||
friend class CHIDDeviceUdev;
|
||||
friend class CHIDDeviceWin32;
|
||||
|
||||
class CDeviceToken* m_token;
|
||||
class IHIDDevice* m_hidDev;
|
||||
void _deviceDisconnected();
|
||||
|
||||
public:
|
||||
CDeviceBase(CDeviceToken* token, IHIDDevice* hidDev);
|
||||
CDeviceBase(CDeviceToken* token);
|
||||
virtual ~CDeviceBase();
|
||||
void closeDevice();
|
||||
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
|
||||
|
|
|
@ -33,9 +33,9 @@ private:
|
|||
|
||||
/* Friend methods for platform-listener to find/insert/remove
|
||||
* 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())
|
||||
return true;
|
||||
return false;
|
||||
|
@ -44,14 +44,14 @@ private:
|
|||
{
|
||||
if (BooDeviceMatchToken(token, m_types)) {
|
||||
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();
|
||||
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())
|
||||
{
|
||||
CDeviceToken& tok = preCheck->second;
|
||||
|
|
|
@ -5,20 +5,13 @@
|
|||
#include "CDeviceBase.hpp"
|
||||
#include "DeviceClasses.hpp"
|
||||
|
||||
#if __APPLE__
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
typedef IOHIDDeviceRef TDeviceHandle;
|
||||
#elif _WIN32
|
||||
#elif __linux__
|
||||
#endif
|
||||
|
||||
class CDeviceToken
|
||||
{
|
||||
unsigned m_vendorId;
|
||||
unsigned m_productId;
|
||||
std::string m_vendorName;
|
||||
std::string m_productName;
|
||||
TDeviceHandle m_devHandle;
|
||||
std::string m_devPath;
|
||||
|
||||
friend class CDeviceBase;
|
||||
CDeviceBase* m_connectedDev;
|
||||
|
@ -34,8 +27,8 @@ class CDeviceToken
|
|||
public:
|
||||
CDeviceToken(const CDeviceToken&) = delete;
|
||||
CDeviceToken(CDeviceToken&&) = default;
|
||||
inline CDeviceToken(unsigned vid, unsigned pid, const char* vname, const char* pname, TDeviceHandle handle)
|
||||
: m_vendorId(vid), m_productId(pid), m_devHandle(handle), m_connectedDev(NULL)
|
||||
inline CDeviceToken(unsigned vid, unsigned pid, const char* vname, const char* pname, const char* path)
|
||||
: m_vendorId(vid), m_productId(pid), m_devPath(path), m_connectedDev(NULL)
|
||||
{
|
||||
if (vname)
|
||||
m_vendorName = vname;
|
||||
|
@ -47,19 +40,19 @@ public:
|
|||
inline unsigned getProductId() const {return m_productId;}
|
||||
inline const std::string& getVendorName() const {return m_vendorName;}
|
||||
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 CDeviceBase* openAndGetDevice()
|
||||
{
|
||||
if (!m_connectedDev)
|
||||
m_connectedDev = BooDeviceNew(this);
|
||||
m_connectedDev = BooDeviceNew(*this);
|
||||
return m_connectedDev;
|
||||
}
|
||||
|
||||
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
|
||||
{return m_devHandle < rhs.m_devHandle;}
|
||||
{return m_devPath < rhs.m_devPath;}
|
||||
};
|
||||
|
||||
#endif // CDEVICETOKEN
|
||||
|
|
|
@ -1,14 +1,63 @@
|
|||
#ifndef CDOLPHINSMASHADAPTER_HPP
|
||||
#define CDOLPHINSMASHADAPTER_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
||||
{
|
||||
IDolphinSmashAdapterCallback* m_callback;
|
||||
uint8_t m_knownControllers;
|
||||
uint8_t m_rumbleRequest;
|
||||
uint8_t m_rumbleState;
|
||||
bool m_didHandshake;
|
||||
void deviceDisconnected();
|
||||
void transferCycle();
|
||||
public:
|
||||
CDolphinSmashAdapter(CDeviceToken* token, IHIDDevice* hidDev);
|
||||
CDolphinSmashAdapter(CDeviceToken* token);
|
||||
~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
|
||||
|
|
|
@ -22,6 +22,6 @@ enum EDeviceMask
|
|||
};
|
||||
|
||||
bool BooDeviceMatchToken(const CDeviceToken& token, EDeviceMask mask);
|
||||
CDeviceBase* BooDeviceNew(CDeviceToken* token);
|
||||
CDeviceBase* BooDeviceNew(CDeviceToken& token);
|
||||
|
||||
#endif // CDEVICECLASSES_HPP
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
#include "inputdev/CDeviceToken.hpp"
|
||||
#include "IHIDDevice.hpp"
|
||||
|
||||
CDeviceBase::CDeviceBase(CDeviceToken* token, IHIDDevice* hidDev)
|
||||
: m_token(token), m_hidDev(hidDev)
|
||||
CDeviceBase::CDeviceBase(CDeviceToken* token)
|
||||
: m_token(token), m_hidDev(NULL)
|
||||
{
|
||||
hidDev->_setDeviceImp(this);
|
||||
}
|
||||
|
||||
CDeviceBase::~CDeviceBase()
|
||||
|
@ -25,11 +24,31 @@ void CDeviceBase::_deviceDisconnected()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void CDeviceBase::closeDevice()
|
||||
{
|
||||
if (m_token)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,143 @@
|
|||
#include "inputdev/CDolphinSmashAdapter.hpp"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
CDolphinSmashAdapter::CDolphinSmashAdapter(CDeviceToken* token, IHIDDevice* hidDev)
|
||||
: CDeviceBase(token, hidDev)
|
||||
/* Reference: https://github.com/ToadKing/wii-u-gc-adapter/blob/master/wii-u-gc-adapter.c
|
||||
*/
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
|
|
|
@ -2,19 +2,162 @@
|
|||
#include "inputdev/CDeviceToken.hpp"
|
||||
#include "inputdev/CDeviceBase.hpp"
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
#include <IOKit/usb/IOUSBLib.h>
|
||||
#include <IOKit/IOCFPlugIn.h>
|
||||
#include <thread>
|
||||
|
||||
#define MAX_REPORT_SIZE 65536
|
||||
|
||||
class CHIDDeviceIOKit final : public IHIDDevice
|
||||
{
|
||||
CDeviceToken* m_token;
|
||||
IOHIDDeviceRef m_dev;
|
||||
CDeviceToken& m_token;
|
||||
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::condition_variable m_initCond;
|
||||
std::thread* m_thread;
|
||||
CFRunLoopRef m_runLoop;
|
||||
CDeviceBase* m_devImp;
|
||||
CFRunLoopRef m_runLoop = NULL;
|
||||
|
||||
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,
|
||||
IOReturn result,
|
||||
|
@ -32,10 +175,11 @@ class CHIDDeviceIOKit final : public IHIDDevice
|
|||
{
|
||||
device->_deviceDisconnected();
|
||||
}
|
||||
static void _threadProc(CHIDDeviceIOKit* device)
|
||||
|
||||
static void _threadProcHL(CHIDDeviceIOKit* device)
|
||||
{
|
||||
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);
|
||||
__block std::unique_lock<std::mutex> lk(device->m_initMutex);
|
||||
device->m_runLoop = CFRunLoopGetCurrent();
|
||||
|
@ -47,14 +191,17 @@ class CHIDDeviceIOKit final : public IHIDDevice
|
|||
}), kCFRunLoopCommonModes);
|
||||
|
||||
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);
|
||||
IOHIDDeviceRegisterRemovalCallback(device->m_dev, (IOHIDCallback)_disconnect, device);
|
||||
IOHIDDeviceScheduleWithRunLoop(device->m_dev, device->m_runLoop, kCFRunLoopDefaultMode);
|
||||
IOHIDDeviceOpen(device->m_dev, kIOHIDOptionsTypeNone);
|
||||
IOHIDDeviceRegisterRemovalCallback(dev, (IOHIDCallback)_disconnect, device);
|
||||
IOHIDDeviceScheduleWithRunLoop(dev, device->m_runLoop, kCFRunLoopDefaultMode);
|
||||
IOHIDDeviceOpen(dev, kIOHIDOptionsTypeNone);
|
||||
CFRunLoopRun();
|
||||
if (device->m_runLoop)
|
||||
IOHIDDeviceClose(device->m_dev, kIOHIDOptionsTypeNone);
|
||||
IOHIDDeviceClose(dev, kIOHIDOptionsTypeNone);
|
||||
CFRelease(dev);
|
||||
}
|
||||
|
||||
void _deviceDisconnected()
|
||||
|
@ -63,21 +210,31 @@ class CHIDDeviceIOKit final : public IHIDDevice
|
|||
m_runLoop = NULL;
|
||||
if (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:
|
||||
|
||||
CHIDDeviceIOKit(CDeviceToken* token)
|
||||
CHIDDeviceIOKit(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel)
|
||||
: 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);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -85,6 +242,7 @@ public:
|
|||
{
|
||||
if (m_runLoop)
|
||||
CFRunLoopStop(m_runLoop);
|
||||
m_runningTransferLoop = false;
|
||||
m_thread->detach();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,51 @@
|
|||
#include "inputdev/CDeviceFinder.hpp"
|
||||
#include <CoreFoundation/CoreFoundation.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
|
||||
{
|
||||
|
@ -9,6 +54,7 @@ class CHIDListenerIOKit final : public IHIDListener
|
|||
|
||||
CFRunLoopRef m_listenerRunLoop;
|
||||
IOHIDManagerRef m_hidManager;
|
||||
IONotificationPortRef m_llPort;
|
||||
bool m_scanningEnabled;
|
||||
|
||||
static void deviceConnected(CHIDListenerIOKit* listener,
|
||||
|
@ -18,7 +64,10 @@ class CHIDListenerIOKit final : public IHIDListener
|
|||
{
|
||||
if (!listener->m_scanningEnabled)
|
||||
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;
|
||||
CFIndex vid, pid;
|
||||
CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberCFIndexType, &vid);
|
||||
|
@ -28,7 +77,7 @@ class CHIDListenerIOKit final : public IHIDListener
|
|||
listener->m_finder._insertToken(CDeviceToken(vid, pid,
|
||||
CFStringGetCStringPtr(manuf, kCFStringEncodingUTF8),
|
||||
CFStringGetCStringPtr(product, kCFStringEncodingUTF8),
|
||||
device));
|
||||
devPath));
|
||||
}
|
||||
|
||||
static void deviceDisconnected(CHIDListenerIOKit* listener,
|
||||
|
@ -44,12 +93,18 @@ class CHIDListenerIOKit final : public IHIDListener
|
|||
CFRunLoopWakeUp(listener->m_listenerRunLoop);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
CFIndex vid, pid;
|
||||
CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberCFIndexType, &vid);
|
||||
|
@ -59,7 +114,84 @@ class CHIDListenerIOKit final : public IHIDListener
|
|||
listener->m_finder._insertToken(CDeviceToken(vid, pid,
|
||||
CFStringGetCStringPtr(manuf, 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:
|
||||
|
@ -78,9 +210,38 @@ public:
|
|||
if (ret != kIOReturnSuccess)
|
||||
throw std::runtime_error("error establishing IOHIDManager");
|
||||
|
||||
/* Initial Device Add */
|
||||
/* Initial HID Device Add */
|
||||
m_scanningEnabled = true;
|
||||
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;
|
||||
|
||||
}
|
||||
|
@ -90,6 +251,8 @@ public:
|
|||
IOHIDManagerUnscheduleFromRunLoop(m_hidManager, m_listenerRunLoop, kCFRunLoopDefaultMode);
|
||||
IOHIDManagerClose(m_hidManager, kIOHIDManagerOptionNone);
|
||||
CFRelease(m_hidManager);
|
||||
CFRunLoopRemoveSource(m_listenerRunLoop, IONotificationPortGetRunLoopSource(m_llPort), kCFRunLoopDefaultMode);
|
||||
IONotificationPortDestroy(m_llPort);
|
||||
}
|
||||
|
||||
/* Automatic device scanning */
|
||||
|
|
|
@ -10,17 +10,29 @@ bool BooDeviceMatchToken(const CDeviceToken& token, EDeviceMask mask)
|
|||
return false;
|
||||
}
|
||||
|
||||
IHIDDevice* IHIDDeviceNew(CDeviceToken* token);
|
||||
CDeviceBase* BooDeviceNew(CDeviceToken* token)
|
||||
IHIDDevice* IHIDDeviceNew(CDeviceToken& token, CDeviceBase& devImp, bool lowLevel);
|
||||
CDeviceBase* BooDeviceNew(CDeviceToken& token)
|
||||
{
|
||||
IHIDDevice* newDev = IHIDDeviceNew(token);
|
||||
if (!newDev)
|
||||
|
||||
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;
|
||||
|
||||
if (token->getVendorId() == VID_NINTENDO && token->getProductId() == PID_SMASH_ADAPTER)
|
||||
return new CDolphinSmashAdapter(token, newDev);
|
||||
else
|
||||
delete newDev;
|
||||
|
||||
IHIDDevice* newDev = IHIDDeviceNew(token, *retval, lowLevel);
|
||||
if (!newDev)
|
||||
{
|
||||
delete retval;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ class CDeviceBase;
|
|||
class IHIDDevice
|
||||
{
|
||||
friend CDeviceBase;
|
||||
virtual void _setDeviceImp(CDeviceBase* dev)=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:
|
||||
inline virtual ~IHIDDevice() {};
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#ifndef IHIDLISTENER_HPP
|
||||
#define IHIDLISTENER_HPP
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include "CDeviceToken.hpp"
|
||||
typedef std::map<TDeviceHandle, CDeviceToken> TDeviceTokens;
|
||||
typedef std::unordered_map<std::string, CDeviceToken> TDeviceTokens;
|
||||
typedef std::pair<TDeviceTokens::iterator, bool> TInsertedDeviceToken;
|
||||
class CDeviceFinder;
|
||||
|
||||
|
|
|
@ -3,9 +3,27 @@
|
|||
#include <stdio.h>
|
||||
#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
|
||||
{
|
||||
CDolphinSmashAdapter* smashAdapter = NULL;
|
||||
CDolphinSmashAdapterCallback m_cb;
|
||||
public:
|
||||
CTestDeviceFinder()
|
||||
: CDeviceFinder(DEV_DOL_SMASH_ADAPTER)
|
||||
|
@ -13,6 +31,7 @@ public:
|
|||
void deviceConnected(CDeviceToken& tok)
|
||||
{
|
||||
smashAdapter = dynamic_cast<CDolphinSmashAdapter*>(tok.openAndGetDevice());
|
||||
smashAdapter->setCallback(&m_cb);
|
||||
}
|
||||
void deviceDisconnected(CDeviceToken&, CDeviceBase* device)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue