mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-27 10:10:57 +00:00
1173 lines
36 KiB
C
1173 lines
36 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2013-2014 - Jason Fetters
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Courtesy Contributor - Olivier Parra
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string/stdstring.h>
|
|
|
|
#include <IOKit/hid/IOHIDManager.h>
|
|
#include <IOKit/hid/IOHIDKeys.h>
|
|
|
|
#include <retro_miscellaneous.h>
|
|
|
|
#include "../input_defines.h"
|
|
#include "../input_driver.h"
|
|
|
|
#include "../connect/joypad_connection.h"
|
|
#include "../../tasks/tasks_internal.h"
|
|
#include "../../verbosity.h"
|
|
|
|
typedef struct apple_input_rec
|
|
{
|
|
IOHIDElementCookie cookie;
|
|
uint32_t id;
|
|
struct apple_input_rec *next;
|
|
} apple_input_rec_t;
|
|
|
|
typedef struct apple_hid
|
|
{
|
|
IOHIDManagerRef ptr;
|
|
joypad_connection_t *slots;
|
|
uint32_t buttons[MAX_USERS];
|
|
int16_t axes[MAX_USERS][11];
|
|
int8_t hats[MAX_USERS][2]; /* MacOS only supports 1 hat AFAICT */
|
|
} iohidmanager_hid_t;
|
|
|
|
struct iohidmanager_hid_adapter
|
|
{
|
|
uint32_t slot;
|
|
IOHIDDeviceRef handle;
|
|
char name[PATH_MAX_LENGTH];
|
|
apple_input_rec_t *axes;
|
|
apple_input_rec_t *hats;
|
|
apple_input_rec_t *buttons;
|
|
uint8_t data[2048];
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
uint32_t uniqueId;
|
|
#endif
|
|
};
|
|
|
|
CFComparisonResult iohidmanager_sort_elements(const void *val1, const void *val2, void *context)
|
|
{
|
|
uint32_t page1 = (uint32_t)IOHIDElementGetUsagePage((IOHIDElementRef)val1);
|
|
uint32_t page2 = (uint32_t)IOHIDElementGetUsagePage((IOHIDElementRef)val2);
|
|
uint32_t use1 = (uint32_t)IOHIDElementGetUsage((IOHIDElementRef)val1);
|
|
uint32_t use2 = (uint32_t)IOHIDElementGetUsage((IOHIDElementRef)val2);
|
|
uint32_t cookie1 = (uint32_t)IOHIDElementGetCookie((IOHIDElementRef)val1);
|
|
uint32_t cookie2 = (uint32_t)IOHIDElementGetCookie((IOHIDElementRef)val2);
|
|
|
|
if (page1 != page2)
|
|
return (CFComparisonResult)(page1 > page2);
|
|
|
|
if (use1 != use2)
|
|
return (CFComparisonResult)(use1 > use2);
|
|
|
|
return (CFComparisonResult)(cookie1 > cookie2);
|
|
}
|
|
|
|
static bool iohidmanager_check_for_id(apple_input_rec_t *rec, uint32_t id)
|
|
{
|
|
while (rec)
|
|
{
|
|
if (rec->id == id)
|
|
return true;
|
|
rec = rec->next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void iohidmanager_append_record(apple_input_rec_t *rec, apple_input_rec_t *b)
|
|
{
|
|
apple_input_rec_t *tmp = rec;
|
|
while (tmp->next)
|
|
tmp = tmp->next;
|
|
tmp->next = b;
|
|
}
|
|
|
|
/* Insert a new detected button into a button ordered list.
|
|
* Button list example with Nimbus Controller:
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
* "id" list member | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 144 | 145 | 146 | 147 | 547 |
|
|
* Final Button ID | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
|
|
* Ranges |< X/Y/A/B/L1/L2/R1/R2 buttons >|< D-PAD >|MENU |
|
|
* In that way, HID button IDs allocation:
|
|
* - becomes robust and determinist
|
|
* - remains compatible with previous algorithm (i.e. btn->id = (uint32_t)(use - 1)) and so
|
|
* compatible with previous autoconfig files.
|
|
*/
|
|
static void iohidmanager_append_record_ordered(apple_input_rec_t **p_rec, apple_input_rec_t *b)
|
|
{
|
|
apple_input_rec_t *tmp = *p_rec;
|
|
while (tmp && (tmp->id <= b->id))
|
|
{
|
|
p_rec = &tmp->next;
|
|
tmp = tmp->next;
|
|
}
|
|
b->next = tmp;
|
|
*p_rec = b;
|
|
}
|
|
|
|
static bool iohidmanager_hid_joypad_query(void *data, unsigned pad)
|
|
{
|
|
return pad < MAX_USERS;
|
|
}
|
|
|
|
static const char *iohidmanager_hid_joypad_name(void *data, unsigned pad)
|
|
{
|
|
/* TODO/FIXME - implement properly */
|
|
if (pad >= MAX_USERS)
|
|
return NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void iohidmanager_hid_joypad_get_buttons(void *data,
|
|
unsigned port, input_bits_t *state)
|
|
{
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)data;
|
|
if (hid)
|
|
return pad_connection_get_buttons(&hid->slots[port], port, state);
|
|
else
|
|
BIT256_CLEAR_ALL_PTR(state);
|
|
}
|
|
|
|
static int16_t iohidmanager_hid_joypad_button(void *data,
|
|
unsigned port, uint16_t joykey)
|
|
{
|
|
unsigned hat_dir;
|
|
input_bits_t buttons;
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)data;
|
|
|
|
if (port >= DEFAULT_MAX_PADS)
|
|
return 0;
|
|
|
|
iohidmanager_hid_joypad_get_buttons(data, port, &buttons);
|
|
|
|
hat_dir = GET_HAT_DIR(joykey);
|
|
|
|
/* Check hat. */
|
|
if (hat_dir)
|
|
{
|
|
unsigned h = GET_HAT(joykey);
|
|
if (h >= 1)
|
|
return 0;
|
|
|
|
switch (hat_dir)
|
|
{
|
|
case HAT_LEFT_MASK:
|
|
return (hid->hats[port][0] < 0);
|
|
case HAT_RIGHT_MASK:
|
|
return (hid->hats[port][0] > 0);
|
|
case HAT_UP_MASK:
|
|
return (hid->hats[port][1] < 0);
|
|
case HAT_DOWN_MASK:
|
|
return (hid->hats[port][1] > 0);
|
|
default:
|
|
break;
|
|
}
|
|
/* hat requested and no hat button down */
|
|
}
|
|
else if (joykey < 32)
|
|
return ((BIT256_GET(buttons, joykey) != 0)
|
|
|| ((hid->buttons[port] & (1 << joykey)) != 0));
|
|
return 0;
|
|
}
|
|
|
|
static int16_t iohidmanager_hid_joypad_axis(void *data,
|
|
unsigned port, uint32_t joyaxis)
|
|
{
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)data;
|
|
|
|
if (AXIS_NEG_GET(joyaxis) < 11)
|
|
{
|
|
int16_t val = hid->axes[port][AXIS_NEG_GET(joyaxis)]
|
|
+ pad_connection_get_axis(&hid->slots[port],
|
|
port, AXIS_NEG_GET(joyaxis));
|
|
|
|
if (val < 0)
|
|
return val;
|
|
}
|
|
else if (AXIS_POS_GET(joyaxis) < 11)
|
|
{
|
|
int16_t val = hid->axes[port][AXIS_POS_GET(joyaxis)]
|
|
+ pad_connection_get_axis(&hid->slots[port],
|
|
port, AXIS_POS_GET(joyaxis));
|
|
|
|
if (val > 0)
|
|
return val;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int16_t iohidmanager_hid_joypad_state(
|
|
void *data,
|
|
rarch_joypad_info_t *joypad_info,
|
|
const void *binds_data,
|
|
unsigned port)
|
|
{
|
|
unsigned i;
|
|
int16_t ret = 0;
|
|
const struct retro_keybind *binds = (const struct retro_keybind*)binds_data;
|
|
uint16_t port_idx = joypad_info->joy_idx;
|
|
|
|
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
|
|
{
|
|
/* Auto-binds are per joypad, not per user. */
|
|
const uint64_t joykey = (binds[i].joykey != NO_BTN)
|
|
? binds[i].joykey : joypad_info->auto_binds[i].joykey;
|
|
const uint32_t joyaxis = (binds[i].joyaxis != AXIS_NONE)
|
|
? binds[i].joyaxis : joypad_info->auto_binds[i].joyaxis;
|
|
if (
|
|
(uint16_t)joykey != NO_BTN
|
|
&& iohidmanager_hid_joypad_button(data, port_idx, (uint16_t)joykey))
|
|
ret |= ( 1 << i);
|
|
else if (joyaxis != AXIS_NONE &&
|
|
((float)abs(iohidmanager_hid_joypad_axis(data, port_idx, joyaxis))
|
|
/ 0x8000) > joypad_info->axis_threshold)
|
|
ret |= (1 << i);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool iohidmanager_hid_joypad_rumble(void *data, unsigned pad,
|
|
enum retro_rumble_effect effect, uint16_t strength)
|
|
{
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)data;
|
|
if (!hid)
|
|
return false;
|
|
return pad_connection_rumble(&hid->slots[pad], pad, effect, strength);
|
|
}
|
|
|
|
static void iohidmanager_hid_device_send_control(void *data,
|
|
uint8_t* data_buf, size_t size)
|
|
{
|
|
struct iohidmanager_hid_adapter *adapter =
|
|
(struct iohidmanager_hid_adapter*)data;
|
|
|
|
if (adapter)
|
|
IOHIDDeviceSetReport(adapter->handle,
|
|
kIOHIDReportTypeOutput, 0x01, data_buf + 1, size - 1);
|
|
}
|
|
|
|
static void iohidmanager_hid_device_report(void *data,
|
|
IOReturn result, void *sender,
|
|
IOHIDReportType type, uint32_t reportID, uint8_t *report,
|
|
CFIndex reportLength)
|
|
{
|
|
struct iohidmanager_hid_adapter *adapter =
|
|
(struct iohidmanager_hid_adapter*)data;
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)hid_driver_get_data();
|
|
|
|
if (hid && adapter)
|
|
pad_connection_packet(&hid->slots[adapter->slot], adapter->slot,
|
|
adapter->data, (uint32_t)(reportLength + 1));
|
|
}
|
|
|
|
/* NOTE: I pieced this together through trial and error,
|
|
* any corrections are welcome. */
|
|
|
|
static void iohidmanager_hid_device_input_callback(void *data, IOReturn result,
|
|
void* sender, IOHIDValueRef value)
|
|
{
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)hid_driver_get_data();
|
|
struct iohidmanager_hid_adapter *adapter = (struct iohidmanager_hid_adapter*)data;
|
|
IOHIDElementRef element = IOHIDValueGetElement(value);
|
|
uint32_t type = (uint32_t)IOHIDElementGetType(element);
|
|
uint32_t page = (uint32_t)IOHIDElementGetUsagePage(element);
|
|
uint32_t use = (uint32_t)IOHIDElementGetUsage(element);
|
|
uint32_t cookie = (uint32_t)IOHIDElementGetCookie(element);
|
|
apple_input_rec_t *tmp = NULL;
|
|
|
|
if (type != kIOHIDElementTypeInput_Misc)
|
|
if (type != kIOHIDElementTypeInput_Button)
|
|
if (type != kIOHIDElementTypeInput_Axis)
|
|
return;
|
|
|
|
/* Joystick handler.
|
|
* TODO: Can GamePad work the same? */
|
|
|
|
int pushed_button = 0;
|
|
|
|
switch (page)
|
|
{
|
|
case kHIDPage_GenericDesktop:
|
|
switch (type)
|
|
{
|
|
case kIOHIDElementTypeInput_Misc:
|
|
switch (use)
|
|
{
|
|
case kHIDUsage_GD_Hatswitch:
|
|
{
|
|
tmp = adapter->hats;
|
|
|
|
while (tmp && tmp->cookie != (IOHIDElementCookie)cookie)
|
|
tmp = tmp->next;
|
|
|
|
if (tmp && tmp->cookie == (IOHIDElementCookie)cookie)
|
|
{
|
|
CFIndex min = IOHIDElementGetLogicalMin(element);
|
|
CFIndex range = IOHIDElementGetLogicalMax(element) - min;
|
|
CFIndex val = IOHIDValueGetIntegerValue(value);
|
|
|
|
if (range == 3)
|
|
val *= 2;
|
|
|
|
if(min == 1)
|
|
val--;
|
|
|
|
switch(val)
|
|
{
|
|
case 0:
|
|
/* pos = up */
|
|
hid->hats[adapter->slot][0] = 0;
|
|
hid->hats[adapter->slot][1] = -1;
|
|
break;
|
|
case 1:
|
|
/* pos = up+right */
|
|
hid->hats[adapter->slot][0] = 1;
|
|
hid->hats[adapter->slot][1] = -1;
|
|
break;
|
|
case 2:
|
|
/* pos = right */
|
|
hid->hats[adapter->slot][0] = 1;
|
|
hid->hats[adapter->slot][1] = 0;
|
|
break;
|
|
case 3:
|
|
/* pos = down+right */
|
|
hid->hats[adapter->slot][0] = 1;
|
|
hid->hats[adapter->slot][1] = 1;
|
|
break;
|
|
case 4:
|
|
/* pos = down */
|
|
hid->hats[adapter->slot][0] = 0;
|
|
hid->hats[adapter->slot][1] = 1;
|
|
break;
|
|
case 5:
|
|
/* pos = down+left */
|
|
hid->hats[adapter->slot][0] = -1;
|
|
hid->hats[adapter->slot][1] = 1;
|
|
break;
|
|
case 6:
|
|
/* pos = left */
|
|
hid->hats[adapter->slot][0] = -1;
|
|
hid->hats[adapter->slot][1] = 0;
|
|
break;
|
|
case 7:
|
|
/* pos = up_left */
|
|
hid->hats[adapter->slot][0] = -1;
|
|
hid->hats[adapter->slot][1] = -1;
|
|
break;
|
|
default:
|
|
/* pos = centered */
|
|
hid->hats[adapter->slot][0] = 0;
|
|
hid->hats[adapter->slot][1] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
tmp = adapter->axes;
|
|
|
|
while (tmp && tmp->cookie != (IOHIDElementCookie)cookie)
|
|
tmp = tmp->next;
|
|
|
|
if (tmp)
|
|
{
|
|
if (tmp->cookie == (IOHIDElementCookie)cookie)
|
|
{
|
|
CFIndex min = IOHIDElementGetPhysicalMin(element);
|
|
CFIndex state = IOHIDValueGetIntegerValue(value) - min;
|
|
CFIndex max = IOHIDElementGetPhysicalMax(element) - min;
|
|
float val = (float)state / (float)max;
|
|
|
|
hid->axes[adapter->slot][tmp->id] =
|
|
((val * 2.0f) - 1.0f) * 32767.0f;
|
|
}
|
|
}
|
|
else
|
|
pushed_button = 1;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case kHIDPage_Consumer:
|
|
case kHIDPage_Button:
|
|
switch (type)
|
|
{
|
|
case kIOHIDElementTypeInput_Misc:
|
|
case kIOHIDElementTypeInput_Button:
|
|
pushed_button = 1;
|
|
break;
|
|
}
|
|
break;
|
|
case kHIDPage_Simulation:
|
|
switch (type)
|
|
{
|
|
case kIOHIDElementTypeInput_Misc:
|
|
switch (use)
|
|
{
|
|
default:
|
|
tmp = adapter->axes;
|
|
|
|
while (tmp && tmp->cookie != (IOHIDElementCookie)cookie)
|
|
{
|
|
tmp = tmp->next;
|
|
}
|
|
if (tmp)
|
|
{
|
|
if (tmp->cookie == (IOHIDElementCookie)cookie)
|
|
{
|
|
CFIndex min = IOHIDElementGetPhysicalMin(element);
|
|
CFIndex state = IOHIDValueGetIntegerValue(value) - min;
|
|
CFIndex max = IOHIDElementGetPhysicalMax(element) - min;
|
|
float val = (float)state / (float)max;
|
|
|
|
hid->axes[adapter->slot][tmp->id] =
|
|
((val * 2.0f) - 1.0f) * 32767.0f;
|
|
}
|
|
}
|
|
else
|
|
pushed_button = 1;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (pushed_button)
|
|
{
|
|
uint8_t bit = 0;
|
|
|
|
tmp = adapter->buttons;
|
|
|
|
while (tmp && tmp->cookie != (IOHIDElementCookie)cookie)
|
|
{
|
|
bit++;
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
if (tmp && tmp->cookie == (IOHIDElementCookie)cookie)
|
|
{
|
|
CFIndex state = IOHIDValueGetIntegerValue(value);
|
|
if (state)
|
|
BIT64_SET(hid->buttons[adapter->slot], bit);
|
|
else
|
|
BIT64_CLEAR(hid->buttons[adapter->slot], bit);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void iohidmanager_hid_device_remove(void *data,
|
|
IOReturn result, void* sender)
|
|
{
|
|
struct iohidmanager_hid_adapter *adapter =
|
|
(struct iohidmanager_hid_adapter*)data;
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*)
|
|
hid_driver_get_data();
|
|
|
|
if (hid && adapter && (adapter->slot < MAX_USERS))
|
|
{
|
|
input_autoconfigure_disconnect(adapter->slot, adapter->name);
|
|
|
|
hid->buttons[adapter->slot] = 0;
|
|
memset(hid->axes[adapter->slot], 0, sizeof(hid->axes));
|
|
|
|
pad_connection_pad_deinit(&hid->slots[adapter->slot], adapter->slot);
|
|
}
|
|
|
|
if (adapter)
|
|
{
|
|
apple_input_rec_t* tmp = NULL;
|
|
while (adapter->hats != NULL)
|
|
{
|
|
tmp = adapter->hats;
|
|
adapter->hats = adapter->hats->next;
|
|
free(tmp);
|
|
}
|
|
|
|
while (adapter->axes != NULL)
|
|
{
|
|
tmp = adapter->axes;
|
|
adapter->axes = adapter->axes->next;
|
|
free(tmp);
|
|
}
|
|
|
|
while (adapter->buttons != NULL)
|
|
{
|
|
tmp = adapter->buttons;
|
|
adapter->buttons = adapter->buttons->next;
|
|
free(tmp);
|
|
}
|
|
free(adapter);
|
|
}
|
|
}
|
|
|
|
static int32_t iohidmanager_hid_device_get_int_property(
|
|
IOHIDDeviceRef device, CFStringRef key)
|
|
{
|
|
CFNumberRef ref = (CFNumberRef)IOHIDDeviceGetProperty(device, key);
|
|
|
|
if (ref && (CFGetTypeID(ref) == CFNumberGetTypeID()))
|
|
{
|
|
int32_t value = 0;
|
|
CFNumberGetValue((CFNumberRef)ref, kCFNumberIntType, &value);
|
|
return value;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint16_t iohidmanager_hid_device_get_vendor_id(IOHIDDeviceRef device)
|
|
{
|
|
return iohidmanager_hid_device_get_int_property(device,
|
|
CFSTR(kIOHIDVendorIDKey));
|
|
}
|
|
|
|
static uint16_t iohidmanager_hid_device_get_product_id(IOHIDDeviceRef device)
|
|
{
|
|
return iohidmanager_hid_device_get_int_property(device,
|
|
CFSTR(kIOHIDProductIDKey));
|
|
}
|
|
|
|
static uint32_t iohidmanager_hid_device_get_location_id(IOHIDDeviceRef device)
|
|
{
|
|
return iohidmanager_hid_device_get_int_property(device,
|
|
CFSTR(kIOHIDLocationIDKey));
|
|
}
|
|
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
static uint32_t iohidmanager_hid_device_get_unique_id(IOHIDDeviceRef device)
|
|
{
|
|
/* osx seems to assign an unique id to each device when they are plugged in
|
|
* the id change if device is unplugged/plugged, but it's unique amongst the
|
|
* other device plugged */
|
|
return iohidmanager_hid_device_get_int_property(device,CFSTR(kIOHIDUniqueIDKey));
|
|
}
|
|
#endif
|
|
|
|
static void iohidmanager_hid_device_get_product_string(
|
|
IOHIDDeviceRef device, char *buf, size_t len)
|
|
{
|
|
CFStringRef ref = (CFStringRef)
|
|
IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
|
|
|
|
if (ref)
|
|
CFStringGetCString(ref, buf, len, kCFStringEncodingUTF8);
|
|
}
|
|
|
|
static void iohidmanager_hid_device_add_autodetect(unsigned idx,
|
|
const char *device_name, const char *driver_name,
|
|
uint16_t dev_vid, uint16_t dev_pid)
|
|
{
|
|
input_autoconfigure_connect(
|
|
device_name,
|
|
NULL,
|
|
"hid",
|
|
idx,
|
|
dev_vid,
|
|
dev_pid
|
|
);
|
|
|
|
RARCH_LOG("Port %d: %s.\n", idx, device_name);
|
|
}
|
|
|
|
#if defined(__ppc__) || defined(__ppc64__)
|
|
static void iohidmanager_hid_device_add(IOHIDDeviceRef device,
|
|
iohidmanager_hid_t* hid)
|
|
#else
|
|
static void iohidmanager_hid_device_add_device(
|
|
IOHIDDeviceRef device, iohidmanager_hid_t* hid)
|
|
#endif
|
|
{
|
|
int i;
|
|
|
|
/* get device unique id */
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
uint32_t deviceUniqueId = iohidmanager_hid_device_get_unique_id(device);
|
|
#endif
|
|
|
|
static const uint32_t axis_use_ids[11] =
|
|
{
|
|
kHIDUsage_GD_X,
|
|
kHIDUsage_GD_Y,
|
|
kHIDUsage_GD_Rx,
|
|
kHIDUsage_GD_Ry,
|
|
kHIDUsage_GD_Z,
|
|
kHIDUsage_GD_Rz,
|
|
kHIDUsage_Sim_Rudder,
|
|
kHIDUsage_Sim_Throttle,
|
|
kHIDUsage_Sim_Steering,
|
|
kHIDUsage_Sim_Accelerator,
|
|
kHIDUsage_Sim_Brake
|
|
};
|
|
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
/* check if pad was already registered previously (by deterministic method)
|
|
* if so do not re-add the pad */
|
|
for (i=0; i<MAX_USERS; i++)
|
|
{
|
|
struct iohidmanager_hid_adapter *a = (struct iohidmanager_hid_adapter*)hid->slots[i].data;
|
|
if (!a)
|
|
continue;
|
|
if (a->uniqueId == deviceUniqueId)
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
IOReturn ret;
|
|
uint16_t dev_vid, dev_pid;
|
|
CFArrayRef elements_raw;
|
|
int count;
|
|
CFMutableArrayRef elements;
|
|
CFRange range;
|
|
bool found_axis[11] =
|
|
{ false, false, false, false, false, false, false, false, false, false, false };
|
|
apple_input_rec_t *tmp = NULL;
|
|
apple_input_rec_t *tmpButtons = NULL;
|
|
apple_input_rec_t *tmpAxes = NULL;
|
|
struct iohidmanager_hid_adapter *adapter = (struct iohidmanager_hid_adapter*)
|
|
calloc(1, sizeof(*adapter));
|
|
|
|
if (!adapter)
|
|
return;
|
|
if (!hid)
|
|
goto error;
|
|
|
|
adapter->handle = device;
|
|
|
|
ret = IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
|
|
|
|
if (ret != kIOReturnSuccess)
|
|
goto error;
|
|
|
|
/* Move the device's run loop to this thread. */
|
|
IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(),
|
|
kCFRunLoopCommonModes);
|
|
IOHIDDeviceRegisterRemovalCallback(device,
|
|
iohidmanager_hid_device_remove, adapter);
|
|
|
|
#ifndef IOS
|
|
iohidmanager_hid_device_get_product_string(device, adapter->name,
|
|
sizeof(adapter->name));
|
|
#endif
|
|
|
|
dev_vid = iohidmanager_hid_device_get_vendor_id (device);
|
|
dev_pid = iohidmanager_hid_device_get_product_id (device);
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
adapter->uniqueId = deviceUniqueId;
|
|
#endif
|
|
|
|
adapter->slot = pad_connection_pad_init(hid->slots,
|
|
adapter->name, dev_vid, dev_pid, adapter,
|
|
&iohidmanager_hid);
|
|
|
|
if (adapter->slot == -1)
|
|
goto error;
|
|
|
|
if (pad_connection_has_interface(hid->slots, adapter->slot))
|
|
IOHIDDeviceRegisterInputReportCallback(device,
|
|
adapter->data + 1, sizeof(adapter->data) - 1,
|
|
iohidmanager_hid_device_report, adapter);
|
|
else
|
|
IOHIDDeviceRegisterInputValueCallback(device,
|
|
iohidmanager_hid_device_input_callback, adapter);
|
|
|
|
if (string_is_empty(adapter->name))
|
|
goto error;
|
|
|
|
/* scan for buttons, axis, hats */
|
|
elements_raw = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
|
|
count = (int)CFArrayGetCount(elements_raw);
|
|
elements = CFArrayCreateMutableCopy(
|
|
kCFAllocatorDefault,(CFIndex)count,elements_raw);
|
|
range = CFRangeMake(0,count);
|
|
|
|
CFArraySortValues(elements,
|
|
range, iohidmanager_sort_elements, NULL);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
IOHIDElementType type;
|
|
uint32_t page, use, cookie;
|
|
int detected_button = 0;
|
|
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
|
|
|
|
if (!element)
|
|
continue;
|
|
|
|
type = IOHIDElementGetType(element);
|
|
page = (uint32_t)IOHIDElementGetUsagePage(element);
|
|
use = (uint32_t)IOHIDElementGetUsage(element);
|
|
cookie = (uint32_t)IOHIDElementGetCookie(element);
|
|
|
|
switch (page)
|
|
{
|
|
case kHIDPage_GenericDesktop:
|
|
switch (type)
|
|
{
|
|
case kIOHIDElementTypeCollection:
|
|
case kIOHIDElementTypeInput_ScanCodes:
|
|
case kIOHIDElementTypeFeature:
|
|
case kIOHIDElementTypeInput_Button:
|
|
case kIOHIDElementTypeOutput:
|
|
case kIOHIDElementTypeInput_Axis:
|
|
/* TODO/FIXME */
|
|
break;
|
|
case kIOHIDElementTypeInput_Misc:
|
|
switch (use)
|
|
{
|
|
case kHIDUsage_GD_Hatswitch:
|
|
{
|
|
/* as far as I can tell, OSX only reports one Hat */
|
|
apple_input_rec_t *hat = (apple_input_rec_t *)malloc(sizeof(apple_input_rec_t));
|
|
hat->id = 0;
|
|
hat->cookie = (IOHIDElementCookie)cookie;
|
|
hat->next = NULL;
|
|
adapter->hats = hat;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
uint32_t i = 0;
|
|
|
|
while (i < 11 && axis_use_ids[i] != use)
|
|
i++;
|
|
|
|
if (i < 11)
|
|
{
|
|
|
|
apple_input_rec_t *axis = (apple_input_rec_t *)malloc(sizeof(apple_input_rec_t));
|
|
axis->id = i;
|
|
axis->cookie = (IOHIDElementCookie)cookie;
|
|
axis->next = NULL;
|
|
|
|
if (iohidmanager_check_for_id(adapter->axes,i))
|
|
{
|
|
/* axis ID already exists, save to tmp for appending later */
|
|
if (tmpAxes)
|
|
iohidmanager_append_record(tmpAxes, axis);
|
|
else
|
|
tmpAxes = axis;
|
|
}
|
|
else
|
|
{
|
|
found_axis[axis->id] = true;
|
|
if (adapter->axes)
|
|
iohidmanager_append_record(adapter->axes, axis);
|
|
else
|
|
adapter->axes = axis;
|
|
}
|
|
}
|
|
else
|
|
detected_button = 1;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case kHIDPage_Consumer:
|
|
case kHIDPage_Button:
|
|
switch (type)
|
|
{
|
|
case kIOHIDElementTypeCollection:
|
|
case kIOHIDElementTypeFeature:
|
|
case kIOHIDElementTypeInput_ScanCodes:
|
|
case kIOHIDElementTypeInput_Axis:
|
|
case kIOHIDElementTypeOutput:
|
|
/* TODO/FIXME */
|
|
break;
|
|
case kIOHIDElementTypeInput_Misc:
|
|
case kIOHIDElementTypeInput_Button:
|
|
detected_button = 1;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case kHIDPage_Simulation:
|
|
switch (use)
|
|
{
|
|
default:
|
|
{
|
|
uint32_t i = 0;
|
|
|
|
while (i < 11 && axis_use_ids[i] != use)
|
|
i++;
|
|
|
|
if (i < 11)
|
|
{
|
|
apple_input_rec_t *axis = (apple_input_rec_t *)malloc(sizeof(apple_input_rec_t));
|
|
axis->id = i;
|
|
axis->cookie = (IOHIDElementCookie)cookie;
|
|
axis->next = NULL;
|
|
|
|
if (iohidmanager_check_for_id(adapter->axes,i))
|
|
{
|
|
/* axis ID already exists, save to tmp for appending later */
|
|
if (tmpAxes)
|
|
iohidmanager_append_record(tmpAxes, axis);
|
|
else
|
|
tmpAxes = axis;
|
|
}
|
|
else
|
|
{
|
|
found_axis[axis->id] = true;
|
|
if (adapter->axes)
|
|
iohidmanager_append_record(adapter->axes, axis);
|
|
else
|
|
adapter->axes = axis;
|
|
}
|
|
}
|
|
else
|
|
detected_button = 1;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (detected_button)
|
|
{
|
|
apple_input_rec_t *btn = (apple_input_rec_t *)malloc(sizeof(apple_input_rec_t));
|
|
btn->id = (uint32_t)use;
|
|
btn->cookie = (IOHIDElementCookie)cookie;
|
|
btn->next = NULL;
|
|
|
|
if (iohidmanager_check_for_id(adapter->buttons,btn->id))
|
|
{
|
|
if (tmpButtons)
|
|
iohidmanager_append_record_ordered(&tmpButtons, btn);
|
|
else
|
|
tmpButtons = btn;
|
|
}
|
|
else
|
|
{
|
|
if (adapter->buttons)
|
|
iohidmanager_append_record_ordered(&adapter->buttons, btn);
|
|
else
|
|
adapter->buttons = btn;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* take care of buttons/axes with duplicate 'use' values */
|
|
for (i = 0; i < 11; i++)
|
|
{
|
|
if (!found_axis[i] && tmpAxes)
|
|
{
|
|
apple_input_rec_t *next = tmpAxes->next;
|
|
tmpAxes->id = i;
|
|
tmpAxes->next = NULL;
|
|
iohidmanager_append_record(adapter->axes, tmpAxes);
|
|
tmpAxes = next;
|
|
}
|
|
}
|
|
|
|
tmp = adapter->buttons;
|
|
|
|
if (tmp)
|
|
{
|
|
while (tmp->next)
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
while (tmpButtons)
|
|
{
|
|
apple_input_rec_t *next = tmpButtons->next;
|
|
|
|
tmpButtons->id = tmp->id;
|
|
tmpButtons->next = NULL;
|
|
tmp->next = tmpButtons;
|
|
|
|
tmp = tmp->next;
|
|
tmpButtons = next;
|
|
}
|
|
|
|
iohidmanager_hid_device_add_autodetect(adapter->slot,
|
|
adapter->name, iohidmanager_hid.ident, dev_vid, dev_pid);
|
|
|
|
return;
|
|
|
|
error:
|
|
{
|
|
apple_input_rec_t *tmp = NULL;
|
|
while (adapter->hats != NULL)
|
|
{
|
|
tmp = adapter->hats;
|
|
adapter->hats = adapter->hats->next;
|
|
free(tmp);
|
|
}
|
|
while (adapter->axes != NULL)
|
|
{
|
|
tmp = adapter->axes;
|
|
adapter->axes = adapter->axes->next;
|
|
free(tmp);
|
|
}
|
|
while (adapter->buttons != NULL)
|
|
{
|
|
tmp = adapter->buttons;
|
|
adapter->buttons = adapter->buttons->next;
|
|
free(tmp);
|
|
}
|
|
while (tmpAxes != NULL)
|
|
{
|
|
tmp = tmpAxes;
|
|
tmpAxes = tmpAxes->next;
|
|
free(tmp);
|
|
}
|
|
while (tmpButtons != NULL)
|
|
{
|
|
tmp = tmpButtons;
|
|
tmpButtons = tmpButtons->next;
|
|
free(tmp);
|
|
}
|
|
free(adapter);
|
|
}
|
|
}
|
|
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
static void iohidmanager_hid_device_add(void *data, IOReturn result,
|
|
void* sender, IOHIDDeviceRef device)
|
|
{
|
|
iohidmanager_hid_t *hid = (iohidmanager_hid_t*) hid_driver_get_data();
|
|
iohidmanager_hid_device_add_device(device, hid);
|
|
}
|
|
#endif
|
|
|
|
static void iohidmanager_hid_append_matching_dictionary(
|
|
CFMutableArrayRef array,
|
|
uint32_t page, uint32_t use)
|
|
{
|
|
CFMutableDictionaryRef matcher = CFDictionaryCreateMutable(
|
|
kCFAllocatorDefault, 0,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
CFNumberRef pagen = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
|
|
CFNumberRef usen = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &use);
|
|
|
|
CFDictionarySetValue(matcher, CFSTR(kIOHIDDeviceUsagePageKey), pagen);
|
|
CFDictionarySetValue(matcher, CFSTR(kIOHIDDeviceUsageKey), usen);
|
|
CFArrayAppendValue(array, matcher);
|
|
|
|
CFRelease(pagen);
|
|
CFRelease(usen);
|
|
CFRelease(matcher);
|
|
}
|
|
|
|
static int iohidmanager_hid_manager_init(iohidmanager_hid_t *hid)
|
|
{
|
|
if (!hid)
|
|
return -1;
|
|
if (hid->ptr) /* already initialized. */
|
|
return 0;
|
|
|
|
hid->ptr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
|
|
|
if (!hid->ptr)
|
|
return -1;
|
|
|
|
IOHIDManagerSetDeviceMatching(hid->ptr, NULL);
|
|
IOHIDManagerScheduleWithRunLoop(hid->ptr, CFRunLoopGetCurrent(),
|
|
kCFRunLoopDefaultMode);
|
|
return 0;
|
|
}
|
|
|
|
static int iohidmanager_hid_manager_free(iohidmanager_hid_t *hid)
|
|
{
|
|
if (!hid || !hid->ptr)
|
|
return -1;
|
|
|
|
IOHIDManagerUnscheduleFromRunLoop(hid->ptr,
|
|
CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
|
|
IOHIDManagerClose(hid->ptr, kIOHIDOptionsTypeNone);
|
|
CFRelease(hid->ptr);
|
|
hid->ptr = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int iohidmanager_hid_manager_set_device_matching(
|
|
iohidmanager_hid_t *hid)
|
|
{
|
|
/* deterministically add all device currently plugged when lanching retroarch
|
|
* order by location id which seems to correspond to usb port number */
|
|
CFSetRef set = IOHIDManagerCopyDevices(hid->ptr);
|
|
CFIndex num_devices = CFSetGetCount(set);
|
|
IOHIDDeviceRef *device_array = (IOHIDDeviceRef*)calloc(num_devices, sizeof(IOHIDDeviceRef));
|
|
CFSetGetValues(set, (const void **) device_array);
|
|
|
|
/* re order device by location id */
|
|
typedef struct hid_list
|
|
{
|
|
IOHIDDeviceRef device;
|
|
uint32_t lid;
|
|
struct hid_list *next;
|
|
} hid_list_t;
|
|
|
|
hid_list_t* devList = NULL;
|
|
for (long i=0; i<num_devices;i++)
|
|
{
|
|
IOHIDDeviceRef dev = device_array[i];
|
|
/* filter gamepad/joystick devices */
|
|
if ( IOHIDDeviceConformsTo(dev, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick)
|
|
|| IOHIDDeviceConformsTo(dev, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad)
|
|
)
|
|
{
|
|
if (!devList)
|
|
{
|
|
devList = (hid_list_t *)malloc(sizeof(hid_list_t));
|
|
devList->device = dev;
|
|
devList->lid = iohidmanager_hid_device_get_location_id(dev);
|
|
devList->next = NULL;
|
|
}
|
|
else
|
|
{
|
|
hid_list_t * devnew = (hid_list_t *)malloc(sizeof(hid_list_t));
|
|
devnew->device = dev;
|
|
devnew->lid = iohidmanager_hid_device_get_location_id(dev);
|
|
devnew->next = NULL;
|
|
|
|
hid_list_t * ptr = devList;
|
|
if (devnew->lid < ptr->lid)
|
|
{
|
|
devnew->next = ptr;
|
|
devList = devnew;
|
|
}
|
|
else
|
|
{
|
|
while ( ( ptr->lid < devnew->lid ) && (ptr->next != NULL) )
|
|
ptr = ptr->next;
|
|
devnew->next = ptr->next;
|
|
ptr->next = devnew;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* register devices */
|
|
hid_list_t * ptr = devList;
|
|
while (ptr != NULL)
|
|
{
|
|
#if defined(__ppc__) || defined(__ppc64__)
|
|
iohidmanager_hid_device_add(ptr->device, hid);
|
|
#else
|
|
iohidmanager_hid_device_add_device(ptr->device, hid);
|
|
#endif
|
|
|
|
//printf("%d\n",ptr->lid);
|
|
ptr = ptr->next;
|
|
free(devList);
|
|
devList = ptr;
|
|
}
|
|
free(device_array);
|
|
|
|
#if !(defined(__ppc__) || defined(__ppc64__))
|
|
/* register call back to dynamically add device plugged when retroarch is
|
|
* running
|
|
* those will be added after the one plugged when retroarch was launched,
|
|
* and by order they are plugged in (so not deterministic) */
|
|
CFMutableArrayRef matcher = CFArrayCreateMutable(kCFAllocatorDefault, 0,
|
|
&kCFTypeArrayCallBacks);
|
|
|
|
if (!matcher)
|
|
return -1;
|
|
|
|
iohidmanager_hid_append_matching_dictionary(matcher,
|
|
kHIDPage_GenericDesktop,
|
|
kHIDUsage_GD_Joystick);
|
|
iohidmanager_hid_append_matching_dictionary(matcher,
|
|
kHIDPage_GenericDesktop,
|
|
kHIDUsage_GD_GamePad);
|
|
|
|
IOHIDManagerSetDeviceMatchingMultiple(hid->ptr, matcher);
|
|
IOHIDManagerRegisterDeviceMatchingCallback(hid->ptr,
|
|
iohidmanager_hid_device_add, 0);
|
|
|
|
CFRelease(matcher);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *iohidmanager_hid_init(void)
|
|
{
|
|
iohidmanager_hid_t *hid_apple = (iohidmanager_hid_t*)
|
|
calloc(1, sizeof(*hid_apple));
|
|
|
|
if (!hid_apple)
|
|
return NULL;
|
|
|
|
hid_apple->slots = pad_connection_init(MAX_USERS);
|
|
|
|
if (!hid_apple->slots)
|
|
goto error;
|
|
if (iohidmanager_hid_manager_init(hid_apple) == -1)
|
|
goto error;
|
|
if (iohidmanager_hid_manager_set_device_matching(hid_apple) == -1)
|
|
goto error;
|
|
|
|
return hid_apple;
|
|
|
|
error:
|
|
if (hid_apple->slots)
|
|
free(hid_apple->slots);
|
|
hid_apple->slots = NULL;
|
|
if (hid_apple)
|
|
free(hid_apple);
|
|
return NULL;
|
|
}
|
|
|
|
static void iohidmanager_hid_free(const void *data)
|
|
{
|
|
iohidmanager_hid_t *hid_apple = (iohidmanager_hid_t*)data;
|
|
|
|
if (!hid_apple || !hid_apple->ptr)
|
|
return;
|
|
|
|
pad_connection_destroy(hid_apple->slots);
|
|
iohidmanager_hid_manager_free(hid_apple);
|
|
|
|
if (hid_apple)
|
|
free(hid_apple);
|
|
}
|
|
|
|
static void iohidmanager_hid_poll(void *data)
|
|
{
|
|
(void)data;
|
|
}
|
|
|
|
hid_driver_t iohidmanager_hid = {
|
|
iohidmanager_hid_init,
|
|
iohidmanager_hid_joypad_query,
|
|
iohidmanager_hid_free,
|
|
iohidmanager_hid_joypad_button,
|
|
iohidmanager_hid_joypad_state,
|
|
iohidmanager_hid_joypad_get_buttons,
|
|
iohidmanager_hid_joypad_axis,
|
|
iohidmanager_hid_poll,
|
|
iohidmanager_hid_joypad_rumble,
|
|
iohidmanager_hid_joypad_name,
|
|
"iohidmanager",
|
|
iohidmanager_hid_device_send_control,
|
|
};
|