1/*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4
5  This software is provided 'as-is', without any express or implied
6  warranty.  In no event will the authors be held liable for any damages
7  arising from the use of this software.
8
9  Permission is granted to anyone to use this software for any purpose,
10  including commercial applications, and to alter it and redistribute it
11  freely, subject to the following restrictions:
12
13  1. The origin of this software must not be misrepresented; you must not
14     claim that you wrote the original software. If you use this software
15     in a product, an acknowledgment in the product documentation would be
16     appreciated but is not required.
17  2. Altered source versions must be plainly marked as such, and must not be
18     misrepresented as being the original software.
19  3. This notice may not be removed or altered from any source distribution.
20*/
21#include "../../SDL_internal.h"
22
23/* This is the iOS implementation of the SDL joystick API */
24#include "SDL_sysjoystick_c.h"
25
26/* needed for SDL_IPHONE_MAX_GFORCE macro */
27#include "SDL_config_iphoneos.h"
28
29#include "SDL_events.h"
30#include "SDL_joystick.h"
31#include "SDL_hints.h"
32#include "SDL_stdinc.h"
33#include "../SDL_sysjoystick.h"
34#include "../SDL_joystick_c.h"
35
36#if !SDL_EVENTS_DISABLED
37#include "../../events/SDL_events_c.h"
38#endif
39
40#if !TARGET_OS_TV
41#import <CoreMotion/CoreMotion.h>
42#endif
43
44#ifdef SDL_JOYSTICK_MFI
45#import <GameController/GameController.h>
46
47static id connectObserver = nil;
48static id disconnectObserver = nil;
49#endif /* SDL_JOYSTICK_MFI */
50
51#if !TARGET_OS_TV
52static const char *accelerometerName = "iOS Accelerometer";
53static CMMotionManager *motionManager = nil;
54#endif /* !TARGET_OS_TV */
55
56static SDL_JoystickDeviceItem *deviceList = NULL;
57
58static int numjoysticks = 0;
59static SDL_JoystickID instancecounter = 0;
60
61static SDL_JoystickDeviceItem *
62GetDeviceForIndex(int device_index)
63{
64    SDL_JoystickDeviceItem *device = deviceList;
65    int i = 0;
66
67    while (i < device_index) {
68        if (device == NULL) {
69            return NULL;
70        }
71        device = device->next;
72        i++;
73    }
74
75    return device;
76}
77
78static void
79SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
80{
81#ifdef SDL_JOYSTICK_MFI
82    const char *name = NULL;
83    /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
84     * struct, and ARC doesn't work with structs. */
85    device->controller = (__bridge GCController *) CFBridgingRetain(controller);
86
87    if (controller.vendorName) {
88        name = controller.vendorName.UTF8String;
89    }
90
91    if (!name) {
92        name = "MFi Gamepad";
93    }
94
95    device->name = SDL_strdup(name);
96
97    device->guid.data[0] = 'M';
98    device->guid.data[1] = 'F';
99    device->guid.data[2] = 'i';
100    device->guid.data[3] = 'G';
101    device->guid.data[4] = 'a';
102    device->guid.data[5] = 'm';
103    device->guid.data[6] = 'e';
104    device->guid.data[7] = 'p';
105    device->guid.data[8] = 'a';
106    device->guid.data[9] = 'd';
107
108    if (controller.extendedGamepad) {
109        device->guid.data[10] = 1;
110    } else if (controller.gamepad) {
111        device->guid.data[10] = 2;
112    }
113#if TARGET_OS_TV
114    else if (controller.microGamepad) {
115        device->guid.data[10] = 3;
116    }
117#endif /* TARGET_OS_TV */
118
119    if (controller.extendedGamepad) {
120        device->naxes = 6; /* 2 thumbsticks and 2 triggers */
121        device->nhats = 1; /* d-pad */
122        device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
123    } else if (controller.gamepad) {
124        device->naxes = 0; /* no traditional analog inputs */
125        device->nhats = 1; /* d-pad */
126        device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
127    }
128#if TARGET_OS_TV
129    else if (controller.microGamepad) {
130        device->naxes = 2; /* treat the touch surface as two axes */
131        device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
132        device->nbuttons = 3; /* AX, pause button */
133
134        controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
135    }
136#endif /* TARGET_OS_TV */
137
138    /* This will be set when the first button press of the controller is
139     * detected. */
140    controller.playerIndex = -1;
141
142#endif /* SDL_JOYSTICK_MFI */
143}
144
145static void
146SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
147{
148    SDL_JoystickDeviceItem *device = deviceList;
149
150    while (device != NULL) {
151        if (device->controller == controller) {
152            return;
153        }
154        device = device->next;
155    }
156
157    device = (SDL_JoystickDeviceItem *) SDL_malloc(sizeof(SDL_JoystickDeviceItem));
158    if (device == NULL) {
159        return;
160    }
161
162    SDL_zerop(device);
163
164    device->accelerometer = accelerometer;
165    device->instance_id = instancecounter++;
166
167    if (accelerometer) {
168#if TARGET_OS_TV
169        SDL_free(device);
170        return;
171#else
172        device->name = SDL_strdup(accelerometerName);
173        device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
174        device->nhats = 0;
175        device->nbuttons = 0;
176
177        /* Use the accelerometer name as a GUID. */
178        SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
179#endif /* TARGET_OS_TV */
180    } else if (controller) {
181        SDL_SYS_AddMFIJoystickDevice(device, controller);
182    }
183
184    if (deviceList == NULL) {
185        deviceList = device;
186    } else {
187        SDL_JoystickDeviceItem *lastdevice = deviceList;
188        while (lastdevice->next != NULL) {
189            lastdevice = lastdevice->next;
190        }
191        lastdevice->next = device;
192    }
193
194    ++numjoysticks;
195
196    SDL_PrivateJoystickAdded(numjoysticks - 1);
197}
198
199static SDL_JoystickDeviceItem *
200SDL_SYS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
201{
202    SDL_JoystickDeviceItem *prev = NULL;
203    SDL_JoystickDeviceItem *next = NULL;
204    SDL_JoystickDeviceItem *item = deviceList;
205
206    if (device == NULL) {
207        return NULL;
208    }
209
210    next = device->next;
211
212    while (item != NULL) {
213        if (item == device) {
214            break;
215        }
216        prev = item;
217        item = item->next;
218    }
219
220    /* Unlink the device item from the device list. */
221    if (prev) {
222        prev->next = device->next;
223    } else if (device == deviceList) {
224        deviceList = device->next;
225    }
226
227    if (device->joystick) {
228        device->joystick->hwdata = NULL;
229    }
230
231#ifdef SDL_JOYSTICK_MFI
232    @autoreleasepool {
233        if (device->controller) {
234            /* The controller was explicitly retained in the struct, so it
235             * should be explicitly released before freeing the struct. */
236            GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
237            controller.controllerPausedHandler = nil;
238            device->controller = nil;
239        }
240    }
241#endif /* SDL_JOYSTICK_MFI */
242
243    --numjoysticks;
244
245	SDL_PrivateJoystickRemoved(device->instance_id);
246
247    SDL_free(device->name);
248    SDL_free(device);
249
250    return next;
251}
252
253#if TARGET_OS_TV
254static void
255SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
256{
257    BOOL allowRotation = newValue != NULL && *newValue != '0';
258
259    @autoreleasepool {
260        for (GCController *controller in [GCController controllers]) {
261            if (controller.microGamepad) {
262                controller.microGamepad.allowsRotation = allowRotation;
263            }
264        }
265    }
266}
267#endif /* TARGET_OS_TV */
268
269/* Function to scan the system for joysticks.
270 * Joystick 0 should be the system default joystick.
271 * It should return 0, or -1 on an unrecoverable fatal error.
272 */
273int
274SDL_SYS_JoystickInit(void)
275{
276    @autoreleasepool {
277        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
278
279#if !TARGET_OS_TV
280        if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
281            /* Default behavior, accelerometer as joystick */
282            SDL_SYS_AddJoystickDevice(nil, SDL_TRUE);
283        }
284#endif /* !TARGET_OS_TV */
285
286#ifdef SDL_JOYSTICK_MFI
287        /* GameController.framework was added in iOS 7. */
288        if (![GCController class]) {
289            return numjoysticks;
290        }
291
292        for (GCController *controller in [GCController controllers]) {
293            SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
294        }
295
296#if TARGET_OS_TV
297        SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
298                            SDL_AppleTVRemoteRotationHintChanged, NULL);
299#endif /* TARGET_OS_TV */
300
301        connectObserver = [center addObserverForName:GCControllerDidConnectNotification
302                                              object:nil
303                                               queue:nil
304                                          usingBlock:^(NSNotification *note) {
305                                              GCController *controller = note.object;
306                                              SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
307                                          }];
308
309        disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
310                                                 object:nil
311                                                  queue:nil
312                                             usingBlock:^(NSNotification *note) {
313                                                 GCController *controller = note.object;
314                                                 SDL_JoystickDeviceItem *device = deviceList;
315                                                 while (device != NULL) {
316                                                     if (device->controller == controller) {
317                                                         SDL_SYS_RemoveJoystickDevice(device);
318                                                         break;
319                                                     }
320                                                     device = device->next;
321                                                 }
322                                             }];
323#endif /* SDL_JOYSTICK_MFI */
324    }
325
326    return numjoysticks;
327}
328
329int SDL_SYS_NumJoysticks()
330{
331    return numjoysticks;
332}
333
334void SDL_SYS_JoystickDetect()
335{
336}
337
338/* Function to get the device-dependent name of a joystick */
339const char *
340SDL_SYS_JoystickNameForDeviceIndex(int device_index)
341{
342    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
343    return device ? device->name : "Unknown";
344}
345
346/* Function to perform the mapping from device index to the instance id for this index */
347SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
348{
349    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
350    return device ? device->instance_id : 0;
351}
352
353/* Function to open a joystick for use.
354   The joystick to open is specified by the device index.
355   This should fill the nbuttons and naxes fields of the joystick structure.
356   It returns 0, or -1 if there is an error.
357 */
358int
359SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
360{
361    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
362    if (device == NULL) {
363        return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
364    }
365
366    joystick->hwdata = device;
367    joystick->instance_id = device->instance_id;
368
369    joystick->naxes = device->naxes;
370    joystick->nhats = device->nhats;
371    joystick->nbuttons = device->nbuttons;
372    joystick->nballs = 0;
373
374    device->joystick = joystick;
375
376    @autoreleasepool {
377        if (device->accelerometer) {
378#if !TARGET_OS_TV
379            if (motionManager == nil) {
380                motionManager = [[CMMotionManager alloc] init];
381            }
382
383            /* Shorter times between updates can significantly increase CPU usage. */
384            motionManager.accelerometerUpdateInterval = 0.1;
385            [motionManager startAccelerometerUpdates];
386#endif /* !TARGET_OS_TV */
387        } else {
388#ifdef SDL_JOYSTICK_MFI
389            GCController *controller = device->controller;
390            controller.controllerPausedHandler = ^(GCController *c) {
391                if (joystick->hwdata) {
392                    ++joystick->hwdata->num_pause_presses;
393                }
394            };
395#endif /* SDL_JOYSTICK_MFI */
396        }
397    }
398
399    return 0;
400}
401
402/* Function to determine if this joystick is attached to the system right now */
403SDL_bool
404SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
405{
406    return joystick->hwdata != NULL;
407}
408
409static void
410SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
411{
412#if !TARGET_OS_TV
413    const float maxgforce = SDL_IPHONE_MAX_GFORCE;
414    const SInt16 maxsint16 = 0x7FFF;
415    CMAcceleration accel;
416
417    @autoreleasepool {
418        if (!motionManager.isAccelerometerActive) {
419            return;
420        }
421
422        accel = motionManager.accelerometerData.acceleration;
423    }
424
425    /*
426     Convert accelerometer data from floating point to Sint16, which is what
427     the joystick system expects.
428
429     To do the conversion, the data is first clamped onto the interval
430     [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
431     by MAX_SINT16 so that it is mapped to the full range of an Sint16.
432
433     You can customize the clamped range of this function by modifying the
434     SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
435
436     Once converted to Sint16, the accelerometer data no longer has coherent
437     units. You can convert the data back to units of g-force by multiplying
438     it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
439     */
440
441    /* clamp the data */
442    accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
443    accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
444    accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
445
446    /* pass in data mapped to range of SInt16 */
447    SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
448    SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
449    SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
450#endif /* !TARGET_OS_TV */
451}
452
453#ifdef SDL_JOYSTICK_MFI
454static Uint8
455SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
456{
457    Uint8 hat = 0;
458
459    if (dpad.up.isPressed) {
460        hat |= SDL_HAT_UP;
461    } else if (dpad.down.isPressed) {
462        hat |= SDL_HAT_DOWN;
463    }
464
465    if (dpad.left.isPressed) {
466        hat |= SDL_HAT_LEFT;
467    } else if (dpad.right.isPressed) {
468        hat |= SDL_HAT_RIGHT;
469    }
470
471    if (hat == 0) {
472        return SDL_HAT_CENTERED;
473    }
474
475    return hat;
476}
477#endif
478
479static void
480SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
481{
482#if SDL_JOYSTICK_MFI
483    @autoreleasepool {
484        GCController *controller = joystick->hwdata->controller;
485        Uint8 hatstate = SDL_HAT_CENTERED;
486        int i;
487        int updateplayerindex = 0;
488
489        if (controller.extendedGamepad) {
490            GCExtendedGamepad *gamepad = controller.extendedGamepad;
491
492            /* Axis order matches the XInput Windows mappings. */
493            Sint16 axes[] = {
494                (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
495                (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
496                (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
497                (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
498                (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
499                (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
500            };
501
502            /* Button order matches the XInput Windows mappings. */
503            Uint8 buttons[] = {
504                gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
505                gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
506                gamepad.leftShoulder.isPressed,
507                gamepad.rightShoulder.isPressed,
508            };
509
510            hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
511
512            for (i = 0; i < SDL_arraysize(axes); i++) {
513                /* The triggers (axes 2 and 5) are resting at -32768 but SDL
514                 * initializes its values to 0. We only want to make sure the
515                 * player index is up to date if the user actually moves an axis. */
516                if ((i != 2 && i != 5) || axes[i] != -32768) {
517                    updateplayerindex |= (joystick->axes[i] != axes[i]);
518                }
519                SDL_PrivateJoystickAxis(joystick, i, axes[i]);
520            }
521
522            for (i = 0; i < SDL_arraysize(buttons); i++) {
523                updateplayerindex |= (joystick->buttons[i] != buttons[i]);
524                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
525            }
526        } else if (controller.gamepad) {
527            GCGamepad *gamepad = controller.gamepad;
528
529            /* Button order matches the XInput Windows mappings. */
530            Uint8 buttons[] = {
531                gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
532                gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
533                gamepad.leftShoulder.isPressed,
534                gamepad.rightShoulder.isPressed,
535            };
536
537            hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
538
539            for (i = 0; i < SDL_arraysize(buttons); i++) {
540                updateplayerindex |= (joystick->buttons[i] != buttons[i]);
541                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
542            }
543        }
544#if TARGET_OS_TV
545        else if (controller.microGamepad) {
546            GCMicroGamepad *gamepad = controller.microGamepad;
547
548            Sint16 axes[] = {
549                (Sint16) (gamepad.dpad.xAxis.value * 32767),
550                (Sint16) (gamepad.dpad.yAxis.value * -32767),
551            };
552
553            for (i = 0; i < SDL_arraysize(axes); i++) {
554                updateplayerindex |= (joystick->axes[i] != axes[i]);
555                SDL_PrivateJoystickAxis(joystick, i, axes[i]);
556            }
557
558            /* Apparently the dpad values are not accurate enough to be useful. */
559            /* hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad); */
560
561            Uint8 buttons[] = {
562                gamepad.buttonA.isPressed,
563                gamepad.buttonX.isPressed,
564            };
565
566            for (i = 0; i < SDL_arraysize(buttons); i++) {
567                updateplayerindex |= (joystick->buttons[i] != buttons[i]);
568                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
569            }
570
571            /* TODO: Figure out what to do with reportsAbsoluteDpadValues */
572        }
573#endif /* TARGET_OS_TV */
574
575        if (joystick->nhats > 0) {
576            updateplayerindex |= (joystick->hats[0] != hatstate);
577            SDL_PrivateJoystickHat(joystick, 0, hatstate);
578        }
579
580        for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
581            /* The pause button is always last. */
582            Uint8 pausebutton = joystick->nbuttons - 1;
583
584            SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED);
585            SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED);
586
587            updateplayerindex = YES;
588        }
589
590        joystick->hwdata->num_pause_presses = 0;
591
592        if (updateplayerindex && controller.playerIndex == -1) {
593            BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
594
595            /* Find the player index of all other connected controllers. */
596            for (GCController *c in [GCController controllers]) {
597                if (c != controller && c.playerIndex >= 0) {
598                    usedPlayerIndexSlots[c.playerIndex] = YES;
599                }
600            }
601
602            /* Set this controller's player index to the first unused index.
603             * FIXME: This logic isn't great... but SDL doesn't expose this
604             * concept in its external API, so we don't have much to go on. */
605            for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) {
606                if (!usedPlayerIndexSlots[i]) {
607                    controller.playerIndex = i;
608                    break;
609                }
610            }
611        }
612    }
613#endif /* SDL_JOYSTICK_MFI */
614}
615
616/* Function to update the state of a joystick - called as a device poll.
617 * This function shouldn't update the joystick structure directly,
618 * but instead should call SDL_PrivateJoystick*() to deliver events
619 * and update joystick device state.
620 */
621void
622SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
623{
624    SDL_JoystickDeviceItem *device = joystick->hwdata;
625
626    if (device == NULL) {
627        return;
628    }
629
630    if (device->accelerometer) {
631        SDL_SYS_AccelerometerUpdate(joystick);
632    } else if (device->controller) {
633        SDL_SYS_MFIJoystickUpdate(joystick);
634    }
635}
636
637/* Function to close a joystick after use */
638void
639SDL_SYS_JoystickClose(SDL_Joystick * joystick)
640{
641    SDL_JoystickDeviceItem *device = joystick->hwdata;
642
643    if (device == NULL) {
644        return;
645    }
646
647    device->joystick = NULL;
648
649    @autoreleasepool {
650        if (device->accelerometer) {
651#if !TARGET_OS_TV
652            [motionManager stopAccelerometerUpdates];
653#endif /* !TARGET_OS_TV */
654        } else if (device->controller) {
655#ifdef SDL_JOYSTICK_MFI
656            GCController *controller = device->controller;
657            controller.controllerPausedHandler = nil;
658            controller.playerIndex = -1;
659#endif
660        }
661    }
662}
663
664/* Function to perform any system-specific joystick related cleanup */
665void
666SDL_SYS_JoystickQuit(void)
667{
668    @autoreleasepool {
669#ifdef SDL_JOYSTICK_MFI
670        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
671
672        if (connectObserver) {
673            [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
674            connectObserver = nil;
675        }
676
677        if (disconnectObserver) {
678            [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
679            disconnectObserver = nil;
680        }
681
682#if TARGET_OS_TV
683        SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
684                            SDL_AppleTVRemoteRotationHintChanged, NULL);
685#endif /* TARGET_OS_TV */
686#endif /* SDL_JOYSTICK_MFI */
687
688        while (deviceList != NULL) {
689            SDL_SYS_RemoveJoystickDevice(deviceList);
690        }
691
692#if !TARGET_OS_TV
693        motionManager = nil;
694#endif /* !TARGET_OS_TV */
695    }
696
697    numjoysticks = 0;
698}
699
700SDL_JoystickGUID
701SDL_SYS_JoystickGetDeviceGUID( int device_index )
702{
703    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
704    SDL_JoystickGUID guid;
705    if (device) {
706        guid = device->guid;
707    } else {
708        SDL_zero(guid);
709    }
710    return guid;
711}
712
713SDL_JoystickGUID
714SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
715{
716    SDL_JoystickGUID guid;
717    if (joystick->hwdata) {
718        guid = joystick->hwdata->guid;
719    } else {
720        SDL_zero(guid);
721    }
722    return guid;
723}
724
725/* vi: set ts=4 sw=4 expandtab: */
726