]> git.sesse.net Git - vlc/blob - modules/control/joystick.c
a1af79d5d6388e57720d54289ccf61aa3f5c42fe
[vlc] / modules / control / joystick.c
1 /*****************************************************************************
2  * joystick.c: control vlc with a joystick
3  *****************************************************************************
4  * Copyright (C) 2002 VideoLAN
5  * $Id: joystick.c,v 1.3 2003/12/22 02:24:51 sam Exp $
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 #include "stream_control.h"
47 #include "input_ext-intf.h"
48
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 */
55
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}"
60
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
67
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
72
73 /*****************************************************************************
74  * intf_sys_t: description and status of interface
75  *****************************************************************************/
76
77 typedef int (*action)(intf_thread_t *p_intf);
78
79 struct joy_axis_t
80 {
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
87                                  * zone ? */
88 };
89
90 struct joy_button_t
91 {
92     action      pf_actup;  /* What to do when button is released */
93     action      pf_actdown;/* What to do when button is pressed */
94 };
95
96 struct intf_sys_t
97 {
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 */
109 };
110
111 /*****************************************************************************
112  * Local prototypes.
113  *****************************************************************************/
114
115 static int  Open   ( vlc_object_t * );
116 static void Close  ( vlc_object_t * );
117 static int  Init   ( intf_thread_t *p_intf );
118
119 static int handle_event   ( intf_thread_t *p_intf, struct js_event event );
120
121
122 /* Actions */
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);
129
130 static int dummy       (intf_thread_t *p_intf);
131
132 /* Exported functions */
133 static void Run       ( intf_thread_t *p_intf );
134
135 /*****************************************************************************
136  * Module descriptor
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)" )
142
143 #define DEVICE_TEXT N_( "Joystick device" )
144 #define DEVICE_LONGTEXT N_( \
145     "The joystick device (usually /dev/js0 or /dev/input/js0)")
146
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 " \
150     "triggered, in milliseconds" )
151
152 #define WAIT_TEXT N_( "Wait time")
153 #define WAIT_LONGTEXT N_(\
154    "The time waited before the repeat starts, in milliseconds.")
155
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." )
159
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" )
164
165 vlc_module_begin();
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 );
182 vlc_module_end();
183
184 /*****************************************************************************
185  * Open: initialize interface
186  *****************************************************************************/
187 static int Open ( vlc_object_t *p_this )
188 {
189     intf_thread_t *p_intf = (intf_thread_t *)p_this;
190
191     /* Allocate instance and initialize some members */
192     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
193
194     if( p_intf->p_sys == NULL )
195     {
196         return 1 ;
197     }
198
199     p_intf->pf_run = Run;
200
201     return 0 ;
202 }
203
204 /*****************************************************************************
205  * Close: destroy the interface
206  *****************************************************************************/
207 static void Close ( vlc_object_t *p_this )
208 {
209     intf_thread_t *p_intf = (intf_thread_t *)p_this;
210
211     /* Destroy structure */
212     if(p_intf->p_sys)
213         free( p_intf->p_sys );
214 }
215
216
217 /*****************************************************************************
218  * Run: main loop
219  *****************************************************************************/
220 static void Run( intf_thread_t *p_intf )
221 {
222     int i_sel_res = 0;
223     int i_read    = 0;
224     int i_axe     = 0;
225     struct js_event event;
226
227     if( Init( p_intf ) < 0 )
228     {
229         msg_Err( p_intf, "can't initialize intf" );
230         return;
231     }
232     msg_Dbg( p_intf, "intf initialized" );
233
234     /* Main loop */
235     while( !p_intf->b_die )
236     {
237         vlc_mutex_lock( &p_intf->change_lock );
238
239         FD_ZERO( &p_intf->p_sys->fds );
240         FD_SET( p_intf->p_sys->i_fd , &p_intf->p_sys->fds );
241
242         p_intf->p_sys->timeout.tv_sec  = 0;
243         p_intf->p_sys->timeout.tv_usec = p_intf->p_sys->i_repeat;
244
245
246         i_sel_res = select ( p_intf->p_sys->i_fd+1,
247                         &p_intf->p_sys->fds,
248                         NULL,
249                         NULL,
250                         &p_intf->p_sys->timeout );
251
252         p_intf->p_sys->p_input = (input_thread_t *)
253            vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
254
255         if( i_sel_res == -1 && errno != EINTR )
256         {
257             msg_Err( p_intf, "select error: %s",strerror(errno) );
258         }
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 ) ;
266         }
267         else if(i_sel_res == 0)
268         /*We have no event, but check if we have an action to repeat */
269         {
270             for(i_axe=0;i_axe<=1;i_axe++)
271             {
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 )
276                 {
277                     p_intf->p_sys->axes[i_axe].pf_actup(p_intf);
278                 }
279
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 )
284                 {
285                     p_intf->p_sys->axes[i_axe].pf_actdown(p_intf);
286                 }
287             }
288         }
289
290         if(p_intf->p_sys->p_input)
291                 vlc_object_release (p_intf->p_sys->p_input);
292
293         vlc_mutex_unlock ( &p_intf->change_lock );
294     }
295 }
296
297 /*****************************************************************************
298  * InitThread: Initialize the interface
299  *****************************************************************************/
300 static int Init( intf_thread_t * p_intf )
301 {
302     char *psz_device;
303     char *psz_parse;
304     char *psz_eof;  /* end of field */
305
306     if( !p_intf->b_die )
307     {
308         vlc_mutex_lock( &p_intf->change_lock );
309
310         psz_device=config_GetPsz( p_intf, "joystick-device");
311
312         if(!psz_device) /* strange... */
313             psz_device = strdup( DEFAULT_DEVICE );
314
315         p_intf->p_sys->i_fd = open ( psz_device , O_RDONLY|O_NONBLOCK );
316
317         if( p_intf->p_sys->i_fd == -1 )
318         {
319             msg_Warn( p_intf, "Unable to open %s for reading: %s"
320                                 ,psz_device,strerror(errno));
321             return -1;
322         }
323
324         p_intf->p_sys->i_repeat = 1000*
325                         config_GetInt( p_intf, "joystick-repeat");
326
327         p_intf->p_sys->i_wait = 1000*
328                         config_GetInt( p_intf, "joystick-wait");
329
330         p_intf->p_sys->i_threshold =
331                         config_GetInt( p_intf, "motion-threshold" );
332
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;
336
337         p_intf->p_sys->i_maxseek =
338                         config_GetInt( p_intf, "joystick-max-seek" );
339
340
341         psz_parse = config_GetPsz( p_intf, "joystick-mapping" ) ;
342
343         if ( ! psz_parse)
344         {
345             msg_Warn (p_intf,"Invalid mapping. Aborting" );
346             return -1;
347         }
348         if( !strlen( psz_parse ) )
349         {
350             msg_Warn( p_intf, "Invalid mapping. Aborting" );
351             return -1;
352         }
353
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;
358
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;
363
364 /* Macro to parse the command line */
365 #define PARSE(name,function)                                                  \
366     if(!strncmp( psz_parse , name , strlen( name ) ) )                        \
367     {                                                                         \
368         psz_parse += strlen( name );                                          \
369         psz_eof = strchr( psz_parse , ',' );                                  \
370         if( !psz_eof)                                                         \
371             psz_eof = strchr( psz_parse, '}' );                               \
372         if( !psz_eof)                                                         \
373             psz_eof = psz_parse + strlen(psz_parse);                          \
374         if( psz_eof )                                                         \
375         {                                                                     \
376             *psz_eof = '\0' ;                                                 \
377         }                                                                     \
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;                                                  \
386         psz_parse ++;                                                         \
387         continue;                                                             \
388     }                                                                         \
389
390         while(1)
391         {
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    );
396
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 );
401
402             if( *psz_parse )
403                 psz_parse++;
404             else
405                 break;
406          }
407
408         p_intf->p_sys->axes[0].b_trigered = VLC_FALSE;
409         p_intf->p_sys->axes[0].l_time     = 0;
410
411         p_intf->p_sys->axes[1].b_trigered = VLC_FALSE;
412         p_intf->p_sys->axes[1].l_time     = 0;
413
414         vlc_mutex_unlock( &p_intf->change_lock );
415
416         return 0;
417     }
418     else
419     {
420         return -1;
421     }
422 }
423
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)
428 {
429     unsigned int i_axe;
430
431     if( event.type == JS_EVENT_AXIS )
432     {
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 )
439         {
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 );
444             return 0;
445         }
446
447         p_intf->p_sys->axes[event.number].b_dowork = VLC_FALSE;
448         p_intf->p_sys->axes[event.number].i_value  = event.value;
449
450         if( abs(event.value) > p_intf->p_sys->i_threshold &&
451              p_intf->p_sys->axes[event.number].b_trigered == VLC_FALSE)
452         {
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();
457         }
458         else if(abs(event.value) > p_intf->p_sys->i_threshold &&
459             p_intf->p_sys->axes[event.number].b_trigered == VLC_TRUE)
460         {
461         /* The axis moved but remained in the trigger zone
462          * Do nothing at this time */
463         }
464         else if ( abs(event.value) < p_intf->p_sys->i_threshold )
465         {
466         /* The axis is not in the trigger zone */
467             p_intf->p_sys->axes[event.number].b_trigered = VLC_FALSE;
468         }
469
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);
475
476
477         /* Handle the first two axes. */
478         for(i_axe = 0; i_axe <= 1 ; i_axe ++)
479         {
480             if(p_intf->p_sys->axes[i_axe].b_dowork == VLC_TRUE)
481             {
482                 if( p_intf->p_sys->axes[i_axe].i_value
483                               > p_intf->p_sys->i_threshold )
484                 {
485                     msg_Dbg(p_intf,"Up for axis %i\n",i_axe);
486                     p_intf->p_sys->axes[i_axe].pf_actup(p_intf);
487                 }
488                 else if( p_intf->p_sys->axes[i_axe].i_value
489                                 < -p_intf->p_sys->i_threshold )
490                 {
491                     msg_Dbg(p_intf,"Down for axis %i\n",i_axe);
492                     p_intf->p_sys->axes[i_axe].pf_actdown(p_intf);
493                 }
494
495             }
496         }
497     }
498     else if( event.type == JS_EVENT_BUTTON)
499     {
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 */
504         {
505             if(p_intf->p_sys->buttons[event.number].pf_actdown)
506                 p_intf->p_sys->buttons[event.number].pf_actdown(p_intf);
507         }
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);
511     }
512     return 0;
513 }
514
515 /****************************************************************************
516  * The actions
517  ****************************************************************************/
518
519 /* Go to next item in the playlist */
520 static int Next( intf_thread_t *p_intf)
521 {
522     playlist_t *p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
523                                    FIND_ANYWHERE );
524     if( p_playlist == NULL )
525     {
526         return -1;
527     }
528     playlist_Next( p_playlist );
529     vlc_object_release( p_playlist );
530     return 0;
531 }
532
533 /* Go to previous item in the playlist */
534 static int Prev( intf_thread_t *p_intf)
535 {
536     playlist_t *p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
537                                    FIND_ANYWHERE );
538     if( p_playlist == NULL )
539     {
540         return -1;
541     }
542     playlist_Prev( p_playlist );
543     vlc_object_release( p_playlist );
544     return 0;
545 }
546
547 /* Seek forward */
548 static int Forward(intf_thread_t *p_intf)
549 {
550     if(p_intf->p_sys->p_input)
551     {
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);
555     return 0;
556     }
557     return -1;
558 }
559
560 /* Seek backwards */
561 static int Back(intf_thread_t *p_intf)
562 {
563     if(p_intf->p_sys->p_input)
564     {
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 );
568         return 0;
569     }
570     return -1;
571 }
572
573 /* Toggle Play/Pause */
574 static int Play(intf_thread_t *p_intf)
575 {
576     if(p_intf->p_sys->p_input)
577     {
578         input_SetStatus( p_intf->p_sys->p_input, INPUT_STATUS_PAUSE );
579         return 0;
580     }
581     return -1;
582 }
583
584 /* Toggle fullscreen mode */
585 static int Fullscreen(intf_thread_t *p_intf)
586 {
587     vout_thread_t * p_vout=vlc_object_find(p_intf,
588                           VLC_OBJECT_VOUT, FIND_ANYWHERE );
589     if(p_vout)
590     {
591         p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
592         vlc_object_release(p_vout);
593     }
594     return 0;
595 }
596
597 /* dummy event. Use it if you don't wan't anything to happen */
598 static int dummy(intf_thread_t *p_intf)
599 {
600    return 0;
601 }