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