Files
itgmania212121/stepmania/src/arch/InputHandler/InputHandler_Carbon.cpp
T

687 lines
17 KiB
C++

#include "global.h"
#include "RageLog.h"
#include <ext/hash_map>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/usb/USB.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Carbon/Carbon.h>
#include "InputHandler_Carbon.h"
#include "ForEach.h"
#include "RageUtil.h"
using namespace std;
using __gnu_cxx::hash_map;
static void AddElement(const void *value, void *context);
// Simple helper, still need to release it
static inline CFNumberRef CFInt(int n)
{
return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &n);
}
static inline void PrintIOErr(IOReturn err, const char *s)
{
LOG->Warn("%s - %s(%x,%d)", s, mach_error_string(err), err, err & 0xFFFFFF);
}
// These gets absurdly long
#define DictValue(d, k) CFDictionaryGetValue(d, CFSTR(k))
#define IntValue(o, n) \
CFNumberGetValue(CFNumberRef(o), kCFNumberIntType, n)
/* This is just awful, these aren't objects, treating them as such leads
* to: (*object)->function(object [, argument]...)
* Instead, do: CALL(object, function [, argument]...)
*/
#define CALL(o,f,...) (*(o))->f((o), ## __VA_ARGS__)
struct Joystick
{
InputDevice id;
// map cookie to button
hash_map<int, int> mapping;
int x_axis, y_axis, z_axis;
int x_min, y_min, z_min;
int x_max, y_max, z_max;
Joystick();
};
Joystick::Joystick() : id(DEVICE_NONE),
x_axis(JOYSTICK_BUTTON_INVALID),
y_axis(JOYSTICK_BUTTON_INVALID),
z_axis(JOYSTICK_BUTTON_INVALID),
x_min(0), y_min(0), z_min(0),
x_max(0), y_max(0), z_max(0)
{
}
class JoystickDevice
{
private:
vector<Joystick> mSticks;
IOHIDDeviceInterface **mInterface;
IOHIDQueueInterface **mQueue;
bool mRunning;
CString mDescription;
static void AddJoystick(const void *value, void *context);
public:
JoystickDevice();
~JoystickDevice();
bool Open(io_object_t device, int num, InputHandler_Carbon *handler);
// returns the number of IDs assigned starting from startID
int AssignJoystickIDs(int startID);
inline int NumberOfSticks() const { return mSticks.size(); }
inline const Joystick& GetStick(int index) const { return mSticks[index]; }
inline const CString& GetDescription() const { return mDescription; }
};
JoystickDevice::JoystickDevice() : mInterface(NULL), mQueue(NULL),
mRunning(false)
{
}
bool JoystickDevice::Open(io_object_t device, int num,
InputHandler_Carbon *handler)
{
IOReturn ret;
CFMutableDictionaryRef properties;
kern_return_t result;
result = IORegistryEntryCreateCFProperties(device, &properties,
kCFAllocatorDefault,
kNilOptions);
if (result != KERN_SUCCESS || !properties)
{
LOG->Warn("Couldn't get properties.");
return false;
}
// XXX Get Device Info
mDescription = "XXX Device Info";
CFArrayRef sticks;
if (!(sticks = (CFArrayRef)DictValue(properties, kIOHIDElementKey)))
{
CFRelease(properties);
return false;
}
size_t count = CFArrayGetCount(sticks);
CFRange r = {0, count};
mSticks.reserve(count);
CFArrayApplyFunction(sticks, r, JoystickDevice::AddJoystick, this);
CFRelease(properties);
// Create the interface
IOCFPlugInInterface **plugInInterface;
// Ugh. HRESULT an awful return type
HRESULT hresult;
SInt32 score;
ret = IOCreatePlugInInterfaceForService(device,
kIOHIDDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface, &score);
if (ret != kIOReturnSuccess)
{
PrintIOErr(ret, "Failed to create plugin interface.");
return false;
}
// Call a method of the plugin to create the device interface
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID);
hresult = CALL(plugInInterface, QueryInterface, bytes,
(void **)&mInterface);
CALL(plugInInterface, Release);
if (hresult != S_OK)
{
LOG->Warn("Couldn't get device interface from plugin interface.");
mInterface = NULL;
return false;
}
// open the interface
if ((ret = CALL(mInterface, open, 0)) != kIOReturnSuccess)
{
PrintIOErr(ret, "Failed to open the interface.");
CALL(mInterface, Release);
mInterface = NULL;
return false;
}
// alloc/create queue
mQueue = CALL(mInterface, allocQueue);
if (!mQueue)
{
LOG->Warn("Couldn't allocate a queue.");
return false;
}
if ((ret = CALL(mQueue, create, 0, 32)) != kIOReturnSuccess)
{
PrintIOErr(ret, "Failed to create the queue.");
CALL(mQueue, Release);
mQueue = NULL;
return false;
}
// Add elements to the queue for each Joystick
for (vector<Joystick>::const_iterator i = mSticks.begin();
i != mSticks.end(); ++i)
{
const Joystick& js = *i;
if (js.x_axis)
CALL(mQueue, addElement, IOHIDElementCookie(js.x_axis), 0);
if (js.y_axis)
CALL(mQueue, addElement, IOHIDElementCookie(js.y_axis), 0);
if (js.z_axis)
CALL(mQueue, addElement, IOHIDElementCookie(js.z_axis), 0);
for (hash_map<int, int>::const_iterator j = js.mapping.begin();
j != js.mapping.end(); ++j)
{
CALL(mQueue, addElement, IOHIDElementCookie(j->first), 0);
}
}
// add the callback
CFRunLoopSourceRef runLoopSource;
ret = CALL(mQueue, createAsyncEventSource, &runLoopSource);
if (ret != kIOReturnSuccess)
{
PrintIOErr(ret, "Failed to create async event source.");
return false;
}
CFRunLoopRef runLoop;
runLoop = CFRunLoopRef(GetCFRunLoopFromEventLoop(GetMainEventLoop()));
if (!CFRunLoopContainsSource(runLoop, runLoopSource,
kCFRunLoopDefaultMode))
{
CFRunLoopAddSource(runLoop, runLoopSource,
kCFRunLoopDefaultMode);
}
CALL(mQueue, setEventCallout, InputHandler_Carbon::QueueCallBack,
handler, (void *)num);
if (ret != kIOReturnSuccess)
{
PrintIOErr(ret, "Failed to set the call back.");
return false;
}
// start the queue
ret = CALL(mQueue, start);
if (ret != kIOReturnSuccess)
{
PrintIOErr(ret, "Failed to start the queue.");
return false;
}
mRunning = true;
return true;
}
JoystickDevice::~JoystickDevice()
{
if (mQueue)
{
CFRunLoopSourceRef runLoopSource;
if (mRunning)
CALL(mQueue, stop);
if ((runLoopSource = CALL(mQueue, getAsyncEventSource)))
{
CFRunLoopRef ref;
ref = CFRunLoopRef(GetCFRunLoopFromEventLoop(GetMainEventLoop()));
if (CFRunLoopContainsSource(ref, runLoopSource,
kCFRunLoopDefaultMode))
{
CFRunLoopRemoveSource(ref, runLoopSource,
kCFRunLoopDefaultMode);
}
CFRelease(runLoopSource);
}
CALL(mQueue, dispose);
CALL(mQueue, Release);
}
if (mInterface)
{
CALL(mInterface, close);
CALL(mInterface, Release);
}
}
void JoystickDevice::AddJoystick(const void *value, void *context)
{
if (CFGetTypeID(CFTypeRef(value)) != CFDictionaryGetTypeID())
return;
CFDictionaryRef dict = CFDictionaryRef(value);
JoystickDevice *This = (JoystickDevice *)context;
CFArrayRef elements;
CFTypeRef object;
int usage, usagePage;
CFTypeID numID = CFNumberGetTypeID();
// Get usage page
object = DictValue(dict, kIOHIDElementUsagePageKey);
if (!object || CFGetTypeID(object) != numID ||
!IntValue(object, &usagePage))
{
return;
}
// Get usage
object = DictValue(dict, kIOHIDElementUsageKey);
if (!object || CFGetTypeID(object) != numID ||
!IntValue(object, &usage))
{
return;
}
if (usagePage != kHIDPage_GenericDesktop ||
usage != kHIDUsage_GD_Joystick)
{
return;
}
if (!(elements = (CFArrayRef)DictValue(dict, kIOHIDElementKey)))
return;
Joystick js;
CFRange r = {0, CFArrayGetCount(elements)};
CFArrayApplyFunction(elements, r, AddElement, &js);
This->mSticks.push_back(js);
}
int JoystickDevice::AssignJoystickIDs(int startID)
{
for (vector<Joystick>::iterator i = mSticks.begin();
i != mSticks.end(); ++i)
{
i->id = InputDevice(startID++);
}
return mSticks.size();
}
// Callback
static void AddElement(const void *value, void *context)
{
if (CFGetTypeID(CFTypeRef(value)) != CFDictionaryGetTypeID())
return;
CFDictionaryRef dict = CFDictionaryRef(value);
Joystick& js = *(Joystick *)context;
CFTypeRef object;
CFTypeID numID = CFNumberGetTypeID();
int cookie, usage, usagePage;
CFArrayRef elements;
// Recursively add elements
if ((elements = (CFArrayRef)DictValue(dict, kIOHIDElementKey)))
{
CFRange r = {0, CFArrayGetCount(elements)};
CFArrayApplyFunction(elements, r, AddElement, context);
}
// Get usage page
object = DictValue(dict, kIOHIDElementUsagePageKey);
if (!object || CFGetTypeID(object) != numID ||
!IntValue(object, &usagePage))
{
return;
}
// Get usage
object = DictValue(dict, kIOHIDElementUsageKey);
if (!object || CFGetTypeID(object) != numID ||
!IntValue(object, &usage))
{
return;
}
// Get cookie
object = DictValue(dict, kIOHIDElementCookieKey);
if (!object || CFGetTypeID(object) != numID ||
!IntValue(object, &cookie))
{
return;
}
switch (usagePage)
{
case kHIDPage_GenericDesktop:
{
int min = 0;
int max = 0;
object = DictValue(dict, kIOHIDElementMinKey);
if (object && CFGetTypeID(object) == numID)
IntValue(object, &min);
object = DictValue(dict, kIOHIDElementMaxKey);
if (object && CFGetTypeID(object) == numID)
IntValue(object, &max);
switch (usage)
{
case kHIDUsage_GD_X:
js.x_axis = cookie;
js.x_min = min;
js.x_max = max;
break;
case kHIDUsage_GD_Y:
js.y_axis = cookie;
js.y_min = min;
js.y_max = max;
break;
case kHIDUsage_GD_Z:
js.z_axis = cookie;
js.z_min = min;
js.z_max = max;
break;
case kHIDUsage_GD_DPadUp:
js.mapping[cookie] = JOY_UP;
break;
case kHIDUsage_GD_DPadDown:
js.mapping[cookie] = JOY_DOWN;
break;
case kHIDUsage_GD_DPadRight:
js.mapping[cookie] = JOY_RIGHT;
break;
case kHIDUsage_GD_DPadLeft:
js.mapping[cookie] = JOY_LEFT;
break;
default:
return;
}
break;
}
case kHIDPage_Button:
{
// button n has usage = n, subtract 1 to ensure
// button 1 = JOY_BUTTON_1
const int buttonID = usage + JOY_BUTTON_1 - 1;
if (buttonID < NUM_JOYSTICK_BUTTONS)
js.mapping[cookie] = buttonID;
break;
}
default:
return;
} // end switch (usagePage)
}
void InputHandler_Carbon::QueueCallBack(void *target, int result,
void *refcon, void *sender)
{
// The result seems useless as you can't actually return anything...
// refcon is the JoystickDevice number
RageTimer now;
InputHandler_Carbon *This = (InputHandler_Carbon *)target;
IOHIDQueueInterface **queue = (IOHIDQueueInterface **)sender;
IOHIDEventStruct event;
AbsoluteTime zeroTime = {0,0};
JoystickDevice *jd = This->mDevices[int(refcon)];
while ((result = CALL(queue, getNextEvent, &event, zeroTime, 0))
== kIOReturnSuccess)
{
int cookie = int(event.elementCookie);
int value = event.value;
for (int i = 0; i < jd->NumberOfSticks(); ++i)
{
const Joystick& js = jd->GetStick(i);
if (js.x_axis == cookie)
{
float level = SCALE(value, js.x_min, js.x_max, -1.0f, 1.0f);
LOG->Trace("(%d) x-axis: %f\n", int(js.id), level);
This->ButtonPressed(DeviceInput(js.id, JOY_LEFT, max(-level, 0.0f), now), level < -0.5f);
This->ButtonPressed(DeviceInput(js.id, JOY_RIGHT, max(level, 0.0f), now), level > 0.5f);
break;
}
else if (js.y_axis == cookie)
{
float level = SCALE(value, js.y_min, js.y_max, -1.0f, 1.0f);
LOG->Trace("(%d) y-axis: %f\n", int(js.id), level);
This->ButtonPressed(DeviceInput(js.id, JOY_UP, max(-level, 0.0f), now), level < -0.5f);
This->ButtonPressed(DeviceInput(js.id, JOY_DOWN, max(level, 0.0f), now), level > 0.5f);
break;
}
else if (js.z_axis == cookie)
{
float level = SCALE(value, js.z_min, js.z_max, -1.0f, 1.0f);
LOG->Trace("(%d) z-axis: %f\n", int(js.id), level);
This->ButtonPressed(DeviceInput(js.id, JOY_Z_UP, max(-level, 0.0f), now), level < -0.5f);
This->ButtonPressed(DeviceInput(js.id, JOY_Z_DOWN, max(level, 0.0f), now), level > 0.5f);
break;
}
else
{
// hash_map<T,U>::operator[] is not const
hash_map<int, int>::const_iterator iter;
iter = js.mapping.find(cookie);
if (iter != js.mapping.end())
{
LOG->Trace("(%d) Button %d: %s\n", int(js.id),
iter->second, (value ? "down" : "up"));
This->ButtonPressed(DeviceInput(js.id, iter->second, value, now), value);
break;
}
}
}
}
}
OSStatus InputHandler_Carbon::EventHandler(EventHandlerCallRef callRef,
EventRef event, void *data)
{
InputHandler_Carbon *This = (InputHandler_Carbon *)data;
UInt32 kind = GetEventKind(event);
UInt32 keyCode;
char charCode;
GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL,
sizeof(keyCode), NULL, &keyCode);
GetEventParameter(event, kEventParamKeyMacCharCodes, typeChar, NULL,
sizeof(charCode), NULL, &charCode);
const char *type;
switch (kind)
{
case kEventRawKeyDown:
type = "down";
break;
case kEventRawKeyRepeat:
type = "repeat";
break;
case kEventRawKeyUp:
type = "up";
default:
type = "unknown";
}
LOG->Trace("(0) %u %c: %s\n", unsigned(keyCode), charCode, type);
return 0;
}
InputHandler_Carbon::~InputHandler_Carbon()
{
for (vector<JoystickDevice *>::iterator i = mDevices.begin();
i != mDevices.end(); ++i)
{
delete *i;
}
if (mMasterPort)
mach_port_deallocate(mach_task_self(), mMasterPort);
RemoveEventHandler(mEventHandlerRef);
DisposeEventHandlerUPP(mEventHandlerUPP);
}
InputHandler_Carbon::InputHandler_Carbon()
{
// Install a Carbon Event handler
mEventHandlerUPP = NewEventHandlerUPP(EventHandler);
EventTypeSpec typeList[] = {
{ kEventClassKeyboard, kEventRawKeyDown },
{ kEventClassKeyboard, kEventRawKeyRepeat },
{ kEventClassKeyboard, kEventRawKeyUp } };
if (InstallEventHandler(GetApplicationEventTarget(), mEventHandlerUPP,
3, typeList, this, &mEventHandlerRef))
{
LOG->Warn("Failed to install the Event Handler.");
}
// Get a Mach port to initiate communication with I/O Kit.
mach_port_t masterPort;
IOReturn ret = IOMasterPort(MACH_PORT_NULL, &masterPort);
if (ret != kIOReturnSuccess)
{
PrintIOErr(ret, "Couldn't create a master I/O Kit port.");
return;
}
mMasterPort = masterPort;
// Build the matching dictionary.
CFMutableDictionaryRef dict;
if ((dict = IOServiceMatching(kIOHIDDeviceKey)) == NULL)
{
LOG->Warn("Couldn't create a matching dictionary.");
mach_port_deallocate(mach_task_self(), masterPort);
return;
}
// Refine the search by only looking for joysticks
CFNumberRef usagePage = CFInt(kHIDPage_GenericDesktop);
CFNumberRef usage = CFInt(kHIDUsage_GD_Joystick);
CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsagePageKey),
usagePage);
CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsageKey),
usage);
// Cleanup after ourselves
CFRelease(usagePage);
CFRelease(usage);
// Find the HID devices.
io_iterator_t iter;
/* Get an iterator to the matching devies
* This consumes a reference to the dictionary so we don't
* have to Release() later.
*/
ret = IOServiceGetMatchingServices(masterPort, dict, &iter);
if ((ret != kIOReturnSuccess) || !iter)
{
mach_port_deallocate(mach_task_self(), masterPort);
masterPort = 0;
return;
}
// Iterate over the devices and add them
io_object_t device;
int id = DEVICE_JOY1;
while ((device = IOIteratorNext(iter)))
{
JoystickDevice *jd = new JoystickDevice;
if (jd->Open(device, mDevices.size(), this))
{
id += jd->AssignJoystickIDs(id);
mDevices.push_back(jd);
puts("Added device.");
}
else
delete jd;
IOObjectRelease(device);
}
IOObjectRelease(iter);
}
void InputHandler_Carbon::GetDevicesAndDescriptions(vector<InputDevice>& dev,
vector<CString>& desc)
{
for (vector<JoystickDevice *>::const_iterator i = mDevices.begin();
i != mDevices.end(); ++i)
{
const JoystickDevice *jd = *i;
for (int j = 0; j < jd->NumberOfSticks(); ++j)
{
dev.push_back(jd->GetStick(j).id);
desc.push_back(jd->GetDescription());
}
}
}
/*
* (c) 2005 Steve Checkoway
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/