]> git.sesse.net Git - vlc/blob - modules/control/joystick.c
Improvements to preferences
[vlc] / modules / control / joystick.c
1 /*****************************************************************************
2  * joystick.c: control vlc with a joystick
3  *****************************************************************************
4  * Copyright (C) 2004 VideoLAN
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@via.ecp.fr>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29 #include <unistd.h>
30
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <sys/select.h>
34
35 #include <errno.h>
36
37 #include <fcntl.h>
38
39 #include <vlc/vlc.h>
40 #include <vlc/intf.h>
41 #include <vlc/vout.h>
42
43 #include <linux/joystick.h>
44
45 #include "audio_output.h"
46
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 */
53
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}"
58
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
65
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
70
71 /*****************************************************************************
72  * intf_sys_t: description and status of interface
73  *****************************************************************************/
74
75 typedef int (*action)(intf_thread_t *p_intf);
76
77 struct joy_axis_t
78 {
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
85                                  * zone ? */
86 };
87
88 struct joy_button_t
89 {
90     action      pf_actup;  /* What to do when button is released */
91     action      pf_actdown;/* What to do when button is pressed */
92 };
93
94 struct intf_sys_t
95 {
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 */
106 };
107
108 /*****************************************************************************
109  * Local prototypes.
110  *****************************************************************************/
111
112 static int  Open   ( vlc_object_t * );
113 static void Close  ( vlc_object_t * );
114 static int  Init   ( intf_thread_t *p_intf );
115
116 static int handle_event   ( intf_thread_t *p_intf, struct js_event event );
117
118
119 /* Actions */
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);
126
127 static int dummy       (intf_thread_t *p_intf);
128
129 /* Exported functions */
130 static void Run       ( intf_thread_t *p_intf );
131
132 /*****************************************************************************
133  * Module descriptor
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)." )
139
140 #define DEVICE_TEXT N_( "Joystick device" )
141 #define DEVICE_LONGTEXT N_( \
142     "The joystick device (usually /dev/js0 or /dev/input/js0).")
143
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." )
148
149 #define WAIT_TEXT N_( "Wait time (ms)")
150 #define WAIT_LONGTEXT N_(\
151    "The time waited before the repeat starts, in milliseconds.")
152
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." )
156
157 #define MAP_TEXT N_( "Action mapping")
158 #define MAP_LONGTEXT N_( "Allows you to remap the actions." )
159
160 vlc_module_begin();
161     set_category( CAT_INTERFACE );
162     set_subcategory( SUBCAT_INTERFACE_CONTROL );
163     add_integer( "motion-threshold", DEFAULT_THRESHOLD, NULL,
164                      THRESHOLD_TEXT, THRESHOLD_LONGTEXT, VLC_TRUE );
165     add_string( "joystick-device", DEFAULT_DEVICE, NULL,
166                      DEVICE_TEXT, DEVICE_LONGTEXT, VLC_TRUE );
167     add_integer ("joystick-repeat", DEFAULT_REPEAT,NULL,
168                      REPEAT_TEXT, REPEAT_LONGTEXT, VLC_TRUE );
169     add_integer ("joystick-wait", DEFAULT_WAIT,NULL,
170                      WAIT_TEXT, WAIT_LONGTEXT, VLC_TRUE );
171     add_integer ("joystick-max-seek",DEFAULT_MAX_SEEK,NULL,
172                      SEEK_TEXT, SEEK_LONGTEXT, VLC_TRUE );
173     add_string("joystick-mapping",DEFAULT_MAPPING,NULL,
174                     MAP_TEXT,MAP_LONGTEXT, VLC_TRUE );
175     set_description( _("Joystick control interface") );
176     set_capability( "interface", 0 );
177     set_callbacks( Open, Close );
178 vlc_module_end();
179
180 /*****************************************************************************
181  * Open: initialize interface
182  *****************************************************************************/
183 static int Open ( vlc_object_t *p_this )
184 {
185     intf_thread_t *p_intf = (intf_thread_t *)p_this;
186
187     /* Allocate instance and initialize some members */
188     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
189
190     if( p_intf->p_sys == NULL )
191     {
192         return VLC_ENOMEM;
193     }
194
195     if( Init( p_intf ) < 0 )
196     {
197         msg_Err( p_intf, "cannot initialize interface" );
198         free( p_intf->p_sys );
199         return VLC_EGENERIC;
200     }
201
202     msg_Dbg( p_intf, "interface initialized" );
203
204     p_intf->pf_run = Run;
205
206     return VLC_SUCCESS;
207 }
208
209 /*****************************************************************************
210  * Close: destroy the interface
211  *****************************************************************************/
212 static void Close ( vlc_object_t *p_this )
213 {
214     intf_thread_t *p_intf = (intf_thread_t *)p_this;
215
216     /* Destroy structure */
217     if( p_intf->p_sys )
218     {
219         free( p_intf->p_sys );
220     }
221 }
222
223
224 /*****************************************************************************
225  * Run: main loop
226  *****************************************************************************/
227 static void Run( intf_thread_t *p_intf )
228 {
229     int i_sel_res = 0;
230     int i_read    = 0;
231     int i_axis     = 0;
232     struct js_event event;
233
234     /* Main loop */
235     while( !p_intf->b_die )
236     {
237         fd_set fds;
238  
239         vlc_mutex_lock( &p_intf->change_lock );
240
241         FD_ZERO( &fds );
242         FD_SET( p_intf->p_sys->i_fd, &fds );
243
244         p_intf->p_sys->timeout.tv_sec  = 0;
245         p_intf->p_sys->timeout.tv_usec = p_intf->p_sys->i_repeat;
246
247
248         i_sel_res = select( p_intf->p_sys->i_fd + 1, &fds,
249                             NULL, NULL, &p_intf->p_sys->timeout );
250
251         p_intf->p_sys->p_input = (input_thread_t *)
252            vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
253
254         if( i_sel_res == -1 && errno != EINTR )
255         {
256             msg_Err( p_intf, "select error: %s",strerror(errno) );
257         }
258         else if(i_sel_res > 0 && FD_ISSET( p_intf->p_sys->i_fd, &fds))
259         {
260             /* We got an event */
261             memset(&event,0,sizeof(struct js_event));
262             i_read = read( p_intf->p_sys->i_fd, &event,
263                                     sizeof(struct js_event));
264             handle_event( p_intf, event ) ;
265         }
266         else if(i_sel_res == 0)
267         {
268             /*We have no event, but check if we have an action to repeat */
269             for(i_axis = 0; i_axis <= 1; i_axis++)
270             {
271                 if( p_intf->p_sys->axes[i_axis].b_trigered &&
272                     mdate()-p_intf->p_sys->axes[i_axis].l_time >
273                         p_intf->p_sys->i_wait &&
274                     p_intf->p_sys->axes[i_axis].i_value > 0 )
275                 {
276                     p_intf->p_sys->axes[i_axis].pf_actup(p_intf);
277                 }
278
279                 if( p_intf->p_sys->axes[i_axis].b_trigered &&
280                     mdate()-p_intf->p_sys->axes[i_axis].l_time >
281                           p_intf->p_sys->i_wait &&
282                     p_intf->p_sys->axes[i_axis].i_value < 0 )
283                 {
284                     p_intf->p_sys->axes[i_axis].pf_actdown(p_intf);
285                 }
286             }
287         }
288
289         if(p_intf->p_sys->p_input)
290                 vlc_object_release (p_intf->p_sys->p_input);
291
292         vlc_mutex_unlock ( &p_intf->change_lock );
293     }
294 }
295
296 /*****************************************************************************
297  * InitThread: Initialize the interface
298  *****************************************************************************/
299 static int Init( intf_thread_t * p_intf )
300 {
301     char *psz_device;
302     char *psz_parse;
303     char *psz_eof;  /* end of field */
304
305     psz_device = config_GetPsz( p_intf, "joystick-device");
306
307     if( !psz_device ) /* strange... */
308     {
309         psz_device = strdup( DEFAULT_DEVICE );
310     }
311
312     p_intf->p_sys->i_fd = open( psz_device, O_RDONLY|O_NONBLOCK );
313
314     if( p_intf->p_sys->i_fd == -1 )
315     {
316         msg_Warn( p_intf, "unable to open %s for reading: %s",
317                           psz_device, strerror(errno) );
318         return VLC_EGENERIC;
319     }
320
321     p_intf->p_sys->i_repeat = 1000 * config_GetInt( p_intf, "joystick-repeat");
322     p_intf->p_sys->i_wait = 1000 * config_GetInt( p_intf, "joystick-wait");
323
324     p_intf->p_sys->i_threshold = config_GetInt( p_intf, "motion-threshold" );
325     if(p_intf->p_sys->i_threshold > 32767 || p_intf->p_sys->i_threshold < 0 )
326         p_intf->p_sys->i_threshold = DEFAULT_THRESHOLD;
327
328     p_intf->p_sys->i_maxseek = config_GetInt( p_intf, "joystick-max-seek" );
329
330     psz_parse = config_GetPsz( p_intf, "joystick-mapping" ) ;
331
332     if( ! psz_parse)
333     {
334         msg_Warn (p_intf,"invalid mapping. aborting" );
335         return VLC_EGENERIC;
336     }
337
338     if( !strlen( psz_parse ) )
339     {
340         msg_Warn( p_intf, "invalid mapping, aborting" );
341         return VLC_EGENERIC;
342     }
343
344     p_intf->p_sys->axes[0].pf_actup  = AXIS_0_UP_ACTION;
345     p_intf->p_sys->axes[0].pf_actdown  = AXIS_0_DOWN_ACTION;
346     p_intf->p_sys->axes[1].pf_actup  = AXIS_1_UP_ACTION;
347     p_intf->p_sys->axes[1].pf_actdown  = AXIS_1_DOWN_ACTION;
348
349     p_intf->p_sys->buttons[0].pf_actdown = BUTTON_1_PRESS_ACTION;
350     p_intf->p_sys->buttons[0].pf_actup   = BUTTON_1_RELEASE_ACTION;
351     p_intf->p_sys->buttons[1].pf_actdown = BUTTON_2_PRESS_ACTION;
352     p_intf->p_sys->buttons[1].pf_actup   = BUTTON_2_RELEASE_ACTION;
353
354 /* Macro to parse the command line */
355 #define PARSE(name,function)                                                  \
356     if(!strncmp( psz_parse, name, strlen( name ) ) )                          \
357     {                                                                         \
358         psz_parse += strlen( name );                                          \
359         psz_eof = strchr( psz_parse, ',' );                                   \
360         if( !psz_eof)                                                         \
361             psz_eof = strchr( psz_parse, '}' );                               \
362         if( !psz_eof)                                                         \
363             psz_eof = psz_parse + strlen(psz_parse);                          \
364         if( psz_eof )                                                         \
365         {                                                                     \
366             *psz_eof = '\0' ;                                                 \
367         }                                                                     \
368         msg_Dbg(p_intf,"%s -> %s", name,psz_parse) ;                          \
369         if(!strcasecmp( psz_parse, "play" ) ) function = Play;                \
370         if(!strcasecmp( psz_parse, "next" ) ) function = Next;                \
371         if(!strcasecmp( psz_parse, "prev" ) ) function = Prev;                \
372         if(!strcasecmp( psz_parse, "fullscreen" ) ) function = Fullscreen;    \
373         if(!strcasecmp( psz_parse, "forward" ) ) function = Forward;          \
374         if(!strcasecmp( psz_parse, "back" ) ) function = Back;                \
375         psz_parse = psz_eof;                                                  \
376         psz_parse ++;                                                         \
377         continue;                                                             \
378     }                                                                         \
379
380     for( ; *psz_parse ; psz_parse++ )
381     {
382         PARSE("axis-0-up=",   p_intf->p_sys->axes[0].pf_actup );
383         PARSE("axis-0-down=", p_intf->p_sys->axes[0].pf_actdown );
384         PARSE("axis-1-up=",   p_intf->p_sys->axes[1].pf_actup );
385         PARSE("axis-1-down=", p_intf->p_sys->axes[1].pf_actdown );
386
387         PARSE("butt-1-up=",   p_intf->p_sys->buttons[0].pf_actup );
388         PARSE("butt-1-down=", p_intf->p_sys->buttons[0].pf_actdown );
389         PARSE("butt-2-up=",   p_intf->p_sys->buttons[1].pf_actup );
390         PARSE("butt-2-down=", p_intf->p_sys->buttons[1].pf_actdown );
391     }
392
393     p_intf->p_sys->axes[0].b_trigered = VLC_FALSE;
394     p_intf->p_sys->axes[0].l_time = 0;
395
396     p_intf->p_sys->axes[1].b_trigered = VLC_FALSE;
397     p_intf->p_sys->axes[1].l_time = 0;
398
399     return VLC_SUCCESS;
400 }
401
402 /*****************************************************************************
403  * handle_event : parse a joystick event and takes the appropriate action    *
404  *****************************************************************************/
405 static int handle_event ( intf_thread_t *p_intf, struct js_event event)
406 {
407     unsigned int i_axis;
408
409     if( event.type == JS_EVENT_AXIS )
410     {
411         /* Third axis is supposed to behave in a different way: it is a
412          * throttle, and will set a value, without triggering anything */
413         if( event.number == 2 &&
414             /* Try to avoid Parkinson joysticks */
415             abs(event.value - p_intf->p_sys->axes[2].i_value) > 200 )
416         {
417             p_intf->p_sys->axes[2].i_value = event.value;
418             msg_Dbg( p_intf, "updating volume" );
419             /* This way, the volume is between 0 and 1024 */
420             aout_VolumeSet( p_intf, (32767-event.value)/64 );
421             return 0;
422         }
423
424         p_intf->p_sys->axes[event.number].b_dowork = VLC_FALSE;
425         p_intf->p_sys->axes[event.number].i_value  = event.value;
426
427         if( abs(event.value) > p_intf->p_sys->i_threshold &&
428              p_intf->p_sys->axes[event.number].b_trigered == VLC_FALSE )
429         {
430             /* The axis entered the trigger zone. Start the event */
431             p_intf->p_sys->axes[event.number].b_trigered = VLC_TRUE;
432             p_intf->p_sys->axes[event.number].b_dowork   = VLC_TRUE;
433             p_intf->p_sys->axes[event.number].l_time     = mdate();
434         }
435         else if( abs(event.value) > p_intf->p_sys->i_threshold &&
436                  p_intf->p_sys->axes[event.number].b_trigered == VLC_TRUE )
437         {
438             /* The axis moved but remained in the trigger zone
439              * Do nothing at this time */
440         }
441         else if( abs(event.value) < p_intf->p_sys->i_threshold )
442         {
443             /* The axis is not in the trigger zone */
444             p_intf->p_sys->axes[event.number].b_trigered = VLC_FALSE;
445         }
446
447         /* Special for seeking */
448         p_intf->p_sys->f_seconds = 1 +
449             (abs(event.value) - p_intf->p_sys->i_threshold) *
450             (p_intf->p_sys->i_maxseek - 1 ) /
451             (32767 - p_intf->p_sys->i_threshold);
452
453
454         /* Handle the first two axes. */
455         for(i_axis = 0; i_axis <= 1; i_axis ++)
456         {
457             if(p_intf->p_sys->axes[i_axis].b_dowork == VLC_TRUE)
458             {
459                 if( p_intf->p_sys->axes[i_axis].i_value
460                               > p_intf->p_sys->i_threshold )
461                 {
462                     msg_Dbg(p_intf,"up for axis %i\n",i_axis);
463                     p_intf->p_sys->axes[i_axis].pf_actup(p_intf);
464                 }
465                 else if( p_intf->p_sys->axes[i_axis].i_value
466                                 < -p_intf->p_sys->i_threshold )
467                 {
468                     msg_Dbg(p_intf,"down for axis %i\n",i_axis);
469                     p_intf->p_sys->axes[i_axis].pf_actdown(p_intf);
470                 }
471
472             }
473         }
474     }
475     else if( event.type == JS_EVENT_BUTTON )
476     {
477         msg_Dbg( p_intf, "button %i %s", event.number,
478                          event.value ? "pressed" : "released" );
479         if( event.number > 1 )
480             return 0; /* Only trigger 2 buttons */
481
482         if( event.value == 1 ) /* Button pressed */
483         {
484             if( p_intf->p_sys->buttons[event.number].pf_actdown )
485                 p_intf->p_sys->buttons[event.number].pf_actdown( p_intf );
486         }
487         else /* Button released */
488         {
489             if( p_intf->p_sys->buttons[event.number].pf_actup )
490                 p_intf->p_sys->buttons[event.number].pf_actup( p_intf );
491         }
492     }
493     return 0;
494 }
495
496 /****************************************************************************
497  * The actions
498  ****************************************************************************/
499
500 /* Go to next item in the playlist */
501 static int Next( intf_thread_t *p_intf )
502 {
503     playlist_t *p_playlist;
504
505     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
506     if( p_playlist == NULL )
507     {
508         return VLC_ENOOBJ;
509     }
510
511     playlist_Next( p_playlist );
512     vlc_object_release( p_playlist );
513     return VLC_SUCCESS;
514 }
515
516 /* Go to previous item in the playlist */
517 static int Prev( intf_thread_t *p_intf )
518 {
519     playlist_t *p_playlist;
520
521     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
522     if( p_playlist == NULL )
523     {
524         return VLC_ENOOBJ;
525     }
526
527     playlist_Prev( p_playlist );
528     vlc_object_release( p_playlist );
529     return VLC_SUCCESS;
530 }
531
532 /* Seek forward */
533 static int Forward( intf_thread_t *p_intf )
534 {
535     if( p_intf->p_sys->p_input )
536     {
537         msg_Dbg( p_intf,"seeking %f seconds",p_intf->p_sys->f_seconds );
538         var_SetTime( p_intf->p_sys->p_input, "time-offset",
539                      (int64_t)p_intf->p_sys->f_seconds * I64C(1000000) );
540         return VLC_SUCCESS;
541     }
542
543     return VLC_ENOOBJ;
544 }
545
546 /* Seek backwards */
547 static int Back( intf_thread_t *p_intf )
548 {
549     if( p_intf->p_sys->p_input )
550     {
551         msg_Dbg( p_intf,"seeking -%f seconds", p_intf->p_sys->f_seconds );
552
553         var_SetTime( p_intf->p_sys->p_input, "time-offset",
554                      -(int64_t)p_intf->p_sys->f_seconds * I64C(1000000) );
555         return VLC_SUCCESS;
556     }
557
558     return VLC_ENOOBJ;
559 }
560
561 /* Toggle Play/Pause */
562 static int Play( intf_thread_t *p_intf )
563 {
564     if( p_intf->p_sys->p_input )
565     {
566         var_SetInteger( p_intf->p_sys->p_input, "state", PAUSE_S );
567         return VLC_SUCCESS;
568     }
569
570     return VLC_ENOOBJ;
571 }
572
573 /* Toggle fullscreen mode */
574 static int Fullscreen( intf_thread_t *p_intf )
575 {
576     vout_thread_t * p_vout;
577
578     p_vout = vlc_object_find(p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
579     if( p_vout )
580     {
581         p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
582         vlc_object_release(p_vout);
583     }
584     return VLC_SUCCESS;
585 }
586
587 /* dummy event. Use it if you don't wan't anything to happen */
588 static int dummy( intf_thread_t *p_intf )
589 {
590     return VLC_SUCCESS;
591 }
592