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 984:a01567058a47
prev736:a02d1475ccfd
next1010:a506a2f66180
author nkeynes
date Sun Feb 15 21:47:39 2009 +0000 (13 years ago)
permissions -rw-r--r--
last change Compile in C99 standards mode, albeit with -D_GNU_SOURCE. Helps portability slightly as well
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 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
    56                                          gpointer data );
    57 static int linux_joystick_scan();
    58 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd );
    59 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str );
    60 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode );
    61 static void linux_joystick_destroy( input_driver_t joy );
    62 static gboolean linux_joystick_install_watch( const gchar *dir );
    63 static void linux_joystick_uninstall_watch( void );
    65 /**
    66  * Convert keysym to keycode. Keysyms are either Button%d or Axis%d[+-], with buttons
    67  * numbered 1 .. button_count, then axes from button_count+1 .. button_count + axis_count*2.
    68  * The first button is Button1. (no Button0)
    69  * The first axis is Axis1+, then Axis1-, Axis2+ and so forth.
    70  */
    71 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str )
    72 {
    73     linux_joystick_t joy = (linux_joystick_t)dev;
    74     if( strncasecmp( str, "Button", 6 ) == 0 ){
    75         unsigned long button = strtoul( str+6, NULL, 10 );
    76         if( button > joy->button_count ) {
    77             return 0;
    78         }
    79         return (uint16_t)button;
    80     } else if( strncasecmp( str, "Axis", 4 ) == 0 ) {
    81         char *endptr;
    82         unsigned long axis = strtoul( str+4, &endptr, 10 );
    83         if( axis > joy->axis_count || axis == 0 ) {
    84             return 0;
    85         }
    86         int keycode = ((axis - 1) << 1) + joy->button_count + 1;
    87         if( *endptr == '-' ) {
    88             return keycode + 1;
    89         } else {
    90             return keycode;
    91         }
    92     } else {
    93         return 0;
    94     }
    95 }
    97 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode )
    98 {
    99     linux_joystick_t joy = (linux_joystick_t)dev;
   100     if( keycode == 0 ) {
   101         return NULL;
   102     }
   103     if( keycode <= joy->button_count ) {
   104         return g_strdup_printf( "Button%d", keycode );
   105     }
   106     if( keycode <= joy->button_count + joy->axis_count*2 ) {
   107         int axis = keycode - joy->button_count - 1;
   108         if( (axis & 1) == 0 ) {
   109             return g_strdup_printf( "Axis%d+", (axis >> 1)+1 );
   110         } else {
   111             return g_strdup_printf( "Axis%d-", (axis >> 1)+1 );
   112         }
   113     }
   114     return NULL;
   115 }
   117 static void linux_joystick_destroy( input_driver_t dev )
   118 {
   119     linux_joystick_t joy = (linux_joystick_t)dev;
   120     g_free( (gchar *)joy->filename );
   121     g_io_channel_shutdown(joy->channel, FALSE, NULL );
   122     g_io_channel_unref(joy->channel);
   123     g_free( joy );
   124 }
   126 /**
   127  * Callback from the GIOChannel whenever data is available, or an error/disconnect
   128  * occurs.
   129  *
   130  * On data, process all pending events and direct them to the input system.
   131  * On error, close the channel and delete the device.
   132  */
   133 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
   134                                          gpointer data )
   135 {
   136     linux_joystick_t joy = (linux_joystick_t)data;
   138     if( condition & G_IO_HUP ) {
   139         INFO( "Joystick '%s' disconnected\n", joy->name );
   140         input_unregister_device((input_driver_t)joy);
   141         return FALSE;
   142     }
   143     if( condition & G_IO_IN ) {
   144         struct js_event event;
   145         while( read( joy->fd, &event, sizeof(event) ) == sizeof(event) ) {
   146             if( event.type == JS_EVENT_BUTTON ) {
   147                 int keycode = event.number+1;
   148                 if( event.value == 0 ) {
   149                     input_event_keyup( (input_driver_t)joy, keycode, 0 );
   150                 } else {
   151                     input_event_keydown( (input_driver_t)joy, keycode, event.value );
   152                 }
   153             } else if( event.type == JS_EVENT_AXIS ) {
   154                 int keycode = (event.number*2) + joy->button_count + 1;
   155                 if( event.value == 0 ) {
   156                     input_event_keyup( (input_driver_t)joy, keycode, 0 );
   157                     input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
   158                 } else if( event.value < 0 ) {
   159                     input_event_keydown( (input_driver_t)joy, keycode+1, -event.value );
   160                     input_event_keyup( (input_driver_t)joy, keycode, 0 );
   161                 } else {
   162                     input_event_keydown( (input_driver_t)joy, keycode, event.value );
   163                     input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
   164                 }
   165             }
   166         }
   167     }
   168     return TRUE;
   169 }
   171 /**
   172  * Create a new joystick device structure given filename and (open) file
   173  * descriptor. The joystick is automatically added to the watch list.
   174  * @return The new joystick, or NULL if an error occurred.
   175  */
   176 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd )
   177 {
   178     linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick));
   179     joy->filename = filename;
   180     joy->fd = fd;
   181     joy->name[0] = '\0';
   182     joy->driver.resolve_keysym = linux_joystick_resolve_keysym;
   183     joy->driver.get_keysym_for_keycode = linux_joystick_keysym_for_keycode;
   184     joy->driver.destroy = linux_joystick_destroy;
   186     char *p = strrchr(filename, '/');
   187     if( p == NULL ) {
   188         joy->driver.id = filename;
   189     } else {
   190         joy->driver.id = p+1;
   191     }
   193     if( ioctl( fd, JSIOCGNAME(128), joy->name ) == -1 ||
   194             ioctl( fd, JSIOCGAXES, &joy->axis_count ) == -1 || 
   195             ioctl( fd, JSIOCGBUTTONS, &joy->button_count ) == -1 ) {
   196         ERROR( "Error reading joystick data from %s (%s)\n", filename, strerror(errno) );
   197         g_free(joy);
   198         return NULL;
   199     }
   201     joy->channel = g_io_channel_unix_new(fd);
   202     g_io_add_watch( joy->channel, G_IO_IN|G_IO_ERR|G_IO_HUP, linux_joystick_callback, joy );
   203     return joy;
   204 }
   206 static int linux_joystick_scan()
   207 {
   208     int joysticks = 0;
   209     struct dirent *ent;
   210     DIR *dir = opendir(INPUT_PATH);
   212     if( dir == NULL ) {
   213         return 0;
   214     }
   216     while( (ent = readdir(dir)) != NULL ) {
   217         if( ent->d_name[0] == 'j' && ent->d_name[1] == 's' &&
   218                 isdigit(ent->d_name[2]) && !input_has_device(ent->d_name) ) {
   219             gchar *name = g_strdup_printf( "%s/%s", INPUT_PATH, ent->d_name );
   220             int fd = open(name, O_RDONLY|O_NONBLOCK);
   221             if( fd == -1 ) {
   222                 g_free( name );
   223             } else {
   224                 linux_joystick_t joy = linux_joystick_new( name, fd );
   225                 input_register_device( (input_driver_t)joy, (joy->axis_count*2) + joy->button_count );
   226                 INFO( "Attached joystick %s named '%s', (%d buttons, %d axes)", 
   227                       joy->driver.id, joy->name, joy->button_count, joy->axis_count );
   228                 joysticks++;
   229             }
   230         }
   231     }
   233     closedir(dir);
   234     return joysticks;
   235 }
   237 gboolean linux_joystick_init()
   238 {
   239     if( !linux_joystick_install_watch(INPUT_PATH) ) {
   240         return FALSE;
   241     }
   242     linux_joystick_scan();
   243     return TRUE;
   244 }
   246 void linux_joystick_shutdown(void)
   247 {
   248     linux_joystick_uninstall_watch();
   249 }
   251 /*************************** dnotify support **********************/
   253 static volatile int need_input_rescan = 0;
   254 static int watch_dir_fd;
   256 static gboolean gtk_loop_check_input(gpointer data)
   257 {
   258     if( need_input_rescan == 1 ) {
   259         need_input_rescan = 0;
   260         int js = linux_joystick_scan();
   261         if( js > 0 ) {
   262             maple_reattach_all();
   263         }
   264     }
   265     return need_input_rescan != 2;
   266 }
   268 static void dnotify_handler(int sig )
   269 {
   270     need_input_rescan = 1;
   271 }
   273 static gboolean linux_joystick_install_watch( const gchar *dir )
   274 {
   275     int fd = open( dir, O_RDONLY|O_NONBLOCK );
   276     if( fd == -1 ) {
   277         return FALSE;
   278     }
   280     signal( SIGRTMIN+1, dnotify_handler );
   281     fcntl(fd, F_SETSIG, SIGRTMIN + 1);
   282     if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) {
   283         close(fd);
   284         return FALSE;
   285     }
   286     watch_dir_fd = fd;
   287     g_timeout_add( 500, gtk_loop_check_input, NULL );
   288     return TRUE;
   289 }
   291 static void linux_joystick_uninstall_watch(void)
   292 {
   293     signal( SIGRTMIN+1, SIG_IGN );
   294     close( watch_dir_fd );
   295     need_input_rescan = 2;
   296 }
.