src/joystick/linux/SDL_sysjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 26 Aug 2016 11:16:44 -0700
changeset 10225 3134026517cb
parent 9998 f67cf37e9cd4
child 10226 cb13d22b7f09
permissions -rw-r--r--
commit 1170112da3776fdb06425f62d57b63144c33dc51
Author: James Zipperer <james.zipperer@synapse.com>
Date: Sun Aug 21 01:19:19 2016 -0700

bugfix for controller / joystick add / remove being in the event queue at the same time
slouken@0
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@9998
     3
  Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
slouken@0
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@0
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@0
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@0
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
slouken@0
    22
slouken@1635
    23
#ifdef SDL_JOYSTICK_LINUX
slouken@1635
    24
icculus@6729
    25
#ifndef SDL_INPUT_LINUXEV
icculus@6729
    26
#error SDL now requires a Linux 2.4+ kernel with /dev/input/event support.
icculus@6729
    27
#endif
icculus@6729
    28
philipp@8138
    29
/* This is the Linux implementation of the SDL joystick API */
slouken@0
    30
slouken@0
    31
#include <sys/stat.h>
slouken@0
    32
#include <unistd.h>
slouken@0
    33
#include <fcntl.h>
slouken@0
    34
#include <sys/ioctl.h>
slouken@1895
    35
#include <limits.h>             /* For the definition of PATH_MAX */
slouken@0
    36
#include <linux/joystick.h>
slouken@0
    37
icculus@6734
    38
#include "SDL_assert.h"
slouken@0
    39
#include "SDL_joystick.h"
icculus@6734
    40
#include "SDL_endian.h"
slouken@1361
    41
#include "../SDL_sysjoystick.h"
slouken@1361
    42
#include "../SDL_joystick_c.h"
slouken@2713
    43
#include "SDL_sysjoystick_c.h"
slouken@0
    44
icculus@6734
    45
/* !!! FIXME: move this somewhere else. */
icculus@6734
    46
#if !SDL_EVENTS_DISABLED
icculus@6734
    47
#include "../../events/SDL_events_c.h"
icculus@6734
    48
#endif
icculus@6734
    49
slouken@6910
    50
/* This isn't defined in older Linux kernel headers */
slouken@6910
    51
#ifndef SYN_DROPPED
slouken@6910
    52
#define SYN_DROPPED 3
slouken@6910
    53
#endif
slouken@6910
    54
gabomdq@7772
    55
#include "../../core/linux/SDL_udev.h"
icculus@6734
    56
gabomdq@7772
    57
static int MaybeAddDevice(const char *path);
gabomdq@7772
    58
#if SDL_USE_LIBUDEV
gabomdq@7772
    59
static int MaybeRemoveDevice(const char *path);
slouken@7788
    60
void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);
gabomdq@7772
    61
#endif /* SDL_USE_LIBUDEV */
slouken@892
    62
slouken@0
    63
icculus@6734
    64
/* A linked list of available joysticks */
icculus@6734
    65
typedef struct SDL_joylist_item
icculus@6734
    66
{
icculus@6734
    67
    int device_instance;
icculus@6734
    68
    char *path;   /* "/dev/input/event2" or whatever */
icculus@6734
    69
    char *name;   /* "SideWinder 3D Pro" or whatever */
slouken@6743
    70
    SDL_JoystickGUID guid;
icculus@6734
    71
    dev_t devnum;
icculus@6734
    72
    struct joystick_hwdata *hwdata;
icculus@6734
    73
    struct SDL_joylist_item *next;
icculus@6734
    74
} SDL_joylist_item;
icculus@6734
    75
icculus@6734
    76
static SDL_joylist_item *SDL_joylist = NULL;
icculus@6734
    77
static SDL_joylist_item *SDL_joylist_tail = NULL;
icculus@6734
    78
static int numjoysticks = 0;
icculus@6734
    79
static int instance_counter = 0;
icculus@6734
    80
slouken@0
    81
#define test_bit(nr, addr) \
icculus@6734
    82
    (((1UL << ((nr) % (sizeof(long) * 8))) & ((addr)[(nr) / (sizeof(long) * 8)])) != 0)
slouken@3404
    83
#define NBITS(x) ((((x)-1)/(sizeof(long) * 8))+1)
slouken@0
    84
slouken@1895
    85
static int
slouken@6743
    86
IsJoystick(int fd, char *namebuf, const size_t namebuflen, SDL_JoystickGUID *guid)
slouken@0
    87
{
gabomdq@7909
    88
    struct input_id inpid;
gabomdq@7909
    89
    Uint16 *guid16 = (Uint16 *) ((char *) &guid->data);
gabomdq@7909
    90
gabomdq@7909
    91
#if !SDL_USE_LIBUDEV
gabomdq@7909
    92
    /* When udev is enabled we only get joystick devices here, so there's no need to test them */
slouken@3404
    93
    unsigned long evbit[NBITS(EV_MAX)] = { 0 };
slouken@3404
    94
    unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
slouken@3404
    95
    unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
slouken@0
    96
slouken@1895
    97
    if ((ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) ||
slouken@1895
    98
        (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) ||
slouken@1895
    99
        (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) {
slouken@1895
   100
        return (0);
slouken@1895
   101
    }
bobbens@3079
   102
slouken@1895
   103
    if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) &&
bobbens@3079
   104
          test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) {
slouken@1895
   105
        return 0;
bobbens@3079
   106
    }
gabomdq@7909
   107
#endif
icculus@6734
   108
icculus@6734
   109
    if (ioctl(fd, EVIOCGNAME(namebuflen), namebuf) < 0) {
icculus@6734
   110
        return 0;
icculus@6734
   111
    }
icculus@6734
   112
icculus@6734
   113
    if (ioctl(fd, EVIOCGID, &inpid) < 0) {
icculus@6734
   114
        return 0;
icculus@6734
   115
    }
icculus@6734
   116
slouken@6831
   117
#ifdef DEBUG_JOYSTICK
slouken@6831
   118
    printf("Joystick: %s, bustype = %d, vendor = 0x%x, product = 0x%x, version = %d\n", namebuf, inpid.bustype, inpid.vendor, inpid.product, inpid.version);
slouken@6831
   119
#endif
slouken@6831
   120
slouken@6831
   121
    SDL_memset(guid->data, 0, sizeof(guid->data));
slouken@6831
   122
icculus@6734
   123
    /* We only need 16 bits for each of these; space them out to fill 128. */
icculus@6734
   124
    /* Byteswap so devices get same GUID on little/big endian platforms. */
icculus@6734
   125
    *(guid16++) = SDL_SwapLE16(inpid.bustype);
icculus@6734
   126
    *(guid16++) = 0;
slouken@6831
   127
slouken@6831
   128
    if (inpid.vendor && inpid.product && inpid.version) {
slouken@6831
   129
        *(guid16++) = SDL_SwapLE16(inpid.vendor);
slouken@6831
   130
        *(guid16++) = 0;
slouken@6831
   131
        *(guid16++) = SDL_SwapLE16(inpid.product);
slouken@6831
   132
        *(guid16++) = 0;
slouken@6831
   133
        *(guid16++) = SDL_SwapLE16(inpid.version);
slouken@6831
   134
        *(guid16++) = 0;
slouken@6831
   135
    } else {
slouken@6831
   136
        SDL_strlcpy((char*)guid16, namebuf, sizeof(guid->data) - 4);
slouken@6831
   137
    }
icculus@6734
   138
icculus@6734
   139
    return 1;
slouken@0
   140
}
slouken@0
   141
gabomdq@7772
   142
#if SDL_USE_LIBUDEV
slouken@7788
   143
void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
gabomdq@7772
   144
{
slouken@9321
   145
    if (devpath == NULL) {
gabomdq@7772
   146
        return;
gabomdq@7772
   147
    }
slouken@9321
   148
slouken@9321
   149
    switch (udev_type) {
gabomdq@7772
   150
        case SDL_UDEV_DEVICEADDED:
slouken@9321
   151
            if (!(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) {
slouken@9321
   152
                return;
slouken@9321
   153
            }
slouken@7802
   154
            MaybeAddDevice(devpath);
gabomdq@7772
   155
            break;
gabomdq@7772
   156
            
gabomdq@7772
   157
        case SDL_UDEV_DEVICEREMOVED:
slouken@7802
   158
            MaybeRemoveDevice(devpath);
gabomdq@7772
   159
            break;
gabomdq@7772
   160
            
gabomdq@7772
   161
        default:
gabomdq@7772
   162
            break;
gabomdq@7772
   163
    }
gabomdq@7772
   164
    
gabomdq@7772
   165
}
gabomdq@7772
   166
#endif /* SDL_USE_LIBUDEV */
gabomdq@7772
   167
slouken@0
   168
icculus@6734
   169
/* !!! FIXME: I would love to dump this code and use libudev instead. */
icculus@6734
   170
static int
icculus@6734
   171
MaybeAddDevice(const char *path)
icculus@6734
   172
{
icculus@6734
   173
    struct stat sb;
icculus@6734
   174
    int fd = -1;
icculus@6734
   175
    int isstick = 0;
icculus@6734
   176
    char namebuf[128];
slouken@6743
   177
    SDL_JoystickGUID guid;
icculus@6734
   178
    SDL_joylist_item *item;
slouken@7802
   179
#if !SDL_EVENTS_DISABLED
slouken@7802
   180
    SDL_Event event;
slouken@7802
   181
#endif
icculus@6734
   182
icculus@6734
   183
    if (path == NULL) {
icculus@6734
   184
        return -1;
icculus@6734
   185
    }
icculus@6734
   186
icculus@6734
   187
    if (stat(path, &sb) == -1) {
icculus@6734
   188
        return -1;
icculus@6734
   189
    }
icculus@6734
   190
icculus@6734
   191
    /* Check to make sure it's not already in list. */
icculus@6734
   192
    for (item = SDL_joylist; item != NULL; item = item->next) {
icculus@6734
   193
        if (sb.st_rdev == item->devnum) {
icculus@6734
   194
            return -1;  /* already have this one */
icculus@6734
   195
        }
icculus@6734
   196
    }
icculus@6734
   197
icculus@6734
   198
    fd = open(path, O_RDONLY, 0);
icculus@6734
   199
    if (fd < 0) {
icculus@6734
   200
        return -1;
icculus@6734
   201
    }
icculus@6734
   202
icculus@6734
   203
#ifdef DEBUG_INPUT_EVENTS
icculus@6734
   204
    printf("Checking %s\n", path);
icculus@6734
   205
#endif
icculus@6734
   206
icculus@6734
   207
    isstick = IsJoystick(fd, namebuf, sizeof (namebuf), &guid);
icculus@6734
   208
    close(fd);
icculus@6734
   209
    if (!isstick) {
icculus@6734
   210
        return -1;
icculus@6734
   211
    }
icculus@6734
   212
icculus@6734
   213
    item = (SDL_joylist_item *) SDL_malloc(sizeof (SDL_joylist_item));
icculus@6734
   214
    if (item == NULL) {
icculus@6734
   215
        return -1;
icculus@6734
   216
    }
icculus@6734
   217
icculus@6734
   218
    SDL_zerop(item);
icculus@6734
   219
    item->devnum = sb.st_rdev;
icculus@6734
   220
    item->path = SDL_strdup(path);
icculus@6734
   221
    item->name = SDL_strdup(namebuf);
icculus@6734
   222
    item->guid = guid;
icculus@6734
   223
icculus@6734
   224
    if ( (item->path == NULL) || (item->name == NULL) ) {
icculus@6734
   225
         SDL_free(item->path);
icculus@6734
   226
         SDL_free(item->name);
icculus@6734
   227
         SDL_free(item);
icculus@6734
   228
         return -1;
icculus@6734
   229
    }
icculus@6734
   230
icculus@6734
   231
    item->device_instance = instance_counter++;
icculus@6734
   232
    if (SDL_joylist_tail == NULL) {
icculus@6734
   233
        SDL_joylist = SDL_joylist_tail = item;
icculus@6734
   234
    } else {
icculus@6734
   235
        SDL_joylist_tail->next = item;
icculus@6734
   236
        SDL_joylist_tail = item;
icculus@6734
   237
    }
icculus@6734
   238
slouken@7916
   239
    /* Need to increment the joystick count before we post the event */
slouken@7916
   240
    ++numjoysticks;
slouken@7916
   241
slouken@7802
   242
    /* !!! FIXME: Move this to an SDL_PrivateJoyDeviceAdded() function? */
slouken@7802
   243
#if !SDL_EVENTS_DISABLED
slouken@7802
   244
    event.type = SDL_JOYDEVICEADDED;
slouken@7802
   245
slouken@7802
   246
    if (SDL_GetEventState(event.type) == SDL_ENABLE) {
slouken@7916
   247
        event.jdevice.which = (numjoysticks - 1);
slouken@7802
   248
        if ( (SDL_EventOK == NULL) ||
slouken@7802
   249
             (*SDL_EventOK) (SDL_EventOKParam, &event) ) {
slouken@7802
   250
            SDL_PushEvent(&event);
slouken@7802
   251
        }
slouken@7802
   252
    }
slouken@7802
   253
#endif /* !SDL_EVENTS_DISABLED */
slouken@7802
   254
slouken@7916
   255
    return numjoysticks;
icculus@6734
   256
}
icculus@6734
   257
icculus@6749
   258
#if SDL_USE_LIBUDEV
icculus@6734
   259
/* !!! FIXME: I would love to dump this code and use libudev instead. */
icculus@6734
   260
static int
icculus@6734
   261
MaybeRemoveDevice(const char *path)
icculus@6734
   262
{
icculus@6734
   263
    SDL_joylist_item *item;
icculus@6734
   264
    SDL_joylist_item *prev = NULL;
slouken@7802
   265
#if !SDL_EVENTS_DISABLED
slouken@7802
   266
    SDL_Event event;
slouken@7802
   267
#endif
icculus@6734
   268
icculus@6734
   269
    if (path == NULL) {
icculus@6734
   270
        return -1;
icculus@6734
   271
    }
slouken@6690
   272
icculus@6734
   273
    for (item = SDL_joylist; item != NULL; item = item->next) {
icculus@6734
   274
        /* found it, remove it. */
icculus@6734
   275
        if (SDL_strcmp(path, item->path) == 0) {
icculus@6734
   276
            const int retval = item->device_instance;
icculus@6734
   277
            if (item->hwdata) {
icculus@6752
   278
                item->hwdata->item = NULL;
icculus@6734
   279
            }
icculus@6734
   280
            if (prev != NULL) {
icculus@6734
   281
                prev->next = item->next;
icculus@6734
   282
            } else {
jorgen@6865
   283
                SDL_assert(SDL_joylist == item);
jorgen@6865
   284
                SDL_joylist = item->next;
jorgen@6865
   285
            }
jorgen@6865
   286
            if (item == SDL_joylist_tail) {
jorgen@6865
   287
                SDL_joylist_tail = prev;
icculus@6734
   288
            }
slouken@7802
   289
slouken@7916
   290
            /* Need to decrement the joystick count before we post the event */
slouken@7916
   291
            --numjoysticks;
slouken@7916
   292
slouken@7802
   293
            /* !!! FIXME: Move this to an SDL_PrivateJoyDeviceRemoved() function? */
slouken@7802
   294
#if !SDL_EVENTS_DISABLED
slouken@7802
   295
            event.type = SDL_JOYDEVICEREMOVED;
slouken@7802
   296
slouken@7802
   297
            if (SDL_GetEventState(event.type) == SDL_ENABLE) {
slouken@10225
   298
				SDL_Event peeped;
slouken@10225
   299
slouken@10225
   300
				/* If there is an existing add event in the queue, it
slouken@10225
   301
				 * needs to be modified to have the right value for which,
slouken@10225
   302
				 * because the number of controllers in the system is now
slouken@10225
   303
				 * one less.
slouken@10225
   304
				 */
slouken@10225
   305
				if ( SDL_PeepEvents(&peeped, 1, SDL_GETEVENT, SDL_JOYDEVICEADDED, SDL_JOYDEVICEADDED) > 0) {
slouken@10225
   306
					peeped.jdevice.which--;
slouken@10225
   307
					SDL_PushEvent(&peeped);
slouken@10225
   308
				}
slouken@10225
   309
slouken@7802
   310
                event.jdevice.which = item->device_instance;
slouken@7802
   311
                if ( (SDL_EventOK == NULL) ||
slouken@7802
   312
                     (*SDL_EventOK) (SDL_EventOKParam, &event) ) {
slouken@7802
   313
                    SDL_PushEvent(&event);
slouken@7802
   314
                }
slouken@7802
   315
            }
slouken@7802
   316
#endif /* !SDL_EVENTS_DISABLED */
slouken@7802
   317
icculus@6734
   318
            SDL_free(item->path);
icculus@6734
   319
            SDL_free(item->name);
icculus@6734
   320
            SDL_free(item);
icculus@6734
   321
            return retval;
icculus@6734
   322
        }
icculus@6734
   323
        prev = item;
icculus@6734
   324
    }
icculus@6734
   325
icculus@6734
   326
    return -1;
icculus@6734
   327
}
icculus@6749
   328
#endif
icculus@6734
   329
icculus@6734
   330
static int
icculus@6734
   331
JoystickInitWithoutUdev(void)
icculus@6734
   332
{
icculus@6734
   333
    int i;
icculus@6734
   334
    char path[PATH_MAX];
icculus@6734
   335
icculus@6734
   336
    /* !!! FIXME: only finds sticks if they're called /dev/input/event[0..31] */
icculus@6734
   337
    /* !!! FIXME:  we could at least readdir() through /dev/input...? */
icculus@6734
   338
    /* !!! FIXME:  (or delete this and rely on libudev?) */
icculus@6734
   339
    for (i = 0; i < 32; i++) {
icculus@6734
   340
        SDL_snprintf(path, SDL_arraysize(path), "/dev/input/event%d", i);
icculus@6734
   341
        MaybeAddDevice(path);
icculus@6734
   342
    }
icculus@6734
   343
icculus@6734
   344
    return numjoysticks;
icculus@6734
   345
}
icculus@6734
   346
icculus@6734
   347
icculus@6734
   348
#if SDL_USE_LIBUDEV
icculus@6734
   349
static int
icculus@6734
   350
JoystickInitWithUdev(void)
icculus@6734
   351
{
gabomdq@7772
   352
    if (SDL_UDEV_Init() < 0) {
gabomdq@7772
   353
        return SDL_SetError("Could not initialize UDEV");
icculus@6734
   354
    }
icculus@6734
   355
gabomdq@7772
   356
    /* Set up the udev callback */
slouken@9321
   357
    if (SDL_UDEV_AddCallback(joystick_udev_callback) < 0) {
gabomdq@7772
   358
        SDL_UDEV_Quit();
gabomdq@7772
   359
        return SDL_SetError("Could not set up joystick <-> udev callback");
icculus@6734
   360
    }
gabomdq@7772
   361
    
gabomdq@7772
   362
    /* Force a scan to build the initial device list */
gabomdq@7772
   363
    SDL_UDEV_Scan();
icculus@6734
   364
icculus@6734
   365
    return numjoysticks;
icculus@6734
   366
}
icculus@6734
   367
#endif
icculus@6734
   368
slouken@1895
   369
int
slouken@1895
   370
SDL_SYS_JoystickInit(void)
slouken@0
   371
{
slouken@5317
   372
    /* First see if the user specified one or more joysticks to use */
slouken@1895
   373
    if (SDL_getenv("SDL_JOYSTICK_DEVICE") != NULL) {
slouken@5317
   374
        char *envcopy, *envpath, *delim;
slouken@5317
   375
        envcopy = SDL_strdup(SDL_getenv("SDL_JOYSTICK_DEVICE"));
slouken@5317
   376
        envpath = envcopy;
slouken@5317
   377
        while (envpath != NULL) {
slouken@5317
   378
            delim = SDL_strchr(envpath, ':');
slouken@5317
   379
            if (delim != NULL) {
slouken@5317
   380
                *delim++ = '\0';
slouken@5317
   381
            }
icculus@6734
   382
            MaybeAddDevice(envpath);
slouken@5317
   383
            envpath = delim;
slouken@1895
   384
        }
slouken@5317
   385
        SDL_free(envcopy);
slouken@1895
   386
    }
slouken@554
   387
icculus@6734
   388
#if SDL_USE_LIBUDEV
gabomdq@7772
   389
    return JoystickInitWithUdev();
slouken@0
   390
#endif
slouken@0
   391
icculus@6734
   392
    return JoystickInitWithoutUdev();
slouken@0
   393
}
slouken@0
   394
slouken@6707
   395
int SDL_SYS_NumJoysticks()
slouken@6707
   396
{
icculus@6734
   397
    return numjoysticks;
icculus@6734
   398
}
icculus@6734
   399
slouken@6707
   400
void SDL_SYS_JoystickDetect()
slouken@6707
   401
{
icculus@6734
   402
#if SDL_USE_LIBUDEV
gabomdq@7772
   403
    SDL_UDEV_Poll();
icculus@6734
   404
#endif
gabomdq@7772
   405
    
slouken@6707
   406
}
slouken@6707
   407
icculus@6734
   408
static SDL_joylist_item *
icculus@6734
   409
JoystickByDevIndex(int device_index)
icculus@6734
   410
{
icculus@6734
   411
    SDL_joylist_item *item = SDL_joylist;
icculus@6734
   412
icculus@6734
   413
    if ((device_index < 0) || (device_index >= numjoysticks)) {
icculus@6734
   414
        return NULL;
icculus@6734
   415
    }
icculus@6734
   416
icculus@6734
   417
    while (device_index > 0) {
icculus@6734
   418
        SDL_assert(item != NULL);
icculus@6734
   419
        device_index--;
icculus@6734
   420
        item = item->next;
icculus@6734
   421
    }
icculus@6734
   422
icculus@6734
   423
    return item;
slouken@6707
   424
}
slouken@6707
   425
slouken@0
   426
/* Function to get the device-dependent name of a joystick */
slouken@1895
   427
const char *
slouken@6707
   428
SDL_SYS_JoystickNameForDeviceIndex(int device_index)
slouken@0
   429
{
icculus@6734
   430
    return JoystickByDevIndex(device_index)->name;
slouken@0
   431
}
slouken@0
   432
slouken@6707
   433
/* Function to perform the mapping from device index to the instance id for this index */
slouken@6707
   434
SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
slouken@6707
   435
{
icculus@6734
   436
    return JoystickByDevIndex(device_index)->device_instance;
slouken@6707
   437
}
slouken@6707
   438
slouken@1895
   439
static int
slouken@1895
   440
allocate_hatdata(SDL_Joystick * joystick)
slouken@0
   441
{
slouken@1895
   442
    int i;
slouken@0
   443
slouken@1895
   444
    joystick->hwdata->hats =
slouken@1895
   445
        (struct hwdata_hat *) SDL_malloc(joystick->nhats *
slouken@1895
   446
                                         sizeof(struct hwdata_hat));
slouken@1895
   447
    if (joystick->hwdata->hats == NULL) {
slouken@1895
   448
        return (-1);
slouken@1895
   449
    }
slouken@1895
   450
    for (i = 0; i < joystick->nhats; ++i) {
slouken@1895
   451
        joystick->hwdata->hats[i].axis[0] = 1;
slouken@1895
   452
        joystick->hwdata->hats[i].axis[1] = 1;
slouken@1895
   453
    }
slouken@1895
   454
    return (0);
slouken@0
   455
}
slouken@0
   456
slouken@1895
   457
static int
slouken@1895
   458
allocate_balldata(SDL_Joystick * joystick)
slouken@0
   459
{
slouken@1895
   460
    int i;
slouken@0
   461
slouken@1895
   462
    joystick->hwdata->balls =
slouken@1895
   463
        (struct hwdata_ball *) SDL_malloc(joystick->nballs *
slouken@1895
   464
                                          sizeof(struct hwdata_ball));
slouken@1895
   465
    if (joystick->hwdata->balls == NULL) {
slouken@1895
   466
        return (-1);
slouken@1895
   467
    }
slouken@1895
   468
    for (i = 0; i < joystick->nballs; ++i) {
slouken@1895
   469
        joystick->hwdata->balls[i].axis[0] = 0;
slouken@1895
   470
        joystick->hwdata->balls[i].axis[1] = 0;
slouken@1895
   471
    }
slouken@1895
   472
    return (0);
slouken@0
   473
}
slouken@0
   474
icculus@6729
   475
static void
icculus@6729
   476
ConfigJoystick(SDL_Joystick * joystick, int fd)
slouken@0
   477
{
slouken@1895
   478
    int i, t;
slouken@3404
   479
    unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
slouken@3404
   480
    unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
slouken@3404
   481
    unsigned long relbit[NBITS(REL_MAX)] = { 0 };
slouken@0
   482
slouken@1895
   483
    /* See if this device uses the new unified event API */
slouken@1895
   484
    if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) &&
slouken@1895
   485
        (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) >= 0) &&
slouken@1895
   486
        (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0)) {
slouken@0
   487
slouken@1895
   488
        /* Get the number of buttons, axes, and other thingamajigs */
slouken@1895
   489
        for (i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
slouken@1895
   490
            if (test_bit(i, keybit)) {
slouken@0
   491
#ifdef DEBUG_INPUT_EVENTS
slouken@1895
   492
                printf("Joystick has button: 0x%x\n", i);
slouken@0
   493
#endif
slouken@1895
   494
                joystick->hwdata->key_map[i - BTN_MISC] = joystick->nbuttons;
slouken@1895
   495
                ++joystick->nbuttons;
slouken@1895
   496
            }
slouken@1895
   497
        }
slouken@1895
   498
        for (i = BTN_MISC; i < BTN_JOYSTICK; ++i) {
slouken@1895
   499
            if (test_bit(i, keybit)) {
slouken@0
   500
#ifdef DEBUG_INPUT_EVENTS
slouken@1895
   501
                printf("Joystick has button: 0x%x\n", i);
slouken@0
   502
#endif
slouken@1895
   503
                joystick->hwdata->key_map[i - BTN_MISC] = joystick->nbuttons;
slouken@1895
   504
                ++joystick->nbuttons;
slouken@1895
   505
            }
slouken@1895
   506
        }
icculus@9630
   507
        for (i = 0; i < ABS_MAX; ++i) {
slouken@1895
   508
            /* Skip hats */
slouken@1895
   509
            if (i == ABS_HAT0X) {
slouken@1895
   510
                i = ABS_HAT3Y;
slouken@1895
   511
                continue;
slouken@1895
   512
            }
slouken@1895
   513
            if (test_bit(i, absbit)) {
slouken@5084
   514
                struct input_absinfo absinfo;
slouken@0
   515
slouken@8053
   516
                if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) {
slouken@1895
   517
                    continue;
slouken@8053
   518
                }
slouken@0
   519
#ifdef DEBUG_INPUT_EVENTS
slouken@8053
   520
                printf("Joystick has absolute axis: 0x%.2x\n", i);
slouken@1895
   521
                printf("Values = { %d, %d, %d, %d, %d }\n",
slouken@5084
   522
                       absinfo.value, absinfo.minimum, absinfo.maximum,
slouken@5084
   523
                       absinfo.fuzz, absinfo.flat);
slouken@0
   524
#endif /* DEBUG_INPUT_EVENTS */
slouken@1895
   525
                joystick->hwdata->abs_map[i] = joystick->naxes;
slouken@5084
   526
                if (absinfo.minimum == absinfo.maximum) {
slouken@1895
   527
                    joystick->hwdata->abs_correct[i].used = 0;
slouken@1895
   528
                } else {
slouken@1895
   529
                    joystick->hwdata->abs_correct[i].used = 1;
slouken@1895
   530
                    joystick->hwdata->abs_correct[i].coef[0] =
slouken@6845
   531
                        (absinfo.maximum + absinfo.minimum) - 2 * absinfo.flat;
slouken@1895
   532
                    joystick->hwdata->abs_correct[i].coef[1] =
slouken@6845
   533
                        (absinfo.maximum + absinfo.minimum) + 2 * absinfo.flat;
slouken@6845
   534
                    t = ((absinfo.maximum - absinfo.minimum) - 4 * absinfo.flat);
slouken@1895
   535
                    if (t != 0) {
slouken@1895
   536
                        joystick->hwdata->abs_correct[i].coef[2] =
slouken@6845
   537
                            (1 << 28) / t;
slouken@1895
   538
                    } else {
slouken@1895
   539
                        joystick->hwdata->abs_correct[i].coef[2] = 0;
slouken@1895
   540
                    }
slouken@1895
   541
                }
slouken@1895
   542
                ++joystick->naxes;
slouken@1895
   543
            }
slouken@1895
   544
        }
slouken@1895
   545
        for (i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) {
slouken@1895
   546
            if (test_bit(i, absbit) || test_bit(i + 1, absbit)) {
slouken@8053
   547
                struct input_absinfo absinfo;
slouken@8053
   548
slouken@8053
   549
                if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) {
slouken@8053
   550
                    continue;
slouken@8053
   551
                }
slouken@0
   552
#ifdef DEBUG_INPUT_EVENTS
slouken@1895
   553
                printf("Joystick has hat %d\n", (i - ABS_HAT0X) / 2);
slouken@8053
   554
                printf("Values = { %d, %d, %d, %d, %d }\n",
slouken@8053
   555
                       absinfo.value, absinfo.minimum, absinfo.maximum,
slouken@8053
   556
                       absinfo.fuzz, absinfo.flat);
slouken@8053
   557
#endif /* DEBUG_INPUT_EVENTS */
slouken@1895
   558
                ++joystick->nhats;
slouken@1895
   559
            }
slouken@1895
   560
        }
slouken@1895
   561
        if (test_bit(REL_X, relbit) || test_bit(REL_Y, relbit)) {
slouken@1895
   562
            ++joystick->nballs;
slouken@1895
   563
        }
slouken@0
   564
slouken@1895
   565
        /* Allocate data to keep track of these thingamajigs */
slouken@1895
   566
        if (joystick->nhats > 0) {
slouken@1895
   567
            if (allocate_hatdata(joystick) < 0) {
slouken@1895
   568
                joystick->nhats = 0;
slouken@1895
   569
            }
slouken@1895
   570
        }
slouken@1895
   571
        if (joystick->nballs > 0) {
slouken@1895
   572
            if (allocate_balldata(joystick) < 0) {
slouken@1895
   573
                joystick->nballs = 0;
slouken@1895
   574
            }
slouken@1895
   575
        }
slouken@1895
   576
    }
slouken@0
   577
}
slouken@0
   578
slouken@892
   579
slouken@0
   580
/* Function to open a joystick for use.
philipp@9380
   581
   The joystick to open is specified by the device index.
slouken@0
   582
   This should fill the nbuttons and naxes fields of the joystick structure.
slouken@0
   583
   It returns 0, or -1 if there is an error.
slouken@0
   584
 */
slouken@1895
   585
int
slouken@6690
   586
SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
slouken@0
   587
{
icculus@6734
   588
    SDL_joylist_item *item = JoystickByDevIndex(device_index);
icculus@6734
   589
    char *fname = NULL;
icculus@6734
   590
    int fd = -1;
slouken@892
   591
icculus@6734
   592
    if (item == NULL) {
icculus@7037
   593
        return SDL_SetError("No such device");
icculus@6734
   594
    }
slouken@892
   595
icculus@6734
   596
    fname = item->path;
icculus@6734
   597
    fd = open(fname, O_RDONLY, 0);
slouken@1895
   598
    if (fd < 0) {
icculus@7037
   599
        return SDL_SetError("Unable to open %s", fname);
slouken@1895
   600
    }
icculus@6734
   601
icculus@6751
   602
    joystick->instance_id = item->device_instance;
slouken@1895
   603
    joystick->hwdata = (struct joystick_hwdata *)
slouken@1895
   604
        SDL_malloc(sizeof(*joystick->hwdata));
slouken@1895
   605
    if (joystick->hwdata == NULL) {
icculus@6734
   606
        close(fd);
icculus@7037
   607
        return SDL_OutOfMemory();
slouken@1895
   608
    }
slouken@1895
   609
    SDL_memset(joystick->hwdata, 0, sizeof(*joystick->hwdata));
icculus@6752
   610
    joystick->hwdata->item = item;
icculus@6734
   611
    joystick->hwdata->guid = item->guid;
slouken@1895
   612
    joystick->hwdata->fd = fd;
icculus@6734
   613
    joystick->hwdata->fname = SDL_strdup(item->path);
icculus@6734
   614
    if (joystick->hwdata->fname == NULL) {
icculus@6734
   615
        SDL_free(joystick->hwdata);
icculus@6734
   616
        joystick->hwdata = NULL;
icculus@6734
   617
        close(fd);
icculus@7037
   618
        return SDL_OutOfMemory();
icculus@6734
   619
    }
icculus@6734
   620
icculus@6734
   621
    SDL_assert(item->hwdata == NULL);
icculus@6734
   622
    item->hwdata = joystick->hwdata;
slouken@0
   623
slouken@1895
   624
    /* Set the joystick to non-blocking read mode */
slouken@1895
   625
    fcntl(fd, F_SETFL, O_NONBLOCK);
slouken@0
   626
slouken@1895
   627
    /* Get the number of buttons and axes on the joystick */
icculus@6729
   628
    ConfigJoystick(joystick, fd);
slouken@554
   629
slouken@7191
   630
    /* mark joystick as fresh and ready */
slouken@6844
   631
    joystick->hwdata->fresh = 1;
slouken@6844
   632
slouken@1895
   633
    return (0);
slouken@0
   634
}
slouken@0
   635
philipp@9561
   636
/* Function to determine if this joystick is attached to the system right now */
slouken@6707
   637
SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
slouken@6707
   638
{
icculus@9433
   639
    return joystick->hwdata->item != NULL;
slouken@6707
   640
}
slouken@6707
   641
slouken@7860
   642
static SDL_INLINE void
slouken@1895
   643
HandleHat(SDL_Joystick * stick, Uint8 hat, int axis, int value)
slouken@0
   644
{
slouken@1895
   645
    struct hwdata_hat *the_hat;
slouken@1895
   646
    const Uint8 position_map[3][3] = {
slouken@1895
   647
        {SDL_HAT_LEFTUP, SDL_HAT_UP, SDL_HAT_RIGHTUP},
slouken@1895
   648
        {SDL_HAT_LEFT, SDL_HAT_CENTERED, SDL_HAT_RIGHT},
slouken@1895
   649
        {SDL_HAT_LEFTDOWN, SDL_HAT_DOWN, SDL_HAT_RIGHTDOWN}
slouken@1895
   650
    };
slouken@0
   651
slouken@1895
   652
    the_hat = &stick->hwdata->hats[hat];
slouken@1895
   653
    if (value < 0) {
slouken@1895
   654
        value = 0;
slouken@1895
   655
    } else if (value == 0) {
slouken@1895
   656
        value = 1;
slouken@1895
   657
    } else if (value > 0) {
slouken@1895
   658
        value = 2;
slouken@1895
   659
    }
slouken@1895
   660
    if (value != the_hat->axis[axis]) {
slouken@1895
   661
        the_hat->axis[axis] = value;
slouken@1895
   662
        SDL_PrivateJoystickHat(stick, hat,
slouken@3013
   663
                               position_map[the_hat->
slouken@3013
   664
                                            axis[1]][the_hat->axis[0]]);
slouken@1895
   665
    }
slouken@0
   666
}
slouken@0
   667
slouken@7860
   668
static SDL_INLINE void
slouken@1895
   669
HandleBall(SDL_Joystick * stick, Uint8 ball, int axis, int value)
slouken@0
   670
{
slouken@1895
   671
    stick->hwdata->balls[ball].axis[axis] += value;
slouken@0
   672
}
slouken@0
   673
slouken@0
   674
slouken@7860
   675
static SDL_INLINE int
icculus@6729
   676
AxisCorrect(SDL_Joystick * joystick, int which, int value)
slouken@0
   677
{
slouken@1895
   678
    struct axis_correct *correct;
slouken@0
   679
slouken@1895
   680
    correct = &joystick->hwdata->abs_correct[which];
slouken@1895
   681
    if (correct->used) {
slouken@6845
   682
        value *= 2;
slouken@1895
   683
        if (value > correct->coef[0]) {
slouken@1895
   684
            if (value < correct->coef[1]) {
slouken@1895
   685
                return 0;
slouken@1895
   686
            }
slouken@1895
   687
            value -= correct->coef[1];
slouken@1895
   688
        } else {
slouken@1895
   689
            value -= correct->coef[0];
slouken@1895
   690
        }
slouken@1895
   691
        value *= correct->coef[2];
slouken@6845
   692
        value >>= 13;
slouken@1895
   693
    }
slouken@554
   694
slouken@1895
   695
    /* Clamp and return */
slouken@1895
   696
    if (value < -32768)
slouken@1895
   697
        return -32768;
slouken@1895
   698
    if (value > 32767)
slouken@1895
   699
        return 32767;
slouken@554
   700
slouken@1895
   701
    return value;
slouken@0
   702
}
slouken@0
   703
slouken@7860
   704
static SDL_INLINE void
slouken@6844
   705
PollAllValues(SDL_Joystick * joystick)
slouken@6844
   706
{
slouken@6844
   707
    struct input_absinfo absinfo;
slouken@6844
   708
    int a, b = 0;
slouken@6844
   709
slouken@7191
   710
    /* Poll all axis */
slouken@6844
   711
    for (a = ABS_X; b < ABS_MAX; a++) {
slouken@6844
   712
        switch (a) {
slouken@6844
   713
        case ABS_HAT0X:
slouken@6844
   714
        case ABS_HAT0Y:
slouken@6844
   715
        case ABS_HAT1X:
slouken@6844
   716
        case ABS_HAT1Y:
slouken@6844
   717
        case ABS_HAT2X:
slouken@6844
   718
        case ABS_HAT2Y:
slouken@6844
   719
        case ABS_HAT3X:
slouken@6844
   720
        case ABS_HAT3Y:
slouken@7191
   721
            /* ingore hats */
slouken@6844
   722
            break;
slouken@6844
   723
        default:
slouken@6844
   724
            if (joystick->hwdata->abs_correct[b].used) {
slouken@6844
   725
                if (ioctl(joystick->hwdata->fd, EVIOCGABS(a), &absinfo) >= 0) {
slouken@6844
   726
                    absinfo.value = AxisCorrect(joystick, b, absinfo.value);
slouken@6844
   727
slouken@6844
   728
#ifdef DEBUG_INPUT_EVENTS
slouken@6844
   729
                    printf("Joystick : Re-read Axis %d (%d) val= %d\n",
slouken@6844
   730
                        joystick->hwdata->abs_map[b], a, absinfo.value);
slouken@6844
   731
#endif
slouken@6844
   732
                    SDL_PrivateJoystickAxis(joystick,
slouken@6844
   733
                            joystick->hwdata->abs_map[b],
slouken@6844
   734
                            absinfo.value);
slouken@6844
   735
                }
slouken@6844
   736
            }
slouken@6844
   737
            b++;
slouken@6844
   738
        }
slouken@6844
   739
    }
slouken@6844
   740
}
slouken@6844
   741
slouken@7860
   742
static SDL_INLINE void
icculus@6729
   743
HandleInputEvents(SDL_Joystick * joystick)
slouken@0
   744
{
slouken@1895
   745
    struct input_event events[32];
slouken@1895
   746
    int i, len;
slouken@1895
   747
    int code;
slouken@0
   748
slouken@6844
   749
    if (joystick->hwdata->fresh) {
slouken@6844
   750
        PollAllValues(joystick);
slouken@6844
   751
        joystick->hwdata->fresh = 0;
slouken@6844
   752
    }
slouken@6844
   753
slouken@1895
   754
    while ((len = read(joystick->hwdata->fd, events, (sizeof events))) > 0) {
slouken@1895
   755
        len /= sizeof(events[0]);
slouken@1895
   756
        for (i = 0; i < len; ++i) {
slouken@1895
   757
            code = events[i].code;
slouken@1895
   758
            switch (events[i].type) {
slouken@1895
   759
            case EV_KEY:
slouken@1895
   760
                if (code >= BTN_MISC) {
slouken@1895
   761
                    code -= BTN_MISC;
icculus@6728
   762
                    SDL_PrivateJoystickButton(joystick,
icculus@6728
   763
                                              joystick->hwdata->key_map[code],
icculus@6728
   764
                                              events[i].value);
slouken@1895
   765
                }
slouken@1895
   766
                break;
slouken@1895
   767
            case EV_ABS:
slouken@1895
   768
                switch (code) {
slouken@1895
   769
                case ABS_HAT0X:
slouken@1895
   770
                case ABS_HAT0Y:
slouken@1895
   771
                case ABS_HAT1X:
slouken@1895
   772
                case ABS_HAT1Y:
slouken@1895
   773
                case ABS_HAT2X:
slouken@1895
   774
                case ABS_HAT2Y:
slouken@1895
   775
                case ABS_HAT3X:
slouken@1895
   776
                case ABS_HAT3Y:
slouken@1895
   777
                    code -= ABS_HAT0X;
slouken@1895
   778
                    HandleHat(joystick, code / 2, code % 2, events[i].value);
slouken@1895
   779
                    break;
slouken@1895
   780
                default:
slouken@1895
   781
                    events[i].value =
icculus@6729
   782
                        AxisCorrect(joystick, code, events[i].value);
icculus@6728
   783
                    SDL_PrivateJoystickAxis(joystick,
icculus@6728
   784
                                            joystick->hwdata->abs_map[code],
icculus@6728
   785
                                            events[i].value);
slouken@1895
   786
                    break;
slouken@1895
   787
                }
slouken@1895
   788
                break;
slouken@1895
   789
            case EV_REL:
slouken@1895
   790
                switch (code) {
slouken@1895
   791
                case REL_X:
slouken@1895
   792
                case REL_Y:
slouken@1895
   793
                    code -= REL_X;
slouken@1895
   794
                    HandleBall(joystick, code / 2, code % 2, events[i].value);
slouken@1895
   795
                    break;
slouken@1895
   796
                default:
slouken@1895
   797
                    break;
slouken@1895
   798
                }
slouken@1895
   799
                break;
slouken@6844
   800
            case EV_SYN:
slouken@6844
   801
                switch (code) {
slouken@6844
   802
                case SYN_DROPPED :
slouken@6844
   803
#ifdef DEBUG_INPUT_EVENTS
philipp@7133
   804
                    printf("Event SYN_DROPPED detected\n");
slouken@6844
   805
#endif
slouken@6844
   806
                    PollAllValues(joystick);
slouken@6844
   807
                    break;
slouken@6844
   808
                default:
slouken@6844
   809
                    break;
slouken@6844
   810
                }
slouken@1895
   811
            default:
slouken@1895
   812
                break;
slouken@1895
   813
            }
slouken@1895
   814
        }
slouken@1895
   815
    }
slouken@0
   816
}
slouken@0
   817
slouken@1895
   818
void
slouken@1895
   819
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
slouken@0
   820
{
slouken@1895
   821
    int i;
slouken@1895
   822
icculus@6729
   823
    HandleInputEvents(joystick);
slouken@0
   824
slouken@1895
   825
    /* Deliver ball motion updates */
slouken@1895
   826
    for (i = 0; i < joystick->nballs; ++i) {
slouken@1895
   827
        int xrel, yrel;
slouken@0
   828
slouken@1895
   829
        xrel = joystick->hwdata->balls[i].axis[0];
slouken@1895
   830
        yrel = joystick->hwdata->balls[i].axis[1];
slouken@1895
   831
        if (xrel || yrel) {
slouken@1895
   832
            joystick->hwdata->balls[i].axis[0] = 0;
slouken@1895
   833
            joystick->hwdata->balls[i].axis[1] = 0;
slouken@1895
   834
            SDL_PrivateJoystickBall(joystick, (Uint8) i, xrel, yrel);
slouken@1895
   835
        }
slouken@1895
   836
    }
slouken@0
   837
}
slouken@0
   838
slouken@0
   839
/* Function to close a joystick after use */
slouken@1895
   840
void
slouken@1895
   841
SDL_SYS_JoystickClose(SDL_Joystick * joystick)
slouken@0
   842
{
slouken@1895
   843
    if (joystick->hwdata) {
icculus@6728
   844
        close(joystick->hwdata->fd);
icculus@6752
   845
        if (joystick->hwdata->item) {
icculus@6752
   846
            joystick->hwdata->item->hwdata = NULL;
icculus@6752
   847
        }
icculus@6734
   848
        SDL_free(joystick->hwdata->hats);
icculus@6734
   849
        SDL_free(joystick->hwdata->balls);
icculus@6734
   850
        SDL_free(joystick->hwdata->fname);
slouken@1895
   851
        SDL_free(joystick->hwdata);
slouken@1895
   852
    }
slouken@0
   853
}
slouken@0
   854
slouken@0
   855
/* Function to perform any system-specific joystick related cleanup */
slouken@1895
   856
void
slouken@1895
   857
SDL_SYS_JoystickQuit(void)
slouken@0
   858
{
icculus@6734
   859
    SDL_joylist_item *item = NULL;
icculus@6734
   860
    SDL_joylist_item *next = NULL;
icculus@6734
   861
icculus@6734
   862
    for (item = SDL_joylist; item; item = next) {
icculus@6734
   863
        next = item->next;
icculus@6734
   864
        SDL_free(item->path);
icculus@6734
   865
        SDL_free(item->name);
icculus@6734
   866
        SDL_free(item);
icculus@6734
   867
    }
icculus@6734
   868
icculus@6734
   869
    SDL_joylist = SDL_joylist_tail = NULL;
slouken@0
   870
icculus@6734
   871
    numjoysticks = 0;
icculus@6734
   872
    instance_counter = 0;
icculus@6734
   873
icculus@6734
   874
#if SDL_USE_LIBUDEV
gabomdq@7772
   875
    SDL_UDEV_DelCallback(joystick_udev_callback);
gabomdq@7772
   876
    SDL_UDEV_Quit();
icculus@6734
   877
#endif
slouken@0
   878
}
slouken@0
   879
slouken@6738
   880
SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
slouken@6690
   881
{
icculus@6734
   882
    return JoystickByDevIndex(device_index)->guid;
slouken@6690
   883
}
slouken@6690
   884
slouken@6738
   885
SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
slouken@6690
   886
{
icculus@6734
   887
    return joystick->hwdata->guid;
slouken@6690
   888
}
slouken@6690
   889
slouken@1635
   890
#endif /* SDL_JOYSTICK_LINUX */
slouken@6693
   891
slouken@1895
   892
/* vi: set ts=4 sw=4 expandtab: */