1 /*****************************************************************************
2 * joystick.c: control vlc with a joystick
3 *****************************************************************************
4 * Copyright (C) 2004 VideoLAN
7 * Authors: Clément Stenac <zorglub@via.ecp.fr>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
27 #include <stdlib.h> /* malloc(), free() */
31 #include <sys/types.h>
33 #include <sys/select.h>
43 #include <linux/joystick.h>
45 #include "audio_output.h"
47 /* Default values for parameters */
48 #define DEFAULT_MAX_SEEK 10 /* seconds */
49 #define DEFAULT_REPEAT 100
50 #define DEFAULT_WAIT 500
51 #define DEFAULT_DEVICE "/dev/input/js0"
52 #define DEFAULT_THRESHOLD 12000 /* 0 -> 32767 */
54 #define DEFAULT_MAPPING \
55 "{axis-0-up=forward,axis-0-down=back," \
56 "axis-1-up=next,axis-1-down=prev," \
57 "butt-1-down=play,butt-2-down=fullscreen}"
59 /* Default Actions (used if there are missing actions in the default
60 * Available actions are: Next,Prev, Forward,Back,Play,Fullscreen,dummy */
61 #define AXIS_0_UP_ACTION Forward
62 #define AXIS_0_DOWN_ACTION Back
63 #define AXIS_1_UP_ACTION Next
64 #define AXIS_1_DOWN_ACTION Prev
66 #define BUTTON_1_PRESS_ACTION Play
67 #define BUTTON_1_RELEASE_ACTION dummy
68 #define BUTTON_2_PRESS_ACTION Fullscreen
69 #define BUTTON_2_RELEASE_ACTION dummy
71 /*****************************************************************************
72 * intf_sys_t: description and status of interface
73 *****************************************************************************/
75 typedef int (*action)(intf_thread_t *p_intf);
79 int b_trigered; /* Are we in the trigger zone ? */
80 int i_value; /* Value of movement */
81 int b_dowork; /* Do we have to do the action ? */
82 action pf_actup; /* Action when axis is up */
83 action pf_actdown; /* Action when axis is down */
84 mtime_t l_time; /* When did the axis enter the trigger
90 action pf_actup; /* What to do when button is released */
91 action pf_actdown;/* What to do when button is pressed */
96 int i_fd; /* File descriptor for joystick */
97 struct timeval timeout; /* Select timeout */
98 int i_threshold; /* motion threshold */
99 int i_wait; /* How much to wait before repeat */
100 int i_repeat; /* Repeat time */
101 int i_maxseek; /* Maximum seek time */
102 struct joy_axis_t axes[3]; /* Axes descriptor */
103 struct joy_button_t buttons[2]; /* Buttons descriptor */
104 input_thread_t *p_input; /* Input thread (for seeking) */
105 float f_seconds; /* How much to seek */
108 /*****************************************************************************
110 *****************************************************************************/
112 static int Open ( vlc_object_t * );
113 static void Close ( vlc_object_t * );
114 static int Init ( intf_thread_t *p_intf );
116 static int handle_event ( intf_thread_t *p_intf, struct js_event event );
120 static int Next (intf_thread_t *p_intf);
121 static int Prev (intf_thread_t *p_intf);
122 static int Back (intf_thread_t *p_intf);
123 static int Forward (intf_thread_t *p_intf);
124 static int Play (intf_thread_t *p_intf);
125 static int Fullscreen (intf_thread_t *p_intf);
127 static int dummy (intf_thread_t *p_intf);
129 /* Exported functions */
130 static void Run ( intf_thread_t *p_intf );
132 /*****************************************************************************
134 *****************************************************************************/
135 #define THRESHOLD_TEXT N_( "Motion threshold" )
136 #define THRESHOLD_LONGTEXT N_( \
137 "Amount of joystick movement required for a movement to be " \
138 "recorded (0->32767)." )
140 #define DEVICE_TEXT N_( "Joystick device" )
141 #define DEVICE_LONGTEXT N_( \
142 "The joystick device (usually /dev/js0 or /dev/input/js0).")
144 #define REPEAT_TEXT N_( "Repeat time (ms)" )
145 #define REPEAT_LONGTEXT N_( \
146 "Delay waited before the action is repeated if it is still " \
147 "triggered, in milliseconds." )
149 #define WAIT_TEXT N_( "Wait time (ms)")
150 #define WAIT_LONGTEXT N_(\
151 "The time waited before the repeat starts, in milliseconds.")
153 #define SEEK_TEXT N_( "Max seek interval (seconds)")
154 #define SEEK_LONGTEXT N_(\
155 "The maximum number of seconds that will be sought at a time." )
157 #define MAP_TEXT N_( "Action mapping")
158 #define MAP_LONGTEXT N_( "Allows you to remap the actions." )
161 add_integer( "motion-threshold", DEFAULT_THRESHOLD, NULL,
162 THRESHOLD_TEXT, THRESHOLD_LONGTEXT, VLC_TRUE );
163 add_string( "joystick-device", DEFAULT_DEVICE, NULL,
164 DEVICE_TEXT, DEVICE_LONGTEXT, VLC_TRUE );
165 add_integer ("joystick-repeat", DEFAULT_REPEAT,NULL,
166 REPEAT_TEXT, REPEAT_LONGTEXT, VLC_TRUE );
167 add_integer ("joystick-wait", DEFAULT_WAIT,NULL,
168 WAIT_TEXT, WAIT_LONGTEXT, VLC_TRUE );
169 add_integer ("joystick-max-seek",DEFAULT_MAX_SEEK,NULL,
170 SEEK_TEXT, SEEK_LONGTEXT, VLC_TRUE );
171 add_string("joystick-mapping",DEFAULT_MAPPING,NULL,
172 MAP_TEXT,MAP_LONGTEXT, VLC_TRUE );
173 set_description( _("Joystick control interface") );
174 set_capability( "interface", 0 );
175 set_callbacks( Open, Close );
178 /*****************************************************************************
179 * Open: initialize interface
180 *****************************************************************************/
181 static int Open ( vlc_object_t *p_this )
183 intf_thread_t *p_intf = (intf_thread_t *)p_this;
185 /* Allocate instance and initialize some members */
186 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
188 if( p_intf->p_sys == NULL )
193 if( Init( p_intf ) < 0 )
195 msg_Err( p_intf, "cannot initialize interface" );
196 free( p_intf->p_sys );
200 msg_Dbg( p_intf, "interface initialized" );
202 p_intf->pf_run = Run;
207 /*****************************************************************************
208 * Close: destroy the interface
209 *****************************************************************************/
210 static void Close ( vlc_object_t *p_this )
212 intf_thread_t *p_intf = (intf_thread_t *)p_this;
214 /* Destroy structure */
217 free( p_intf->p_sys );
222 /*****************************************************************************
224 *****************************************************************************/
225 static void Run( intf_thread_t *p_intf )
230 struct js_event event;
233 while( !p_intf->b_die )
237 vlc_mutex_lock( &p_intf->change_lock );
240 FD_SET( p_intf->p_sys->i_fd, &fds );
242 p_intf->p_sys->timeout.tv_sec = 0;
243 p_intf->p_sys->timeout.tv_usec = p_intf->p_sys->i_repeat;
246 i_sel_res = select( p_intf->p_sys->i_fd + 1, &fds,
247 NULL, NULL, &p_intf->p_sys->timeout );
249 p_intf->p_sys->p_input = (input_thread_t *)
250 vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
252 if( i_sel_res == -1 && errno != EINTR )
254 msg_Err( p_intf, "select error: %s",strerror(errno) );
256 else if(i_sel_res > 0 && FD_ISSET( p_intf->p_sys->i_fd, &fds))
258 /* We got an event */
259 memset(&event,0,sizeof(struct js_event));
260 i_read = read( p_intf->p_sys->i_fd, &event,
261 sizeof(struct js_event));
262 handle_event( p_intf, event ) ;
264 else if(i_sel_res == 0)
266 /*We have no event, but check if we have an action to repeat */
267 for(i_axis = 0; i_axis <= 1; i_axis++)
269 if( p_intf->p_sys->axes[i_axis].b_trigered &&
270 mdate()-p_intf->p_sys->axes[i_axis].l_time >
271 p_intf->p_sys->i_wait &&
272 p_intf->p_sys->axes[i_axis].i_value > 0 )
274 p_intf->p_sys->axes[i_axis].pf_actup(p_intf);
277 if( p_intf->p_sys->axes[i_axis].b_trigered &&
278 mdate()-p_intf->p_sys->axes[i_axis].l_time >
279 p_intf->p_sys->i_wait &&
280 p_intf->p_sys->axes[i_axis].i_value < 0 )
282 p_intf->p_sys->axes[i_axis].pf_actdown(p_intf);
287 if(p_intf->p_sys->p_input)
288 vlc_object_release (p_intf->p_sys->p_input);
290 vlc_mutex_unlock ( &p_intf->change_lock );
294 /*****************************************************************************
295 * InitThread: Initialize the interface
296 *****************************************************************************/
297 static int Init( intf_thread_t * p_intf )
301 char *psz_eof; /* end of field */
303 psz_device = config_GetPsz( p_intf, "joystick-device");
305 if( !psz_device ) /* strange... */
307 psz_device = strdup( DEFAULT_DEVICE );
310 p_intf->p_sys->i_fd = open( psz_device, O_RDONLY|O_NONBLOCK );
312 if( p_intf->p_sys->i_fd == -1 )
314 msg_Warn( p_intf, "unable to open %s for reading: %s",
315 psz_device, strerror(errno) );
319 p_intf->p_sys->i_repeat = 1000 * config_GetInt( p_intf, "joystick-repeat");
320 p_intf->p_sys->i_wait = 1000 * config_GetInt( p_intf, "joystick-wait");
322 p_intf->p_sys->i_threshold = config_GetInt( p_intf, "motion-threshold" );
323 if(p_intf->p_sys->i_threshold > 32767 || p_intf->p_sys->i_threshold < 0 )
324 p_intf->p_sys->i_threshold = DEFAULT_THRESHOLD;
326 p_intf->p_sys->i_maxseek = config_GetInt( p_intf, "joystick-max-seek" );
328 psz_parse = config_GetPsz( p_intf, "joystick-mapping" ) ;
332 msg_Warn (p_intf,"invalid mapping. aborting" );
336 if( !strlen( psz_parse ) )
338 msg_Warn( p_intf, "invalid mapping, aborting" );
342 p_intf->p_sys->axes[0].pf_actup = AXIS_0_UP_ACTION;
343 p_intf->p_sys->axes[0].pf_actdown = AXIS_0_DOWN_ACTION;
344 p_intf->p_sys->axes[1].pf_actup = AXIS_1_UP_ACTION;
345 p_intf->p_sys->axes[1].pf_actdown = AXIS_1_DOWN_ACTION;
347 p_intf->p_sys->buttons[0].pf_actdown = BUTTON_1_PRESS_ACTION;
348 p_intf->p_sys->buttons[0].pf_actup = BUTTON_1_RELEASE_ACTION;
349 p_intf->p_sys->buttons[1].pf_actdown = BUTTON_2_PRESS_ACTION;
350 p_intf->p_sys->buttons[1].pf_actup = BUTTON_2_RELEASE_ACTION;
352 /* Macro to parse the command line */
353 #define PARSE(name,function) \
354 if(!strncmp( psz_parse, name, strlen( name ) ) ) \
356 psz_parse += strlen( name ); \
357 psz_eof = strchr( psz_parse, ',' ); \
359 psz_eof = strchr( psz_parse, '}' ); \
361 psz_eof = psz_parse + strlen(psz_parse); \
366 msg_Dbg(p_intf,"%s -> %s", name,psz_parse) ; \
367 if(!strcasecmp( psz_parse, "play" ) ) function = Play; \
368 if(!strcasecmp( psz_parse, "next" ) ) function = Next; \
369 if(!strcasecmp( psz_parse, "prev" ) ) function = Prev; \
370 if(!strcasecmp( psz_parse, "fullscreen" ) ) function = Fullscreen; \
371 if(!strcasecmp( psz_parse, "forward" ) ) function = Forward; \
372 if(!strcasecmp( psz_parse, "back" ) ) function = Back; \
373 psz_parse = psz_eof; \
378 for( ; *psz_parse ; psz_parse++ )
380 PARSE("axis-0-up=", p_intf->p_sys->axes[0].pf_actup );
381 PARSE("axis-0-down=", p_intf->p_sys->axes[0].pf_actdown );
382 PARSE("axis-1-up=", p_intf->p_sys->axes[1].pf_actup );
383 PARSE("axis-1-down=", p_intf->p_sys->axes[1].pf_actdown );
385 PARSE("butt-1-up=", p_intf->p_sys->buttons[0].pf_actup );
386 PARSE("butt-1-down=", p_intf->p_sys->buttons[0].pf_actdown );
387 PARSE("butt-2-up=", p_intf->p_sys->buttons[1].pf_actup );
388 PARSE("butt-2-down=", p_intf->p_sys->buttons[1].pf_actdown );
391 p_intf->p_sys->axes[0].b_trigered = VLC_FALSE;
392 p_intf->p_sys->axes[0].l_time = 0;
394 p_intf->p_sys->axes[1].b_trigered = VLC_FALSE;
395 p_intf->p_sys->axes[1].l_time = 0;
400 /*****************************************************************************
401 * handle_event : parse a joystick event and takes the appropriate action *
402 *****************************************************************************/
403 static int handle_event ( intf_thread_t *p_intf, struct js_event event)
407 if( event.type == JS_EVENT_AXIS )
409 /* Third axis is supposed to behave in a different way: it is a
410 * throttle, and will set a value, without triggering anything */
411 if( event.number == 2 &&
412 /* Try to avoid Parkinson joysticks */
413 abs(event.value - p_intf->p_sys->axes[2].i_value) > 200 )
415 p_intf->p_sys->axes[2].i_value = event.value;
416 msg_Dbg( p_intf, "updating volume" );
417 /* This way, the volume is between 0 and 1024 */
418 aout_VolumeSet( p_intf, (32767-event.value)/64 );
422 p_intf->p_sys->axes[event.number].b_dowork = VLC_FALSE;
423 p_intf->p_sys->axes[event.number].i_value = event.value;
425 if( abs(event.value) > p_intf->p_sys->i_threshold &&
426 p_intf->p_sys->axes[event.number].b_trigered == VLC_FALSE )
428 /* The axis entered the trigger zone. Start the event */
429 p_intf->p_sys->axes[event.number].b_trigered = VLC_TRUE;
430 p_intf->p_sys->axes[event.number].b_dowork = VLC_TRUE;
431 p_intf->p_sys->axes[event.number].l_time = mdate();
433 else if( abs(event.value) > p_intf->p_sys->i_threshold &&
434 p_intf->p_sys->axes[event.number].b_trigered == VLC_TRUE )
436 /* The axis moved but remained in the trigger zone
437 * Do nothing at this time */
439 else if( abs(event.value) < p_intf->p_sys->i_threshold )
441 /* The axis is not in the trigger zone */
442 p_intf->p_sys->axes[event.number].b_trigered = VLC_FALSE;
445 /* Special for seeking */
446 p_intf->p_sys->f_seconds = 1 +
447 (abs(event.value) - p_intf->p_sys->i_threshold) *
448 (p_intf->p_sys->i_maxseek - 1 ) /
449 (32767 - p_intf->p_sys->i_threshold);
452 /* Handle the first two axes. */
453 for(i_axis = 0; i_axis <= 1; i_axis ++)
455 if(p_intf->p_sys->axes[i_axis].b_dowork == VLC_TRUE)
457 if( p_intf->p_sys->axes[i_axis].i_value
458 > p_intf->p_sys->i_threshold )
460 msg_Dbg(p_intf,"up for axis %i\n",i_axis);
461 p_intf->p_sys->axes[i_axis].pf_actup(p_intf);
463 else if( p_intf->p_sys->axes[i_axis].i_value
464 < -p_intf->p_sys->i_threshold )
466 msg_Dbg(p_intf,"down for axis %i\n",i_axis);
467 p_intf->p_sys->axes[i_axis].pf_actdown(p_intf);
473 else if( event.type == JS_EVENT_BUTTON )
475 msg_Dbg( p_intf, "button %i %s", event.number,
476 event.value ? "pressed" : "released" );
477 if( event.number > 1 )
478 return 0; /* Only trigger 2 buttons */
480 if( event.value == 1 ) /* Button pressed */
482 if( p_intf->p_sys->buttons[event.number].pf_actdown )
483 p_intf->p_sys->buttons[event.number].pf_actdown( p_intf );
485 else /* Button released */
487 if( p_intf->p_sys->buttons[event.number].pf_actup )
488 p_intf->p_sys->buttons[event.number].pf_actup( p_intf );
494 /****************************************************************************
496 ****************************************************************************/
498 /* Go to next item in the playlist */
499 static int Next( intf_thread_t *p_intf )
501 playlist_t *p_playlist;
503 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
504 if( p_playlist == NULL )
509 playlist_Next( p_playlist );
510 vlc_object_release( p_playlist );
514 /* Go to previous item in the playlist */
515 static int Prev( intf_thread_t *p_intf )
517 playlist_t *p_playlist;
519 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
520 if( p_playlist == NULL )
525 playlist_Prev( p_playlist );
526 vlc_object_release( p_playlist );
531 static int Forward( intf_thread_t *p_intf )
533 if( p_intf->p_sys->p_input )
535 msg_Dbg( p_intf,"seeking %f seconds",p_intf->p_sys->f_seconds );
536 var_SetTime( p_intf->p_sys->p_input, "time-offset",
537 (int64_t)p_intf->p_sys->f_seconds * I64C(1000000) );
545 static int Back( intf_thread_t *p_intf )
547 if( p_intf->p_sys->p_input )
549 msg_Dbg( p_intf,"seeking -%f seconds", p_intf->p_sys->f_seconds );
551 var_SetTime( p_intf->p_sys->p_input, "time-offset",
552 -(int64_t)p_intf->p_sys->f_seconds * I64C(1000000) );
559 /* Toggle Play/Pause */
560 static int Play( intf_thread_t *p_intf )
562 if( p_intf->p_sys->p_input )
564 var_SetInteger( p_intf->p_sys->p_input, "state", PAUSE_S );
571 /* Toggle fullscreen mode */
572 static int Fullscreen( intf_thread_t *p_intf )
574 vout_thread_t * p_vout;
576 p_vout = vlc_object_find(p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
579 p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
580 vlc_object_release(p_vout);
585 /* dummy event. Use it if you don't wan't anything to happen */
586 static int dummy( intf_thread_t *p_intf )