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