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 1296:30ecee61f811
prev1010:a506a2f66180
author nkeynes
date Wed May 27 09:42:24 2015 +1000 (8 years ago)
permissions -rw-r--r--
last change Fix stack alignment when calling the end-block callback (broken on OS X)
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.h>
    37 #include "lxdream.h"
    38 #include "display.h"
    39 #include "maple/maple.h"
    40 #include "drivers/joy_linux.h"
    42 #define INPUT_PATH "/dev/input"
    44 typedef struct linux_joystick {
    45     struct input_driver driver;
    46     const gchar *filename;
    47     char name[128];
    48     int fd;
    49     int button_count, axis_count;
    50     GIOChannel *channel;
    52 } *linux_joystick_t;
    54 /* Linux joysticks return data in the range -32767 to 32767 - rescale this to 
    55  * -127 .. 127
    56  */
    57 #define SCALE_PRESSURE(x) ((x)>>8)
    59 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
    60                                          gpointer data );
    61 static int linux_joystick_scan();
    62 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd );
    63 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str );
    64 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode );
    65 static void linux_joystick_destroy( input_driver_t joy );
    66 static gboolean linux_joystick_install_watch( const gchar *dir );
    67 static void linux_joystick_uninstall_watch( void );
    69 /**
    70  * Convert keysym to keycode. Keysyms are either Button%d or Axis%d[+-], with buttons
    71  * numbered 1 .. button_count, then axes from button_count+1 .. button_count + axis_count*2.
    72  * The first button is Button1. (no Button0)
    73  * The first axis is Axis1+, then Axis1-, Axis2+ and so forth.
    74  */
    75 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str )
    76 {
    77     linux_joystick_t joy = (linux_joystick_t)dev;
    78     if( strncasecmp( str, "Button", 6 ) == 0 ){
    79         unsigned long button = strtoul( str+6, NULL, 10 );
    80         if( button > joy->button_count ) {
    81             return 0;
    82         }
    83         return (uint16_t)button;
    84     } else if( strncasecmp( str, "Axis", 4 ) == 0 ) {
    85         char *endptr;
    86         unsigned long axis = strtoul( str+4, &endptr, 10 );
    87         if( axis > joy->axis_count || axis == 0 ) {
    88             return 0;
    89         }
    90         int keycode = ((axis - 1) << 1) + joy->button_count + 1;
    91         if( *endptr == '-' ) {
    92             return keycode + 1;
    93         } else {
    94             return keycode;
    95         }
    96     } else {
    97         return 0;
    98     }
    99 }
   101 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode )
   102 {
   103     linux_joystick_t joy = (linux_joystick_t)dev;
   104     if( keycode == 0 ) {
   105         return NULL;
   106     }
   107     if( keycode <= joy->button_count ) {
   108         return g_strdup_printf( "Button%d", keycode );
   109     }
   110     if( keycode <= joy->button_count + joy->axis_count*2 ) {
   111         int axis = keycode - joy->button_count - 1;
   112         if( (axis & 1) == 0 ) {
   113             return g_strdup_printf( "Axis%d+", (axis >> 1)+1 );
   114         } else {
   115             return g_strdup_printf( "Axis%d-", (axis >> 1)+1 );
   116         }
   117     }
   118     return NULL;
   119 }
   121 static void linux_joystick_destroy( input_driver_t dev )
   122 {
   123     linux_joystick_t joy = (linux_joystick_t)dev;
   124     g_free( (gchar *)joy->filename );
   125     g_io_channel_shutdown(joy->channel, FALSE, NULL );
   126     g_io_channel_unref(joy->channel);
   127     g_free( joy );
   128 }
   130 /**
   131  * Callback from the GIOChannel whenever data is available, or an error/disconnect
   132  * occurs.
   133  *
   134  * On data, process all pending events and direct them to the input system.
   135  * On error, close the channel and delete the device.
   136  */
   137 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
   138                                          gpointer data )
   139 {
   140     linux_joystick_t joy = (linux_joystick_t)data;
   142     if( condition & G_IO_HUP ) {
   143         INFO( "Joystick '%s' disconnected\n", joy->name );
   144         input_unregister_device((input_driver_t)joy);
   145         return FALSE;
   146     }
   147     if( condition & G_IO_IN ) {
   148         struct js_event event;
   149         while( read( joy->fd, &event, sizeof(event) ) == sizeof(event) ) {
   150             if( event.type == JS_EVENT_BUTTON ) {
   151                 int keycode = event.number+1;
   152                 if( event.value == 0 ) {
   153                     input_event_keyup( (input_driver_t)joy, keycode );
   154                 } else {
   155                     input_event_keydown( (input_driver_t)joy, keycode, MAX_PRESSURE );
   156                 }
   157             } else if( event.type == JS_EVENT_AXIS ) {
   158                 int keycode = (event.number*2) + joy->button_count + 1;
   159                 if( event.value == 0 ) {
   160                     input_event_keyup( (input_driver_t)joy, keycode );
   161                     input_event_keyup( (input_driver_t)joy, keycode+1 );
   162                 } else if( event.value < 0 ) {
   163                     input_event_keyup( (input_driver_t)joy, keycode );
   164                     input_event_keydown( (input_driver_t)joy, keycode+1, SCALE_PRESSURE(-event.value) );
   165                 } else {
   166                     input_event_keyup( (input_driver_t)joy, keycode+1 );
   167                     input_event_keydown( (input_driver_t)joy, keycode, SCALE_PRESSURE(event.value) );
   168                 }
   169             }
   170         }
   171     }
   172     return TRUE;
   173 }
   175 /**
   176  * Create a new joystick device structure given filename and (open) file
   177  * descriptor. The joystick is automatically added to the watch list.
   178  * @return The new joystick, or NULL if an error occurred.
   179  */
   180 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd )
   181 {
   182     linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick));
   183     joy->filename = filename;
   184     joy->fd = fd;
   185     joy->name[0] = '\0';
   186     joy->driver.resolve_keysym = linux_joystick_resolve_keysym;
   187     joy->driver.get_keysym_for_keycode = linux_joystick_keysym_for_keycode;
   188     joy->driver.destroy = linux_joystick_destroy;
   190     char *p = strrchr(filename, '/');
   191     if( p == NULL ) {
   192         joy->driver.id = filename;
   193     } else {
   194         joy->driver.id = p+1;
   195     }
   197     if( ioctl( fd, JSIOCGNAME(128), joy->name ) == -1 ||
   198             ioctl( fd, JSIOCGAXES, &joy->axis_count ) == -1 || 
   199             ioctl( fd, JSIOCGBUTTONS, &joy->button_count ) == -1 ) {
   200         ERROR( "Error reading joystick data from %s (%s)\n", filename, strerror(errno) );
   201         g_free(joy);
   202         return NULL;
   203     }
   205     joy->channel = g_io_channel_unix_new(fd);
   206     g_io_add_watch( joy->channel, G_IO_IN|G_IO_ERR|G_IO_HUP, linux_joystick_callback, joy );
   207     return joy;
   208 }
   210 static int linux_joystick_scan()
   211 {
   212     int joysticks = 0;
   213     struct dirent *ent;
   214     DIR *dir = opendir(INPUT_PATH);
   216     if( dir == NULL ) {
   217         return 0;
   218     }
   220     while( (ent = readdir(dir)) != NULL ) {
   221         if( ent->d_name[0] == 'j' && ent->d_name[1] == 's' &&
   222                 isdigit(ent->d_name[2]) && !input_has_device(ent->d_name) ) {
   223             gchar *name = g_strdup_printf( "%s/%s", INPUT_PATH, ent->d_name );
   224             int fd = open(name, O_RDONLY|O_NONBLOCK);
   225             if( fd == -1 ) {
   226                 g_free( name );
   227             } else {
   228                 linux_joystick_t joy = linux_joystick_new( name, fd );
   229                 input_register_device( (input_driver_t)joy, (joy->axis_count*2) + joy->button_count );
   230                 INFO( "Attached joystick %s named '%s', (%d buttons, %d axes)", 
   231                       joy->driver.id, joy->name, joy->button_count, joy->axis_count );
   232                 joysticks++;
   233             }
   234         }
   235     }
   237     closedir(dir);
   238     return joysticks;
   239 }
   241 gboolean linux_joystick_init()
   242 {
   243     if( !linux_joystick_install_watch(INPUT_PATH) ) {
   244         return FALSE;
   245     }
   246     linux_joystick_scan();
   247     return TRUE;
   248 }
   250 void linux_joystick_shutdown(void)
   251 {
   252     linux_joystick_uninstall_watch();
   253 }
   255 /*************************** dnotify support **********************/
   257 static volatile int need_input_rescan = 0;
   258 static int watch_dir_fd;
   260 static gboolean gtk_loop_check_input(gpointer data)
   261 {
   262     if( need_input_rescan == 1 ) {
   263         need_input_rescan = 0;
   264         int js = linux_joystick_scan();
   265         if( js > 0 ) {
   266             maple_reattach_all();
   267         }
   268     }
   269     return need_input_rescan != 2;
   270 }
   272 static void dnotify_handler(int sig )
   273 {
   274     need_input_rescan = 1;
   275 }
   277 static gboolean linux_joystick_install_watch( const gchar *dir )
   278 {
   279     int fd = open( dir, O_RDONLY|O_NONBLOCK );
   280     if( fd == -1 ) {
   281         return FALSE;
   282     }
   284     signal( SIGRTMIN+1, dnotify_handler );
   285     fcntl(fd, F_SETSIG, SIGRTMIN + 1);
   286     if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) {
   287         close(fd);
   288         return FALSE;
   289     }
   290     watch_dir_fd = fd;
   291     g_timeout_add( 500, gtk_loop_check_input, NULL );
   292     return TRUE;
   293 }
   295 static void linux_joystick_uninstall_watch(void)
   296 {
   297     signal( SIGRTMIN+1, SIG_IGN );
   298     close( watch_dir_fd );
   299     need_input_rescan = 2;
   300 }
.