nkeynes@614: /** nkeynes@614: * $Id: joy_linux.c,v 1.12 2007-11-08 11:54:16 nkeynes Exp $ nkeynes@614: * nkeynes@614: * Linux joystick input device support nkeynes@614: * nkeynes@614: * Copyright (c) 2008 Nathan Keynes. nkeynes@614: * nkeynes@614: * This program is free software; you can redistribute it and/or modify nkeynes@614: * it under the terms of the GNU General Public License as published by nkeynes@614: * the Free Software Foundation; either version 2 of the License, or nkeynes@614: * (at your option) any later version. nkeynes@614: * nkeynes@614: * This program is distributed in the hope that it will be useful, nkeynes@614: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@614: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@614: * GNU General Public License for more details. nkeynes@614: */ nkeynes@614: nkeynes@620: #define _GNU_SOURCE nkeynes@614: #include nkeynes@614: #include nkeynes@614: #include nkeynes@614: #include nkeynes@620: #include nkeynes@614: #include nkeynes@614: #include nkeynes@614: #include nkeynes@614: #include nkeynes@614: nkeynes@614: #include nkeynes@614: #include nkeynes@614: #include nkeynes@614: nkeynes@614: #include "lxdream.h" nkeynes@614: #include "display.h" nkeynes@614: nkeynes@614: #define INPUT_PATH "/dev/input" nkeynes@614: nkeynes@614: typedef struct linux_joystick { nkeynes@614: struct input_driver driver; nkeynes@614: const gchar *filename; nkeynes@614: char name[128]; nkeynes@614: int fd; nkeynes@614: int button_count, axis_count; nkeynes@614: GIOChannel *channel; nkeynes@614: nkeynes@614: } *linux_joystick_t; nkeynes@614: nkeynes@614: static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, nkeynes@614: gpointer data ); nkeynes@614: static linux_joystick_t linux_joystick_add( const gchar *filename, int fd ); nkeynes@614: static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str ); nkeynes@614: static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode ); nkeynes@614: static void linux_joystick_destroy( input_driver_t joy ); nkeynes@620: static gboolean linux_joystick_install_watch( const gchar *dir ); nkeynes@620: static void linux_joystick_uninstall_watch( void ); nkeynes@614: nkeynes@614: /** nkeynes@615: * Convert keysym to keycode. Keysyms are either Button%d or Axis%d[+-], with buttons nkeynes@615: * numbered 1 .. button_count, then axes from button_count+1 .. button_count + axis_count*2. nkeynes@614: * The first button is Button1. (no Button0) nkeynes@615: * The first axis is Axis1+, then Axis1-, Axis2+ and so forth. nkeynes@614: */ nkeynes@614: static uint16_t linux_joystick_resolve_keysym( input_driver_t dev, const gchar *str ) nkeynes@614: { nkeynes@614: linux_joystick_t joy = (linux_joystick_t)dev; nkeynes@614: if( strncasecmp( str, "Button", 6 ) == 0 ){ nkeynes@614: unsigned long button = strtoul( str+6, NULL, 10 ); nkeynes@614: if( button > joy->button_count ) { nkeynes@614: return 0; nkeynes@614: } nkeynes@614: return (uint16_t)button; nkeynes@614: } else if( strncasecmp( str, "Axis", 4 ) == 0 ) { nkeynes@615: char *endptr; nkeynes@615: unsigned long axis = strtoul( str+4, &endptr, 10 ); nkeynes@614: if( axis > joy->axis_count || axis == 0 ) { nkeynes@614: return 0; nkeynes@614: } nkeynes@615: int keycode = ((axis - 1) << 1) + joy->button_count + 1; nkeynes@615: if( *endptr == '-' ) { nkeynes@615: return keycode + 1; nkeynes@615: } else { nkeynes@615: return keycode; nkeynes@615: } nkeynes@614: } else { nkeynes@614: return 0; nkeynes@614: } nkeynes@614: } nkeynes@614: nkeynes@614: static gchar *linux_joystick_keysym_for_keycode( input_driver_t dev, uint16_t keycode ) nkeynes@614: { nkeynes@614: linux_joystick_t joy = (linux_joystick_t)dev; nkeynes@614: if( keycode == 0 ) { nkeynes@614: return NULL; nkeynes@614: } nkeynes@614: if( keycode <= joy->button_count ) { nkeynes@614: return g_strdup_printf( "Button%d", keycode ); nkeynes@614: } nkeynes@615: if( keycode <= joy->button_count + joy->axis_count*2 ) { nkeynes@615: int axis = keycode - joy->button_count - 1; nkeynes@615: if( (axis & 1) == 0 ) { nkeynes@615: return g_strdup_printf( "Axis%d+", (axis >> 1)+1 ); nkeynes@615: } else { nkeynes@615: return g_strdup_printf( "Axis%d-", (axis >> 1)+1 ); nkeynes@615: } nkeynes@614: } nkeynes@614: return NULL; nkeynes@614: } nkeynes@614: nkeynes@614: static void linux_joystick_destroy( input_driver_t dev ) nkeynes@614: { nkeynes@614: linux_joystick_t joy = (linux_joystick_t)dev; nkeynes@614: g_free( (gchar *)joy->filename ); nkeynes@614: g_io_channel_shutdown(joy->channel, FALSE, NULL ); nkeynes@614: g_io_channel_unref(joy->channel); nkeynes@614: g_free( joy ); nkeynes@614: } nkeynes@614: nkeynes@614: /** nkeynes@614: * Callback from the GIOChannel whenever data is available, or an error/disconnect nkeynes@614: * occurs. nkeynes@614: * nkeynes@614: * On data, process all pending events and direct them to the input system. nkeynes@614: * On error, close the channel and delete the device. nkeynes@614: */ nkeynes@614: static gboolean linux_joystick_callback( GIOChannel *source, GIOCondition condition, nkeynes@614: gpointer data ) nkeynes@614: { nkeynes@614: linux_joystick_t joy = (linux_joystick_t)data; nkeynes@614: nkeynes@614: if( condition & G_IO_HUP ) { nkeynes@614: INFO( "Joystick '%s' disconnected\n", joy->name ); nkeynes@614: input_unregister_device((input_driver_t)joy); nkeynes@614: return FALSE; nkeynes@614: } nkeynes@614: if( condition & G_IO_IN ) { nkeynes@614: struct js_event event; nkeynes@614: while( read( joy->fd, &event, sizeof(event) ) == sizeof(event) ) { nkeynes@614: if( event.type == JS_EVENT_BUTTON ) { nkeynes@615: int keycode = event.number+1; nkeynes@614: if( event.value == 0 ) { nkeynes@615: input_event_keyup( (input_driver_t)joy, keycode, 0 ); nkeynes@614: } else { nkeynes@614: input_event_keydown( (input_driver_t)joy, keycode, event.value ); nkeynes@614: } nkeynes@615: } else if( event.type == JS_EVENT_AXIS ) { nkeynes@615: int keycode = (event.number*2) + joy->button_count + 1; nkeynes@615: if( event.value == 0 ) { nkeynes@615: input_event_keyup( (input_driver_t)joy, keycode, 0 ); nkeynes@615: input_event_keyup( (input_driver_t)joy, keycode+1, 0 ); nkeynes@615: } else if( event.value < 0 ) { nkeynes@615: input_event_keydown( (input_driver_t)joy, keycode+1, -event.value ); nkeynes@615: input_event_keyup( (input_driver_t)joy, keycode, 0 ); nkeynes@615: } else { nkeynes@615: input_event_keydown( (input_driver_t)joy, keycode, event.value ); nkeynes@615: input_event_keyup( (input_driver_t)joy, keycode+1, 0 ); nkeynes@615: } nkeynes@614: } nkeynes@614: } nkeynes@614: return TRUE; nkeynes@614: } nkeynes@614: } nkeynes@614: nkeynes@614: /** nkeynes@614: * Create a new joystick device structure given filename and (open) file nkeynes@614: * descriptor. The joystick is automatically added to the watch list. nkeynes@614: * @return The new joystick, or NULL if an error occurred. nkeynes@614: */ nkeynes@615: linux_joystick_t linux_joystick_new( const gchar *filename, int fd ) nkeynes@614: { nkeynes@614: linux_joystick_t joy = g_malloc0(sizeof(struct linux_joystick)); nkeynes@614: joy->filename = filename; nkeynes@614: joy->fd = fd; nkeynes@614: joy->name[0] = '\0'; nkeynes@614: joy->driver.resolve_keysym = linux_joystick_resolve_keysym; nkeynes@614: joy->driver.get_keysym_for_keycode = linux_joystick_keysym_for_keycode; nkeynes@614: joy->driver.destroy = linux_joystick_destroy; nkeynes@614: nkeynes@614: char *p = strrchr(filename, '/'); nkeynes@614: if( p == NULL ) { nkeynes@614: joy->driver.id = filename; nkeynes@614: } else { nkeynes@614: joy->driver.id = p+1; nkeynes@614: } nkeynes@614: nkeynes@614: if( ioctl( fd, JSIOCGNAME(128), joy->name ) == -1 || nkeynes@614: ioctl( fd, JSIOCGAXES, &joy->axis_count ) == -1 || nkeynes@614: ioctl( fd, JSIOCGBUTTONS, &joy->button_count ) == -1 ) { nkeynes@614: ERROR( "Error reading joystick data from %s (%s)\n", filename, strerror(errno) ); nkeynes@614: g_free(joy); nkeynes@614: return NULL; nkeynes@614: } nkeynes@614: nkeynes@614: joy->channel = g_io_channel_unix_new(fd); nkeynes@614: g_io_add_watch( joy->channel, G_IO_IN|G_IO_ERR|G_IO_HUP, linux_joystick_callback, joy ); nkeynes@614: return joy; nkeynes@614: } nkeynes@614: nkeynes@614: int linux_joystick_init() nkeynes@614: { nkeynes@620: linux_joystick_install_watch(INPUT_PATH); nkeynes@620: linux_joystick_scan(); nkeynes@620: } nkeynes@620: nkeynes@620: int linux_joystick_scan() nkeynes@620: { nkeynes@615: int joysticks = 0; nkeynes@614: struct dirent *ent; nkeynes@614: DIR *dir = opendir(INPUT_PATH); nkeynes@614: nkeynes@614: if( dir == NULL ) { nkeynes@614: return 0; nkeynes@614: } nkeynes@614: nkeynes@614: while( (ent = readdir(dir)) != NULL ) { nkeynes@614: if( ent->d_name[0] == 'j' && ent->d_name[1] == 's' && nkeynes@615: isdigit(ent->d_name[2]) && !input_has_device(ent->d_name) ) { nkeynes@614: gchar *name = g_strdup_printf( "%s/%s", INPUT_PATH, ent->d_name ); nkeynes@614: int fd = open(name, O_RDONLY|O_NONBLOCK); nkeynes@614: if( fd == -1 ) { nkeynes@614: g_free( name ); nkeynes@614: } else { nkeynes@615: linux_joystick_t joy = linux_joystick_new( name, fd ); nkeynes@615: input_register_device( (input_driver_t)joy, (joy->axis_count*2) + joy->button_count ); nkeynes@615: INFO( "Attached joystick %s named '%s', (%d buttons, %d axes)", nkeynes@615: joy->driver.id, joy->name, joy->button_count, joy->axis_count ); nkeynes@615: joysticks++; nkeynes@614: } nkeynes@614: } nkeynes@614: } nkeynes@614: nkeynes@614: closedir(dir); nkeynes@615: return joysticks; nkeynes@614: } nkeynes@615: nkeynes@620: void linux_joystick_shutdown(void) nkeynes@620: { nkeynes@620: linux_joystick_uninstall_watch(); nkeynes@620: } nkeynes@620: nkeynes@620: /*************************** dnotify support **********************/ nkeynes@620: nkeynes@620: static volatile gboolean need_input_rescan = FALSE; nkeynes@620: static int watch_dir_fd; nkeynes@620: nkeynes@620: static gboolean gtk_loop_check_input(gpointer data) nkeynes@620: { nkeynes@620: if( need_input_rescan ) { nkeynes@620: int js = linux_joystick_scan(); nkeynes@620: if( js > 0 ) { nkeynes@620: maple_reattach_all(); nkeynes@620: } nkeynes@620: } nkeynes@620: return TRUE; nkeynes@620: } nkeynes@620: nkeynes@620: static void dnotify_handler(int sig ) nkeynes@620: { nkeynes@620: need_input_rescan = TRUE; nkeynes@620: } nkeynes@620: nkeynes@620: static gboolean linux_joystick_install_watch( const gchar *dir ) nkeynes@620: { nkeynes@620: int fd = open( dir, O_RDONLY|O_NONBLOCK ); nkeynes@620: if( fd == -1 ) { nkeynes@620: return FALSE; nkeynes@620: } nkeynes@620: nkeynes@620: signal( SIGRTMIN+1, dnotify_handler ); nkeynes@620: fcntl(fd, F_SETSIG, SIGRTMIN + 1); nkeynes@620: if( fcntl(fd, F_NOTIFY, DN_CREATE|DN_MULTISHOT) == -1 ) { nkeynes@620: close(fd); nkeynes@620: return FALSE; nkeynes@620: } nkeynes@620: watch_dir_fd = fd; nkeynes@620: g_timeout_add( 500, gtk_loop_check_input, NULL ); nkeynes@620: } nkeynes@620: nkeynes@620: static void linux_joystick_uninstall_watch(void) nkeynes@620: { nkeynes@620: signal( SIGRTMIN+1, SIG_IGN ); nkeynes@620: close( watch_dir_fd ); nkeynes@620: }