filename | src/drivers/joy_linux.c |
changeset | 1296:30ecee61f811 |
prev | 1010:a506a2f66180 |
author | nkeynes |
date | Sat Jan 26 14:00:48 2013 +1000 (10 years ago) |
permissions | -rw-r--r-- |
last change | Change glib includes to #include <glib.h> rather than the individual headers, as recent glib versions are breaking on this |
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 }
.