1 /*****************************************************************************
2 * joystick.c: control vlc with a joystick
3 *****************************************************************************
4 * Copyright (C) 2002 VideoLAN
5 * $Id: joystick.c,v 1.2 2003/07/31 08:18:30 zorglub Exp $
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"
46 #include "stream_control.h"
47 #include "input_ext-intf.h"
49 /* Default values for parameters */
50 #define DEFAULT_MAX_SEEK 10 /* seconds */
51 #define DEFAULT_REPEAT 100
52 #define DEFAULT_WAIT 500
53 #define DEFAULT_DEVICE "/dev/input/js0"
54 #define DEFAULT_THRESHOLD 12000 /* 0 -> 32767 */
56 #define DEFAULT_MAPPING \
57 "{axis-0-up=forward,axis-0-down=back," \
58 "axis-1-up=next,axis-1-down=prev," \
59 "butt-1-down=play,butt-2-down=fullscreen}"
61 /* Default Actions (used if there are missing actions in the default
62 * Available actions are: Next,Prev, Forward,Back,Play,Fullscreen,dummy */
63 #define AXE_0_UP_ACTION Forward
64 #define AXE_0_DOWN_ACTION Back
65 #define AXE_1_UP_ACTION Next
66 #define AXE_1_DOWN_ACTION Prev
68 #define BUTTON_1_PRESS_ACTION Play
69 #define BUTTON_1_RELEASE_ACTION dummy
70 #define BUTTON_2_PRESS_ACTION Fullscreen
71 #define BUTTON_2_RELEASE_ACTION dummy
73 /*****************************************************************************
74 * intf_sys_t: description and status of interface
75 *****************************************************************************/
77 typedef int (*action)(intf_thread_t *p_intf);
81 int b_trigered; /* Are we in the trigger zone ? */
82 int i_value; /* Value of movement */
83 int b_dowork; /* Do we have to do the action ? */
84 action pf_actup; /* Action when axis is up */
85 action pf_actdown; /* Action when axis is down */
86 mtime_t l_time; /* When did the axis enter the trigger
92 action pf_actup; /* What to do when button is released */
93 action pf_actdown;/* What to do when button is pressed */
98 fd_set fds; /* File descriptor set (select) */
99 int i_fd; /* File descriptor for joystick */
100 struct timeval timeout; /* Select timeout */
101 int i_threshold; /* motion threshold */
102 int i_wait; /* How much to wait before repeat */
103 int i_repeat; /* Repeat time */
104 int i_maxseek; /* Maximum seek time */
105 struct joy_axis_t axes[3]; /* Axes descriptor */
106 struct joy_button_t buttons[2]; /* Buttons descriptor */
107 input_thread_t *p_input; /* Input thread (for seeking) */
108 float f_seconds; /* How much to seek */
111 /*****************************************************************************
113 *****************************************************************************/
115 static int Open ( vlc_object_t * );
116 static void Close ( vlc_object_t * );
117 static int Init ( intf_thread_t *p_intf );
119 static int handle_event ( intf_thread_t *p_intf, struct js_event event );
123 static int Next (intf_thread_t *p_intf);
124 static int Prev (intf_thread_t *p_intf);
125 static int Back (intf_thread_t *p_intf);
126 static int Forward (intf_thread_t *p_intf);
127 static int Play (intf_thread_t *p_intf);
128 static int Fullscreen (intf_thread_t *p_intf);
130 static int dummy (intf_thread_t *p_intf);
132 /* Exported functions */
133 static void Run ( intf_thread_t *p_intf );
135 /*****************************************************************************
137 *****************************************************************************/
138 #define THRESHOLD_TEXT N_( "Motion threshold" )
139 #define THRESHOLD_LONGTEXT N_( \
140 "The amount of joystick movement required for a movement to be " \
141 "recorded (0->32767)" )
143 #define DEVICE_TEXT N_( "Joystick device" )
144 #define DEVICE_LONGTEXT N_( \
145 "The joystick device (usually /dev/js0 or /dev/input/js0)")
147 #define REPEAT_TEXT N_( "Repeat time" )
148 #define REPEAT_LONGTEXT N_( \
149 "The time waited before the action is repeated if it is still trigered, " \
152 #define WAIT_TEXT N_( "Wait time")
153 #define WAIT_LONGTEXT N_(\
154 "The time waited before the repeat starts, in milliseconds ")
156 #define SEEK_TEXT N_( "Max seek interval")
157 #define SEEK_LONGTEXT N_(\
158 "The maximum number of seconds that will be seeked at a time." )
160 #define MAP_TEXT N_( "Action mapping")
161 #define MAP_LONGTEXT N_(\
162 "Allows you to remap the actions. For details," \
163 " please have a look at http://wiki.videolan.org/index.php/Joystick" )
166 add_category_hint( N_( "Joystick" ), NULL, VLC_FALSE );
167 add_integer( "motion-threshold", DEFAULT_THRESHOLD , NULL,
168 THRESHOLD_TEXT, THRESHOLD_LONGTEXT, VLC_TRUE );
169 add_string( "joystick-device", DEFAULT_DEVICE , NULL,
170 DEVICE_TEXT, DEVICE_LONGTEXT, VLC_TRUE );
171 add_integer ("joystick-repeat", DEFAULT_REPEAT,NULL,
172 REPEAT_TEXT, REPEAT_LONGTEXT, VLC_TRUE );
173 add_integer ("joystick-wait", DEFAULT_WAIT,NULL,
174 WAIT_TEXT, WAIT_LONGTEXT, VLC_TRUE );
175 add_integer ("joystick-max-seek",DEFAULT_MAX_SEEK,NULL,
176 SEEK_TEXT, SEEK_LONGTEXT, VLC_TRUE );
177 add_string("joystick-mapping",DEFAULT_MAPPING,NULL,
178 MAP_TEXT,MAP_LONGTEXT, VLC_TRUE );
179 set_description( _("joystick control interface") );
180 set_capability( "interface", 0 );
181 set_callbacks( Open, Close );
184 /*****************************************************************************
185 * Open: initialize interface
186 *****************************************************************************/
187 static int Open ( vlc_object_t *p_this )
189 intf_thread_t *p_intf = (intf_thread_t *)p_this;
191 /* Allocate instance and initialize some members */
192 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
194 if( p_intf->p_sys == NULL )
199 p_intf->pf_run = Run;
204 /*****************************************************************************
205 * Close: destroy the interface
206 *****************************************************************************/
207 static void Close ( vlc_object_t *p_this )
209 intf_thread_t *p_intf = (intf_thread_t *)p_this;
211 /* Destroy structure */
213 free( p_intf->p_sys );
217 /*****************************************************************************
219 *****************************************************************************/
220 static void Run( intf_thread_t *p_intf )
225 struct js_event event;
227 if( Init( p_intf ) < 0 )
229 msg_Err( p_intf, "can't initialize intf" );
232 msg_Dbg( p_intf, "intf initialized" );
235 while( !p_intf->b_die )
237 vlc_mutex_lock( &p_intf->change_lock );
239 FD_ZERO( &p_intf->p_sys->fds );
240 FD_SET( p_intf->p_sys->i_fd , &p_intf->p_sys->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,
250 &p_intf->p_sys->timeout );
252 p_intf->p_sys->p_input = (input_thread_t *)
253 vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
255 if( i_sel_res == -1 && errno != EINTR )
257 msg_Err( p_intf, "select error: %s",strerror(errno) );
259 else if(i_sel_res > 0 && FD_ISSET( p_intf->p_sys->i_fd,
260 &p_intf->p_sys->fds))
261 { /* We got an event */
262 memset(&event,0,sizeof(struct js_event));
263 i_read = read( p_intf->p_sys->i_fd, &event ,
264 sizeof(struct js_event));
265 handle_event( p_intf , event ) ;
267 else if(i_sel_res == 0)
268 /*We have no event, but check if we have an action to repeat */
270 for(i_axe=0;i_axe<=1;i_axe++)
272 if( p_intf->p_sys->axes[i_axe].b_trigered &&
273 mdate()-p_intf->p_sys->axes[i_axe].l_time >
274 p_intf->p_sys->i_wait &&
275 p_intf->p_sys->axes[i_axe].i_value > 0 )
277 p_intf->p_sys->axes[i_axe].pf_actup(p_intf);
280 if( p_intf->p_sys->axes[i_axe].b_trigered &&
281 mdate()-p_intf->p_sys->axes[i_axe].l_time >
282 p_intf->p_sys->i_wait &&
283 p_intf->p_sys->axes[i_axe].i_value < 0 )
285 p_intf->p_sys->axes[i_axe].pf_actdown(p_intf);
290 if(p_intf->p_sys->p_input)
291 vlc_object_release (p_intf->p_sys->p_input);
293 vlc_mutex_unlock ( &p_intf->change_lock );
297 /*****************************************************************************
298 * InitThread: Initialize the interface
299 *****************************************************************************/
300 static int Init( intf_thread_t * p_intf )
304 char *psz_eof; /* end of field */
308 vlc_mutex_lock( &p_intf->change_lock );
310 psz_device=config_GetPsz( p_intf, "joystick-device");
312 if(!psz_device) /* strange... */
313 psz_device = strdup( DEFAULT_DEVICE );
315 p_intf->p_sys->i_fd = open ( psz_device , O_RDONLY|O_NONBLOCK );
317 if( p_intf->p_sys->i_fd == -1 )
319 msg_Warn( p_intf, "Unable to open %s for reading: %s"
320 ,psz_device,strerror(errno));
324 p_intf->p_sys->i_repeat = 1000*
325 config_GetInt( p_intf, "joystick-repeat");
327 p_intf->p_sys->i_wait = 1000*
328 config_GetInt( p_intf, "joystick-wait");
330 p_intf->p_sys->i_threshold =
331 config_GetInt( p_intf, "motion-threshold" );
333 if(p_intf->p_sys->i_threshold > 32767 ||
334 p_intf->p_sys->i_threshold < 0 )
335 p_intf->p_sys->i_threshold = DEFAULT_THRESHOLD;
337 p_intf->p_sys->i_maxseek =
338 config_GetInt( p_intf, "joystick-max-seek" );
341 psz_parse = config_GetPsz( p_intf, "joystick-mapping" ) ;
345 msg_Warn (p_intf,"Invalid mapping. Aborting" );
348 if( !strlen( psz_parse ) )
350 msg_Warn( p_intf, "Invalid mapping. Aborting" );
354 p_intf->p_sys->axes[0].pf_actup = AXE_0_UP_ACTION;
355 p_intf->p_sys->axes[0].pf_actdown = AXE_0_DOWN_ACTION;
356 p_intf->p_sys->axes[1].pf_actup = AXE_1_UP_ACTION;
357 p_intf->p_sys->axes[1].pf_actdown = AXE_1_DOWN_ACTION;
359 p_intf->p_sys->buttons[0].pf_actdown = BUTTON_1_PRESS_ACTION;
360 p_intf->p_sys->buttons[0].pf_actup = BUTTON_1_RELEASE_ACTION;
361 p_intf->p_sys->buttons[1].pf_actdown = BUTTON_2_PRESS_ACTION;
362 p_intf->p_sys->buttons[1].pf_actup = BUTTON_2_RELEASE_ACTION;
364 /* Macro to parse the command line */
365 #define PARSE(name,function) \
366 if(!strncmp( psz_parse , name , strlen( name ) ) ) \
368 psz_parse += strlen( name ); \
369 psz_eof = strchr( psz_parse , ',' ); \
371 psz_eof = strchr( psz_parse, '}' ); \
373 psz_eof = psz_parse + strlen(psz_parse); \
378 msg_Dbg(p_intf,"%s -> %s", name,psz_parse) ; \
379 if(!strcasecmp( psz_parse , "play" ) ) function = Play; \
380 if(!strcasecmp( psz_parse , "next" ) ) function = Next; \
381 if(!strcasecmp( psz_parse , "prev" ) ) function = Prev; \
382 if(!strcasecmp( psz_parse , "fullscreen" ) ) function = Fullscreen; \
383 if(!strcasecmp( psz_parse , "forward" ) ) function = Forward; \
384 if(!strcasecmp( psz_parse , "back" ) ) function = Back; \
385 psz_parse = psz_eof; \
392 PARSE("axis-0-up=" ,p_intf->p_sys->axes[0].pf_actup );
393 PARSE("axis-0-down=" ,p_intf->p_sys->axes[0].pf_actdown );
394 PARSE("axis-1-up=" ,p_intf->p_sys->axes[1].pf_actup );
395 PARSE("axis-1-down=" ,p_intf->p_sys->axes[1].pf_actdown );
397 PARSE("butt-1-up=" ,p_intf->p_sys->buttons[0].pf_actup );
398 PARSE("butt-1-down=" ,p_intf->p_sys->buttons[0].pf_actdown );
399 PARSE("butt-2-up=" ,p_intf->p_sys->buttons[1].pf_actup );
400 PARSE("butt-2-down=" ,p_intf->p_sys->buttons[1].pf_actdown );
408 p_intf->p_sys->axes[0].b_trigered = VLC_FALSE;
409 p_intf->p_sys->axes[0].l_time = 0;
411 p_intf->p_sys->axes[1].b_trigered = VLC_FALSE;
412 p_intf->p_sys->axes[1].l_time = 0;
414 vlc_mutex_unlock( &p_intf->change_lock );
424 /*****************************************************************************
425 * handle_event : parse a joystick event and takes the appropriate action *
426 *****************************************************************************/
427 static int handle_event ( intf_thread_t *p_intf, struct js_event event)
431 if( event.type == JS_EVENT_AXIS )
433 /* Third axe is supposed to behave in a different way:
434 * it is a throttle, and will set a value, without
435 * triggering something */
436 if( event.number == 2 &&
437 /* Try to avoid Parkinson joysticks */
438 abs(event.value - p_intf->p_sys->axes[2].i_value) > 200 )
440 p_intf->p_sys->axes[2].i_value = event.value;
441 msg_Dbg( p_intf , "Updating volume" );
442 /* This way, the volume is between 0 and 1024 */
443 aout_VolumeSet( p_intf, (32767-event.value)/64 );
447 p_intf->p_sys->axes[event.number].b_dowork = VLC_FALSE;
448 p_intf->p_sys->axes[event.number].i_value = event.value;
450 if( abs(event.value) > p_intf->p_sys->i_threshold &&
451 p_intf->p_sys->axes[event.number].b_trigered == VLC_FALSE)
453 /* The axis entered the trigger zone. Start the event */
454 p_intf->p_sys->axes[event.number].b_trigered = VLC_TRUE;
455 p_intf->p_sys->axes[event.number].b_dowork = VLC_TRUE;
456 p_intf->p_sys->axes[event.number].l_time = mdate();
458 else if(abs(event.value) > p_intf->p_sys->i_threshold &&
459 p_intf->p_sys->axes[event.number].b_trigered == VLC_TRUE)
461 /* The axis moved but remained in the trigger zone
462 * Do nothing at this time */
464 else if ( abs(event.value) < p_intf->p_sys->i_threshold )
466 /* The axis is not in the trigger zone */
467 p_intf->p_sys->axes[event.number].b_trigered = VLC_FALSE;
470 /* Special for seeking */
471 p_intf->p_sys->f_seconds = 1+
472 (abs(event.value)-p_intf->p_sys->i_threshold)*
473 (p_intf->p_sys->i_maxseek - 1 )/
474 (32767-p_intf->p_sys->i_threshold);
477 /* Handle the first two axes. */
478 for(i_axe = 0; i_axe <= 1 ; i_axe ++)
480 if(p_intf->p_sys->axes[i_axe].b_dowork == VLC_TRUE)
482 if( p_intf->p_sys->axes[i_axe].i_value
483 > p_intf->p_sys->i_threshold )
485 msg_Dbg(p_intf,"Up for axis %i\n",i_axe);
486 p_intf->p_sys->axes[i_axe].pf_actup(p_intf);
488 else if( p_intf->p_sys->axes[i_axe].i_value
489 < -p_intf->p_sys->i_threshold )
491 msg_Dbg(p_intf,"Down for axis %i\n",i_axe);
492 p_intf->p_sys->axes[i_axe].pf_actdown(p_intf);
498 else if( event.type == JS_EVENT_BUTTON)
500 msg_Dbg(p_intf,"Button %i %s",event.number,
501 event.value ? "pressed":"released");
502 if(event.number >1) return 0; /* Only trigger 2 buttons */
503 if(event.value == 1) /* Button pressed */
505 if(p_intf->p_sys->buttons[event.number].pf_actdown)
506 p_intf->p_sys->buttons[event.number].pf_actdown(p_intf);
508 else /* Button released */
509 if(p_intf->p_sys->buttons[event.number].pf_actup)
510 p_intf->p_sys->buttons[event.number].pf_actup(p_intf);
515 /****************************************************************************
517 ****************************************************************************/
519 /* Go to next item in the playlist */
520 static int Next( intf_thread_t *p_intf)
522 playlist_t *p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
524 if( p_playlist == NULL )
528 playlist_Next( p_playlist );
529 vlc_object_release( p_playlist );
533 /* Go to previous item in the playlist */
534 static int Prev( intf_thread_t *p_intf)
536 playlist_t *p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
538 if( p_playlist == NULL )
542 playlist_Prev( p_playlist );
543 vlc_object_release( p_playlist );
548 static int Forward(intf_thread_t *p_intf)
550 if(p_intf->p_sys->p_input)
552 msg_Dbg(p_intf,"Seeking %f seconds",p_intf->p_sys->f_seconds);
553 input_Seek( p_intf->p_sys->p_input, p_intf->p_sys->f_seconds,
554 INPUT_SEEK_SECONDS | INPUT_SEEK_CUR);
561 static int Back(intf_thread_t *p_intf)
563 if(p_intf->p_sys->p_input)
565 msg_Dbg(p_intf,"Seeking -%f seconds",p_intf->p_sys->f_seconds);
566 input_Seek( p_intf->p_sys->p_input, -(p_intf->p_sys->f_seconds),
567 INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
573 /* Toggle Play/Pause */
574 static int Play(intf_thread_t *p_intf)
576 if(p_intf->p_sys->p_input)
578 input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PAUSE );
584 /* Toggle fullscreen mode */
585 static int Fullscreen(intf_thread_t *p_intf)
587 vout_thread_t * p_vout=vlc_object_find(p_intf,
588 VLC_OBJECT_VOUT, FIND_ANYWHERE );
591 p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
592 vlc_object_release(p_vout);
597 /* dummy event. Use it if you don't wan't anything to happen */
598 static int dummy(intf_thread_t *p_intf)