mosaic: use vlc_picture_chain_HasNext to check if we can drop a picture
[vlc.git] / modules / control / gestures.c
1 /*****************************************************************************
2  * gestures.c: control vlc with mouse gestures
3  *****************************************************************************
4  * Copyright (C) 2004-2009 the VideoLAN team
5  *
6  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 /*****************************************************************************
24  * Preamble
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_interface.h>
35 #include <vlc_vout.h>
36 #include <vlc_player.h>
37 #include <vlc_playlist.h>
38 #include <vlc_vector.h>
39 #include <assert.h>
40
41 /*****************************************************************************
42  * intf_sys_t: description and status of interface
43  *****************************************************************************/
44
45 typedef struct VLC_VECTOR(vout_thread_t *) vout_vector;
46 struct intf_sys_t
47 {
48     vlc_playlist_t         *playlist;
49     vlc_player_listener_id *player_listener;
50     vlc_mutex_t             lock;
51     vout_vector             vout_vector;
52     bool                    b_button_pressed;
53     int                     i_last_x, i_last_y;
54     unsigned int            i_pattern;
55     unsigned int            i_num_gestures;
56     int                     i_threshold;
57     int                     i_button_mask;
58 };
59
60 /*****************************************************************************
61  * Local prototypes.
62  *****************************************************************************/
63 #define UP 1
64 #define DOWN 2
65 #define LEFT 3
66 #define RIGHT 4
67 #define NONE 0
68 #define GESTURE( a, b, c, d ) (a | ( b << 4 ) | ( c << 8 ) | ( d << 12 ))
69
70 static int  Open   ( vlc_object_t * );
71 static void Close  ( vlc_object_t * );
72
73 /*****************************************************************************
74  * Module descriptor
75  *****************************************************************************/
76 #define THRESHOLD_TEXT N_( "Motion threshold (10-100)" )
77 #define THRESHOLD_LONGTEXT N_( \
78     "Amount of movement required for a mouse gesture to be recorded." )
79
80 #define BUTTON_TEXT N_( "Trigger button" )
81 #define BUTTON_LONGTEXT N_( \
82     "Trigger button for mouse gestures." )
83
84 #define BUTTON_DEFAULT "left"
85
86 static const char *const button_list[] = { "left", "middle", "right" };
87 static const char *const button_list_text[] =
88                                    { N_("Left"), N_("Middle"), N_("Right") };
89
90 vlc_module_begin ()
91     set_shortname( N_("Gestures"))
92     set_category( CAT_INTERFACE )
93     set_subcategory( SUBCAT_INTERFACE_CONTROL )
94     add_integer( "gestures-threshold", 30,
95                  THRESHOLD_TEXT, THRESHOLD_LONGTEXT, true )
96     add_string( "gestures-button", BUTTON_DEFAULT,
97                 BUTTON_TEXT, BUTTON_LONGTEXT, false )
98         change_string_list( button_list, button_list_text )
99     set_description( N_("Mouse gestures control interface") )
100
101     set_capability( "interface", 0 )
102     set_callbacks( Open, Close )
103 vlc_module_end ()
104
105 static void player_on_vout_changed(vlc_player_t *player,
106                                    enum vlc_player_vout_action action,
107                                    vout_thread_t *vout,
108                                    enum vlc_vout_order order,
109                                    vlc_es_id_t *es_id,
110                                    void *data);
111 static int MovedEvent( vlc_object_t *, char const *,
112                        vlc_value_t, vlc_value_t, void * );
113 static int ButtonEvent( vlc_object_t *, char const *,
114                         vlc_value_t, vlc_value_t, void * );
115
116 /*****************************************************************************
117  * OpenIntf: initialize interface
118  *****************************************************************************/
119 static int Open ( vlc_object_t *p_this )
120 {
121     intf_thread_t *p_intf = (intf_thread_t *)p_this;
122
123     /* Allocate instance and initialize some members */
124     intf_sys_t *p_sys = p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
125     if( unlikely(p_sys == NULL) )
126         return VLC_ENOMEM;
127
128     // Configure the module
129     p_sys->playlist = vlc_intf_GetMainPlaylist(p_intf);
130     vlc_mutex_init( &p_sys->lock );
131     vlc_vector_init(&p_sys->vout_vector);
132
133     static const struct vlc_player_cbs cbs =
134     {
135         .on_vout_changed = player_on_vout_changed,
136     };
137     vlc_player_t *player = vlc_playlist_GetPlayer(p_sys->playlist);
138     vlc_player_Lock(player);
139     p_sys->player_listener = vlc_player_AddListener(player, &cbs, p_intf);
140     vlc_player_Unlock(player);
141     if (!p_sys->player_listener)
142         goto error;
143
144     p_sys->b_button_pressed = false;
145     p_sys->i_threshold = var_InheritInteger( p_intf, "gestures-threshold" );
146
147     // Choose the tight button to use
148     char *psz_button = var_InheritString( p_intf, "gestures-button" );
149     if( psz_button && !strcmp( psz_button, "left" ) )
150         p_sys->i_button_mask = 1;
151     else if( psz_button && !strcmp( psz_button, "middle" ) )
152         p_sys->i_button_mask = 2;
153     else // psz_button == "right"
154         p_sys->i_button_mask = 4;
155     free( psz_button );
156
157     p_sys->i_pattern = 0;
158     p_sys->i_num_gestures = 0;
159
160     return VLC_SUCCESS;
161
162 error:
163     vlc_vector_clear(&p_sys->vout_vector);
164     free(p_sys);
165     return VLC_EGENERIC;
166 }
167
168 /*****************************************************************************
169  * gesture: return a subpattern within a pattern
170  *****************************************************************************/
171 static inline unsigned gesture( unsigned i_pattern, unsigned i_num )
172 {
173     return ( i_pattern >> ( i_num * 4 ) ) & 0xF;
174 }
175
176 /*****************************************************************************
177  * CloseIntf: destroy dummy interface
178  *****************************************************************************/
179 static void Close ( vlc_object_t *p_this )
180 {
181     intf_thread_t *p_intf = (intf_thread_t *)p_this;
182     intf_sys_t *p_sys = p_intf->p_sys;
183
184     vlc_player_t *player = vlc_playlist_GetPlayer(p_sys->playlist);
185     vlc_player_Lock(player);
186     vlc_player_RemoveListener(player, p_sys->player_listener);
187     vlc_player_Unlock(player);
188
189     /* Destroy the callbacks (the order matters!) */
190     vout_thread_t *vout;
191     vlc_vector_foreach(vout, &p_sys->vout_vector)
192     {
193         var_DelCallback(vout, "mouse-moved", MovedEvent, p_intf);
194         var_DelCallback(vout, "mouse-button-down", ButtonEvent, p_intf);
195         vout_Release(vout);
196     }
197     vlc_vector_clear(&p_sys->vout_vector);
198
199     /* Destroy structure */
200     free( p_sys );
201 }
202
203 static void ProcessGesture( intf_thread_t *p_intf )
204 {
205     intf_sys_t *p_sys = p_intf->p_sys;
206     vlc_playlist_t *playlist = p_sys->playlist;
207     vlc_player_t *player = vlc_playlist_GetPlayer(playlist);
208
209     /* Do something */
210     /* If you modify this, please try to follow this convention:
211        Start with LEFT, RIGHT for playback related commands
212        and UP, DOWN, for other commands */
213     vlc_playlist_Lock(playlist);
214     switch( p_sys->i_pattern )
215     {
216         case LEFT:
217         case RIGHT:
218         {
219             msg_Dbg( p_intf, "Go %s in the movie!",
220                      p_sys->i_pattern == LEFT ? "backward" : "forward" );
221             int it = var_InheritInteger( p_intf , "short-jump-size" );
222             if( it > 0 )
223             {
224                 vlc_tick_t jump = p_sys->i_pattern == LEFT ? -it : it;
225                 vlc_player_JumpTime(player, vlc_tick_from_sec(jump));
226             }
227             break;
228         }
229
230         case GESTURE(LEFT,UP,NONE,NONE):
231             msg_Dbg( p_intf, "Going slower." );
232             vlc_player_DecrementRate(player);
233             break;
234
235         case GESTURE(RIGHT,UP,NONE,NONE):
236             msg_Dbg( p_intf, "Going faster." );
237             vlc_player_IncrementRate(player);
238             break;
239
240         case GESTURE(LEFT,RIGHT,NONE,NONE):
241         case GESTURE(RIGHT,LEFT,NONE,NONE):
242         {
243             msg_Dbg( p_intf, "Play/Pause" );
244             vlc_player_TogglePause(player);
245             break;
246         }
247
248         case GESTURE(LEFT,DOWN,NONE,NONE):
249             vlc_playlist_Prev(playlist);
250             break;
251
252         case GESTURE(RIGHT,DOWN,NONE,NONE):
253             vlc_playlist_Next(playlist);
254             break;
255
256         case UP:
257             msg_Dbg(p_intf, "Louder");
258             vlc_player_aout_IncrementVolume(player, 1, NULL);
259             break;
260
261         case DOWN:
262             msg_Dbg(p_intf, "Quieter");
263             vlc_player_aout_DecrementVolume(player, 1, NULL);
264             break;
265
266         case GESTURE(UP,DOWN,NONE,NONE):
267         case GESTURE(DOWN,UP,NONE,NONE):
268             msg_Dbg( p_intf, "Mute sound" );
269             vlc_player_aout_ToggleMute(player);
270             break;
271
272         case GESTURE(UP,RIGHT,NONE,NONE):
273         case GESTURE(DOWN,RIGHT,NONE,NONE):
274         {
275             enum es_format_category_e cat =
276                 p_sys->i_pattern == GESTURE(UP,RIGHT,NONE,NONE) ?
277                 AUDIO_ES : SPU_ES;
278             vlc_player_SelectNextTrack(player, cat);
279             break;
280         }
281
282         case GESTURE(UP,LEFT,NONE,NONE):
283         {
284             vlc_player_vout_ToggleFullscreen(player);
285             break;
286         }
287
288         case GESTURE(DOWN,LEFT,NONE,NONE):
289             vlc_playlist_Unlock(playlist);
290             /* FIXME: Should close the vout!"*/
291             libvlc_Quit( vlc_object_instance(p_intf) );
292             break;
293
294         case GESTURE(DOWN,LEFT,UP,RIGHT):
295         case GESTURE(UP,RIGHT,DOWN,LEFT):
296             msg_Dbg( p_intf, "a square was drawn!" );
297             break;
298     }
299     vlc_playlist_Unlock(playlist);
300
301     p_sys->i_num_gestures = 0;
302     p_sys->i_pattern = 0;
303 }
304
305 static int MovedEvent(vlc_object_t *this, char const *psz_var,
306                       vlc_value_t oldval, vlc_value_t newval, void *data)
307 {
308     VLC_UNUSED(this); VLC_UNUSED(psz_var); VLC_UNUSED(oldval);
309
310     intf_thread_t *intf = data;
311     intf_sys_t *sys = intf->p_sys;
312
313     vlc_mutex_lock(&sys->lock);
314     if (sys->b_button_pressed)
315     {
316         unsigned int pattern = 0;
317         int xdelta = newval.coords.x - sys->i_last_x;
318         xdelta /= sys->i_threshold;
319         int ydelta = newval.coords.y - sys->i_last_y;
320         ydelta /= sys->i_threshold;
321
322         char const *dir;
323         unsigned int delta;
324         if (abs(xdelta) > abs(ydelta))
325         {
326             pattern = xdelta < 0 ? LEFT : RIGHT;
327             dir = xdelta < 0 ? "left" : "right";
328             delta = abs(xdelta);
329         }
330         else if (abs(ydelta) > 0)
331         {
332             pattern = ydelta < 0 ? UP : DOWN;
333             dir = ydelta < 0 ? "up" : "down";
334             delta = abs(ydelta);
335         }
336
337         if (pattern)
338         {
339             sys->i_last_x = newval.coords.x;
340             sys->i_last_y = newval.coords.y;
341             if (sys->i_num_gestures > 0 &&
342                 gesture(sys->i_pattern, sys->i_num_gestures - 1) != pattern)
343             {
344                 sys->i_pattern |= pattern << (sys->i_num_gestures * 4);
345                 sys->i_num_gestures++;
346             }
347             else if (sys->i_num_gestures == 0)
348             {
349                 sys->i_pattern = pattern;
350                 sys->i_num_gestures = 1;
351             }
352             msg_Dbg(intf, "%s gesture (%u)", dir, delta);
353         }
354     }
355     vlc_mutex_unlock(&sys->lock);
356     return VLC_SUCCESS;
357 }
358
359 static int ButtonEvent( vlc_object_t *p_this, char const *psz_var,
360                         vlc_value_t oldval, vlc_value_t val, void *p_data )
361 {
362     intf_thread_t *p_intf = p_data;
363     intf_sys_t *p_sys = p_intf->p_sys;
364
365     (void) psz_var; (void) oldval;
366
367     vlc_mutex_lock( &p_sys->lock );
368     if( val.i_int & p_sys->i_button_mask )
369     {
370         if( !p_sys->b_button_pressed )
371         {
372             p_sys->b_button_pressed = true;
373             var_GetCoords( p_this, "mouse-moved",
374                            &p_sys->i_last_x, &p_sys->i_last_y );
375         }
376     }
377     else
378     {
379         if( p_sys->b_button_pressed )
380         {
381             p_sys->b_button_pressed = false;
382             ProcessGesture( p_intf );
383         }
384     }
385     vlc_mutex_unlock( &p_sys->lock );
386
387     return VLC_SUCCESS;
388 }
389
390 static void
391 player_on_vout_changed(vlc_player_t *player,
392                        enum vlc_player_vout_action action,
393                        vout_thread_t *vout,
394                        enum vlc_vout_order order,
395                        vlc_es_id_t *es_id, void *data)
396 {
397     VLC_UNUSED(player); VLC_UNUSED(order);
398     intf_thread_t *intf = data;
399     intf_sys_t *sys = intf->p_sys;
400
401     if (vlc_es_id_GetCat(es_id) != VIDEO_ES)
402         return;
403
404     switch (action)
405     {
406         case VLC_PLAYER_VOUT_STARTED:
407             if (vlc_vector_push(&sys->vout_vector, vout))
408             {
409                 vout_Hold(vout);
410                 var_AddCallback(vout, "mouse-moved", MovedEvent, intf);
411                 var_AddCallback(vout, "mouse-button-down", ButtonEvent, intf);
412             }
413             break;
414         case VLC_PLAYER_VOUT_STOPPED:
415             for (size_t i = 0; i < sys->vout_vector.size; ++i)
416             {
417                 vout_thread_t *it = sys->vout_vector.data[i];
418                 if (it == vout)
419                 {
420                     vlc_vector_remove(&sys->vout_vector, i);
421                     var_DelCallback(vout, "mouse-moved", MovedEvent, intf);
422                     var_DelCallback(vout, "mouse-button-down", ButtonEvent, intf);
423                     vout_Release(vout);
424                     break;
425                 }
426             }
427             break;
428         default:
429             vlc_assert_unreachable();
430     }
431 }