Skip to content

Commit

Permalink
Developed by @NewCreature to make enumerating connected joysticks mor…
Browse files Browse the repository at this point in the history
…e robust. The current implementation assumes each HID device is one controller/joystick. The patch fixes this by interpreting each collection reported by the device as a separate controller/joystick (this is part of the USB HID spec). The names of the various elements are generated from the data reported by the HID device.
  • Loading branch information
pedro-w authored and SiegeLord committed May 29, 2016
1 parent cdbc0a9 commit 0828211
Showing 1 changed file with 183 additions and 39 deletions.
222 changes: 183 additions & 39 deletions src/macosx/hidjoy.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@
#define JOYSTICK_USAGE_NUMBER 0x04
#define GAMEPAD_USAGE_NUMBER 0x05

#define JOYSTICK_NAME_MAX 256

typedef struct {
ALLEGRO_JOYSTICK parent;
char name[JOYSTICK_NAME_MAX];
IOHIDElementRef buttons[_AL_MAX_JOYSTICK_BUTTONS];
IOHIDElementRef axes[_AL_MAX_JOYSTICK_STICKS][_AL_MAX_JOYSTICK_AXES];
IOHIDElementRef dpad;
Expand Down Expand Up @@ -130,12 +133,37 @@ static CFMutableDictionaryRef CreateDeviceMatchingDictionary(
return result;
}

static ALLEGRO_JOYSTICK_OSX *find_joystick(IOHIDDeviceRef ident)
static bool joystick_uses_element(ALLEGRO_JOYSTICK_OSX *joy, IOHIDElementRef elem)
{
int i, j;

if(elem) {
for (i = 0; i < joy->parent.info.num_buttons; i++) {
if(joy->buttons[i] == elem)
return true;
}
for (i = 0; i < joy->parent.info.num_sticks; i++) {
for(j = 0; j < joy->parent.info.stick[i].num_axes; j++) {
if(joy->axes[i][j] == elem)
return true;
}
}
if (joy->dpad == elem)
return true;
}
else {
return true;
}

return false;
}

static ALLEGRO_JOYSTICK_OSX *find_joystick(IOHIDDeviceRef ident, IOHIDElementRef elem)
{
int i;
for (i = 0; i < (int)_al_vector_size(&joysticks); i++) {
ALLEGRO_JOYSTICK_OSX *joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i);
if (ident == joy->ident) {
if (ident == joy->ident && joystick_uses_element(joy, elem)) {
return joy;
}
}
Expand All @@ -145,10 +173,12 @@ static CFMutableDictionaryRef CreateDeviceMatchingDictionary(

static const char *get_element_name(IOHIDElementRef elem, const char *default_name)
{
const char *name_cstr = NULL;
CFStringRef name = IOHIDElementGetName(elem);
if (name) {
return CFStringGetCStringPtr(name, kCFStringEncodingUTF8);
}
if (name)
name_cstr = CFStringGetCStringPtr(name, kCFStringEncodingUTF8);
if (name_cstr)
return name_cstr;
else
return default_name;
}
Expand Down Expand Up @@ -182,31 +212,58 @@ static void add_axis(ALLEGRO_JOYSTICK_OSX *joy, int stick_index, int axis_index,
joy->axes[stick_index][axis_index] = elem;
}

static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy)
static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy, int device_joystick)
{
int i;
int i, start_i = 0;
char default_name[100];
int stick_class = -1;
int axis_index = 0;
bool collection_started = false;
int current_joystick = -1;

joy_null(joy);

/* look for device_joystick */
for (i = 0; i < CFArrayGetCount(elements); i++) {
IOHIDElementRef elem = (IOHIDElementRef)CFArrayGetValueAtIndex(
elements,
i
);
int etype = IOHIDElementGetType(elem);
if (etype == kIOHIDElementTypeCollection) {
collection_started = true;
}
else if (etype == kIOHIDElementTypeInput_Button || etype == kIOHIDElementTypeInput_Misc) {
if (collection_started) {
current_joystick++;
collection_started = false;
if (current_joystick == device_joystick) {
start_i = i;
break;
}
}
}
}

for (i = start_i; i < CFArrayGetCount(elements); i++) {
IOHIDElementRef elem = (IOHIDElementRef)CFArrayGetValueAtIndex(
elements,
i
);

int usage = IOHIDElementGetUsage(elem);
if (IOHIDElementGetType(elem) == kIOHIDElementTypeCollection) {
break;
}
if (IOHIDElementGetType(elem) == kIOHIDElementTypeInput_Button) {
if (usage >= 0 && usage < _AL_MAX_JOYSTICK_BUTTONS &&
!joy->buttons[usage-1]) {
joy->buttons[usage-1] = elem;
sprintf(default_name, "Button %d", usage-1);
if (usage >= 0 && joy->parent.info.num_buttons < _AL_MAX_JOYSTICK_BUTTONS &&
!joy->buttons[joy->parent.info.num_buttons]) {
joy->buttons[joy->parent.info.num_buttons] = elem;
sprintf(default_name, "Button %d", joy->parent.info.num_buttons);
const char *name = get_element_name(elem, default_name);
char *str = al_malloc(strlen(name)+1);
strcpy(str, name);
joy->parent.info.button[usage-1].name = str;
joy->parent.info.button[joy->parent.info.num_buttons].name = str;
joy->parent.info.num_buttons++;
}
}
Expand All @@ -216,18 +273,33 @@ static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy)
long max = IOHIDElementGetLogicalMax(elem);
int new_stick_class = -1;
int stick_index = joy->parent.info.num_sticks - 1;
int axis_type = -1;

switch (usage) {
case kHIDUsage_GD_X:
new_stick_class = 1;
axis_type = 0;
break;
case kHIDUsage_GD_Y:
new_stick_class = 1;
axis_type = 1;
break;
case kHIDUsage_GD_Z:
new_stick_class = 1;
axis_type = 2;
break;

case kHIDUsage_GD_Rx:
new_stick_class = 2;
axis_type = 0;
break;
case kHIDUsage_GD_Ry:
new_stick_class = 2;
axis_type = 1;
break;
case kHIDUsage_GD_Rz:
new_stick_class = 2;
axis_type = 2;
break;

case kHIDUsage_GD_Hatswitch:
Expand All @@ -250,7 +322,19 @@ static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy)
stick_class = new_stick_class;

char *buf = al_malloc(20);
sprintf(buf, "Stick %d", stick_index);
switch (stick_class) {
case 1:
sprintf(buf, "Primary Stick");
break;
case 2:
sprintf(buf, "Secondary Stick");
break;
case 3:
sprintf(buf, "Hat Switch");
break;
default:
sprintf(buf, "Stick %d", stick_index);
}
joy->parent.info.stick[stick_index].name = buf;
}
else
Expand All @@ -261,14 +345,14 @@ static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy)
joy->dpad = elem;

joy->dpad_axis_horiz = axis_index;
sprintf(default_name, "Axis %i", axis_index);
sprintf(default_name, "X-Axis");
char *str = al_malloc(strlen(default_name)+1);
strcpy(str, default_name);
joy->parent.info.stick[stick_index].axis[axis_index].name = str;

++axis_index;
joy->dpad_axis_vert = axis_index;
sprintf(default_name, "Axis %i", axis_index);
sprintf(default_name, "Y-Axis");
str = al_malloc(strlen(default_name)+1);
strcpy(str, default_name);
add_axis(joy, stick_index, axis_index, min, max, str, elem);
Expand All @@ -277,7 +361,19 @@ static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy)
joy->parent.info.stick[stick_index].num_axes = 2;
}
else {
sprintf(default_name, "Axis %i", axis_index);
switch (axis_type) {
case 0:
sprintf(default_name, "X-Axis");
break;
case 1:
sprintf(default_name, "Y-Axis");
break;
case 2:
sprintf(default_name, "Z-Axis");
break;
default:
sprintf(default_name, "Axis %i", axis_index);
}
const char *name = get_element_name(elem, default_name);
char *str = al_malloc(strlen(name)+1);
strcpy(str, name);
Expand All @@ -298,44 +394,92 @@ static void osx_joy_generate_configure_event(void)
_al_generate_joystick_event(&event);
}

static int device_count_joysticks(IOHIDDeviceRef ref)
{
int i;
int count = 0;
bool collection_started = false;

CFArrayRef elements = IOHIDDeviceCopyMatchingElements(
ref,
NULL,
kIOHIDOptionsTypeNone
);
for (i = 0; i < CFArrayGetCount(elements); i++) {
IOHIDElementRef elem = (IOHIDElementRef)CFArrayGetValueAtIndex(
elements,
i
);

int etype = IOHIDElementGetType(elem);
if (etype == kIOHIDElementTypeCollection) {
collection_started = true;
}
else if (etype == kIOHIDElementTypeInput_Button || etype == kIOHIDElementTypeInput_Misc) {
if (collection_started) {
count++;
collection_started = false;
}
}
}
CFRelease(elements);
return count;
}

static void device_setup_joystick(IOHIDDeviceRef ref, ALLEGRO_JOYSTICK_OSX *joy, int device_joystick)
{
CFStringRef product_name;
joy->cfg_state = new_joystick_state;

CFArrayRef elements = IOHIDDeviceCopyMatchingElements(
ref,
NULL,
kIOHIDOptionsTypeNone
);

add_elements(elements, joy, device_joystick);
product_name = IOHIDDeviceGetProperty(ref, CFSTR(kIOHIDProductKey));
if(product_name)
{
CFStringGetCString(product_name, joy->name, JOYSTICK_NAME_MAX, kCFStringEncodingUTF8);
}
CFRelease(elements);
}

static void device_add_callback(
void *context,
IOReturn result,
void *sender,
IOHIDDeviceRef ref
) {
int i;
int device_joysticks;
(void)context;
(void)result;
(void)sender;

al_lock_mutex(add_mutex);

ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref);
ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref, NULL);
if (joy == NULL) {
joy = al_calloc(1, sizeof(ALLEGRO_JOYSTICK_OSX));
joy->ident = ref;
ALLEGRO_JOYSTICK_OSX **back = _al_vector_alloc_back(&joysticks);
*back = joy;
device_joysticks = device_count_joysticks(ref);
for (i = 0; i < device_joysticks; i++) {
joy = al_calloc(1, sizeof(ALLEGRO_JOYSTICK_OSX));
joy->ident = ref;
ALLEGRO_JOYSTICK_OSX **back = _al_vector_alloc_back(&joysticks);
*back = joy;
device_setup_joystick(ref, joy, i);
ALLEGRO_INFO("Found joystick (%d buttons, %d sticks)\n",
joy->parent.info.num_buttons, joy->parent.info.num_sticks);
}
}
else {
device_setup_joystick(ref, joy, 0);
}
joy->cfg_state = new_joystick_state;

CFArrayRef elements = IOHIDDeviceCopyMatchingElements(
ref,
NULL,
kIOHIDOptionsTypeNone
);

add_elements(elements, joy);

CFRelease(elements);


al_unlock_mutex(add_mutex);

osx_joy_generate_configure_event();

ALLEGRO_INFO("Found joystick (%d buttons, %d sticks)\n",
joy->parent.info.num_buttons, joy->parent.info.num_sticks);
}

static void device_remove_callback(
Expand Down Expand Up @@ -432,7 +576,7 @@ static void value_callback(

IOHIDElementRef elem = IOHIDValueGetElement(value);
IOHIDDeviceRef ref = IOHIDElementGetDevice(elem);
ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref);
ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref, elem);

if (!joy) return;

Expand Down Expand Up @@ -733,8 +877,8 @@ static bool reconfigure_joysticks(void)
// FIXME!
static const char *get_joystick_name(ALLEGRO_JOYSTICK *joy_)
{
(void)joy_;
return "Joystick";
ALLEGRO_JOYSTICK_OSX *joy = (ALLEGRO_JOYSTICK_OSX *)joy_;
return joy->name;
}

static bool get_joystick_active(ALLEGRO_JOYSTICK *joy_)
Expand Down

6 comments on commit 0828211

@SiegeLord
Copy link
Member

@SiegeLord SiegeLord commented on 0828211 Aug 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I figured that might happen. Let's add an allegro5.cfg switch that will by default revert to the old button order (hopefully the old order is possible to reconstruct using the new code?).

@SiegeLord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a look too, sorry about that.

@NewCreature
Copy link
Contributor

@NewCreature NewCreature commented on 0828211 Aug 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, didn't know the driver situation for the 360 controller on Mac had been addressed or I would have tested my 360 controller (you previously had to install an unsigned driver and disable an OS security feature just to use the controller).

I tested this out and the controller is being split up into two ALLEGRO_JOYSTICKs by the current code. Digging deeper, the driver reports 3 collections. The first collection has no inputs, the second has 2 inputs, and the third has 24 inputs. The current Allegro code is splitting this up according to my understanding of how HID collections work. Either my understanding is wrong (maybe there is more that needs to happen when looking at a collection), or the 360 driver is using collections incorrectly.

I tried the current Allegro code in one of my projects that allows custom controller binding and was able to bind and use the 360 controller as normal (chose an action and press the button/axis on the controller to bind to action) so the code is working for the most part.

Since my 360 controller is a Guitar Hero X-plorer, I'm not 100% sure what the second collection actually is (the above-mentioned collection that has 2 inputs). All the inputs on my controller are on the second ALLEGRO_JOYSTICK, which is all the inputs in the third collection. It could be that the second collection is something that is supposed to be ignored.

@SiegeLord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added #662 with these changes reinstated so we don't forget about them. I don't have an XBox controller to test with however...

@NewCreature
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably better to leave it rolled back for now. Most people are going to be plugging in single-gamepad devices, which work fine with the old implementation. I'll get back to work on this at some point unless someone beats me to it. I think I can get everything working with just one implementation, but I don't have to have this working for at least a few more months.

It might be a good idea to pull the code that copies the device and element names into the joystick struct and apply it to the old implementation. That would at least provide some improvement without messing up the functionality.

@SiegeLord
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to pull the code that copies the device and element names into the joystick struct and apply it to the old implementation. That would at least provide some improvement without messing up the functionality.

That sounds like a good idea. I can do that.

Please sign in to comment.