aee43cb7cdc5becd608f2a16b5184871f69fc3c1
[vlc.git] / modules / control / gestures.c
1 /*****************************************************************************
2  * gestures.c: control vlc with mouse gestures
3  *****************************************************************************
4  * Copyright (C) 2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc/vlc.h>
33 #include <vlc_interface.h>
34 #include <vlc_vout.h>
35 #include <vlc_aout.h>
36 #include <vlc_playlist.h>
37
38 #ifdef HAVE_UNISTD_H
39 #    include <unistd.h>
40 #endif
41
42 /*****************************************************************************
43  * intf_sys_t: description and status of interface
44  *****************************************************************************/
45 struct intf_sys_t
46 {
47     vlc_object_t *      p_vout;
48     vlc_bool_t          b_got_gesture;
49     vlc_bool_t          b_button_pressed;
50     int                 i_mouse_x, i_mouse_y;
51     int                 i_last_x, i_last_y;
52     unsigned int        i_pattern;
53     int                 i_num_gestures;
54     int                 i_threshold;
55     int                 i_button_mask;
56 };
57
58 /*****************************************************************************
59  * Local prototypes.
60  *****************************************************************************/
61 #define UP 1
62 #define DOWN 2
63 #define LEFT 3
64 #define RIGHT 4
65 #define NONE 0
66 #define GESTURE( a, b, c, d ) (a | ( b << 4 ) | ( c << 8 ) | ( d << 12 ))
67
68 int  E_(Open)   ( vlc_object_t * );
69 void E_(Close)  ( vlc_object_t * );
70 static int  InitThread     ( intf_thread_t *p_intf );
71 static void EndThread      ( intf_thread_t *p_intf );
72 static int  MouseEvent     ( vlc_object_t *, char const *,
73                              vlc_value_t, vlc_value_t, void * );
74
75 /* Exported functions */
76 static void RunIntf        ( intf_thread_t *p_intf );
77
78 /*****************************************************************************
79  * Module descriptor
80  *****************************************************************************/
81 #define THRESHOLD_TEXT N_( "Motion threshold (10-100)" )
82 #define THRESHOLD_LONGTEXT N_( \
83     "Amount of movement required for a mouse gesture to be recorded." )
84
85 #define BUTTON_TEXT N_( "Trigger button" )
86 #define BUTTON_LONGTEXT N_( \
87     "Trigger button for mouse gestures." )
88
89 static const char *button_list[] = { "left", "middle", "right" };
90 static const char *button_list_text[] =
91                                    { N_("Left"), N_("Middle"), N_("Right") };
92
93 vlc_module_begin();
94     set_shortname( _("Gestures"));
95     set_category( CAT_INTERFACE );
96     set_subcategory( SUBCAT_INTERFACE_CONTROL );
97     add_integer( "gestures-threshold", 30, NULL,
98                  THRESHOLD_TEXT, THRESHOLD_LONGTEXT, VLC_TRUE );
99     add_string( "gestures-button", "right", NULL,
100                 BUTTON_TEXT, BUTTON_LONGTEXT, VLC_FALSE );
101         change_string_list( button_list, button_list_text, 0 );
102     set_description( _("Mouse gestures control interface") );
103
104     set_capability( "interface", 0 );
105     set_callbacks( E_(Open), E_(Close) );
106 vlc_module_end();
107
108 /*****************************************************************************
109  * OpenIntf: initialize interface
110  *****************************************************************************/
111 int E_(Open) ( vlc_object_t *p_this )
112 {
113     intf_thread_t *p_intf = (intf_thread_t *)p_this;
114
115     /* Allocate instance and initialize some members */
116     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
117     if( p_intf->p_sys == NULL )
118     {
119         return( 1 );
120     };
121
122     p_intf->pf_run = RunIntf;
123
124     return( 0 );
125 }
126
127 /*****************************************************************************
128  * gesture: return a subpattern within a pattern
129  *****************************************************************************/
130 static int gesture( int i_pattern, int i_num )
131 {
132     return ( i_pattern >> ( i_num * 4 ) ) & 0xF;
133 }
134
135 /*****************************************************************************
136  * input_from_playlist: don't forget to release the return value
137  *  Also this function should really be available from core.
138  *****************************************************************************/
139 static input_thread_t * input_from_playlist ( playlist_t *p_playlist )
140 {
141     input_thread_t * p_input;
142
143     PL_LOCK;
144     p_input = p_playlist->p_input;
145     if( p_input )
146         vlc_object_yield( p_input );
147     PL_UNLOCK;
148
149     return p_input;
150 }
151
152 /*****************************************************************************
153  * CloseIntf: destroy dummy interface
154  *****************************************************************************/
155 void E_(Close) ( vlc_object_t *p_this )
156 {
157     intf_thread_t *p_intf = (intf_thread_t *)p_this;
158
159     /* Destroy structure */
160     free( p_intf->p_sys );
161 }
162
163
164 /*****************************************************************************
165  * RunIntf: main loop
166  *****************************************************************************/
167 static void RunIntf( intf_thread_t *p_intf )
168 {
169     playlist_t * p_playlist = NULL;
170
171     vlc_mutex_lock( &p_intf->change_lock );
172     p_intf->p_sys->p_vout = NULL;
173     vlc_mutex_unlock( &p_intf->change_lock );
174
175     if( InitThread( p_intf ) < 0 )
176     {
177         msg_Err( p_intf, "can't initialize interface thread" );
178         return;
179     }
180     msg_Dbg( p_intf, "interface thread initialized" );
181
182     /* Main loop */
183     while( !intf_ShouldDie( p_intf ) )
184     {
185         vlc_mutex_lock( &p_intf->change_lock );
186
187         /*
188          * mouse cursor
189          */
190         if( p_intf->p_sys->b_got_gesture )
191         {
192             vlc_value_t val;
193             int i_interval = 0;
194             /* Do something */
195             /* If you modify this, please try to follow this convention:
196                Start with LEFT, RIGHT for playback related commands
197                and UP, DOWN, for other commands */
198             switch( p_intf->p_sys->i_pattern )
199             {
200             case LEFT:
201                 i_interval = config_GetInt( p_intf , "short-jump-size" );
202                 if ( i_interval > 0 ) {
203                     val.i_time = ( (mtime_t)( -i_interval ) * 1000000L);
204                     var_Set( p_intf, "time-offset", val );
205                 }
206                 msg_Dbg(p_intf, "Go backward in the movie!");
207                 break;
208             case RIGHT:
209                 i_interval = config_GetInt( p_intf , "short-jump-size" );
210                 if ( i_interval > 0 ) {
211                     val.i_time = ( (mtime_t)( i_interval ) * 1000000L);
212                     var_Set( p_intf, "time-offset", val );
213                 }
214                 msg_Dbg(p_intf, "Go forward in the movie!");
215                 break;
216             case GESTURE(LEFT,UP,NONE,NONE):
217                 /*FIXME BF*/
218                 msg_Dbg(p_intf, "Going slower.");
219                 break;
220             case GESTURE(RIGHT,UP,NONE,NONE):
221                 /*FIXME FF*/
222                 msg_Dbg(p_intf, "Going faster.");
223                 break;
224             case GESTURE(LEFT,RIGHT,NONE,NONE):
225             case GESTURE(RIGHT,LEFT,NONE,NONE):
226                 {
227                     input_thread_t * p_input;
228                     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
229                                               FIND_ANYWHERE );
230
231                    if( !p_playlist )
232                         break;
233
234                     p_input = input_from_playlist( p_playlist );
235                     vlc_object_release( p_playlist );
236  
237                     if( !p_input )
238                         break;
239  
240                     val.i_int = PLAYING_S;
241                     if( p_input )
242                     {
243                         var_Get( p_input, "state", &val);
244                         if( val.i_int == PAUSE_S )
245                         {
246                             val.i_int = PLAYING_S;
247                         }
248                         else
249                         {
250                             val.i_int = PAUSE_S;
251                         }
252                         var_Set( p_input, "state", val);
253                     }
254                     msg_Dbg(p_intf, "Play/Pause");
255                     vlc_object_release( p_input );
256                 }
257                 break;
258             case GESTURE(LEFT,DOWN,NONE,NONE):
259                 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
260                                               FIND_ANYWHERE );
261                 if( p_playlist == NULL )
262                 {
263                     break;
264                 }
265
266                 playlist_Prev( p_playlist );
267                 vlc_object_release( p_playlist );
268                 break;
269             case GESTURE(RIGHT,DOWN,NONE,NONE):
270                 p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
271                                               FIND_ANYWHERE );
272                 if( p_playlist == NULL )
273                 {
274                     break;
275                 }
276
277                 playlist_Next( p_playlist );
278                 vlc_object_release( p_playlist );
279                 break;
280             case UP:
281                 {
282                     audio_volume_t i_newvol;
283                     aout_VolumeUp( p_intf, 1, &i_newvol );
284                     msg_Dbg(p_intf, "Louder");
285                 }
286                 break;
287             case DOWN:
288                 {
289                     audio_volume_t i_newvol;
290                     aout_VolumeDown( p_intf, 1, &i_newvol );
291                     msg_Dbg(p_intf, "Quieter");
292                 }
293                 break;
294             case GESTURE(UP,DOWN,NONE,NONE):
295             case GESTURE(DOWN,UP,NONE,NONE):
296                 {
297                     audio_volume_t i_newvol = -1;
298                     aout_VolumeMute( p_intf, &i_newvol );
299                     msg_Dbg(p_intf, "Mute sound");
300                 }
301                 break;
302             case GESTURE(UP,RIGHT,NONE,NONE):
303                 {
304                    input_thread_t * p_input;
305                    vlc_value_t val, list, list2;
306                    int i_count, i;
307
308                     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
309                                               FIND_ANYWHERE );
310
311                    if( !p_playlist )
312                         break;
313
314                     p_input = input_from_playlist( p_playlist );
315
316                     vlc_object_release( p_playlist );
317
318                     if( !p_input )
319                         break;
320
321                    var_Get( p_input, "audio-es", &val );
322                    var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES,
323                                &list, &list2 );
324                    i_count = list.p_list->i_count;
325                    if( i_count <= 1 )
326                    {
327                        vlc_object_release( p_input );
328                        break;
329                    }
330                    for( i = 0; i < i_count; i++ )
331                    {
332                        if( val.i_int == list.p_list->p_values[i].i_int )
333                        {
334                            break;
335                        }
336                    }
337                    /* value of audio-es was not in choices list */
338                    if( i == i_count )
339                    {
340                        msg_Warn( p_input,
341                                "invalid current audio track, selecting 0" );
342                        var_Set( p_input, "audio-es",
343                                list.p_list->p_values[0] );
344                        i = 0;
345                    }
346                    else if( i == i_count - 1 )
347                    {
348                        var_Set( p_input, "audio-es",
349                                list.p_list->p_values[1] );
350                        i = 1;
351                    }
352                    else
353                    {
354                        var_Set( p_input, "audio-es",
355                                list.p_list->p_values[i+1] );
356                        i++;
357                    }
358                    vlc_object_release( p_input );
359                 }
360                 break;
361             case GESTURE(DOWN,RIGHT,NONE,NONE):
362                 {
363                     input_thread_t * p_input;
364                     vlc_value_t val, list, list2;
365                     int i_count, i;
366
367                     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
368                                               FIND_ANYWHERE );
369
370                     if( !p_playlist )
371                         break;
372
373                     p_input = input_from_playlist( p_playlist );
374                     vlc_object_release( p_playlist );
375
376                     if( !p_input )
377                         break;
378
379                     var_Get( p_input, "spu-es", &val );
380
381                     var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
382                             &list, &list2 );
383                     i_count = list.p_list->i_count;
384                     if( i_count <= 1 )
385                     {
386                         vlc_object_release( p_input );
387                         break;
388                     }
389                     for( i = 0; i < i_count; i++ )
390                     {
391                         if( val.i_int == list.p_list->p_values[i].i_int )
392                         {
393                             break;
394                         }
395                     }
396                     /* value of spu-es was not in choices list */
397                     if( i == i_count )
398                     {
399                         msg_Warn( p_input,
400                                 "invalid current subtitle track, selecting 0" );
401                         var_Set( p_input, "spu-es", list.p_list->p_values[0] );
402                         i = 0;
403                     }
404                     else if( i == i_count - 1 )
405                     {
406                         var_Set( p_input, "spu-es", list.p_list->p_values[0] );
407                         i = 0;
408                     }
409                     else
410                     {
411                         var_Set( p_input, "spu-es",
412                                 list.p_list->p_values[i+1] );
413                         i = i + 1;
414                     }
415                     vlc_object_release( p_input );
416                 }
417                 break;
418             case GESTURE(UP,LEFT,NONE,NONE):
419                 if (p_intf->p_sys->p_vout )
420                 {
421                     ((vout_thread_t *)p_intf->p_sys->p_vout)->i_changes |=
422                         VOUT_FULLSCREEN_CHANGE;
423                 }
424                 break;
425             case GESTURE(DOWN,LEFT,NONE,NONE):
426                 /* FIXME: Should close the vout!"*/
427                 vlc_object_kill( p_intf->p_libvlc );
428                 break;
429             case GESTURE(DOWN,LEFT,UP,RIGHT):
430             case GESTURE(UP,RIGHT,DOWN,LEFT):
431                 msg_Dbg(p_intf, "a square was drawn!" );
432                 break;
433             default:
434                 break;
435             }
436             p_intf->p_sys->i_num_gestures = 0;
437             p_intf->p_sys->i_pattern = 0;
438             p_intf->p_sys->b_got_gesture = VLC_FALSE;
439         }
440
441         /*
442          * video output
443          */
444         if( p_intf->p_sys->p_vout && p_intf->p_sys->p_vout->b_die )
445         {
446             var_DelCallback( p_intf->p_sys->p_vout, "mouse-moved",
447                              MouseEvent, p_intf );
448             var_DelCallback( p_intf->p_sys->p_vout, "mouse-button-down",
449                              MouseEvent, p_intf );
450             vlc_object_release( p_intf->p_sys->p_vout );
451             p_intf->p_sys->p_vout = NULL;
452         }
453
454         if( p_intf->p_sys->p_vout == NULL )
455         {
456             p_intf->p_sys->p_vout = vlc_object_find( p_intf,
457                                       VLC_OBJECT_VOUT, FIND_ANYWHERE );
458             if( p_intf->p_sys->p_vout )
459             {
460                 var_AddCallback( p_intf->p_sys->p_vout, "mouse-moved",
461                                  MouseEvent, p_intf );
462                 var_AddCallback( p_intf->p_sys->p_vout, "mouse-button-down",
463                                  MouseEvent, p_intf );
464             }
465         }
466
467         vlc_mutex_unlock( &p_intf->change_lock );
468
469         /* Wait a bit */
470         msleep( INTF_IDLE_SLEEP );
471     }
472
473     EndThread( p_intf );
474 }
475
476 /*****************************************************************************
477  * InitThread:
478  *****************************************************************************/
479 static int InitThread( intf_thread_t * p_intf )
480 {
481     char *psz_button;
482     /* we might need some locking here */
483     if( !intf_ShouldDie( p_intf ) )
484     {
485         /* p_intf->change_lock locking strategy:
486          * - Every access to p_intf->p_sys are locked threw p_intf->change_lock
487          * - make sure there won't be  cross increment/decrement ref count
488          *   of p_intf->p_sys members p_intf->change_lock should be locked
489          *   during those operations */
490         vlc_mutex_lock( &p_intf->change_lock );
491
492         p_intf->p_sys->b_got_gesture = VLC_FALSE;
493         p_intf->p_sys->b_button_pressed = VLC_FALSE;
494         p_intf->p_sys->i_threshold =
495                      config_GetInt( p_intf, "gestures-threshold" );
496         psz_button = config_GetPsz( p_intf, "gestures-button" );
497         if ( !strcmp( psz_button, "left" ) )
498         {
499             p_intf->p_sys->i_button_mask = 1;
500         }
501         else if ( !strcmp( psz_button, "middle" ) )
502         {
503             p_intf->p_sys->i_button_mask = 2;
504         }
505         else if ( !strcmp( psz_button, "right" ) )
506         {
507             p_intf->p_sys->i_button_mask = 4;
508         }
509
510         p_intf->p_sys->i_pattern = 0;
511         p_intf->p_sys->i_num_gestures = 0;
512         vlc_mutex_unlock( &p_intf->change_lock );
513
514         return 0;
515     }
516     else
517     {
518         return -1;
519     }
520 }
521
522 /*****************************************************************************
523  * EndThread:
524  *****************************************************************************/
525 static void EndThread( intf_thread_t * p_intf )
526 {
527     vlc_mutex_lock( &p_intf->change_lock );
528
529     if( p_intf->p_sys->p_vout )
530     {
531         var_DelCallback( p_intf->p_sys->p_vout, "mouse-moved",
532                          MouseEvent, p_intf );
533         var_DelCallback( p_intf->p_sys->p_vout, "mouse-button-down",
534                          MouseEvent, p_intf );
535         vlc_object_release( p_intf->p_sys->p_vout );
536     }
537
538     vlc_mutex_unlock( &p_intf->change_lock );
539 }
540
541 /*****************************************************************************
542  * MouseEvent: callback for mouse events
543  *****************************************************************************/
544 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
545                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
546 {
547     VLC_UNUSED(p_this); VLC_UNUSED(oldval);
548     vlc_value_t val;
549     int pattern = 0;
550
551     signed int i_horizontal, i_vertical;
552     intf_thread_t *p_intf = (intf_thread_t *)p_data;
553
554     vlc_mutex_lock( &p_intf->change_lock );
555
556     /* don't process new gestures before the last events are processed */
557     if( p_intf->p_sys->b_got_gesture )
558     {
559         vlc_mutex_unlock( &p_intf->change_lock );
560         return VLC_SUCCESS;
561     }
562
563     if( !strcmp(psz_var, "mouse-moved" ) && p_intf->p_sys->b_button_pressed )
564     {
565         var_Get( p_intf->p_sys->p_vout, "mouse-x", &val );
566         p_intf->p_sys->i_mouse_x = val.i_int;
567         var_Get( p_intf->p_sys->p_vout, "mouse-y", &val );
568         p_intf->p_sys->i_mouse_y = val.i_int;
569         i_horizontal = p_intf->p_sys->i_mouse_x -
570             p_intf->p_sys->i_last_x;
571         i_horizontal = i_horizontal / p_intf->p_sys->i_threshold;
572         i_vertical = p_intf->p_sys->i_mouse_y
573             - p_intf->p_sys->i_last_y;
574         i_vertical = i_vertical / p_intf->p_sys->i_threshold;
575
576         if( i_horizontal < 0 )
577         {
578             msg_Dbg( p_intf, "left gesture (%d)", i_horizontal );
579             pattern = LEFT;
580         }
581         else if( i_horizontal > 0 )
582         {
583             msg_Dbg( p_intf, "right gesture (%d)", i_horizontal );
584             pattern = RIGHT;
585         }
586         if( i_vertical < 0 )
587         {
588             msg_Dbg( p_intf, "up gesture (%d)", i_vertical );
589             pattern = UP;
590         }
591         else if( i_vertical > 0 )
592         {
593             msg_Dbg( p_intf, "down gesture (%d)", i_vertical );
594             pattern = DOWN;
595         }
596         if( pattern )
597         {
598             p_intf->p_sys->i_last_y = p_intf->p_sys->i_mouse_y;
599             p_intf->p_sys->i_last_x = p_intf->p_sys->i_mouse_x;
600             if( gesture( p_intf->p_sys->i_pattern,
601                          p_intf->p_sys->i_num_gestures - 1 ) != pattern )
602             {
603                 p_intf->p_sys->i_pattern |=
604                     pattern << ( p_intf->p_sys->i_num_gestures * 4 );
605                 p_intf->p_sys->i_num_gestures++;
606             }
607         }
608
609     }
610     if( !strcmp( psz_var, "mouse-button-down" )
611         && newval.i_int & p_intf->p_sys->i_button_mask
612         && !p_intf->p_sys->b_button_pressed )
613     {
614         p_intf->p_sys->b_button_pressed = VLC_TRUE;
615         var_Get( p_intf->p_sys->p_vout, "mouse-x", &val );
616         p_intf->p_sys->i_last_x = val.i_int;
617         var_Get( p_intf->p_sys->p_vout, "mouse-y", &val );
618         p_intf->p_sys->i_last_y = val.i_int;
619     }
620     if( !strcmp( psz_var, "mouse-button-down" )
621         && !( newval.i_int & p_intf->p_sys->i_button_mask )
622         && p_intf->p_sys->b_button_pressed )
623     {
624         p_intf->p_sys->b_button_pressed = VLC_FALSE;
625         p_intf->p_sys->b_got_gesture = VLC_TRUE;
626     }
627
628     vlc_mutex_unlock( &p_intf->change_lock );
629
630     return VLC_SUCCESS;
631 }