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 621:225d147d2b43
prev620:d9b28f78b952
next637:6a284eff5311
author nkeynes
date Wed Jan 30 21:36:58 2008 +0000 (12 years ago)
permissions -rw-r--r--
last change Tidy up watch flag handling
view annotate diff log raw
     1 /**
     2  * $Id: joy_linux.c,v 1.12 2007-11-08 11:54:16 nkeynes Exp $
     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 #define _GNU_SOURCE
    20 #include <sys/types.h>
    21 #include <sys/ioctl.h>
    22 #include <errno.h>
    23 #include <stdio.h>
    24 #include <signal.h>
    25 #include <string.h>
    26 #include <fcntl.h>
    27 #include <dirent.h>
    28 #include <ctype.h>
    30 #include <linux/joystick.h>
    31 #include <glib/giochannel.h>
    32 #include <glib.h>
    34 #include "lxdream.h"
    35 #include "display.h"
    37 #define INPUT_PATH "/dev/input"
    39 typedef struct linux_joystick {
    40     struct input_driver driver;
    41     const gchar *filename;
    42     char name[128];
    43     int fd;
    44     int button_count, axis_count;
    45     GIOChannel *channel;
    47 } *linux_joystick_t;
    49 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
    50 					 gpointer data );
    51 static linux_joystick_t linux_joystick_add( const gchar *filename, int fd );
    52 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str );
    53 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode );
    54 static void linux_joystick_destroy( input_driver_t joy );
    55 static gboolean linux_joystick_install_watch( const gchar *dir );
    56 static void linux_joystick_uninstall_watch( void );
    58 /**
    59  * Convert keysym to keycode. Keysyms are either Button%d or Axis%d[+-], with buttons
    60  * numbered 1 .. button_count, then axes from button_count+1 .. button_count + axis_count*2.
    61  * The first button is Button1. (no Button0)
    62  * The first axis is Axis1+, then Axis1-, Axis2+ and so forth.
    63  */
    64 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str )
    65 {
    66     linux_joystick_t joy = (linux_joystick_t)dev;
    67     if( strncasecmp( str, "Button", 6 ) == 0 ){
    68 	unsigned long button = strtoul( str+6, NULL, 10 );
    69 	if( button > joy->button_count ) {
    70 	    return 0;
    71 	}
    72 	return (uint16_t)button;
    73     } else if( strncasecmp( str, "Axis", 4 ) == 0 ) {
    74 	char *endptr;
    75 	unsigned long axis = strtoul( str+4, &endptr, 10 );
    76 	if( axis > joy->axis_count || axis == 0 ) {
    77 	    return 0;
    78 	}
    79 	int keycode = ((axis - 1) << 1) + joy->button_count + 1;
    80 	if( *endptr == '-' ) {
    81 	    return keycode + 1;
    82 	} else {
    83 	    return keycode;
    84 	}
    85     } else {
    86 	return 0;
    87     }
    88 }
    90 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode )
    91 {
    92     linux_joystick_t joy = (linux_joystick_t)dev;
    93     if( keycode == 0 ) {
    94 	return NULL;
    95     }
    96     if( keycode <= joy->button_count ) {
    97 	return g_strdup_printf( "Button%d", keycode );
    98     }
    99     if( keycode <= joy->button_count + joy->axis_count*2 ) {
   100 	int axis = keycode - joy->button_count - 1;
   101 	if( (axis & 1) == 0 ) {
   102 	    return g_strdup_printf( "Axis%d+", (axis >> 1)+1 );
   103 	} else {
   104 	    return g_strdup_printf( "Axis%d-", (axis >> 1)+1 );
   105 	}
   106     }
   107     return NULL;
   108 }
   110 static void linux_joystick_destroy( input_driver_t dev )
   111 {
   112     linux_joystick_t joy = (linux_joystick_t)dev;
   113     g_free( (gchar *)joy->filename );
   114     g_io_channel_shutdown(joy->channel, FALSE, NULL );
   115     g_io_channel_unref(joy->channel);
   116     g_free( joy );
   117 }
   119 /**
   120  * Callback from the GIOChannel whenever data is available, or an error/disconnect
   121  * occurs.
   122  *
   123  * On data, process all pending events and direct them to the input system.
   124  * On error, close the channel and delete the device.
   125  */
   126 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
   127 					 gpointer data )
   128 {
   129     linux_joystick_t joy = (linux_joystick_t)data;
   131     if( condition & G_IO_HUP ) {
   132 	INFO( "Joystick '%s' disconnected\n", joy->name );
   133 	input_unregister_device((input_driver_t)joy);
   134 	return FALSE;
   135     }
   136     if( condition & G_IO_IN ) {
   137 	struct js_event event;
   138 	while( read( joy->fd, &event, sizeof(event) ) == sizeof(event) ) {
   139 	    if( event.type == JS_EVENT_BUTTON ) {
   140 		int keycode = event.number+1;
   141 		if( event.value == 0 ) {
   142 		    input_event_keyup( (input_driver_t)joy, keycode, 0 );
   143 		} else {
   144 		    input_event_keydown( (input_driver_t)joy, keycode, event.value );
   145 		}
   146 	    } else if( event.type == JS_EVENT_AXIS ) {
   147 		int keycode = (event.number*2) + joy->button_count + 1;
   148 		if( event.value == 0 ) {
   149 		    input_event_keyup( (input_driver_t)joy, keycode, 0 );
   150 		    input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
   151 		} else if( event.value < 0 ) {
   152 		    input_event_keydown( (input_driver_t)joy, keycode+1, -event.value );
   153 		    input_event_keyup( (input_driver_t)joy, keycode, 0 );
   154 		} else {
   155 		    input_event_keydown( (input_driver_t)joy, keycode, event.value );
   156 		    input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
   157 		}
   158 	    }
   159 	}
   160 	return TRUE;
   161     }
   162 }
   164 /**
   165  * Create a new joystick device structure given filename and (open) file
   166  * descriptor. The joystick is automatically added to the watch list.
   167  * @return The new joystick, or NULL if an error occurred.
   168  */
   169 linux_joystick_t linux_joystick_new( const gchar *filename, int fd )
   170 {
   171     linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick));
   172     joy->filename = filename;
   173     joy->fd = fd;
   174     joy->name[0] = '\0';
   175     joy->driver.resolve_keysym = linux_joystick_resolve_keysym;
   176     joy->driver.get_keysym_for_keycode = linux_joystick_keysym_for_keycode;
   177     joy->driver.destroy = linux_joystick_destroy;
   179     char *p = strrchr(filename, '/');
   180     if( p == NULL ) {
   181 	joy->driver.id = filename;
   182     } else {
   183 	joy->driver.id = p+1;
   184     }
   186     if( ioctl( fd, JSIOCGNAME(128), joy->name ) == -1 ||
   187 	ioctl( fd, JSIOCGAXES, &joy->axis_count ) == -1 || 
   188 	ioctl( fd, JSIOCGBUTTONS, &joy->button_count ) == -1 ) {
   189 	ERROR( "Error reading joystick data from %s (%s)\n", filename, strerror(errno) );
   190 	g_free(joy);
   191 	return NULL;
   192     }
   194     joy->channel = g_io_channel_unix_new(fd);
   195     g_io_add_watch( joy->channel, G_IO_IN|G_IO_ERR|G_IO_HUP, linux_joystick_callback, joy );
   196     return joy;
   197 }
   199 int linux_joystick_init()
   200 {
   201     linux_joystick_install_watch(INPUT_PATH);
   202     linux_joystick_scan();
   203 }
   205 int linux_joystick_scan()
   206 {
   207     int joysticks = 0;
   208     struct dirent *ent;
   209     DIR *dir = opendir(INPUT_PATH);
   211     if( dir == NULL ) {
   212 	return 0;
   213     }
   215     while( (ent = readdir(dir)) != NULL ) {
   216 	if( ent->d_name[0] == 'j' && ent->d_name[1] == 's' &&
   217 	    isdigit(ent->d_name[2]) && !input_has_device(ent->d_name) ) {
   218 	    gchar *name = g_strdup_printf( "%s/%s", INPUT_PATH, ent->d_name );
   219 	    int fd = open(name, O_RDONLY|O_NONBLOCK);
   220 	    if( fd == -1 ) {
   221 		g_free( name );
   222 	    } else {
   223 		linux_joystick_t joy = linux_joystick_new( name, fd );
   224 		input_register_device( (input_driver_t)joy, (joy->axis_count*2) + joy->button_count );
   225 		INFO( "Attached joystick %s named '%s', (%d buttons, %d axes)", 
   226 		      joy->driver.id, joy->name, joy->button_count, joy->axis_count );
   227 		joysticks++;
   228 	    }
   229 	}
   230     }
   232     closedir(dir);
   233     return joysticks;
   234 }
   236 void linux_joystick_shutdown(void)
   237 {
   238     linux_joystick_uninstall_watch();
   239 }
   241 /*************************** dnotify support **********************/
   243 static volatile int need_input_rescan = 0;
   244 static int watch_dir_fd;
   246 static gboolean gtk_loop_check_input(gpointer data)
   247 {
   248     if( need_input_rescan == 1 ) {
   249 	need_input_rescan = 0;
   250 	int js = linux_joystick_scan();
   251 	if( js > 0 ) {
   252 	    maple_reattach_all();
   253 	}
   254     }
   255     return need_input_rescan != 2;
   256 }
   258 static void dnotify_handler(int sig )
   259 {
   260     need_input_rescan = 1;
   261 }
   263 static gboolean linux_joystick_install_watch( const gchar *dir )
   264 {
   265     int fd = open( dir, O_RDONLY|O_NONBLOCK );
   266     if( fd == -1 ) {
   267 	return FALSE;
   268     }
   270     signal( SIGRTMIN+1, dnotify_handler );
   271     fcntl(fd, F_SETSIG, SIGRTMIN + 1);
   272     if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) {
   273 	close(fd);
   274 	return FALSE;
   275     }
   276     watch_dir_fd = fd;
   277     g_timeout_add( 500, gtk_loop_check_input, NULL );
   278 }
   280 static void linux_joystick_uninstall_watch(void)
   281 {
   282     signal( SIGRTMIN+1, SIG_IGN );
   283     close( watch_dir_fd );
   284     need_input_rescan = 2;
   285 }
.