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