]> git.sesse.net Git - vlc/blob - modules/control/hotkeys.c
Added a "key-rate-normal" to reset input playback rate to 1x
[vlc] / 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 ) var_TriggerCallback( p_vout, "video-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_RATE_NORMAL )
761             {
762                 var_SetInteger( p_input, "rate", INPUT_RATE_DEFAULT );
763                 vout_OSDMessage( VLC_OBJECT(p_input), DEFAULT_CHAN,
764                                  _("1x") );
765             }
766             else if( i_action == ACTIONID_POSITION && b_seekable )
767             {
768                 DisplayPosition( p_intf, p_vout, p_input );
769             }
770             else if( i_action >= ACTIONID_PLAY_BOOKMARK1 &&
771                      i_action <= ACTIONID_PLAY_BOOKMARK10 )
772             {
773                 PlayBookmark( p_intf, i_action - ACTIONID_PLAY_BOOKMARK1 + 1 );
774             }
775             else if( i_action >= ACTIONID_SET_BOOKMARK1 &&
776                      i_action <= ACTIONID_SET_BOOKMARK10 )
777             {
778                 SetBookmark( p_intf, i_action - ACTIONID_SET_BOOKMARK1 + 1 );
779             }
780             /* Only makes sense with DVD */
781             else if( i_action == ACTIONID_TITLE_PREV )
782                 var_SetVoid( p_input, "prev-title" );
783             else if( i_action == ACTIONID_TITLE_NEXT )
784                 var_SetVoid( p_input, "next-title" );
785             else if( i_action == ACTIONID_CHAPTER_PREV )
786                 var_SetVoid( p_input, "prev-chapter" );
787             else if( i_action == ACTIONID_CHAPTER_NEXT )
788                 var_SetVoid( p_input, "next-chapter" );
789             else if( i_action == ACTIONID_DISC_MENU )
790                 var_SetInteger( p_input, "title  0", 2 );
791
792             else if( i_action == ACTIONID_SUBDELAY_DOWN )
793             {
794                 int64_t i_delay = var_GetTime( p_input, "spu-delay" );
795                 i_delay -= 50000;    /* 50 ms */
796                 var_SetTime( p_input, "spu-delay", i_delay );
797                 ClearChannels( p_intf, p_vout );
798                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
799                                  _( "Subtitle delay %i ms" ),
800                                  (int)(i_delay/1000) );
801             }
802             else if( i_action == ACTIONID_SUBDELAY_UP )
803             {
804                 int64_t i_delay = var_GetTime( p_input, "spu-delay" );
805                 i_delay += 50000;    /* 50 ms */
806                 var_SetTime( p_input, "spu-delay", i_delay );
807                 ClearChannels( p_intf, p_vout );
808                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
809                                 _( "Subtitle delay %i ms" ),
810                                  (int)(i_delay/1000) );
811             }
812             else if( i_action == ACTIONID_AUDIODELAY_DOWN )
813             {
814                 int64_t i_delay = var_GetTime( p_input, "audio-delay" );
815                 i_delay -= 50000;    /* 50 ms */
816                 var_SetTime( p_input, "audio-delay", i_delay );
817                 ClearChannels( p_intf, p_vout );
818                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
819                                 _( "Audio delay %i ms" ),
820                                  (int)(i_delay/1000) );
821             }
822             else if( i_action == ACTIONID_AUDIODELAY_UP )
823             {
824                 int64_t i_delay = var_GetTime( p_input, "audio-delay" );
825                 i_delay += 50000;    /* 50 ms */
826                 var_SetTime( p_input, "audio-delay", i_delay );
827                 ClearChannels( p_intf, p_vout );
828                 vout_OSDMessage( p_intf, DEFAULT_CHAN,
829                                 _( "Audio delay %i ms" ),
830                                  (int)(i_delay/1000) );
831             }
832             else if( i_action == ACTIONID_PLAY )
833             {
834                 var_Get( p_input, "rate", &val );
835                 if( val.i_int != INPUT_RATE_DEFAULT )
836                 {
837                     /* Return to normal speed */
838                     var_SetInteger( p_input, "rate", INPUT_RATE_DEFAULT );
839                 }
840                 else
841                 {
842                     ClearChannels( p_intf, p_vout );
843                     vout_OSDIcon( VLC_OBJECT( p_intf ), DEFAULT_CHAN,
844                                   OSD_PLAY_ICON );
845                     playlist_Play( p_playlist );
846                 }
847             }
848             else if( i_action == ACTIONID_MENU_ON )
849             {
850                 osd_MenuShow( VLC_OBJECT(p_intf) );
851             }
852             else if( i_action == ACTIONID_MENU_OFF )
853             {
854                 osd_MenuHide( VLC_OBJECT(p_intf) );
855             }
856             else if( i_action == ACTIONID_MENU_LEFT )
857             {
858                 osd_MenuPrev( VLC_OBJECT(p_intf) );
859             }
860             else if( i_action == ACTIONID_MENU_RIGHT )
861             {
862                 osd_MenuNext( VLC_OBJECT(p_intf) );
863             }
864             else if( i_action == ACTIONID_MENU_UP )
865             {
866                 osd_MenuUp( VLC_OBJECT(p_intf) );
867             }
868             else if( i_action == ACTIONID_MENU_DOWN )
869             {
870                 osd_MenuDown( VLC_OBJECT(p_intf) );
871             }
872             else if( i_action == ACTIONID_MENU_SELECT )
873             {
874                 osd_MenuActivate( VLC_OBJECT(p_intf) );
875             }
876             else if( i_action == ACTIONID_RECORD )
877             {
878                 if( var_GetBool( p_input, "can-record" ) )
879                 {
880                     const bool b_record = !var_GetBool( p_input, "record" );
881
882                     if( b_record )
883                         vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording") );
884                     else
885                         vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording done") );
886                     var_SetBool( p_input, "record", b_record );
887                 }
888             }
889         }
890         if( p_vout )
891             vlc_object_release( p_vout );
892         if( p_input )
893             vlc_object_release( p_input );
894     }
895
896     /* dead code */
897     abort();
898     vlc_cleanup_pop();
899 }
900
901 static int GetAction( intf_thread_t *p_intf )
902 {
903     intf_sys_t *p_sys = p_intf->p_sys;
904     int i_ret;
905
906     vlc_mutex_lock( &p_sys->lock );
907     mutex_cleanup_push( &p_sys->lock );
908
909     while( p_sys->i_size == 0 )
910         vlc_cond_wait( &p_sys->wait, &p_sys->lock );
911
912     i_ret = p_sys->p_actions[ 0 ];
913     p_sys->i_size--;
914     for( int i = 0; i < p_sys->i_size; i++ )
915         p_sys->p_actions[i] = p_sys->p_actions[i + 1];
916
917     vlc_cleanup_run();
918     return i_ret;
919 }
920
921 static int PutAction( intf_thread_t *p_intf, int i_action )
922 {
923     intf_sys_t *p_sys = p_intf->p_sys;
924     int i_ret = VLC_EGENERIC;
925
926     vlc_mutex_lock( &p_sys->lock );
927     if ( p_sys->i_size >= BUFFER_SIZE )
928         msg_Warn( p_intf, "event buffer full, dropping key actions" );
929     else
930         p_sys->p_actions[p_sys->i_size++] = i_action;
931
932     vlc_cond_signal( &p_sys->wait );
933     vlc_mutex_unlock( &p_sys->lock );
934     return i_ret;
935 }
936
937 /*****************************************************************************
938  * SpecialKeyEvent: callback for mouse events
939  *****************************************************************************/
940 static int SpecialKeyEvent( vlc_object_t *libvlc, char const *psz_var,
941                             vlc_value_t oldval, vlc_value_t newval,
942                             void *p_data )
943 {
944     intf_thread_t *p_intf = (intf_thread_t *)p_data;
945     int i_action = 0;
946
947     (void)psz_var;
948     (void)oldval;
949
950     /* Special action for mouse event */
951     /* FIXME: This should probably be configurable */
952     /* FIXME: rework hotkeys handling to allow more than 1 event
953      * to trigger one same action */
954     switch (newval.i_int & KEY_SPECIAL)
955     {
956         case KEY_MOUSEWHEELUP:
957             i_action = ACTIONID_VOL_UP;
958             break;
959         case KEY_MOUSEWHEELDOWN:
960             i_action = ACTIONID_VOL_DOWN;
961             break;
962         case KEY_MOUSEWHEELLEFT:
963             i_action = ACTIONID_JUMP_BACKWARD_EXTRASHORT;
964             break;
965         case KEY_MOUSEWHEELRIGHT:
966             i_action = ACTIONID_JUMP_FORWARD_EXTRASHORT;
967             break;
968         case KEY_MENU:
969             var_SetBool( libvlc, "intf-popupmenu", true );
970             break;
971         default:
972           return VLC_SUCCESS;
973     }
974
975     if( i_action )
976         return PutAction( p_intf, i_action );
977     return VLC_SUCCESS;
978 }
979
980 /*****************************************************************************
981  * ActionEvent: callback for hotkey actions
982  *****************************************************************************/
983 static int ActionEvent( vlc_object_t *libvlc, char const *psz_var,
984                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
985 {
986     intf_thread_t *p_intf = (intf_thread_t *)p_data;
987
988     (void)libvlc;
989     (void)psz_var;
990     (void)oldval;
991
992     return PutAction( p_intf, newval.i_int );
993 }
994
995 static void PlayBookmark( intf_thread_t *p_intf, int i_num )
996 {
997     char *psz_bookmark_name;
998     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
999         return;
1000
1001     playlist_t *p_playlist = pl_Hold( p_intf );
1002     char *psz_bookmark = var_CreateGetString( p_intf, psz_bookmark_name );
1003
1004     PL_LOCK;
1005     FOREACH_ARRAY( playlist_item_t *p_item, p_playlist->items )
1006         char *psz_uri = input_item_GetURI( p_item->p_input );
1007         if( !strcmp( psz_bookmark, psz_uri ) )
1008         {
1009             free( psz_uri );
1010             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
1011                               NULL, p_item );
1012             break;
1013         }
1014         else
1015             free( psz_uri );
1016     FOREACH_END();
1017     PL_UNLOCK;
1018
1019     free( psz_bookmark );
1020     free( psz_bookmark_name );
1021     pl_Release( p_intf );
1022 }
1023
1024 static void SetBookmark( intf_thread_t *p_intf, int i_num )
1025 {
1026     char *psz_bookmark_name;
1027     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
1028         return;
1029
1030     playlist_t *p_playlist = pl_Hold( p_intf );
1031     var_Create( p_intf, psz_bookmark_name,
1032                 VLC_VAR_STRING|VLC_VAR_DOINHERIT );
1033     playlist_item_t * p_item = playlist_CurrentPlayingItem( p_playlist );
1034     if( p_item )
1035     {
1036         char *psz_uri = input_item_GetURI( p_item->p_input );
1037         config_PutPsz( p_intf, psz_bookmark_name, psz_uri);
1038         msg_Info( p_intf, "setting playlist bookmark %i to %s", i_num, psz_uri);
1039         free( psz_uri );
1040         config_SaveConfigFile( p_intf, "hotkeys" );
1041     }
1042
1043     pl_Release( p_intf );
1044     free( psz_bookmark_name );
1045 }
1046
1047 static void DisplayPosition( intf_thread_t *p_intf, vout_thread_t *p_vout,
1048                              input_thread_t *p_input )
1049 {
1050     char psz_duration[MSTRTIME_MAX_SIZE];
1051     char psz_time[MSTRTIME_MAX_SIZE];
1052     vlc_value_t time, pos;
1053     mtime_t i_seconds;
1054
1055     if( p_vout == NULL ) return;
1056
1057     ClearChannels( p_intf, p_vout );
1058
1059     var_Get( p_input, "time", &time );
1060     i_seconds = time.i_time / 1000000;
1061     secstotimestr ( psz_time, i_seconds );
1062
1063     var_Get( p_input, "length", &time );
1064     if( time.i_time > 0 )
1065     {
1066         secstotimestr( psz_duration, time.i_time / 1000000 );
1067         vout_OSDMessage( p_input, POSITION_TEXT_CHAN, (char *) "%s / %s",
1068                          psz_time, psz_duration );
1069     }
1070     else if( i_seconds > 0 )
1071     {
1072         vout_OSDMessage( p_input, POSITION_TEXT_CHAN, psz_time );
1073     }
1074
1075     if( p_vout->b_fullscreen )
1076     {
1077         var_Get( p_input, "position", &pos );
1078         vout_OSDSlider( VLC_OBJECT( p_input ), POSITION_WIDGET_CHAN,
1079                         pos.f_float * 100, OSD_HOR_SLIDER );
1080     }
1081 }
1082
1083 static void DisplayVolume( intf_thread_t *p_intf, vout_thread_t *p_vout,
1084                            audio_volume_t i_vol )
1085 {
1086     if( p_vout == NULL )
1087     {
1088         return;
1089     }
1090     ClearChannels( p_intf, p_vout );
1091
1092     if( p_vout->b_fullscreen )
1093     {
1094         vout_OSDSlider( VLC_OBJECT( p_vout ), VOLUME_WIDGET_CHAN,
1095             i_vol*100/AOUT_VOLUME_MAX, OSD_VERT_SLIDER );
1096     }
1097     else
1098     {
1099         vout_OSDMessage( p_vout, VOLUME_TEXT_CHAN, _( "Volume %d%%" ),
1100                          i_vol*400/AOUT_VOLUME_MAX );
1101     }
1102 }
1103
1104 static void ClearChannels( intf_thread_t *p_intf, vout_thread_t *p_vout )
1105 {
1106     int i;
1107
1108     if( p_vout )
1109     {
1110         spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
1111         for( i = 0; i < CHANNELS_NUMBER; i++ )
1112         {
1113             spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR,
1114                          p_intf->p_sys->p_channels[ i ] );
1115         }
1116     }
1117 }