1 /** \file   joy-osx-hidmgr.c
2  * \brief   Mac OS X joystick support using IOHIDManager
3  *
4  * \author  Christian Vogelgsang <chris@vogelgsang.org>
5  */
6 
7 /*
8  * This file is part of VICE, the Versatile Commodore Emulator.
9  * See README for copyright notice.
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24  *  02111-1307  USA.
25  *
26  */
27 
28 #define JOY_INTERNAL
29 
30 #include "vice.h"
31 
32 #ifdef MACOSX_SUPPORT
33 
34 #include "joy.h"
35 #include "log.h"
36 #include "lib.h"
37 #include "joy-osx-hidlib.h"
38 
39 #ifdef HAS_JOYSTICK
40 #ifdef HAS_HIDMGR
41 
42 /* ----- Statics ----- */
43 
44 static IOHIDManagerRef mgr;
45 
46 /* ----- Tools ----- */
47 
CFSetApplierFunctionCopyToCFArray(const void * value,void * context)48 static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context)
49 {
50     CFArrayAppendValue( ( CFMutableArrayRef ) context, value );
51 }
52 
IOHIDDevice_GetLongProperty(IOHIDDeviceRef inIOHIDDeviceRef,CFStringRef inKey,long * outValue)53 static Boolean IOHIDDevice_GetLongProperty( IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, long * outValue )
54 {
55     Boolean result = FALSE;
56 
57     if ( inIOHIDDeviceRef ) {
58         CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty( inIOHIDDeviceRef, inKey );
59         if ( tCFTypeRef ) {
60             // if this is a number
61             if ( CFNumberGetTypeID() == CFGetTypeID( tCFTypeRef ) ) {
62                 // get it's value
63                 result = CFNumberGetValue( ( CFNumberRef ) tCFTypeRef, kCFNumberSInt32Type, outValue );
64             }
65         }
66     }
67     return result;
68 }
69 
70 /* ----- API ----- */
71 
joy_hidlib_init(void)72 int  joy_hidlib_init(void)
73 {
74     if ( !mgr ) {
75         // create the manager
76         mgr = IOHIDManagerCreate( kCFAllocatorDefault, 0L );
77     }
78     if ( mgr ) {
79         // open it
80         IOReturn tIOReturn = IOHIDManagerOpen( mgr, 0L);
81         if ( kIOReturnSuccess != tIOReturn ) {
82             return -1;
83         } else {
84             IOHIDManagerSetDeviceMatching( mgr, NULL );
85             return 0;
86         }
87     } else {
88         return -1;
89     }
90 }
91 
joy_hidlib_exit(void)92 void joy_hidlib_exit(void)
93 {
94     if(mgr) {
95         IOHIDManagerClose( mgr, 0 );
96         mgr = NULL;
97     }
98 }
99 
is_joystick(IOHIDDeviceRef ref)100 static int is_joystick(IOHIDDeviceRef ref)
101 {
102     return
103         IOHIDDeviceConformsTo( ref, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick ) ||
104         IOHIDDeviceConformsTo( ref, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad );
105 }
106 
count_joysticks(joy_hid_device_array_t * dev_array)107 static int count_joysticks(joy_hid_device_array_t *dev_array)
108 {
109     int i;
110     int num_devices = dev_array->num_internal_devices;
111     int num_joys = 0;
112     CFArrayRef devices = dev_array->internal_devices;
113     for ( i = 0; i < num_devices ; i++ ) {
114         IOHIDDeviceRef dev = ( IOHIDDeviceRef ) CFArrayGetValueAtIndex( devices, i );
115         if(is_joystick(dev)) {
116                num_joys++;
117         }
118     }
119 
120     dev_array->num_devices = num_joys;
121     return num_joys;
122 }
123 
build_joystick_list(joy_hid_device_array_t * dev_array)124 static void build_joystick_list(joy_hid_device_array_t *dev_array)
125 {
126     int i;
127     int num_devices = dev_array->num_internal_devices;
128     CFArrayRef devices = dev_array->internal_devices;
129     joy_hid_device_t *d = dev_array->devices;
130     for ( i = 0; i < num_devices ; i++ ) {
131         IOHIDDeviceRef dev = ( IOHIDDeviceRef ) CFArrayGetValueAtIndex( devices, i );
132         if(is_joystick(dev)) {
133 
134             long vendor_id = 0;
135             IOHIDDevice_GetLongProperty( dev, CFSTR( kIOHIDVendorIDKey ), &vendor_id );
136             long product_id = 0;
137             IOHIDDevice_GetLongProperty( dev, CFSTR( kIOHIDProductIDKey ), &product_id );
138             CFStringRef product_key;
139             product_key = IOHIDDeviceGetProperty( dev, CFSTR( kIOHIDProductKey ) );
140             char *product_name = "N/A";
141             if(product_key) {
142                char buffer[256];
143                if(CFStringGetCString(product_key, buffer, 256, kCFStringEncodingUTF8)) {
144                    product_name = strdup(buffer);
145                }
146             }
147 
148             d->internal_device = dev;
149             d->vendor_id = (int)vendor_id;
150             d->product_id = (int)product_id;
151             d->serial = 0; /* will be filled in later */
152             d->product_name = product_name;
153 
154             d++;
155         }
156     }
157 }
158 
joy_hidlib_enumerate_devices(void)159 joy_hid_device_array_t *joy_hidlib_enumerate_devices(void)
160 {
161     if( !mgr ) {
162         return NULL;
163     }
164 
165     /* create set of devices */
166     CFSetRef device_set = IOHIDManagerCopyDevices( mgr );
167     if ( !device_set ) {
168         return NULL;
169     }
170 
171     /* create array of devices */
172     CFMutableArrayRef device_array = CFArrayCreateMutable( kCFAllocatorDefault, 0,
173                                                     & kCFTypeArrayCallBacks );
174     if( ! device_array ) {
175         CFRelease( device_set );
176         return NULL;
177     }
178     CFSetApplyFunction( device_set, CFSetApplierFunctionCopyToCFArray, device_array );
179     CFRelease( device_set );
180 
181     /* allocate result */
182     joy_hid_device_array_t *result =
183         (joy_hid_device_array_t *)lib_malloc(sizeof(joy_hid_device_array_t));
184     if(result == NULL) {
185         CFRelease( device_array );
186         return NULL;
187     }
188 
189     /* get size */
190     CFIndex cnt = CFArrayGetCount( device_array );
191 
192     /* fill internal struct */
193     result->num_internal_devices = (int)cnt;
194     result->internal_devices = device_array;
195     result->num_devices = 0;
196     result->devices = NULL;
197     result->driver_name = "IOHIDManager";
198 
199     /* count joysticks -> num devices */
200     count_joysticks(result);
201     if(result->num_devices == 0)
202         return result;
203 
204     /* allocate our device structs */
205     joy_hid_device_t *devices =
206         (joy_hid_device_t *)lib_malloc(sizeof(joy_hid_device_t) * result->num_devices);
207     if(devices == NULL) {
208         lib_free(result);
209         CFRelease( device_array );
210         return NULL;
211     }
212     result->devices = devices;
213 
214     build_joystick_list(result);
215     return result;
216 }
217 
joy_hidlib_free_devices(joy_hid_device_array_t * devices)218 void joy_hidlib_free_devices(joy_hid_device_array_t *devices)
219 {
220     if(devices == NULL) {
221         return;
222     }
223 
224     int num_devices = devices->num_devices;
225     int i;
226     for(i = 0; i<num_devices; i++) {
227         joy_hidlib_free_elements(&devices->devices[i]);
228     }
229 
230     if(devices->internal_devices) {
231         CFRelease( devices->internal_devices );
232         devices->internal_devices = NULL;
233     }
234 
235     if(devices != NULL) {
236         lib_free(devices);
237         devices = NULL;
238     }
239 }
240 
joy_hidlib_open_device(joy_hid_device_t * device)241 int  joy_hidlib_open_device(joy_hid_device_t *device)
242 {
243     if( device->internal_device == NULL ) {
244         return -2;
245     }
246     IOReturn result = IOHIDDeviceOpen( device->internal_device, 0 );
247     if(result != kIOReturnSuccess) {
248         return -1;
249     } else {
250         return 0;
251     }
252 }
253 
joy_hidlib_close_device(joy_hid_device_t * device)254 void joy_hidlib_close_device(joy_hid_device_t *device)
255 {
256     /* close old device */
257     if(device->internal_device != NULL) {
258         IOHIDDeviceClose(device->internal_device, 0);
259     }
260 }
261 
joy_hidlib_enumerate_elements(joy_hid_device_t * device)262 int  joy_hidlib_enumerate_elements(joy_hid_device_t *device)
263 {
264     IOHIDDeviceRef dev = device->internal_device;
265     if(dev == NULL) {
266         return -1;
267     }
268 
269     /* get all elements of device */
270     CFArrayRef internal_elements = IOHIDDeviceCopyMatchingElements( dev, NULL, 0 );
271     if(!internal_elements) {
272         return -1;
273     }
274 
275     /* get number of elements */
276     CFIndex cnt = CFArrayGetCount( internal_elements );
277     device->num_elements = (int)cnt;
278 
279     /* create elements array */
280     joy_hid_element_t *elements = (joy_hid_element_t *)
281         lib_malloc(sizeof(joy_hid_element_t) * cnt);
282     if(elements == NULL) {
283         CFRelease(internal_elements);
284         internal_elements = NULL;
285         return -1;
286     }
287 
288     /* enumerate and convert all elements */
289     CFIndex i;
290     joy_hid_element_t *e = elements;
291     for(i=0;i<cnt;i++) {
292         IOHIDElementRef internal_element =
293             ( IOHIDElementRef ) CFArrayGetValueAtIndex( internal_elements, i );
294         if ( internal_element ) {
295             uint32_t usage_page = IOHIDElementGetUsagePage( internal_element );
296             uint32_t usage = IOHIDElementGetUsage( internal_element );
297             CFIndex pmin = IOHIDElementGetPhysicalMin( internal_element );
298             CFIndex pmax = IOHIDElementGetPhysicalMax( internal_element );
299             CFIndex lmin = IOHIDElementGetLogicalMin( internal_element );
300             CFIndex lmax = IOHIDElementGetLogicalMax( internal_element );
301 
302             e->usage_page = (int)usage_page;
303             e->usage      = (int)usage;
304             e->min_pvalue = (int)pmin;
305             e->max_pvalue = (int)pmax;
306             e->min_lvalue = (int)lmin;
307             e->max_lvalue = (int)lmax;
308             e->internal_element = internal_element;
309         } else {
310             e->usage_page = -1;
311             e->usage      = -1;
312             e->min_pvalue = -1;
313             e->max_pvalue = -1;
314             e->min_lvalue = -1;
315             e->max_lvalue = -1;
316             e->internal_element = NULL;
317         }
318         e++;
319     }
320 
321     /* keep the reference until the elements are free'ed again */
322     device->internal_elements = internal_elements;
323     device->elements = elements;
324 
325     return (int)cnt;
326 }
327 
joy_hidlib_free_elements(joy_hid_device_t * device)328 void joy_hidlib_free_elements(joy_hid_device_t *device)
329 {
330     if(device == NULL) {
331         return;
332     }
333     if(device->elements) {
334         lib_free(device->elements);
335         device->elements = NULL;
336     }
337     if(device->internal_elements) {
338         CFRelease(device->internal_elements);
339         device->internal_elements = NULL;
340     }
341 }
342 
joy_hidlib_get_value(joy_hid_device_t * device,joy_hid_element_t * element,int * value,int phys)343 int  joy_hidlib_get_value(joy_hid_device_t *device,
344                           joy_hid_element_t *element,
345                           int *value, int phys)
346 {
347     IOHIDValueRef value_ref;
348     IOReturn result = IOHIDDeviceGetValue( device->internal_device,
349                                            element->internal_element,
350                                            &value_ref );
351     if(result == kIOReturnSuccess) {
352         if(phys) {
353             *value = (int)IOHIDValueGetScaledValue( value_ref, kIOHIDValueScaleTypePhysical );
354         } else {
355             *value = (int)IOHIDValueGetIntegerValue( value_ref );
356         }
357         return 0;
358     } else {
359         return -1;
360     }
361 }
362 
363 #endif /* HAS_HIDMGR */
364 #endif /* HAS_JOYSTICK */
365 #endif
366 
367