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