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 669:ab344e42bca9
prev637:6a284eff5311
next736:a02d1475ccfd
author nkeynes
date Mon May 12 10:00:13 2008 +0000 (15 years ago)
permissions -rw-r--r--
last change Cleanup most of the -Wall warnings (getting a bit sloppy...)
Convert FP code to use fixed banks rather than indirect pointer
(3-4% faster this way now)
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 #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 <stdlib.h>
    27 #include <unistd.h>
    28 #include <fcntl.h>
    29 #include <dirent.h>
    30 #include <ctype.h>
    32 #include <linux/joystick.h>
    33 #include <glib/giochannel.h>
    34 #include <glib.h>
    36 #include "lxdream.h"
    37 #include "display.h"
    38 #include "maple/maple.h"
    39 #include "drivers/joy_linux.h"
    41 #define INPUT_PATH "/dev/input"
    43 typedef struct linux_joystick {
    44     struct input_driver driver;
    45     const gchar *filename;
    46     char name[128];
    47     int fd;
    48     int button_count, axis_count;
    49     GIOChannel *channel;
    51 } *linux_joystick_t;
    53 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
    54 					 gpointer data );
    55 static int linux_joystick_scan();
    56 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd );
    57 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str );
    58 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode );
    59 static void linux_joystick_destroy( input_driver_t joy );
    60 static gboolean linux_joystick_install_watch( const gchar *dir );
    61 static void linux_joystick_uninstall_watch( void );
    63 /**
    64  * Convert keysym to keycode. Keysyms are either Button%d or Axis%d[+-], with buttons
    65  * numbered 1 .. button_count, then axes from button_count+1 .. button_count + axis_count*2.
    66  * The first button is Button1. (no Button0)
    67  * The first axis is Axis1+, then Axis1-, Axis2+ and so forth.
    68  */
    69 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str )
    70 {
    71     linux_joystick_t joy = (linux_joystick_t)dev;
    72     if( strncasecmp( str, "Button", 6 ) == 0 ){
    73 	unsigned long button = strtoul( str+6, NULL, 10 );
    74 	if( button > joy->button_count ) {
    75 	    return 0;
    76 	}
    77 	return (uint16_t)button;
    78     } else if( strncasecmp( str, "Axis", 4 ) == 0 ) {
    79 	char *endptr;
    80 	unsigned long axis = strtoul( str+4, &endptr, 10 );
    81 	if( axis > joy->axis_count || axis == 0 ) {
    82 	    return 0;
    83 	}
    84 	int keycode = ((axis - 1) << 1) + joy->button_count + 1;
    85 	if( *endptr == '-' ) {
    86 	    return keycode + 1;
    87 	} else {
    88 	    return keycode;
    89 	}
    90     } else {
    91 	return 0;
    92     }
    93 }
    95 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode )
    96 {
    97     linux_joystick_t joy = (linux_joystick_t)dev;
    98     if( keycode == 0 ) {
    99 	return NULL;
   100     }
   101     if( keycode <= joy->button_count ) {
   102 	return g_strdup_printf( "Button%d", keycode );
   103     }
   104     if( keycode <= joy->button_count + joy->axis_count*2 ) {
   105 	int axis = keycode - joy->button_count - 1;
   106 	if( (axis & 1) == 0 ) {
   107 	    return g_strdup_printf( "Axis%d+", (axis >> 1)+1 );
   108 	} else {
   109 	    return g_strdup_printf( "Axis%d-", (axis >> 1)+1 );
   110 	}
   111     }
   112     return NULL;
   113 }
   115 static void linux_joystick_destroy( input_driver_t dev )
   116 {
   117     linux_joystick_t joy = (linux_joystick_t)dev;
   118     g_free( (gchar *)joy->filename );
   119     g_io_channel_shutdown(joy->channel, FALSE, NULL );
   120     g_io_channel_unref(joy->channel);
   121     g_free( joy );
   122 }
   124 /**
   125  * Callback from the GIOChannel whenever data is available, or an error/disconnect
   126  * occurs.
   127  *
   128  * On data, process all pending events and direct them to the input system.
   129  * On error, close the channel and delete the device.
   130  */
   131 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, 
   132 					 gpointer data )
   133 {
   134     linux_joystick_t joy = (linux_joystick_t)data;
   136     if( condition & G_IO_HUP ) {
   137 	INFO( "Joystick '%s' disconnected\n", joy->name );
   138 	input_unregister_device((input_driver_t)joy);
   139 	return FALSE;
   140     }
   141     if( condition & G_IO_IN ) {
   142 	struct js_event event;
   143 	while( read( joy->fd, &event, sizeof(event) ) == sizeof(event) ) {
   144 	    if( event.type == JS_EVENT_BUTTON ) {
   145 		int keycode = event.number+1;
   146 		if( event.value == 0 ) {
   147 		    input_event_keyup( (input_driver_t)joy, keycode, 0 );
   148 		} else {
   149 		    input_event_keydown( (input_driver_t)joy, keycode, event.value );
   150 		}
   151 	    } else if( event.type == JS_EVENT_AXIS ) {
   152 		int keycode = (event.number*2) + joy->button_count + 1;
   153 		if( event.value == 0 ) {
   154 		    input_event_keyup( (input_driver_t)joy, keycode, 0 );
   155 		    input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
   156 		} else if( event.value < 0 ) {
   157 		    input_event_keydown( (input_driver_t)joy, keycode+1, -event.value );
   158 		    input_event_keyup( (input_driver_t)joy, keycode, 0 );
   159 		} else {
   160 		    input_event_keydown( (input_driver_t)joy, keycode, event.value );
   161 		    input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
   162 		}
   163 	    }
   164 	}
   165     }
   166     return TRUE;
   167 }
   169 /**
   170  * Create a new joystick device structure given filename and (open) file
   171  * descriptor. The joystick is automatically added to the watch list.
   172  * @return The new joystick, or NULL if an error occurred.
   173  */
   174 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd )
   175 {
   176     linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick));
   177     joy->filename = filename;
   178     joy->fd = fd;
   179     joy->name[0] = '\0';
   180     joy->driver.resolve_keysym = linux_joystick_resolve_keysym;
   181     joy->driver.get_keysym_for_keycode = linux_joystick_keysym_for_keycode;
   182     joy->driver.destroy = linux_joystick_destroy;
   184     char *p = strrchr(filename, '/');
   185     if( p == NULL ) {
   186 	joy->driver.id = filename;
   187     } else {
   188 	joy->driver.id = p+1;
   189     }
   191     if( ioctl( fd, JSIOCGNAME(128), joy->name ) == -1 ||
   192 	ioctl( fd, JSIOCGAXES, &joy->axis_count ) == -1 || 
   193 	ioctl( fd, JSIOCGBUTTONS, &joy->button_count ) == -1 ) {
   194 	ERROR( "Error reading joystick data from %s (%s)\n", filename, strerror(errno) );
   195 	g_free(joy);
   196 	return NULL;
   197     }
   199     joy->channel = g_io_channel_unix_new(fd);
   200     g_io_add_watch( joy->channel, G_IO_IN|G_IO_ERR|G_IO_HUP, linux_joystick_callback, joy );
   201     return joy;
   202 }
   204 static int linux_joystick_scan()
   205 {
   206     int joysticks = 0;
   207     struct dirent *ent;
   208     DIR *dir = opendir(INPUT_PATH);
   210     if( dir == NULL ) {
   211 	return 0;
   212     }
   214     while( (ent = readdir(dir)) != NULL ) {
   215 	if( ent->d_name[0] == 'j' && ent->d_name[1] == 's' &&
   216 	    isdigit(ent->d_name[2]) && !input_has_device(ent->d_name) ) {
   217 	    gchar *name = g_strdup_printf( "%s/%s", INPUT_PATH, ent->d_name );
   218 	    int fd = open(name, O_RDONLY|O_NONBLOCK);
   219 	    if( fd == -1 ) {
   220 		g_free( name );
   221 	    } else {
   222 		linux_joystick_t joy = linux_joystick_new( name, fd );
   223 		input_register_device( (input_driver_t)joy, (joy->axis_count*2) + joy->button_count );
   224 		INFO( "Attached joystick %s named '%s', (%d buttons, %d axes)", 
   225 		      joy->driver.id, joy->name, joy->button_count, joy->axis_count );
   226 		joysticks++;
   227 	    }
   228 	}
   229     }
   231     closedir(dir);
   232     return joysticks;
   233 }
   235 gboolean linux_joystick_init()
   236 {
   237     if( !linux_joystick_install_watch(INPUT_PATH) ) {
   238 	return FALSE;
   239     }
   240     linux_joystick_scan();
   241     return TRUE;
   242 }
   244 void linux_joystick_shutdown(void)
   245 {
   246     linux_joystick_uninstall_watch();
   247 }
   249 /*************************** dnotify support **********************/
   251 static volatile int need_input_rescan = 0;
   252 static int watch_dir_fd;
   254 static gboolean gtk_loop_check_input(gpointer data)
   255 {
   256     if( need_input_rescan == 1 ) {
   257 	need_input_rescan = 0;
   258 	int js = linux_joystick_scan();
   259 	if( js > 0 ) {
   260 	    maple_reattach_all();
   261 	}
   262     }
   263     return need_input_rescan != 2;
   264 }
   266 static void dnotify_handler(int sig )
   267 {
   268     need_input_rescan = 1;
   269 }
   271 static gboolean linux_joystick_install_watch( const gchar *dir )
   272 {
   273     int fd = open( dir, O_RDONLY|O_NONBLOCK );
   274     if( fd == -1 ) {
   275 	return FALSE;
   276     }
   278     signal( SIGRTMIN+1, dnotify_handler );
   279     fcntl(fd, F_SETSIG, SIGRTMIN + 1);
   280     if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) {
   281 	close(fd);
   282 	return FALSE;
   283     }
   284     watch_dir_fd = fd;
   285     g_timeout_add( 500, gtk_loop_check_input, NULL );
   286     return TRUE;
   287 }
   289 static void linux_joystick_uninstall_watch(void)
   290 {
   291     signal( SIGRTMIN+1, SIG_IGN );
   292     close( watch_dir_fd );
   293     need_input_rescan = 2;
   294 }
.