4 * Linux joystick input device support
6 * Copyright (c) 2008 Nathan Keynes.
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.
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.
22 #include <sys/types.h>
23 #include <sys/ioctl.h>
34 #include <linux/joystick.h>
35 #include <glib/giochannel.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;
50 int button_count, axis_count;
55 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition,
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 );
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.
71 static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str )
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 ) {
79 return (uint16_t)button;
80 } else if( strncasecmp( str, "Axis", 4 ) == 0 ) {
82 unsigned long axis = strtoul( str+4, &endptr, 10 );
83 if( axis > joy->axis_count || axis == 0 ) {
86 int keycode = ((axis - 1) << 1) + joy->button_count + 1;
87 if( *endptr == '-' ) {
97 static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode )
99 linux_joystick_t joy = (linux_joystick_t)dev;
103 if( keycode <= joy->button_count ) {
104 return g_strdup_printf( "Button%d", keycode );
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 );
111 return g_strdup_printf( "Axis%d-", (axis >> 1)+1 );
117 static void linux_joystick_destroy( input_driver_t dev )
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);
127 * Callback from the GIOChannel whenever data is available, or an error/disconnect
130 * On data, process all pending events and direct them to the input system.
131 * On error, close the channel and delete the device.
133 static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition,
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);
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 );
151 input_event_keydown( (input_driver_t)joy, keycode, event.value );
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 );
162 input_event_keydown( (input_driver_t)joy, keycode, event.value );
163 input_event_keyup( (input_driver_t)joy, keycode+1, 0 );
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.
176 static linux_joystick_t linux_joystick_new( const gchar *filename, int fd )
178 linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick));
179 joy->filename = filename;
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, '/');
188 joy->driver.id = filename;
190 joy->driver.id = p+1;
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) );
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 );
206 static int linux_joystick_scan()
210 DIR *dir = opendir(INPUT_PATH);
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);
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 );
237 gboolean linux_joystick_init()
239 if( !linux_joystick_install_watch(INPUT_PATH) ) {
242 linux_joystick_scan();
246 void linux_joystick_shutdown(void)
248 linux_joystick_uninstall_watch();
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)
258 if( need_input_rescan == 1 ) {
259 need_input_rescan = 0;
260 int js = linux_joystick_scan();
262 maple_reattach_all();
265 return need_input_rescan != 2;
268 static void dnotify_handler(int sig )
270 need_input_rescan = 1;
273 static gboolean linux_joystick_install_watch( const gchar *dir )
275 int fd = open( dir, O_RDONLY|O_NONBLOCK );
280 signal( SIGRTMIN+1, dnotify_handler );
281 fcntl(fd, F_SETSIG, SIGRTMIN + 1);
282 if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) {
287 g_timeout_add( 500, gtk_loop_check_input, NULL );
291 static void linux_joystick_uninstall_watch(void)
293 signal( SIGRTMIN+1, SIG_IGN );
294 close( watch_dir_fd );
295 need_input_rescan = 2;
.