Search
lxdream.org :: lxdream/src/drivers/joy_linux.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/drivers/joy_linux.c
changeset 1010:a506a2f66180
prev984:a01567058a47
next1296:30ecee61f811
author nkeynes
date Sun Apr 12 02:04:27 2009 +0000 (15 years ago)
permissions -rw-r--r--
last change Fix analogue joystick input
file annotate diff log raw
nkeynes@614
     1
/**
nkeynes@637
     2
 * $Id$
nkeynes@614
     3
 *
nkeynes@614
     4
 * Linux joystick input device support
nkeynes@614
     5
 *
nkeynes@614
     6
 * Copyright (c) 2008 Nathan Keynes.
nkeynes@614
     7
 *
nkeynes@614
     8
 * This program is free software; you can redistribute it and/or modify
nkeynes@614
     9
 * it under the terms of the GNU General Public License as published by
nkeynes@614
    10
 * the Free Software Foundation; either version 2 of the License, or
nkeynes@614
    11
 * (at your option) any later version.
nkeynes@614
    12
 *
nkeynes@614
    13
 * This program is distributed in the hope that it will be useful,
nkeynes@614
    14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
nkeynes@614
    15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
nkeynes@614
    16
 * GNU General Public License for more details.
nkeynes@614
    17
 */
nkeynes@614
    18
nkeynes@984
    19
#ifndef _GNU_SOURCE
nkeynes@620
    20
#define _GNU_SOURCE
nkeynes@984
    21
#endif
nkeynes@614
    22
#include <sys/types.h>
nkeynes@614
    23
#include <sys/ioctl.h>
nkeynes@614
    24
#include <errno.h>
nkeynes@614
    25
#include <stdio.h>
nkeynes@620
    26
#include <signal.h>
nkeynes@614
    27
#include <string.h>
nkeynes@669
    28
#include <stdlib.h>
nkeynes@669
    29
#include <unistd.h>
nkeynes@614
    30
#include <fcntl.h>
nkeynes@614
    31
#include <dirent.h>
nkeynes@614
    32
#include <ctype.h>
nkeynes@614
    33
nkeynes@614
    34
#include <linux/joystick.h>
nkeynes@614
    35
#include <glib/giochannel.h>
nkeynes@614
    36
#include <glib.h>
nkeynes@614
    37
nkeynes@614
    38
#include "lxdream.h"
nkeynes@614
    39
#include "display.h"
nkeynes@669
    40
#include "maple/maple.h"
nkeynes@669
    41
#include "drivers/joy_linux.h"
nkeynes@614
    42
nkeynes@614
    43
#define INPUT_PATH "/dev/input"
nkeynes@614
    44
nkeynes@614
    45
typedef struct linux_joystick {
nkeynes@614
    46
    struct input_driver driver;
nkeynes@614
    47
    const gchar *filename;
nkeynes@614
    48
    char name[128];
nkeynes@614
    49
    int fd;
nkeynes@614
    50
    int button_count, axis_count;
nkeynes@614
    51
    GIOChannel *channel;
nkeynes@614
    52
nkeynes@614
    53
} *linux_joystick_t;
nkeynes@614
    54
nkeynes@1010
    55
/* Linux joysticks return data in the range -32767 to 32767 - rescale this to 
nkeynes@1010
    56
 * -127 .. 127
nkeynes@1010
    57
 */
nkeynes@1010
    58
#define SCALE_PRESSURE(x) ((x)>>8)
nkeynes@1010
    59
nkeynes@614
    60
static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
nkeynes@736
    61
                                         gpointer data );
nkeynes@669
    62
static int linux_joystick_scan();
nkeynes@669
    63
static linux_joystick_t linux_joystick_new( const gchar *filename, int fd );
nkeynes@614
    64
static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str );
nkeynes@614
    65
static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode );
nkeynes@614
    66
static void linux_joystick_destroy( input_driver_t joy );
nkeynes@620
    67
static gboolean linux_joystick_install_watch( const gchar *dir );
nkeynes@620
    68
static void linux_joystick_uninstall_watch( void );
nkeynes@614
    69
nkeynes@614
    70
/**
nkeynes@615
    71
 * Convert keysym to keycode. Keysyms are either Button%d or Axis%d[+-], with buttons
nkeynes@615
    72
 * numbered 1 .. button_count, then axes from button_count+1 .. button_count + axis_count*2.
nkeynes@614
    73
 * The first button is Button1. (no Button0)
nkeynes@615
    74
 * The first axis is Axis1+, then Axis1-, Axis2+ and so forth.
nkeynes@614
    75
 */
nkeynes@614
    76
static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str )
nkeynes@614
    77
{
nkeynes@614
    78
    linux_joystick_t joy = (linux_joystick_t)dev;
nkeynes@614
    79
    if( strncasecmp( str, "Button", 6 ) == 0 ){
nkeynes@736
    80
        unsigned long button = strtoul( str+6, NULL, 10 );
nkeynes@736
    81
        if( button > joy->button_count ) {
nkeynes@736
    82
            return 0;
nkeynes@736
    83
        }
nkeynes@736
    84
        return (uint16_t)button;
nkeynes@614
    85
    } else if( strncasecmp( str, "Axis", 4 ) == 0 ) {
nkeynes@736
    86
        char *endptr;
nkeynes@736
    87
        unsigned long axis = strtoul( str+4, &endptr, 10 );
nkeynes@736
    88
        if( axis > joy->axis_count || axis == 0 ) {
nkeynes@736
    89
            return 0;
nkeynes@736
    90
        }
nkeynes@736
    91
        int keycode = ((axis - 1) << 1) + joy->button_count + 1;
nkeynes@736
    92
        if( *endptr == '-' ) {
nkeynes@736
    93
            return keycode + 1;
nkeynes@736
    94
        } else {
nkeynes@736
    95
            return keycode;
nkeynes@736
    96
        }
nkeynes@614
    97
    } else {
nkeynes@736
    98
        return 0;
nkeynes@614
    99
    }
nkeynes@614
   100
}
nkeynes@614
   101
nkeynes@614
   102
static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode )
nkeynes@614
   103
{
nkeynes@614
   104
    linux_joystick_t joy = (linux_joystick_t)dev;
nkeynes@614
   105
    if( keycode == 0 ) {
nkeynes@736
   106
        return NULL;
nkeynes@614
   107
    }
nkeynes@614
   108
    if( keycode <= joy->button_count ) {
nkeynes@736
   109
        return g_strdup_printf( "Button%d", keycode );
nkeynes@614
   110
    }
nkeynes@615
   111
    if( keycode <= joy->button_count + joy->axis_count*2 ) {
nkeynes@736
   112
        int axis = keycode - joy->button_count - 1;
nkeynes@736
   113
        if( (axis & 1) == 0 ) {
nkeynes@736
   114
            return g_strdup_printf( "Axis%d+", (axis >> 1)+1 );
nkeynes@736
   115
        } else {
nkeynes@736
   116
            return g_strdup_printf( "Axis%d-", (axis >> 1)+1 );
nkeynes@736
   117
        }
nkeynes@614
   118
    }
nkeynes@614
   119
    return NULL;
nkeynes@614
   120
}
nkeynes@614
   121
nkeynes@614
   122
static void linux_joystick_destroy( input_driver_t dev )
nkeynes@614
   123
{
nkeynes@614
   124
    linux_joystick_t joy = (linux_joystick_t)dev;
nkeynes@614
   125
    g_free( (gchar *)joy->filename );
nkeynes@614
   126
    g_io_channel_shutdown(joy->channel, FALSE, NULL );
nkeynes@614
   127
    g_io_channel_unref(joy->channel);
nkeynes@614
   128
    g_free( joy );
nkeynes@614
   129
}
nkeynes@614
   130
nkeynes@614
   131
/**
nkeynes@614
   132
 * Callback from the GIOChannel whenever data is available, or an error/disconnect
nkeynes@614
   133
 * occurs.
nkeynes@614
   134
 *
nkeynes@614
   135
 * On data, process all pending events and direct them to the input system.
nkeynes@614
   136
 * On error, close the channel and delete the device.
nkeynes@614
   137
 */
nkeynes@614
   138
static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
nkeynes@736
   139
                                         gpointer data )
nkeynes@614
   140
{
nkeynes@614
   141
    linux_joystick_t joy = (linux_joystick_t)data;
nkeynes@614
   142
nkeynes@614
   143
    if( condition & G_IO_HUP ) {
nkeynes@736
   144
        INFO( "Joystick '%s' disconnected\n", joy->name );
nkeynes@736
   145
        input_unregister_device((input_driver_t)joy);
nkeynes@736
   146
        return FALSE;
nkeynes@614
   147
    }
nkeynes@614
   148
    if( condition & G_IO_IN ) {
nkeynes@736
   149
        struct js_event event;
nkeynes@736
   150
        while( read( joy->fd, &event, sizeof(event) ) == sizeof(event) ) {
nkeynes@736
   151
            if( event.type == JS_EVENT_BUTTON ) {
nkeynes@736
   152
                int keycode = event.number+1;
nkeynes@736
   153
                if( event.value == 0 ) {
nkeynes@1010
   154
                    input_event_keyup( (input_driver_t)joy, keycode );
nkeynes@736
   155
                } else {
nkeynes@1010
   156
                    input_event_keydown( (input_driver_t)joy, keycode, MAX_PRESSURE );
nkeynes@736
   157
                }
nkeynes@736
   158
            } else if( event.type == JS_EVENT_AXIS ) {
nkeynes@736
   159
                int keycode = (event.number*2) + joy->button_count + 1;
nkeynes@736
   160
                if( event.value == 0 ) {
nkeynes@1010
   161
                    input_event_keyup( (input_driver_t)joy, keycode );
nkeynes@1010
   162
                    input_event_keyup( (input_driver_t)joy, keycode+1 );
nkeynes@736
   163
                } else if( event.value < 0 ) {
nkeynes@1010
   164
                    input_event_keyup( (input_driver_t)joy, keycode );
nkeynes@1010
   165
                    input_event_keydown( (input_driver_t)joy, keycode+1, SCALE_PRESSURE(-event.value) );
nkeynes@736
   166
                } else {
nkeynes@1010
   167
                    input_event_keyup( (input_driver_t)joy, keycode+1 );
nkeynes@1010
   168
                    input_event_keydown( (input_driver_t)joy, keycode, SCALE_PRESSURE(event.value) );
nkeynes@736
   169
                }
nkeynes@736
   170
            }
nkeynes@736
   171
        }
nkeynes@614
   172
    }
nkeynes@669
   173
    return TRUE;
nkeynes@614
   174
}
nkeynes@614
   175
nkeynes@614
   176
/**
nkeynes@614
   177
 * Create a new joystick device structure given filename and (open) file
nkeynes@614
   178
 * descriptor. The joystick is automatically added to the watch list.
nkeynes@614
   179
 * @return The new joystick, or NULL if an error occurred.
nkeynes@614
   180
 */
nkeynes@669
   181
static linux_joystick_t linux_joystick_new( const gchar *filename, int fd )
nkeynes@614
   182
{
nkeynes@614
   183
    linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick));
nkeynes@614
   184
    joy->filename = filename;
nkeynes@614
   185
    joy->fd = fd;
nkeynes@614
   186
    joy->name[0] = '\0';
nkeynes@614
   187
    joy->driver.resolve_keysym = linux_joystick_resolve_keysym;
nkeynes@614
   188
    joy->driver.get_keysym_for_keycode = linux_joystick_keysym_for_keycode;
nkeynes@614
   189
    joy->driver.destroy = linux_joystick_destroy;
nkeynes@614
   190
nkeynes@614
   191
    char *p = strrchr(filename, '/');
nkeynes@614
   192
    if( p == NULL ) {
nkeynes@736
   193
        joy->driver.id = filename;
nkeynes@614
   194
    } else {
nkeynes@736
   195
        joy->driver.id = p+1;
nkeynes@614
   196
    }
nkeynes@614
   197
nkeynes@614
   198
    if( ioctl( fd, JSIOCGNAME(128), joy->name ) == -1 ||
nkeynes@736
   199
            ioctl( fd, JSIOCGAXES, &joy->axis_count ) == -1 || 
nkeynes@736
   200
            ioctl( fd, JSIOCGBUTTONS, &joy->button_count ) == -1 ) {
nkeynes@736
   201
        ERROR( "Error reading joystick data from %s (%s)\n", filename, strerror(errno) );
nkeynes@736
   202
        g_free(joy);
nkeynes@736
   203
        return NULL;
nkeynes@614
   204
    }
nkeynes@614
   205
nkeynes@614
   206
    joy->channel = g_io_channel_unix_new(fd);
nkeynes@614
   207
    g_io_add_watch( joy->channel, G_IO_IN|G_IO_ERR|G_IO_HUP, linux_joystick_callback, joy );
nkeynes@614
   208
    return joy;
nkeynes@614
   209
}
nkeynes@614
   210
nkeynes@669
   211
static int linux_joystick_scan()
nkeynes@620
   212
{
nkeynes@615
   213
    int joysticks = 0;
nkeynes@614
   214
    struct dirent *ent;
nkeynes@614
   215
    DIR *dir = opendir(INPUT_PATH);
nkeynes@614
   216
nkeynes@614
   217
    if( dir == NULL ) {
nkeynes@736
   218
        return 0;
nkeynes@614
   219
    }
nkeynes@736
   220
nkeynes@614
   221
    while( (ent = readdir(dir)) != NULL ) {
nkeynes@736
   222
        if( ent->d_name[0] == 'j' && ent->d_name[1] == 's' &&
nkeynes@736
   223
                isdigit(ent->d_name[2]) && !input_has_device(ent->d_name) ) {
nkeynes@736
   224
            gchar *name = g_strdup_printf( "%s/%s", INPUT_PATH, ent->d_name );
nkeynes@736
   225
            int fd = open(name, O_RDONLY|O_NONBLOCK);
nkeynes@736
   226
            if( fd == -1 ) {
nkeynes@736
   227
                g_free( name );
nkeynes@736
   228
            } else {
nkeynes@736
   229
                linux_joystick_t joy = linux_joystick_new( name, fd );
nkeynes@736
   230
                input_register_device( (input_driver_t)joy, (joy->axis_count*2) + joy->button_count );
nkeynes@736
   231
                INFO( "Attached joystick %s named '%s', (%d buttons, %d axes)", 
nkeynes@736
   232
                      joy->driver.id, joy->name, joy->button_count, joy->axis_count );
nkeynes@736
   233
                joysticks++;
nkeynes@736
   234
            }
nkeynes@736
   235
        }
nkeynes@614
   236
    }
nkeynes@614
   237
nkeynes@614
   238
    closedir(dir);
nkeynes@615
   239
    return joysticks;
nkeynes@614
   240
}
nkeynes@615
   241
nkeynes@669
   242
gboolean linux_joystick_init()
nkeynes@669
   243
{
nkeynes@669
   244
    if( !linux_joystick_install_watch(INPUT_PATH) ) {
nkeynes@736
   245
        return FALSE;
nkeynes@669
   246
    }
nkeynes@669
   247
    linux_joystick_scan();
nkeynes@669
   248
    return TRUE;
nkeynes@669
   249
}
nkeynes@669
   250
nkeynes@620
   251
void linux_joystick_shutdown(void)
nkeynes@620
   252
{
nkeynes@620
   253
    linux_joystick_uninstall_watch();
nkeynes@620
   254
}
nkeynes@620
   255
nkeynes@620
   256
/*************************** dnotify support **********************/
nkeynes@620
   257
nkeynes@621
   258
static volatile int need_input_rescan = 0;
nkeynes@620
   259
static int watch_dir_fd;
nkeynes@620
   260
nkeynes@620
   261
static gboolean gtk_loop_check_input(gpointer data)
nkeynes@620
   262
{
nkeynes@621
   263
    if( need_input_rescan == 1 ) {
nkeynes@736
   264
        need_input_rescan = 0;
nkeynes@736
   265
        int js = linux_joystick_scan();
nkeynes@736
   266
        if( js > 0 ) {
nkeynes@736
   267
            maple_reattach_all();
nkeynes@736
   268
        }
nkeynes@620
   269
    }
nkeynes@621
   270
    return need_input_rescan != 2;
nkeynes@620
   271
}
nkeynes@620
   272
nkeynes@620
   273
static void dnotify_handler(int sig )
nkeynes@620
   274
{
nkeynes@621
   275
    need_input_rescan = 1;
nkeynes@620
   276
}
nkeynes@620
   277
nkeynes@620
   278
static gboolean linux_joystick_install_watch( const gchar *dir )
nkeynes@620
   279
{
nkeynes@620
   280
    int fd = open( dir, O_RDONLY|O_NONBLOCK );
nkeynes@620
   281
    if( fd == -1 ) {
nkeynes@736
   282
        return FALSE;
nkeynes@620
   283
    }
nkeynes@736
   284
nkeynes@620
   285
    signal( SIGRTMIN+1, dnotify_handler );
nkeynes@620
   286
    fcntl(fd, F_SETSIG, SIGRTMIN + 1);
nkeynes@620
   287
    if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) {
nkeynes@736
   288
        close(fd);
nkeynes@736
   289
        return FALSE;
nkeynes@620
   290
    }
nkeynes@620
   291
    watch_dir_fd = fd;
nkeynes@620
   292
    g_timeout_add( 500, gtk_loop_check_input, NULL );
nkeynes@669
   293
    return TRUE;
nkeynes@620
   294
}
nkeynes@620
   295
nkeynes@620
   296
static void linux_joystick_uninstall_watch(void)
nkeynes@620
   297
{
nkeynes@620
   298
    signal( SIGRTMIN+1, SIG_IGN );
nkeynes@620
   299
    close( watch_dir_fd );
nkeynes@621
   300
    need_input_rescan = 2;
nkeynes@620
   301
}
.