Core implementation of --[no]-autoscale and --scale with x11 vout support
[vlc.git] / modules / control / hotkeys.c
1 /*****************************************************************************
2  * hotkeys.c: Hotkey handling for vlc
3  *****************************************************************************
4  * Copyright (C) 2005-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8  *          Jean-Paul Saman <jpsaman #_at_# m2x.nl>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_interface.h>
36 #include <vlc_input.h>
37 #include <vlc_vout.h>
38 #include <vlc_aout.h>
39 #include <vlc_osd.h>
40 #include <vlc_playlist.h>
41 #include "vlc_keys.h"
42
43 #define BUFFER_SIZE 10
44
45 #define CHANNELS_NUMBER 4
46 #define VOLUME_TEXT_CHAN     p_intf->p_sys->p_channels[ 0 ]
47 #define VOLUME_WIDGET_CHAN   p_intf->p_sys->p_channels[ 1 ]
48 #define POSITION_TEXT_CHAN   p_intf->p_sys->p_channels[ 2 ]
49 #define POSITION_WIDGET_CHAN p_intf->p_sys->p_channels[ 3 ]
50 /*****************************************************************************
51  * intf_sys_t: description and status of FB interface
52  *****************************************************************************/
53 struct intf_sys_t
54 {
55     int                 p_actions[ BUFFER_SIZE ]; /* buffer that contains
56                                                    * action events */
57     int                 i_size;        /* number of events in buffer */
58     int                 p_channels[ CHANNELS_NUMBER ]; /* contains registered
59                                                         * channel IDs */
60     input_thread_t *    p_input;       /* pointer to input */
61     vout_thread_t *     p_vout;        /* pointer to vout object */
62     vlc_mutex_t         lock; /* callback lock */
63     vlc_cond_t          wait; /* callback event */
64 };
65
66 /*****************************************************************************
67  * Local prototypes
68  *****************************************************************************/
69 static int  Open    ( vlc_object_t * );
70 static void Close   ( vlc_object_t * );
71 static void Run     ( intf_thread_t * );
72 static int  GetAction( intf_thread_t *);
73 static int  ActionEvent( vlc_object_t *, char const *,
74                          vlc_value_t, vlc_value_t, void * );
75 static int  SpecialKeyEvent( vlc_object_t *, char const *,
76                              vlc_value_t, vlc_value_t, void * );
77 static void PlayBookmark( intf_thread_t *, int );
78 static void SetBookmark ( intf_thread_t *, int );
79 static void DisplayPosition( intf_thread_t *, vout_thread_t *, input_thread_t * );
80 static void DisplayVolume  ( intf_thread_t *, vout_thread_t *, audio_volume_t );
81 static void ClearChannels  ( intf_thread_t *, vout_thread_t * );
82
83 /*****************************************************************************
84  * Module descriptor
85  *****************************************************************************/
86 #define BOOKMARK1_TEXT    N_("Playlist bookmark 1")
87 #define BOOKMARK2_TEXT    N_("Playlist bookmark 2")
88 #define BOOKMARK3_TEXT    N_("Playlist bookmark 3")
89 #define BOOKMARK4_TEXT    N_("Playlist bookmark 4")
90 #define BOOKMARK5_TEXT    N_("Playlist bookmark 5")
91 #define BOOKMARK6_TEXT    N_("Playlist bookmark 6")
92 #define BOOKMARK7_TEXT    N_("Playlist bookmark 7")
93 #define BOOKMARK8_TEXT    N_("Playlist bookmark 8")
94 #define BOOKMARK9_TEXT    N_("Playlist bookmark 9")
95 #define BOOKMARK10_TEXT   N_("Playlist bookmark 10")
96 #define BOOKMARK_LONGTEXT N_("Define playlist bookmarks.")
97
98 vlc_module_begin ()
99     set_shortname( N_("Hotkeys") )
100     set_description( N_("Hotkeys management interface") )
101     set_capability( "interface", 0 )
102     set_callbacks( Open, Close )
103 vlc_module_end ()
104
105 /*****************************************************************************
106  * Open: initialize interface
107  *****************************************************************************/
108 static int Open( vlc_object_t *p_this )
109 {
110     intf_thread_t *p_intf = (intf_thread_t *)p_this;
111     intf_sys_t *p_sys;
112     p_sys = malloc( sizeof( intf_sys_t ) );
113     if( !p_sys )
114         return VLC_ENOMEM;
115
116     p_intf->p_sys = p_sys;
117     p_intf->pf_run = Run;
118
119     p_sys->i_size = 0;
120     vlc_mutex_init( &p_sys->lock );
121     vlc_cond_init( &p_sys->wait );
122
123     var_AddCallback( p_intf->p_libvlc, "key-pressed", SpecialKeyEvent, p_intf );
124     var_AddCallback( p_intf->p_libvlc, "key-action", ActionEvent, p_intf );
125     return VLC_SUCCESS;
126 }
127
128 /*****************************************************************************
129  * Close: destroy interface
130  *****************************************************************************/
131 static void Close( vlc_object_t *p_this )
132 {
133     intf_thread_t *p_intf = (intf_thread_t *)p_this;
134     intf_sys_t *p_sys = p_intf->p_sys;
135
136     var_DelCallback( p_intf->p_libvlc, "key-action", ActionEvent, p_intf );
137     var_DelCallback( p_intf->p_libvlc, "key-pressed", SpecialKeyEvent, p_intf );
138
139     vlc_cond_destroy( &p_sys->wait );
140     vlc_mutex_destroy( &p_sys->lock );
141
142     /* Destroy structure */
143     free( p_intf->p_sys );
144 }
145
146 /*****************************************************************************
147  * Run: main loop
148  *****************************************************************************/
149 static void Run( intf_thread_t *p_intf )
150 {
151     vout_thread_t *p_vout = NULL;
152     vlc_value_t val;
153     int i;
154     playlist_t *p_playlist = pl_Hold( p_intf );
155     int canc = vlc_savecancel();
156
157     vlc_cleanup_push( __pl_Release, p_intf );
158
159     /* Initialize hotkey structure */
160     for( struct hotkey *p_hotkey = p_intf->p_libvlc->p_hotkeys;
161          p_hotkey->psz_action != NULL;
162          p_hotkey++ )
163     {
164         p_hotkey->i_key = config_GetInt( p_intf, p_hotkey->psz_action );
165     }
166
167     for( ;; )
168     {
169         input_thread_t *p_input;
170         vout_thread_t *p_last_vout;
171         int i_action;
172
173         vlc_restorecancel( canc );
174         i_action = GetAction( p_intf );
175
176         canc = vlc_savecancel();
177
178         /* Update the input */
179         p_input = playlist_CurrentInput( p_playlist );
180
181         /* Update the vout */
182         p_last_vout = p_vout;
183         p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
184
185         /* Register OSD channels */
186         if( p_vout && p_vout != p_last_vout )
187         {
188             for( i = 0; i < CHANNELS_NUMBER; i++ )
189             {
190                 spu_Control( p_vout->p_spu, SPU_CHANNEL_REGISTER,
191                              &p_intf->p_sys->p_channels[ i ] );
192             }
193         }
194
195         /* Quit */
196         if( i_action == ACTIONID_QUIT )
197         {
198             libvlc_Quit( p_intf->p_libvlc );
199
200             ClearChannels( p_intf, p_vout );
201             vout_OSDMessage( p_intf, DEFAULT_CHAN, _( "Quit" ) );
202             if( p_vout )
203                 vlc_object_release( p_vout );
204             if( p_input )
205                 vlc_object_release( p_input );
206             continue;
207         }
208         /* Volume and audio actions */
209         else if( i_action == ACTIONID_VOL_UP )
210         {
211             audio_volume_t i_newvol;
212             aout_VolumeUp( p_intf, 1, &i_newvol );
213             DisplayVolume( p_intf, p_vout, i_newvol );
214         }
215         else if( i_action == ACTIONID_VOL_DOWN )
216         {
217             audio_volume_t i_newvol;
218             aout_VolumeDown( p_intf, 1, &i_newvol );
219             DisplayVolume( p_intf, p_vout, i_newvol );
220         }
221         else if( i_action == ACTIONID_VOL_MUTE )
222         {
223             audio_volume_t i_newvol = -1;
224             aout_VolumeMute( p_intf, &i_newvol );
225             if( p_vout )
226             {
227                 if( i_newvol == 0 )
228                 {
229                     ClearChannels( p_intf, p_vout );
230                     vout_OSDIcon( VLC_OBJECT( p_intf ), DEFAULT_CHAN,
231                                   OSD_MUTE_ICON );
232                 }
233                 else
234                 {
235                     DisplayVolume( p_intf, p_vout, i_newvol );
236                 }
237             }
238         }
239         /* Interface showing */
240         else if( i_action == ACTIONID_INTF_SHOW )
241             var_SetBool( p_intf->p_libvlc, "intf-show", true );
242         else if( i_action == ACTIONID_INTF_HIDE )
243             var_SetBool( p_intf->p_libvlc, "intf-show", false );
244         /* Video Output actions */
245         else if( i_action == ACTIONID_SNAPSHOT )
246         {
247             if( p_vout ) vout_Control( p_vout, VOUT_SNAPSHOT );
248         }
249         else if( i_action == ACTIONID_TOGGLE_FULLSCREEN )
250         {
251             if( p_vout )
252             {
253                 var_Get( p_vout, "fullscreen", &val );
254                 val.b_bool = !val.b_bool;
255                 var_Set( p_vout, "fullscreen", val );
256             }
257             else
258             {
259                 var_Get( p_playlist, "fullscreen", &val );
260                 val.b_bool = !val.b_bool;
261                 var_Set( p_playlist, "fullscreen", val );
262             }
263         }
264         else if( i_action == ACTIONID_LEAVE_FULLSCREEN )
265         {
266             if( p_vout && var_GetBool( p_vout, "fullscreen" ) )
267             {
268                 var_SetBool( p_vout, "fullscreen", false );
269             }
270         }
271         else if( i_action == ACTIONID_ZOOM_QUARTER ||
272                  i_action == ACTIONID_ZOOM_HALF ||
273                  i_action == ACTIONID_ZOOM_ORIGINAL ||
274                  i_action == ACTIONID_ZOOM_DOUBLE )
275         {
276             if( p_vout )
277             {
278                 if( i_action == ACTIONID_ZOOM_QUARTER )
279                     val.f_float = 0.25;
280                 if( i_action == ACTIONID_ZOOM_HALF )
281                     val.f_float = 0.5;
282                 if( i_action == ACTIONID_ZOOM_ORIGINAL )
283                     val.f_float = 1;
284                 if( i_action == ACTIONID_ZOOM_DOUBLE )
285                     val.f_float = 2;
286                 var_Set( p_vout, "zoom", val );
287             }
288         }
289         else if( i_action == ACTIONID_WALLPAPER )
290         {
291             if( p_vout )
292             {
293                 var_Get( p_vout, "directx-wallpaper", &val );
294                 val.b_bool = !val.b_bool;
295                 var_Set( p_vout, "directx-wallpaper", val );
296             }
297             else
298             {
299                 var_Get( p_playlist, "directx-wallpaper", &val );
300                 val.b_bool = !val.b_bool;
301                 var_Set( p_playlist, "directx-wallpaper", val );
302             }
303         }
304         /* Playlist actions */
305         else if( i_action == ACTIONID_LOOP )
306         {
307             /* Toggle Normal -> Loop -> Repeat -> Normal ... */
308             vlc_value_t val2;
309             var_Get( p_playlist, "loop", &val );
310             var_Get( p_playlist, "repeat", &val2 );
311             if( val2.b_bool == true )
312             {
313                 val.b_bool = false;
314                 val2.b_bool = false;
315             }
316             else if( val.b_bool == true )
317             {
318                 val.b_bool = false;
319                 val2.b_bool = true;
320             }
321             else
322             {
323                 val.b_bool = true;
324             }
325             var_Set( p_playlist, "loop", val );
326             var_Set( p_playlist, "repeat", val2 );
327         }
328         else if( i_action == ACTIONID_RANDOM )
329         {
330             var_Get( p_playlist, "random", &val );
331             val.b_bool = !val.b_bool;
332             var_Set( p_playlist, "random", val );
333         }
334         else if( i_action == ACTIONID_PLAY_PAUSE )
335         {
336             val.i_int = PLAYING_S;
337             if( p_input )
338             {
339                 ClearChannels( p_intf, p_vout );
340
341                 var_Get( p_input, "state", &val );
342                 if( val.i_int != PAUSE_S )
343                 {
344                     vout_OSDIcon( VLC_OBJECT( p_intf ), DEFAULT_CHAN,
345                                   OSD_PAUSE_ICON );
346                     val.i_int = PAUSE_S;
347                 }
348                 else
349                 {
350                     vout_OSDIcon( VLC_OBJECT( p_intf ), DEFAULT_CHAN,
351                                   OSD_PLAY_ICON );
352                     val.i_int = PLAYING_S;
353                 }
354                 var_Set( p_input, "state", val );
355             }
356             else
357             {
358                 playlist_Play( p_playlist );
359             }
360         }
361         else if( i_action == ACTIONID_AUDIODEVICE_CYCLE )
362         {
363             vlc_value_t val, list, list2;
364             int i_count, i;
365
366             aout_instance_t *p_aout =
367                 vlc_object_find( p_intf, VLC_OBJECT_AOUT, FIND_ANYWHERE );
368             var_Get( p_aout, "audio-device", &val );
369             var_Change( p_aout, "audio-device", VLC_VAR_GETCHOICES,
370                     &list, &list2 );
371             i_count = list.p_list->i_count;
372
373             /* Not enough device to switch between */
374             if( i_count <= 1 )
375                 continue;
376
377             for( i = 0; i < i_count; i++ )
378             {
379                 if( val.i_int == list.p_list->p_values[i].i_int )
380                 {
381                     break;
382                 }
383             }
384             if( i == i_count )
385             {
386                 msg_Warn( p_aout,
387                         "invalid current audio device, selecting 0" );
388                 var_Set( p_aout, "audio-device",
389                         list.p_list->p_values[0] );
390                 i = 0;
391             }
392             else if( i == i_count -1 )
393             {
394                 var_Set( p_aout, "audio-device",
395                         list.p_list->p_values[0] );
396                 i = 0;
397             }
398             else
399             {
400                 var_Set( p_aout, "audio-device",
401                         list.p_list->p_values[i+1] );
402                 i++;
403             }
404             vout_OSDMessage( p_intf, DEFAULT_CHAN,
405                     _("Audio Device: %s"),
406                     list2.p_list->p_values[i].psz_string);
407             vlc_object_release( p_aout );
408         }
409         /* Input options */
410         else if( p_input )
411         {
412             bool b_seekable = var_GetBool( p_input, "can-seek" );
413             int i_interval =0;
414
415             if( i_action == ACTIONID_PAUSE )
416             {
417                 var_Get( p_input, "state", &val );
418                 if( val.i_int != PAUSE_S )
419                 {
420                     ClearChannels( p_intf, p_vout );
421                     vout_OSDIcon( VLC_OBJECT( p_intf ), DEFAULT_CHAN,
422                                   OSD_PAUSE_ICON );
423                     val.i_int = PAUSE_S;
424                     var_Set( p_input, "state", val );
425                 }
426             }
427             else if( i_action == ACTIONID_JUMP_BACKWARD_EXTRASHORT
428                      && b_seekable )
429             {
430 #define SET_TIME( a, b ) \
431     i_interval = config_GetInt( p_input, a "-jump-size" ); \
432     if( i_interval > 0 ) { \
433         val.i_time = (mtime_t)(i_interval * b) * 1000000L; \
434         var_Set( p_input, "time-offset", val ); \
435         DisplayPosition( p_intf, p_vout, p_input ); \
436     }
437                 SET_TIME( "extrashort", -1 );
438             }
439             else if( i_action == ACTIONID_JUMP_FORWARD_EXTRASHORT && b_seekable )
440             {
441                 SET_TIME( "extrashort", 1 );
442             }
443             else if( i_action == ACTIONID_JUMP_BACKWARD_SHORT && b_seekable )
444             {
445                 SET_TIME( "short", -1 );
446             }
447             else if( i_action == ACTIONID_JUMP_FORWARD_SHORT && b_seekable )
448             {
449                 SET_TIME( "short", 1 );
450             }
451             else if( i_action == ACTIONID_JUMP_BACKWARD_MEDIUM && b_seekable )
452             {
453                 SET_TIME( "medium", -1 );
454             }
455             else if( i_action == ACTIONID_JUMP_FORWARD_MEDIUM && b_seekable )
456             {
457                 SET_TIME( "medium", 1 );
458             }
459             else if( i_action == ACTIONID_JUMP_BACKWARD_LONG && b_seekable )
460             {
461                 SET_TIME( "long", -1 );
462             }
463             else if( i_action == ACTIONID_JUMP_FORWARD_LONG && b_seekable )
464             {
465                 SET_TIME( "long", 1 );
466 #undef SET_TIME
467             }
468             else if( i_action == ACTIONID_AUDIO_TRACK )
469             {
470                 vlc_value_t val, list, list2;
471                 int i_count, i;
472                 var_Get( p_input, "audio-es", &val );
473                 var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES,
474                             &list, &list2 );
475                 i_count = list.p_list->i_count;
476                 if( i_count <= 1 )
477                 {
478                     continue;
479                 }
480                 for( i = 0; i < i_count; i++ )
481                 {
482                     if( val.i_int == list.p_list->p_values[i].i_int )
483                     {
484                         break;
485                     }
486                 }
487                 /* value of audio-es was not in choices list */
488                 if( i == i_count )
489                 {
490                     msg_Warn( p_input,
491                               "invalid current audio track, selecting 0" );
492                     var_Set( p_input, "audio-es",
493                              list.p_list->p_values[0] );
494                     i = 0;
495                 }
496                 else if( i == i_count - 1 )
497                 {
498                     var_Set( p_input, "audio-es",
499                              list.p_list->p_values[1] );
500                     i = 1;
501                 }
502                 else
503                 {
504                     var_Set( p_input, "audio-es",
505                              list.p_list->p_values[i+1] );
506                     i++;
507                 }
508                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
509                                  _("Audio track: %s"),
510                                  list2.p_list->p_values[i].psz_string );
511             }
512             else if( i_action == ACTIONID_SUBTITLE_TRACK )
513             {
514                 vlc_value_t val, list, list2;
515                 int i_count, i;
516                 var_Get( p_input, "spu-es", &val );
517
518                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
519                             &list, &list2 );
520                 i_count = list.p_list->i_count;
521                 if( i_count <= 1 )
522                 {
523                     vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
524                                      _("Subtitle track: %s"), _("N/A") );
525                     continue;
526                 }
527                 for( i = 0; i < i_count; i++ )
528                 {
529                     if( val.i_int == list.p_list->p_values[i].i_int )
530                     {
531                         break;
532                     }
533                 }
534                 /* value of spu-es was not in choices list */
535                 if( i == i_count )
536                 {
537                     msg_Warn( p_input,
538                               "invalid current subtitle track, selecting 0" );
539                     var_Set( p_input, "spu-es", list.p_list->p_values[0] );
540                     i = 0;
541                 }
542                 else if( i == i_count - 1 )
543                 {
544                     var_Set( p_input, "spu-es", list.p_list->p_values[0] );
545                     i = 0;
546                 }
547                 else
548                 {
549                     var_Set( p_input, "spu-es", list.p_list->p_values[i+1] );
550                     i = i + 1;
551                 }
552                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
553                                  _("Subtitle track: %s"),
554                                  list2.p_list->p_values[i].psz_string );
555             }
556             else if( i_action == ACTIONID_ASPECT_RATIO && p_vout )
557             {
558                 vlc_value_t val={0}, val_list, text_list;
559                 var_Get( p_vout, "aspect-ratio", &val );
560                 if( var_Change( p_vout, "aspect-ratio", VLC_VAR_GETLIST,
561                                 &val_list, &text_list ) >= 0 )
562                 {
563                     int i;
564                     for( i = 0; i < val_list.p_list->i_count; i++ )
565                     {
566                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
567                                      val.psz_string ) )
568                         {
569                             i++;
570                             break;
571                         }
572                     }
573                     if( i == val_list.p_list->i_count ) i = 0;
574                     var_SetString( p_vout, "aspect-ratio",
575                                    val_list.p_list->p_values[i].psz_string );
576                     vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
577                                      _("Aspect ratio: %s"),
578                                      text_list.p_list->p_values[i].psz_string );
579
580                     var_Change( p_vout, "aspect-ratio", VLC_VAR_FREELIST, &val_list, &text_list );
581                 }
582                 free( val.psz_string );
583             }
584             else if( i_action == ACTIONID_CROP && p_vout )
585             {
586                 vlc_value_t val={0}, val_list, text_list;
587                 var_Get( p_vout, "crop", &val );
588                 if( var_Change( p_vout, "crop", VLC_VAR_GETLIST,
589                                 &val_list, &text_list ) >= 0 )
590                 {
591                     int i;
592                     for( i = 0; i < val_list.p_list->i_count; i++ )
593                     {
594                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
595                                      val.psz_string ) )
596                         {
597                             i++;
598                             break;
599                         }
600                     }
601                     if( i == val_list.p_list->i_count ) i = 0;
602                     var_SetString( p_vout, "crop",
603                                    val_list.p_list->p_values[i].psz_string );
604                     vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
605                                      _("Crop: %s"),
606                                      text_list.p_list->p_values[i].psz_string );
607
608                     var_Change( p_vout, "crop", VLC_VAR_FREELIST, &val_list, &text_list );
609                 }
610                 free( val.psz_string );
611             }
612             else if( i_action == ACTIONID_TOGGLE_AUTOSCALE && p_vout )
613             {
614                 float f_scalefactor = var_GetFloat( p_vout, "scale" );
615                 if ( f_scalefactor != 1.0 )
616                 {
617                     var_SetFloat( p_vout, "scale", 1.0 );
618                     vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
619                                          _("Zooming reset") );
620                 }
621                 else
622                 {
623                     bool b_autoscale = !var_GetBool( p_vout, "autoscale" );
624                     var_SetBool( p_vout, "autoscale", b_autoscale );
625                     if( b_autoscale )
626                         vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
627                                          _("Scaled to screen") );
628                     else
629                         vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
630                                          _("Original Size") );
631                 }
632             }
633             else if( i_action == ACTIONID_SCALE_UP && p_vout )
634             {
635                float f_scalefactor;
636
637                f_scalefactor = var_GetFloat( p_vout, "scale" );
638                if( f_scalefactor < 10. )
639                    f_scalefactor += .1;
640                var_SetFloat( p_vout, "scale", f_scalefactor );
641             }
642             else if( i_action == ACTIONID_SCALE_DOWN && p_vout )
643             {
644                float f_scalefactor;
645
646                f_scalefactor = var_GetFloat( p_vout, "scale" );
647                if( f_scalefactor > .3 )
648                    f_scalefactor -= .1;
649                var_SetFloat( p_vout, "scale", f_scalefactor );
650             }
651             else if( i_action == ACTIONID_DEINTERLACE && p_vout )
652             {
653                 vlc_value_t val={0}, val_list, text_list;
654                 var_Get( p_vout, "deinterlace", &val );
655                 if( var_Change( p_vout, "deinterlace", VLC_VAR_GETLIST,
656                                 &val_list, &text_list ) >= 0 )
657                 {
658                     int i;
659                     for( i = 0; i < val_list.p_list->i_count; i++ )
660                     {
661                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
662                                      val.psz_string ) )
663                         {
664                             i++;
665                             break;
666                         }
667                     }
668                     if( i == val_list.p_list->i_count ) i = 0;
669                     var_SetString( p_vout, "deinterlace",
670                                    val_list.p_list->p_values[i].psz_string );
671                     vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
672                                      _("Deinterlace mode: %s"),
673                                      text_list.p_list->p_values[i].psz_string );
674
675                     var_Change( p_vout, "deinterlace", VLC_VAR_FREELIST, &val_list, &text_list );
676                 }
677                 free( val.psz_string );
678             }
679             else if( ( i_action == ACTIONID_ZOOM || i_action == ACTIONID_UNZOOM ) && p_vout )
680             {
681                 vlc_value_t val={0}, val_list, text_list;
682                 var_Get( p_vout, "zoom", &val );
683                 if( var_Change( p_vout, "zoom", VLC_VAR_GETLIST,
684                                 &val_list, &text_list ) >= 0 )
685                 {
686                     int i;
687                     for( i = 0; i < val_list.p_list->i_count; i++ )
688                     {
689                         if( val_list.p_list->p_values[i].f_float
690                            == val.f_float )
691                         {
692                             if( i_action == ACTIONID_ZOOM )
693                                 i++;
694                             else /* ACTIONID_UNZOOM */
695                                 i--;
696                             break;
697                         }
698                     }
699                     if( i == val_list.p_list->i_count ) i = 0;
700                     if( i == -1 ) i = val_list.p_list->i_count-1;
701                     var_SetFloat( p_vout, "zoom",
702                                   val_list.p_list->p_values[i].f_float );
703                     vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
704                                      _("Zoom mode: %s"),
705                                 text_list.p_list->p_values[i].var.psz_name );
706
707                     var_Change( p_vout, "zoom", VLC_VAR_FREELIST, &val_list, &text_list );
708                 }
709             }
710             else if( i_action == ACTIONID_CROP_TOP && p_vout )
711                 var_IncInteger( p_vout, "crop-top" );
712             else if( i_action == ACTIONID_UNCROP_TOP && p_vout )
713                 var_DecInteger( p_vout, "crop-top" );
714             else if( i_action == ACTIONID_CROP_BOTTOM && p_vout )
715                 var_IncInteger( p_vout, "crop-bottom" );
716             else if( i_action == ACTIONID_UNCROP_BOTTOM && p_vout )
717                  var_DecInteger( p_vout, "crop-bottom" );
718             else if( i_action == ACTIONID_CROP_LEFT && p_vout )
719                  var_IncInteger( p_vout, "crop-left" );
720             else if( i_action == ACTIONID_UNCROP_LEFT && p_vout )
721                  var_DecInteger( p_vout, "crop-left" );
722             else if( i_action == ACTIONID_CROP_RIGHT && p_vout )
723                  var_IncInteger( p_vout, "crop-right" );
724             else if( i_action == ACTIONID_UNCROP_RIGHT && p_vout )
725                  var_DecInteger( p_vout, "crop-right" );
726
727             else if( i_action == ACTIONID_NEXT )
728             {
729                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN, _("Next") );
730                 playlist_Next( p_playlist );
731             }
732             else if( i_action == ACTIONID_PREV )
733             {
734                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
735                                  _("Previous") );
736                 playlist_Prev( p_playlist );
737             }
738             else if( i_action == ACTIONID_STOP )
739             {
740                 playlist_Stop( p_playlist );
741             }
742             else if( i_action == ACTIONID_FRAME_NEXT )
743             {
744                 var_SetVoid( p_input, "frame-next" );
745                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
746                                  _("Next frame") );
747             }
748             else if( i_action == ACTIONID_FASTER )
749             {
750                 var_SetVoid( p_input, "rate-faster" );
751                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
752                                  _("Faster") );
753             }
754             else if( i_action == ACTIONID_SLOWER )
755             {
756                 var_SetVoid( p_input, "rate-slower" );
757                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
758                                  _("Slower") );
759             }
760             else if( i_action == ACTIONID_POSITION && b_seekable )
761             {
762                 DisplayPosition( p_intf, p_vout, p_input );
763             }
764             else if( i_action >= ACTIONID_PLAY_BOOKMARK1 &&
765                      i_action <= ACTIONID_PLAY_BOOKMARK10 )
766             {
767                 PlayBookmark( p_intf, i_action - ACTIONID_PLAY_BOOKMARK1 + 1 );
768             }
769             else if( i_action >= ACTIONID_SET_BOOKMARK1 &&
770                      i_action <= ACTIONID_SET_BOOKMARK10 )
771             {
772                 SetBookmark( p_intf, i_action - ACTIONID_SET_BOOKMARK1 + 1 );
773             }
774             /* Only makes sense with DVD */
775             else if( i_action == ACTIONID_TITLE_PREV )
776                 var_SetVoid( p_input, "prev-title" );
777             else if( i_action == ACTIONID_TITLE_NEXT )
778                 var_SetVoid( p_input, "next-title" );
779             else if( i_action == ACTIONID_CHAPTER_PREV )
780                 var_SetVoid( p_input, "prev-chapter" );
781             else if( i_action == ACTIONID_CHAPTER_NEXT )
782                 var_SetVoid( p_input, "next-chapter" );
783             else if( i_action == ACTIONID_DISC_MENU )
784                 var_SetInteger( p_input, "title  0", 2 );
785
786             else if( i_action == ACTIONID_SUBDELAY_DOWN )
787             {
788                 int64_t i_delay = var_GetTime( p_input, "spu-delay" );
789                 i_delay -= 50000;    /* 50 ms */
790                 var_SetTime( p_input, "spu-delay", i_delay );
791                 ClearChannels( p_intf, p_vout );
792                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
793                                  _( "Subtitle delay %i ms" ),
794                                  (int)(i_delay/1000) );
795             }
796             else if( i_action == ACTIONID_SUBDELAY_UP )
797             {
798                 int64_t i_delay = var_GetTime( p_input, "spu-delay" );
799                 i_delay += 50000;    /* 50 ms */
800                 var_SetTime( p_input, "spu-delay", i_delay );
801                 ClearChannels( p_intf, p_vout );
802                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
803                                 _( "Subtitle delay %i ms" ),
804                                  (int)(i_delay/1000) );
805             }
806             else if( i_action == ACTIONID_AUDIODELAY_DOWN )
807             {
808                 int64_t i_delay = var_GetTime( p_input, "audio-delay" );
809                 i_delay -= 50000;    /* 50 ms */
810                 var_SetTime( p_input, "audio-delay", i_delay );
811                 ClearChannels( p_intf, p_vout );
812                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
813                                 _( "Audio delay %i ms" ),
814                                  (int)(i_delay/1000) );
815             }
816             else if( i_action == ACTIONID_AUDIODELAY_UP )
817             {
818                 int64_t i_delay = var_GetTime( p_input, "audio-delay" );
819                 i_delay += 50000;    /* 50 ms */
820                 var_SetTime( p_input, "audio-delay", i_delay );
821                 ClearChannels( p_intf, p_vout );
822                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
823                                 _( "Audio delay %i ms" ),
824                                  (int)(i_delay/1000) );
825             }
826             else if( i_action == ACTIONID_PLAY )
827             {
828                 var_Get( p_input, "rate", &val );
829                 if( val.i_int != INPUT_RATE_DEFAULT )
830                 {
831                     /* Return to normal speed */
832                     var_SetInteger( p_input, "rate", INPUT_RATE_DEFAULT );
833                 }
834                 else
835                 {
836                     ClearChannels( p_intf, p_vout );
837                     vout_OSDIcon( VLC_OBJECT( p_intf ), DEFAULT_CHAN,
838                                   OSD_PLAY_ICON );
839                     playlist_Play( p_playlist );
840                 }
841             }
842             else if( i_action == ACTIONID_MENU_ON )
843             {
844                 osd_MenuShow( VLC_OBJECT(p_intf) );
845             }
846             else if( i_action == ACTIONID_MENU_OFF )
847             {
848                 osd_MenuHide( VLC_OBJECT(p_intf) );
849             }
850             else if( i_action == ACTIONID_MENU_LEFT )
851             {
852                 osd_MenuPrev( VLC_OBJECT(p_intf) );
853             }
854             else if( i_action == ACTIONID_MENU_RIGHT )
855             {
856                 osd_MenuNext( VLC_OBJECT(p_intf) );
857             }
858             else if( i_action == ACTIONID_MENU_UP )
859             {
860                 osd_MenuUp( VLC_OBJECT(p_intf) );
861             }
862             else if( i_action == ACTIONID_MENU_DOWN )
863             {
864                 osd_MenuDown( VLC_OBJECT(p_intf) );
865             }
866             else if( i_action == ACTIONID_MENU_SELECT )
867             {
868                 osd_MenuActivate( VLC_OBJECT(p_intf) );
869             }
870             else if( i_action == ACTIONID_RECORD )
871             {
872                 if( var_GetBool( p_input, "can-record" ) )
873                 {
874                     const bool b_record = !var_GetBool( p_input, "record" );
875
876                     if( b_record )
877                         vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording") );
878                     else
879                         vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording done") );
880                     var_SetBool( p_input, "record", b_record );
881                 }
882             }
883         }
884         if( p_vout )
885             vlc_object_release( p_vout );
886         if( p_input )
887             vlc_object_release( p_input );
888     }
889
890     /* dead code */
891     abort();
892     vlc_cleanup_pop();
893 }
894
895 static int GetAction( intf_thread_t *p_intf )
896 {
897     intf_sys_t *p_sys = p_intf->p_sys;
898     int i_ret;
899
900     vlc_mutex_lock( &p_sys->lock );
901     mutex_cleanup_push( &p_sys->lock );
902
903     while( p_sys->i_size == 0 )
904         vlc_cond_wait( &p_sys->wait, &p_sys->lock );
905
906     i_ret = p_sys->p_actions[ 0 ];
907     p_sys->i_size--;
908     for( int i = 0; i < p_sys->i_size; i++ )
909         p_sys->p_actions[i] = p_sys->p_actions[i + 1];
910
911     vlc_cleanup_run();
912     return i_ret;
913 }
914
915 static int PutAction( intf_thread_t *p_intf, int i_action )
916 {
917     intf_sys_t *p_sys = p_intf->p_sys;
918     int i_ret = VLC_EGENERIC;
919
920     vlc_mutex_lock( &p_sys->lock );
921     if ( p_sys->i_size >= BUFFER_SIZE )
922         msg_Warn( p_intf, "event buffer full, dropping key actions" );
923     else
924         p_sys->p_actions[p_sys->i_size++] = i_action;
925
926     vlc_cond_signal( &p_sys->wait );
927     vlc_mutex_unlock( &p_sys->lock );
928     return i_ret;
929 }
930
931 /*****************************************************************************
932  * SpecialKeyEvent: callback for mouse events
933  *****************************************************************************/
934 static int SpecialKeyEvent( vlc_object_t *libvlc, char const *psz_var,
935                             vlc_value_t oldval, vlc_value_t newval,
936                             void *p_data )
937 {
938     intf_thread_t *p_intf = (intf_thread_t *)p_data;
939     int i_action;
940
941     (void)libvlc;
942     (void)psz_var;
943     (void)oldval;
944
945     /* Special action for mouse event */
946     /* FIXME: This should probably be configurable */
947     /* FIXME: rework hotkeys handling to allow more than 1 event
948      * to trigger one same action */
949     switch (newval.i_int & KEY_SPECIAL)
950     {
951         case KEY_MOUSEWHEELUP:
952             i_action = ACTIONID_VOL_UP;
953             break;
954         case KEY_MOUSEWHEELDOWN:
955             i_action = ACTIONID_VOL_DOWN;
956             break;
957         case KEY_MOUSEWHEELLEFT:
958             i_action = ACTIONID_JUMP_BACKWARD_EXTRASHORT;
959             break;
960         case KEY_MOUSEWHEELRIGHT:
961             i_action = ACTIONID_JUMP_FORWARD_EXTRASHORT;
962             break;
963         default:
964           return VLC_SUCCESS;
965     }
966
967     if( i_action )
968         return PutAction( p_intf, i_action );
969     return VLC_SUCCESS;
970 }
971
972 /*****************************************************************************
973  * ActionEvent: callback for hotkey actions
974  *****************************************************************************/
975 static int ActionEvent( vlc_object_t *libvlc, char const *psz_var,
976                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
977 {
978     intf_thread_t *p_intf = (intf_thread_t *)p_data;
979
980     (void)libvlc;
981     (void)psz_var;
982     (void)oldval;
983
984     return PutAction( p_intf, newval.i_int );
985 }
986
987 static void PlayBookmark( intf_thread_t *p_intf, int i_num )
988 {
989     char *psz_bookmark_name;
990     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
991         return;
992
993     playlist_t *p_playlist = pl_Hold( p_intf );
994     char *psz_bookmark = var_CreateGetString( p_intf, psz_bookmark_name );
995
996     PL_LOCK;
997     FOREACH_ARRAY( playlist_item_t *p_item, p_playlist->items )
998         char *psz_uri = input_item_GetURI( p_item->p_input );
999         if( !strcmp( psz_bookmark, psz_uri ) )
1000         {
1001             free( psz_uri );
1002             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
1003                               NULL, p_item );
1004             break;
1005         }
1006         else
1007             free( psz_uri );
1008     FOREACH_END();
1009     PL_UNLOCK;
1010
1011     free( psz_bookmark );
1012     free( psz_bookmark_name );
1013     pl_Release( p_intf );
1014 }
1015
1016 static void SetBookmark( intf_thread_t *p_intf, int i_num )
1017 {
1018     char *psz_bookmark_name;
1019     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
1020         return;
1021
1022     playlist_t *p_playlist = pl_Hold( p_intf );
1023     var_Create( p_intf, psz_bookmark_name,
1024                 VLC_VAR_STRING|VLC_VAR_DOINHERIT );
1025     playlist_item_t * p_item = playlist_CurrentPlayingItem( p_playlist );
1026     if( p_item )
1027     {
1028         char *psz_uri = input_item_GetURI( p_item->p_input );
1029         config_PutPsz( p_intf, psz_bookmark_name, psz_uri);
1030         msg_Info( p_intf, "setting playlist bookmark %i to %s", i_num, psz_uri);
1031         free( psz_uri );
1032         config_SaveConfigFile( p_intf, "hotkeys" );
1033     }
1034
1035     pl_Release( p_intf );
1036     free( psz_bookmark_name );
1037 }
1038
1039 static void DisplayPosition( intf_thread_t *p_intf, vout_thread_t *p_vout,
1040                              input_thread_t *p_input )
1041 {
1042     char psz_duration[MSTRTIME_MAX_SIZE];
1043     char psz_time[MSTRTIME_MAX_SIZE];
1044     vlc_value_t time, pos;
1045     mtime_t i_seconds;
1046
1047     if( p_vout == NULL ) return;
1048
1049     ClearChannels( p_intf, p_vout );
1050
1051     var_Get( p_input, "time", &time );
1052     i_seconds = time.i_time / 1000000;
1053     secstotimestr ( psz_time, i_seconds );
1054
1055     var_Get( p_input, "length", &time );
1056     if( time.i_time > 0 )
1057     {
1058         secstotimestr( psz_duration, time.i_time / 1000000 );
1059         vout_OSDMessage( p_input, POSITION_TEXT_CHAN, (char *) "%s / %s",
1060                          psz_time, psz_duration );
1061     }
1062     else if( i_seconds > 0 )
1063     {
1064         vout_OSDMessage( p_input, POSITION_TEXT_CHAN, psz_time );
1065     }
1066
1067     if( p_vout->b_fullscreen )
1068     {
1069         var_Get( p_input, "position", &pos );
1070         vout_OSDSlider( VLC_OBJECT( p_input ), POSITION_WIDGET_CHAN,
1071                         pos.f_float * 100, OSD_HOR_SLIDER );
1072     }
1073 }
1074
1075 static void DisplayVolume( intf_thread_t *p_intf, vout_thread_t *p_vout,
1076                            audio_volume_t i_vol )
1077 {
1078     if( p_vout == NULL )
1079     {
1080         return;
1081     }
1082     ClearChannels( p_intf, p_vout );
1083
1084     if( p_vout->b_fullscreen )
1085     {
1086         vout_OSDSlider( VLC_OBJECT( p_vout ), VOLUME_WIDGET_CHAN,
1087             i_vol*100/AOUT_VOLUME_MAX, OSD_VERT_SLIDER );
1088     }
1089     else
1090     {
1091         vout_OSDMessage( p_vout, VOLUME_TEXT_CHAN, _( "Volume %d%%" ),
1092                          i_vol*400/AOUT_VOLUME_MAX );
1093     }
1094 }
1095
1096 static void ClearChannels( intf_thread_t *p_intf, vout_thread_t *p_vout )
1097 {
1098     int i;
1099
1100     if( p_vout )
1101     {
1102         spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
1103         for( i = 0; i < CHANNELS_NUMBER; i++ )
1104         {
1105             spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR,
1106                          p_intf->p_sys->p_channels[ i ] );
1107         }
1108     }
1109 }