]> git.sesse.net Git - vlc/blob - modules/control/hotkeys.c
vlc_plugin: fix non-LGPL plugins meta infos
[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 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_interface.h>
37 #include <vlc_input.h>
38 #include <vlc_aout.h>
39 #include <vlc_vout.h>
40 #include <vlc_vout_osd.h>
41 #include <vlc_playlist.h>
42 #include <vlc_keys.h>
43 #include "math.h"
44
45 /*****************************************************************************
46  * intf_sys_t: description and status of FB interface
47  *****************************************************************************/
48 struct intf_sys_t
49 {
50     vout_thread_t      *p_last_vout;
51     int slider_chan;
52
53     /*subtitle_delaybookmarks: placeholder for storing subtitle sync timestamps*/
54     struct
55     {
56         int64_t i_time_subtitle;
57         int64_t i_time_audio;
58     } subtitle_delaybookmarks;
59 };
60
61 /*****************************************************************************
62  * Local prototypes
63  *****************************************************************************/
64 static int  Open    ( vlc_object_t * );
65 static void Close   ( vlc_object_t * );
66 static int  ActionEvent( vlc_object_t *, char const *,
67                          vlc_value_t, vlc_value_t, void * );
68 static void PlayBookmark( intf_thread_t *, int );
69 static void SetBookmark ( intf_thread_t *, int );
70 static void DisplayPosition( intf_thread_t *, vout_thread_t *, input_thread_t * );
71 static void DisplayVolume( intf_thread_t *, vout_thread_t *, float );
72 static void DisplayRate ( vout_thread_t *, float );
73 static float AdjustRateFine( vlc_object_t *, const int );
74 static void ClearChannels  ( intf_thread_t *, vout_thread_t * );
75
76 #define DisplayMessage(vout, ...) \
77     do { \
78         if (vout) \
79             vout_OSDMessage(vout, SPU_DEFAULT_CHANNEL, __VA_ARGS__); \
80     } while(0)
81 #define DisplayIcon(vout, icon) \
82     do { if(vout) vout_OSDIcon(vout, SPU_DEFAULT_CHANNEL, icon); } while(0)
83
84 /*****************************************************************************
85  * Module descriptor
86  *****************************************************************************/
87
88 vlc_module_begin ()
89     set_shortname( N_("Hotkeys") )
90     set_description( N_("Hotkeys management interface") )
91     set_capability( "interface", 0 )
92     set_callbacks( Open, Close )
93     set_category( CAT_INTERFACE )
94     set_subcategory( SUBCAT_INTERFACE_HOTKEYS )
95
96 vlc_module_end ()
97
98 /*****************************************************************************
99  * Open: initialize interface
100  *****************************************************************************/
101 static int Open( vlc_object_t *p_this )
102 {
103     intf_thread_t *p_intf = (intf_thread_t *)p_this;
104     intf_sys_t *p_sys;
105     p_sys = malloc( sizeof( intf_sys_t ) );
106     if( !p_sys )
107         return VLC_ENOMEM;
108
109     p_intf->p_sys = p_sys;
110
111     p_sys->p_last_vout = NULL;
112     p_sys->subtitle_delaybookmarks.i_time_audio = 0;
113     p_sys->subtitle_delaybookmarks.i_time_subtitle = 0;
114
115     var_AddCallback( p_intf->p_libvlc, "key-action", ActionEvent, p_intf );
116     return VLC_SUCCESS;
117 }
118
119 /*****************************************************************************
120  * Close: destroy interface
121  *****************************************************************************/
122 static void Close( vlc_object_t *p_this )
123 {
124     intf_thread_t *p_intf = (intf_thread_t *)p_this;
125     intf_sys_t *p_sys = p_intf->p_sys;
126
127     var_DelCallback( p_intf->p_libvlc, "key-action", ActionEvent, p_intf );
128
129     /* Destroy structure */
130     free( p_sys );
131 }
132
133 static int PutAction( intf_thread_t *p_intf, int i_action )
134 {
135     intf_sys_t *p_sys = p_intf->p_sys;
136     playlist_t *p_playlist = pl_Get( p_intf );
137
138     /* Update the input */
139     input_thread_t *p_input = playlist_CurrentInput( p_playlist );
140
141     /* Update the vout */
142     vout_thread_t *p_vout = p_input ? input_GetVout( p_input ) : NULL;
143
144     /* Register OSD channels */
145     /* FIXME: this check can fail if the new vout is reallocated at the same
146      * address as the old one... We should rather listen to vout events.
147      * Alternatively, we should keep a reference to the vout thread. */
148     if( p_vout && p_vout != p_sys->p_last_vout )
149         p_sys->slider_chan = vout_RegisterSubpictureChannel( p_vout );
150     p_sys->p_last_vout = p_vout;
151
152     /* Quit */
153     switch( i_action )
154     {
155         /* Libvlc / interface actions */
156         case ACTIONID_QUIT:
157             libvlc_Quit( p_intf->p_libvlc );
158
159             ClearChannels( p_intf, p_vout );
160             DisplayMessage( p_vout, _( "Quit" ) );
161             break;
162
163         case ACTIONID_INTF_TOGGLE_FSC:
164         case ACTIONID_INTF_HIDE:
165             var_TriggerCallback( p_intf->p_libvlc, "intf-toggle-fscontrol" );
166             break;
167         case ACTIONID_INTF_BOSS:
168             var_TriggerCallback( p_intf->p_libvlc, "intf-boss" );
169             break;
170         case ACTIONID_INTF_POPUP_MENU:
171             var_TriggerCallback( p_intf->p_libvlc, "intf-popupmenu" );
172             break;
173
174         /* Playlist actions (including audio) */
175         case ACTIONID_LOOP:
176         {
177             /* Toggle Normal -> Loop -> Repeat -> Normal ... */
178             const char *mode;
179             if( var_GetBool( p_playlist, "repeat" ) )
180             {
181                 var_SetBool( p_playlist, "repeat", false );
182                 mode = N_("Off");
183             }
184             else
185             if( var_GetBool( p_playlist, "loop" ) )
186             { /* FIXME: this is not atomic, we should use a real tristate */
187                 var_SetBool( p_playlist, "loop", false );
188                 var_SetBool( p_playlist, "repeat", true );
189                 mode = N_("One");
190             }
191             else
192             {
193                 var_SetBool( p_playlist, "loop", true );
194                 mode = N_("All");
195             }
196             DisplayMessage( p_vout, _("Loop: %s"), vlc_gettext(mode) );
197             break;
198         }
199
200         case ACTIONID_RANDOM:
201         {
202             const bool state = var_ToggleBool( p_playlist, "random" );
203             DisplayMessage( p_vout, _("Random: %s"),
204                             vlc_gettext( state ? N_("On") : N_("Off") ) );
205             break;
206         }
207
208         case ACTIONID_NEXT:
209             DisplayMessage( p_vout, _("Next") );
210             playlist_Next( p_playlist );
211             break;
212         case ACTIONID_PREV:
213             DisplayMessage( p_vout, _("Previous") );
214             playlist_Prev( p_playlist );
215             break;
216
217         case ACTIONID_STOP:
218             playlist_Stop( p_playlist );
219             break;
220
221         case ACTIONID_RATE_NORMAL:
222             var_SetFloat( p_playlist, "rate", 1.f );
223             DisplayRate( p_vout, 1.f );
224             break;
225         case ACTIONID_FASTER:
226             var_TriggerCallback( p_playlist, "rate-faster" );
227             DisplayRate( p_vout, var_GetFloat( p_playlist, "rate" ) );
228             break;
229         case ACTIONID_SLOWER:
230             var_TriggerCallback( p_playlist, "rate-slower" );
231             DisplayRate( p_vout, var_GetFloat( p_playlist, "rate" ) );
232             break;
233         case ACTIONID_RATE_FASTER_FINE:
234         case ACTIONID_RATE_SLOWER_FINE:
235         {
236             const int i_dir = i_action == ACTIONID_RATE_FASTER_FINE ? 1 : -1;
237             float rate = AdjustRateFine( VLC_OBJECT(p_playlist), i_dir );
238
239             var_SetFloat( p_playlist, "rate", rate );
240             DisplayRate( p_vout, rate );
241             break;
242         }
243
244         case ACTIONID_PLAY_BOOKMARK1:
245         case ACTIONID_PLAY_BOOKMARK2:
246         case ACTIONID_PLAY_BOOKMARK3:
247         case ACTIONID_PLAY_BOOKMARK4:
248         case ACTIONID_PLAY_BOOKMARK5:
249         case ACTIONID_PLAY_BOOKMARK6:
250         case ACTIONID_PLAY_BOOKMARK7:
251         case ACTIONID_PLAY_BOOKMARK8:
252         case ACTIONID_PLAY_BOOKMARK9:
253         case ACTIONID_PLAY_BOOKMARK10:
254             PlayBookmark( p_intf, i_action - ACTIONID_PLAY_BOOKMARK1 + 1 );
255             break;
256
257         case ACTIONID_SET_BOOKMARK1:
258         case ACTIONID_SET_BOOKMARK2:
259         case ACTIONID_SET_BOOKMARK3:
260         case ACTIONID_SET_BOOKMARK4:
261         case ACTIONID_SET_BOOKMARK5:
262         case ACTIONID_SET_BOOKMARK6:
263         case ACTIONID_SET_BOOKMARK7:
264         case ACTIONID_SET_BOOKMARK8:
265         case ACTIONID_SET_BOOKMARK9:
266         case ACTIONID_SET_BOOKMARK10:
267             SetBookmark( p_intf, i_action - ACTIONID_SET_BOOKMARK1 + 1 );
268             break;
269         case ACTIONID_PLAY_CLEAR:
270         {
271             playlist_t *p_playlist = pl_Get( p_intf );
272             playlist_Clear( p_playlist, pl_Unlocked );
273             break;
274         }
275         case ACTIONID_VOL_UP:
276         {
277             float vol;
278             if( playlist_VolumeUp( p_playlist, 1, &vol ) == 0 )
279                 DisplayVolume( p_intf, p_vout, vol );
280             break;
281         }
282         case ACTIONID_VOL_DOWN:
283         {
284             float vol;
285             if( playlist_VolumeDown( p_playlist, 1, &vol ) == 0 )
286                 DisplayVolume( p_intf, p_vout, vol );
287             break;
288         }
289         case ACTIONID_VOL_MUTE:
290         {
291             int mute = playlist_MuteGet( p_playlist );
292             if( mute < 0 )
293                 break;
294             mute = !mute;
295             if( playlist_MuteSet( p_playlist, mute ) )
296                 break;
297
298             float vol = playlist_VolumeGet( p_playlist );
299             if( mute || vol == 0.f )
300             {
301                 ClearChannels( p_intf, p_vout );
302                 DisplayIcon( p_vout, OSD_MUTE_ICON );
303             }
304             else
305                 DisplayVolume( p_intf, p_vout, vol );
306             break;
307         }
308
309         case ACTIONID_AUDIODEVICE_CYCLE:
310         {
311             audio_output_t *p_aout = playlist_GetAout( p_playlist );
312             if( p_aout == NULL )
313                 break;
314
315             char **ids, **names;
316             int n = aout_DevicesList( p_aout, &ids, &names );
317             if( n == -1 )
318                 break;
319
320             char *dev = aout_DeviceGet( p_aout );
321             const char *devstr = (dev != NULL) ? dev : "";
322
323             int idx = 0;
324             for( int i = 0; i < n; i++ )
325             {
326                 if( !strcmp(devstr, ids[i]) )
327                     idx = (i + 1) % n;
328             }
329             free( dev );
330
331             if( !aout_DeviceSet( p_aout, ids[idx] ) )
332                 DisplayMessage( p_vout, _("Audio Device: %s"), names[idx] );
333             vlc_object_release( p_aout );
334
335             for( int i = 0; i < n; i++ )
336             {
337                 free( ids[i] );
338                 free( names[i] );
339             }
340             free( ids );
341             free( names );
342             break;
343         }
344
345         /* Playlist + input actions */
346         case ACTIONID_PLAY_PAUSE:
347             if( p_input )
348             {
349                 ClearChannels( p_intf, p_vout );
350
351                 int state = var_GetInteger( p_input, "state" );
352                 DisplayIcon( p_vout, state != PAUSE_S ? OSD_PAUSE_ICON : OSD_PLAY_ICON );
353             }
354             playlist_TogglePause( p_playlist );
355             break;
356
357         case ACTIONID_PLAY:
358             if( p_input && var_GetFloat( p_input, "rate" ) != 1.f )
359                 /* Return to normal speed */
360                 var_SetFloat( p_input, "rate", 1.f );
361             else
362             {
363                 ClearChannels( p_intf, p_vout );
364                 DisplayIcon( p_vout, OSD_PLAY_ICON );
365                 playlist_Play( p_playlist );
366             }
367             break;
368
369         /* Playlist + video output actions */
370         case ACTIONID_WALLPAPER:
371         {
372             bool wp = var_ToggleBool( p_playlist, "video-wallpaper" );
373             if( p_vout )
374                 var_SetBool( p_vout, "video-wallpaper", wp );
375             break;
376         }
377
378         /* Input actions */
379         case ACTIONID_PAUSE:
380             if( p_input && var_GetInteger( p_input, "state" ) != PAUSE_S )
381             {
382                 ClearChannels( p_intf, p_vout );
383                 DisplayIcon( p_vout, OSD_PAUSE_ICON );
384                 var_SetInteger( p_input, "state", PAUSE_S );
385             }
386             break;
387
388         case ACTIONID_RECORD:
389             if( p_input && var_GetBool( p_input, "can-record" ) )
390             {
391                 const bool on = var_ToggleBool( p_input, "record" );
392                 DisplayMessage( p_vout, vlc_gettext(on
393                                    ? N_("Recording") : N_("Recording done")) );
394             }
395             break;
396
397         case ACTIONID_FRAME_NEXT:
398             if( p_input )
399             {
400                 var_TriggerCallback( p_input, "frame-next" );
401                 DisplayMessage( p_vout, _("Next frame") );
402             }
403             break;
404
405         case ACTIONID_SUBSYNC_MARKAUDIO:
406         {
407             p_sys->subtitle_delaybookmarks.i_time_audio = mdate();
408             DisplayMessage( p_vout, _("Sub sync: bookmarked audio time"));
409             break;
410         }
411         case ACTIONID_SUBSYNC_MARKSUB:
412             if( p_input )
413             {
414                 vlc_value_t val, list, list2;
415                 int i_count;
416                 var_Get( p_input, "spu-es", &val );
417
418                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
419                             &list, &list2 );
420                 i_count = list.p_list->i_count;
421                 if( i_count < 1 || val.i_int < 0 )
422                 {
423                     DisplayMessage( p_vout, _("No active subtitle") );
424                     var_FreeList( &list, &list2 );
425                     break;
426                 }
427                 p_sys->subtitle_delaybookmarks.i_time_subtitle = mdate();
428                 DisplayMessage( p_vout,
429                                 _("Sub sync: bookmarked subtitle time"));
430                 var_FreeList( &list, &list2 );
431             }
432             break;
433         case ACTIONID_SUBSYNC_APPLY:
434         {
435             /* Warning! Can yield a pause in the playback.
436              * For example, the following succession of actions will yield a 5 second delay :
437              * - Pressing Shift-H (ACTIONID_SUBSYNC_MARKAUDIO)
438              * - wait 5 second
439              * - Press Shift-J (ACTIONID_SUBSYNC_MARKSUB)
440              * - Press Shift-K (ACTIONID_SUBSYNC_APPLY)
441              * --> 5 seconds pause
442              * This is due to var_SetTime() (and ultimately UpdatePtsDelay())
443              * which causes the video to pause for an equivalent duration
444              * (This problem is also present in the "Track synchronization" window) */
445             if ( p_input )
446             {
447                 if ( (p_sys->subtitle_delaybookmarks.i_time_audio == 0) || (p_sys->subtitle_delaybookmarks.i_time_subtitle == 0) )
448                 {
449                     DisplayMessage( p_vout, _( "Sub sync: set bookmarks first!" ) );
450                 }
451                 else
452                 {
453                     int64_t i_current_subdelay = var_GetTime( p_input, "spu-delay" );
454                     int64_t i_additional_subdelay = p_sys->subtitle_delaybookmarks.i_time_audio - p_sys->subtitle_delaybookmarks.i_time_subtitle;
455                     int64_t i_total_subdelay = i_current_subdelay + i_additional_subdelay;
456                     var_SetTime( p_input, "spu-delay", i_total_subdelay);
457                     ClearChannels( p_intf, p_vout );
458                     DisplayMessage( p_vout, _( "Sub sync: corrected %i ms (total delay = %i ms)" ),
459                                             (int)(i_additional_subdelay / 1000),
460                                             (int)(i_total_subdelay / 1000) );
461                     p_sys->subtitle_delaybookmarks.i_time_audio = 0;
462                     p_sys->subtitle_delaybookmarks.i_time_subtitle = 0;
463                 }
464             }
465             break;
466         }
467         case ACTIONID_SUBSYNC_RESET:
468         {
469             var_SetTime( p_input, "spu-delay", 0);
470             ClearChannels( p_intf, p_vout );
471             DisplayMessage( p_vout, _( "Sub sync: delay reset" ) );
472             p_sys->subtitle_delaybookmarks.i_time_audio = 0;
473             p_sys->subtitle_delaybookmarks.i_time_subtitle = 0;
474             break;
475         }
476
477         case ACTIONID_SUBDELAY_DOWN:
478         case ACTIONID_SUBDELAY_UP:
479         {
480             int diff = (i_action == ACTIONID_SUBDELAY_UP) ? 50000 : -50000;
481             if( p_input )
482             {
483                 vlc_value_t val, list, list2;
484                 int i_count;
485                 var_Get( p_input, "spu-es", &val );
486
487                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
488                             &list, &list2 );
489                 i_count = list.p_list->i_count;
490                 if( i_count < 1 || val.i_int < 0 )
491                 {
492                     DisplayMessage( p_vout, _("No active subtitle") );
493                     var_FreeList( &list, &list2 );
494                     break;
495                 }
496                 int64_t i_delay = var_GetTime( p_input, "spu-delay" ) + diff;
497
498                 var_SetTime( p_input, "spu-delay", i_delay );
499                 ClearChannels( p_intf, p_vout );
500                 DisplayMessage( p_vout, _( "Subtitle delay %i ms" ),
501                                 (int)(i_delay/1000) );
502                 var_FreeList( &list, &list2 );
503             }
504             break;
505         }
506         case ACTIONID_AUDIODELAY_DOWN:
507         case ACTIONID_AUDIODELAY_UP:
508         {
509             int diff = (i_action == ACTIONID_AUDIODELAY_UP) ? 50000 : -50000;
510             if( p_input )
511             {
512                 int64_t i_delay = var_GetTime( p_input, "audio-delay" ) + diff;
513
514                 var_SetTime( p_input, "audio-delay", i_delay );
515                 ClearChannels( p_intf, p_vout );
516                 DisplayMessage( p_vout, _( "Audio delay %i ms" ),
517                                  (int)(i_delay/1000) );
518             }
519             break;
520         }
521
522         case ACTIONID_AUDIO_TRACK:
523             if( p_input )
524             {
525                 vlc_value_t val, list, list2;
526                 int i_count, i;
527                 var_Get( p_input, "audio-es", &val );
528                 var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES,
529                             &list, &list2 );
530                 i_count = list.p_list->i_count;
531                 if( i_count > 1 )
532                 {
533                     for( i = 0; i < i_count; i++ )
534                     {
535                         if( val.i_int == list.p_list->p_values[i].i_int )
536                         {
537                             break;
538                         }
539                     }
540                     /* value of audio-es was not in choices list */
541                     if( i == i_count )
542                     {
543                         msg_Warn( p_input,
544                                   "invalid current audio track, selecting 0" );
545                         i = 0;
546                     }
547                     else if( i == i_count - 1 )
548                         i = 1;
549                     else
550                         i++;
551                     var_Set( p_input, "audio-es", list.p_list->p_values[i] );
552                     DisplayMessage( p_vout, _("Audio track: %s"),
553                                     list2.p_list->p_values[i].psz_string );
554                 }
555                 var_FreeList( &list, &list2 );
556             }
557             break;
558         case ACTIONID_SUBTITLE_TRACK:
559             if( p_input )
560             {
561                 vlc_value_t val, list, list2;
562                 int i_count, i;
563                 var_Get( p_input, "spu-es", &val );
564
565                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
566                             &list, &list2 );
567                 i_count = list.p_list->i_count;
568                 if( i_count <= 1 )
569                 {
570                     DisplayMessage( p_vout, _("Subtitle track: %s"),
571                                     _("N/A") );
572                     var_FreeList( &list, &list2 );
573                     break;
574                 }
575                 for( i = 0; i < i_count; i++ )
576                 {
577                     if( val.i_int == list.p_list->p_values[i].i_int )
578                     {
579                         break;
580                     }
581                 }
582                 /* value of spu-es was not in choices list */
583                 if( i == i_count )
584                 {
585                     msg_Warn( p_input,
586                               "invalid current subtitle track, selecting 0" );
587                     i = 0;
588                 }
589                 else if( i == i_count - 1 )
590                     i = 0;
591                 else
592                     i++;
593                 var_SetInteger( p_input, "spu-es", list.p_list->p_values[i].i_int );
594                 var_SetInteger( p_input, "spu-choice", list.p_list->p_values[i].i_int );
595                 DisplayMessage( p_vout, _("Subtitle track: %s"),
596                                 list2.p_list->p_values[i].psz_string );
597                 var_FreeList( &list, &list2 );
598             }
599             break;
600         case ACTIONID_SUBTITLE_TOGGLE:
601             if( p_input )
602             {
603                 vlc_value_t list, list2;
604                 int i_count, i_sel_index, i_sel_id, i_old_id, i_new_index;
605                 i_old_id = var_GetInteger( p_input, "spu-es" );
606                 i_sel_id = var_GetInteger( p_input, "spu-choice" );
607
608                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
609                             &list, &list2 );
610                 i_count = list.p_list->i_count;
611                 if( i_count <= 1 )
612                 {
613                     DisplayMessage( p_vout, _("Subtitle track: %s"),
614                                     _("N/A") );
615                     var_FreeList( &list, &list2 );
616                     break;
617                 }
618                 for( i_sel_index = 0; i_sel_index < i_count; i_sel_index++ )
619                 {
620                     if( i_sel_id == list.p_list->p_values[i_sel_index].i_int )
621                     {
622                         break;
623                     }
624                 }
625                 /* if there is nothing to toggle choose the first track */
626                 if( !i_sel_index ) {
627                     i_sel_index = 1;
628                     i_sel_id = list.p_list->p_values[1].i_int;
629                     var_SetInteger( p_input, "spu-choice", i_sel_id );
630                 }
631
632                 i_new_index = 0;
633                 if( i_old_id != i_sel_id )
634                 {
635                     if( i_sel_index >= i_count )
636                     {
637                         var_SetInteger( p_input, "spu-choice", list.p_list->p_values[0].i_int );
638                     }
639                     else
640                     {
641                         i_new_index = i_sel_index;
642                     }
643                 }
644                 var_SetInteger( p_input, "spu-es", list.p_list->p_values[i_new_index].i_int );
645                 DisplayMessage( p_vout, _("Subtitle track: %s"),
646                                 list2.p_list->p_values[i_new_index].psz_string );
647                 var_FreeList( &list, &list2 );
648             }
649             break;
650         case ACTIONID_PROGRAM_SID_NEXT:
651         case ACTIONID_PROGRAM_SID_PREV:
652             if( p_input )
653             {
654                 vlc_value_t val, list, list2;
655                 int i_count, i;
656                 var_Get( p_input, "program", &val );
657
658                 var_Change( p_input, "program", VLC_VAR_GETCHOICES,
659                             &list, &list2 );
660                 i_count = list.p_list->i_count;
661                 if( i_count <= 1 )
662                 {
663                     DisplayMessage( p_vout, _("Program Service ID: %s"),
664                                     _("N/A") );
665                     var_FreeList( &list, &list2 );
666                     break;
667                 }
668                 for( i = 0; i < i_count; i++ )
669                 {
670                     if( val.i_int == list.p_list->p_values[i].i_int )
671                     {
672                         break;
673                     }
674                 }
675                 /* value of program sid was not in choices list */
676                 if( i == i_count )
677                 {
678                     msg_Warn( p_input,
679                               "invalid current program SID, selecting 0" );
680                     i = 0;
681                 }
682                 else if( i_action == ACTIONID_PROGRAM_SID_NEXT ) {
683                     if( i == i_count - 1 )
684                         i = 0;
685                     else
686                         i++;
687                     }
688                 else { /* ACTIONID_PROGRAM_SID_PREV */
689                     if( i == 0 )
690                         i = i_count - 1;
691                     else
692                         i--;
693                     }
694                 var_Set( p_input, "program", list.p_list->p_values[i] );
695                 DisplayMessage( p_vout, _("Program Service ID: %s"),
696                                 list2.p_list->p_values[i].psz_string );
697                 var_FreeList( &list, &list2 );
698             }
699             break;
700
701         case ACTIONID_JUMP_BACKWARD_EXTRASHORT:
702         case ACTIONID_JUMP_FORWARD_EXTRASHORT:
703         case ACTIONID_JUMP_BACKWARD_SHORT:
704         case ACTIONID_JUMP_FORWARD_SHORT:
705         case ACTIONID_JUMP_BACKWARD_MEDIUM:
706         case ACTIONID_JUMP_FORWARD_MEDIUM:
707         case ACTIONID_JUMP_BACKWARD_LONG:
708         case ACTIONID_JUMP_FORWARD_LONG:
709         {
710             if( p_input == NULL || !var_GetBool( p_input, "can-seek" ) )
711                 break;
712
713             const char *varname;
714             int sign = +1;
715             switch( i_action )
716             {
717                 case ACTIONID_JUMP_BACKWARD_EXTRASHORT:
718                     sign = -1;
719                 case ACTIONID_JUMP_FORWARD_EXTRASHORT:
720                     varname = "extrashort-jump-size";
721                     break;
722                 case ACTIONID_JUMP_BACKWARD_SHORT:
723                     sign = -1;
724                 case ACTIONID_JUMP_FORWARD_SHORT:
725                     varname = "short-jump-size";
726                     break;
727                 case ACTIONID_JUMP_BACKWARD_MEDIUM:
728                     sign = -1;
729                 case ACTIONID_JUMP_FORWARD_MEDIUM:
730                     varname = "medium-jump-size";
731                     break;
732                 case ACTIONID_JUMP_BACKWARD_LONG:
733                     sign = -1;
734                 case ACTIONID_JUMP_FORWARD_LONG:
735                     varname = "long-jump-size";
736                     break;
737             }
738
739             mtime_t it = var_InheritInteger( p_input, varname );
740             if( it < 0 )
741                 break;
742             var_SetTime( p_input, "time-offset", it * sign * CLOCK_FREQ );
743             DisplayPosition( p_intf, p_vout, p_input );
744             break;
745         }
746
747         /* Input navigation */
748         case ACTIONID_TITLE_PREV:
749             if( p_input )
750                 var_TriggerCallback( p_input, "prev-title" );
751             break;
752         case ACTIONID_TITLE_NEXT:
753             if( p_input )
754                 var_TriggerCallback( p_input, "next-title" );
755             break;
756         case ACTIONID_CHAPTER_PREV:
757             if( p_input )
758                 var_TriggerCallback( p_input, "prev-chapter" );
759             break;
760         case ACTIONID_CHAPTER_NEXT:
761             if( p_input )
762                 var_TriggerCallback( p_input, "next-chapter" );
763             break;
764         case ACTIONID_DISC_MENU:
765             if( p_input )
766                 var_SetInteger( p_input, "title  0", 2 );
767             break;
768         case ACTIONID_NAV_ACTIVATE:
769         case ACTIONID_NAV_UP:
770         case ACTIONID_NAV_DOWN:
771         case ACTIONID_NAV_LEFT:
772         case ACTIONID_NAV_RIGHT:
773             if( p_input )
774                 input_Control( p_input, i_action - ACTIONID_NAV_ACTIVATE
775                                + INPUT_NAV_ACTIVATE, NULL );
776             break;
777
778         /* Video Output actions */
779         case ACTIONID_SNAPSHOT:
780             if( p_vout )
781                 var_TriggerCallback( p_vout, "video-snapshot" );
782             break;
783
784         case ACTIONID_TOGGLE_FULLSCREEN:
785         {
786             if( p_vout )
787             {
788                 bool fs = var_ToggleBool( p_vout, "fullscreen" );
789                 var_SetBool( p_playlist, "fullscreen", fs );
790             }
791             else
792                 var_ToggleBool( p_playlist, "fullscreen" );
793             break;
794         }
795
796         case ACTIONID_LEAVE_FULLSCREEN:
797             if( p_vout )
798                 var_SetBool( p_vout, "fullscreen", false );
799             var_SetBool( p_playlist, "fullscreen", false );
800             break;
801
802         case ACTIONID_ASPECT_RATIO:
803             if( p_vout )
804             {
805                 vlc_value_t val={0}, val_list, text_list;
806                 var_Get( p_vout, "aspect-ratio", &val );
807                 if( var_Change( p_vout, "aspect-ratio", VLC_VAR_GETCHOICES,
808                                 &val_list, &text_list ) >= 0 )
809                 {
810                     int i;
811                     for( i = 0; i < val_list.p_list->i_count; i++ )
812                     {
813                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
814                                      val.psz_string ) )
815                         {
816                             i++;
817                             break;
818                         }
819                     }
820                     if( i == val_list.p_list->i_count ) i = 0;
821                     var_SetString( p_vout, "aspect-ratio",
822                                    val_list.p_list->p_values[i].psz_string );
823                     DisplayMessage( p_vout, _("Aspect ratio: %s"),
824                                     text_list.p_list->p_values[i].psz_string );
825
826                     var_FreeList( &val_list, &text_list );
827                 }
828                 free( val.psz_string );
829             }
830             break;
831
832         case ACTIONID_CROP:
833             if( p_vout )
834             {
835                 vlc_value_t val={0}, val_list, text_list;
836                 var_Get( p_vout, "crop", &val );
837                 if( var_Change( p_vout, "crop", VLC_VAR_GETCHOICES,
838                                 &val_list, &text_list ) >= 0 )
839                 {
840                     int i;
841                     for( i = 0; i < val_list.p_list->i_count; i++ )
842                     {
843                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
844                                      val.psz_string ) )
845                         {
846                             i++;
847                             break;
848                         }
849                     }
850                     if( i == val_list.p_list->i_count ) i = 0;
851                     var_SetString( p_vout, "crop",
852                                    val_list.p_list->p_values[i].psz_string );
853                     DisplayMessage( p_vout, _("Crop: %s"),
854                                     text_list.p_list->p_values[i].psz_string );
855
856                     var_FreeList( &val_list, &text_list );
857                 }
858                 free( val.psz_string );
859             }
860             break;
861         case ACTIONID_CROP_TOP:
862             if( p_vout )
863                 var_IncInteger( p_vout, "crop-top" );
864             break;
865         case ACTIONID_UNCROP_TOP:
866             if( p_vout )
867                 var_DecInteger( p_vout, "crop-top" );
868             break;
869         case ACTIONID_CROP_BOTTOM:
870             if( p_vout )
871                 var_IncInteger( p_vout, "crop-bottom" );
872             break;
873         case ACTIONID_UNCROP_BOTTOM:
874             if( p_vout )
875                 var_DecInteger( p_vout, "crop-bottom" );
876             break;
877         case ACTIONID_CROP_LEFT:
878             if( p_vout )
879                 var_IncInteger( p_vout, "crop-left" );
880             break;
881         case ACTIONID_UNCROP_LEFT:
882             if( p_vout )
883                 var_DecInteger( p_vout, "crop-left" );
884             break;
885         case ACTIONID_CROP_RIGHT:
886             if( p_vout )
887                 var_IncInteger( p_vout, "crop-right" );
888             break;
889         case ACTIONID_UNCROP_RIGHT:
890             if( p_vout )
891                 var_DecInteger( p_vout, "crop-right" );
892             break;
893
894          case ACTIONID_TOGGLE_AUTOSCALE:
895             if( p_vout )
896             {
897                 float f_scalefactor = var_GetFloat( p_vout, "zoom" );
898                 if ( f_scalefactor != 1.f )
899                 {
900                     var_SetFloat( p_vout, "zoom", 1.f );
901                     DisplayMessage( p_vout, _("Zooming reset") );
902                 }
903                 else
904                 {
905                     bool b_autoscale = !var_GetBool( p_vout, "autoscale" );
906                     var_SetBool( p_vout, "autoscale", b_autoscale );
907                     if( b_autoscale )
908                         DisplayMessage( p_vout, _("Scaled to screen") );
909                     else
910                         DisplayMessage( p_vout, _("Original Size") );
911                 }
912             }
913             break;
914         case ACTIONID_SCALE_UP:
915             if( p_vout )
916             {
917                float f_scalefactor = var_GetFloat( p_vout, "zoom" );
918
919                if( f_scalefactor < 10.f )
920                    f_scalefactor += .1f;
921                var_SetFloat( p_vout, "zoom", f_scalefactor );
922             }
923             break;
924         case ACTIONID_SCALE_DOWN:
925             if( p_vout )
926             {
927                float f_scalefactor = var_GetFloat( p_vout, "zoom" );
928
929                if( f_scalefactor > .3f )
930                    f_scalefactor -= .1f;
931                var_SetFloat( p_vout, "zoom", f_scalefactor );
932             }
933             break;
934
935         case ACTIONID_ZOOM_QUARTER:
936         case ACTIONID_ZOOM_HALF:
937         case ACTIONID_ZOOM_ORIGINAL:
938         case ACTIONID_ZOOM_DOUBLE:
939             if( p_vout )
940             {
941                 float f;
942                 switch( i_action )
943                 {
944                     case ACTIONID_ZOOM_QUARTER:  f = 0.25; break;
945                     case ACTIONID_ZOOM_HALF:     f = 0.5;  break;
946                     case ACTIONID_ZOOM_ORIGINAL: f = 1.;   break;
947                      /*case ACTIONID_ZOOM_DOUBLE:*/
948                     default:                     f = 2.;   break;
949                 }
950                 var_SetFloat( p_vout, "zoom", f );
951             }
952             break;
953         case ACTIONID_ZOOM:
954         case ACTIONID_UNZOOM:
955             if( p_vout )
956             {
957                 vlc_value_t val={0}, val_list, text_list;
958                 var_Get( p_vout, "zoom", &val );
959                 if( var_Change( p_vout, "zoom", VLC_VAR_GETCHOICES,
960                                 &val_list, &text_list ) >= 0 )
961                 {
962                     int i;
963                     for( i = 0; i < val_list.p_list->i_count; i++ )
964                     {
965                         if( val_list.p_list->p_values[i].f_float
966                            == val.f_float )
967                         {
968                             if( i_action == ACTIONID_ZOOM )
969                                 i++;
970                             else /* ACTIONID_UNZOOM */
971                                 i--;
972                             break;
973                         }
974                     }
975                     if( i == val_list.p_list->i_count ) i = 0;
976                     if( i == -1 ) i = val_list.p_list->i_count-1;
977                     var_SetFloat( p_vout, "zoom",
978                                   val_list.p_list->p_values[i].f_float );
979                     DisplayMessage( p_vout, _("Zoom mode: %s"),
980                                     text_list.p_list->p_values[i].psz_string );
981
982                     var_FreeList( &val_list, &text_list );
983                 }
984             }
985             break;
986
987         case ACTIONID_DEINTERLACE:
988             if( p_vout )
989             {
990                 int i_deinterlace = var_GetInteger( p_vout, "deinterlace" );
991                 if( i_deinterlace != 0 )
992                 {
993                     var_SetInteger( p_vout, "deinterlace", 0 );
994                     DisplayMessage( p_vout, _("Deinterlace off") );
995                 }
996                 else
997                 {
998                     var_SetInteger( p_vout, "deinterlace", 1 );
999
1000                     char *psz_mode = var_GetString( p_vout, "deinterlace-mode" );
1001                     vlc_value_t vlist, tlist;
1002                     if( psz_mode && !var_Change( p_vout, "deinterlace-mode", VLC_VAR_GETCHOICES, &vlist, &tlist ) )
1003                     {
1004                         const char *psz_text = NULL;
1005                         for( int i = 0; i < vlist.p_list->i_count; i++ )
1006                         {
1007                             if( !strcmp( vlist.p_list->p_values[i].psz_string, psz_mode ) )
1008                             {
1009                                 psz_text = tlist.p_list->p_values[i].psz_string;
1010                                 break;
1011                             }
1012                         }
1013                         DisplayMessage( p_vout, "%s (%s)", _("Deinterlace on"),
1014                                         psz_text ? psz_text : psz_mode );
1015
1016                         var_FreeList( &vlist, &tlist );
1017                     }
1018                     free( psz_mode );
1019                 }
1020             }
1021             break;
1022         case ACTIONID_DEINTERLACE_MODE:
1023             if( p_vout )
1024             {
1025                 char *psz_mode = var_GetString( p_vout, "deinterlace-mode" );
1026                 vlc_value_t vlist, tlist;
1027                 if( psz_mode && !var_Change( p_vout, "deinterlace-mode", VLC_VAR_GETCHOICES, &vlist, &tlist ))
1028                 {
1029                     const char *psz_text = NULL;
1030                     int i;
1031                     for( i = 0; i < vlist.p_list->i_count; i++ )
1032                     {
1033                         if( !strcmp( vlist.p_list->p_values[i].psz_string, psz_mode ) )
1034                         {
1035                             i++;
1036                             break;
1037                         }
1038                     }
1039                     if( i == vlist.p_list->i_count ) i = 0;
1040                     psz_text = tlist.p_list->p_values[i].psz_string;
1041                     var_SetString( p_vout, "deinterlace-mode", vlist.p_list->p_values[i].psz_string );
1042
1043                     int i_deinterlace = var_GetInteger( p_vout, "deinterlace" );
1044                     if( i_deinterlace != 0 )
1045                     {
1046                       DisplayMessage( p_vout, "%s (%s)", _("Deinterlace on"),
1047                                       psz_text ? psz_text : psz_mode );
1048                     }
1049                     else
1050                     {
1051                       DisplayMessage( p_vout, "%s (%s)", _("Deinterlace off"),
1052                                       psz_text ? psz_text : psz_mode );
1053                     }
1054
1055                     var_FreeList( &vlist, &tlist );
1056                 }
1057                 free( psz_mode );
1058             }
1059             break;
1060
1061         case ACTIONID_SUBPOS_DOWN:
1062         case ACTIONID_SUBPOS_UP:
1063         {
1064             if( p_input )
1065             {
1066                 vlc_value_t val, list, list2;
1067                 int i_count;
1068                 var_Get( p_input, "spu-es", &val );
1069
1070                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
1071                             &list, &list2 );
1072                 i_count = list.p_list->i_count;
1073                 if( i_count < 1 || val.i_int < 0 )
1074                 {
1075                     DisplayMessage( p_vout,
1076                                     _("Subtitle position: no active subtitle") );
1077                     var_FreeList( &list, &list2 );
1078                     break;
1079                 }
1080
1081                 int i_pos;
1082                 if( i_action == ACTIONID_SUBPOS_DOWN )
1083                     i_pos = var_DecInteger( p_vout, "sub-margin" );
1084                 else
1085                     i_pos = var_IncInteger( p_vout, "sub-margin" );
1086
1087                 ClearChannels( p_intf, p_vout );
1088                 DisplayMessage( p_vout, _( "Subtitle position %d px" ), i_pos );
1089                 var_FreeList( &list, &list2 );
1090             }
1091             break;
1092         }
1093
1094         /* Input + video output */
1095         case ACTIONID_POSITION:
1096             if( p_vout && vout_OSDEpg( p_vout, input_GetItem( p_input ) ) )
1097                 DisplayPosition( p_intf, p_vout, p_input );
1098             break;
1099     }
1100
1101     if( p_vout )
1102         vlc_object_release( p_vout );
1103     if( p_input )
1104         vlc_object_release( p_input );
1105     return VLC_SUCCESS;
1106 }
1107
1108 /*****************************************************************************
1109  * ActionEvent: callback for hotkey actions
1110  *****************************************************************************/
1111 static int ActionEvent( vlc_object_t *libvlc, char const *psz_var,
1112                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
1113 {
1114     intf_thread_t *p_intf = (intf_thread_t *)p_data;
1115
1116     (void)libvlc;
1117     (void)psz_var;
1118     (void)oldval;
1119
1120     return PutAction( p_intf, newval.i_int );
1121 }
1122
1123 static void PlayBookmark( intf_thread_t *p_intf, int i_num )
1124 {
1125     char *psz_bookmark_name;
1126     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
1127         return;
1128
1129     playlist_t *p_playlist = pl_Get( p_intf );
1130     char *psz_bookmark = var_CreateGetString( p_intf, psz_bookmark_name );
1131
1132     PL_LOCK;
1133     FOREACH_ARRAY( playlist_item_t *p_item, p_playlist->items )
1134         char *psz_uri = input_item_GetURI( p_item->p_input );
1135         if( !strcmp( psz_bookmark, psz_uri ) )
1136         {
1137             free( psz_uri );
1138             playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked,
1139                               NULL, p_item );
1140             break;
1141         }
1142         else
1143             free( psz_uri );
1144     FOREACH_END();
1145     PL_UNLOCK;
1146
1147     free( psz_bookmark );
1148     free( psz_bookmark_name );
1149 }
1150
1151 static void SetBookmark( intf_thread_t *p_intf, int i_num )
1152 {
1153     char *psz_bookmark_name;
1154     char *psz_uri = NULL;
1155     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
1156         return;
1157
1158     playlist_t *p_playlist = pl_Get( p_intf );
1159     var_Create( p_intf, psz_bookmark_name,
1160                 VLC_VAR_STRING|VLC_VAR_DOINHERIT );
1161
1162     PL_LOCK;
1163     playlist_item_t * p_item = playlist_CurrentPlayingItem( p_playlist );
1164     if( p_item ) psz_uri = input_item_GetURI( p_item->p_input );
1165     PL_UNLOCK;
1166
1167     if( p_item )
1168     {
1169         config_PutPsz( p_intf, psz_bookmark_name, psz_uri);
1170         msg_Info( p_intf, "setting playlist bookmark %i to %s", i_num, psz_uri);
1171     }
1172
1173     free( psz_uri );
1174     free( psz_bookmark_name );
1175 }
1176
1177 static void DisplayPosition( intf_thread_t *p_intf, vout_thread_t *p_vout,
1178                              input_thread_t *p_input )
1179 {
1180     char psz_duration[MSTRTIME_MAX_SIZE];
1181     char psz_time[MSTRTIME_MAX_SIZE];
1182     vlc_value_t time, pos;
1183     mtime_t i_seconds;
1184
1185     if( p_vout == NULL ) return;
1186
1187     ClearChannels( p_intf, p_vout );
1188
1189     var_Get( p_input, "time", &time );
1190     i_seconds = time.i_time / 1000000;
1191     secstotimestr ( psz_time, i_seconds );
1192
1193     var_Get( p_input, "length", &time );
1194     if( time.i_time > 0 )
1195     {
1196         secstotimestr( psz_duration, time.i_time / 1000000 );
1197         DisplayMessage( p_vout, "%s / %s", psz_time, psz_duration );
1198     }
1199     else if( i_seconds > 0 )
1200     {
1201         DisplayMessage( p_vout, "%s", psz_time );
1202     }
1203
1204     if( var_GetBool( p_vout, "fullscreen" ) )
1205     {
1206         var_Get( p_input, "position", &pos );
1207         vout_OSDSlider( p_vout, p_intf->p_sys->slider_chan,
1208                         pos.f_float * 100, OSD_HOR_SLIDER );
1209     }
1210 }
1211
1212 static void DisplayVolume( intf_thread_t *p_intf, vout_thread_t *p_vout,
1213                            float vol )
1214 {
1215     if( p_vout == NULL )
1216         return;
1217     ClearChannels( p_intf, p_vout );
1218
1219     if( var_GetBool( p_vout, "fullscreen" ) )
1220         vout_OSDSlider( p_vout, p_intf->p_sys->slider_chan,
1221                         lroundf(vol * 100.f), OSD_VERT_SLIDER );
1222     DisplayMessage( p_vout, _( "Volume %ld%%" ), lroundf(vol * 100.f) );
1223 }
1224
1225 static void DisplayRate( vout_thread_t *p_vout, float f_rate )
1226 {
1227     DisplayMessage( p_vout, _("Speed: %.2fx"), (double) f_rate );
1228 }
1229
1230 static float AdjustRateFine( vlc_object_t *p_obj, const int i_dir )
1231 {
1232     const float f_rate_min = (float)INPUT_RATE_DEFAULT / INPUT_RATE_MAX;
1233     const float f_rate_max = (float)INPUT_RATE_DEFAULT / INPUT_RATE_MIN;
1234     float f_rate = var_GetFloat( p_obj, "rate" );
1235
1236     int i_sign = f_rate < 0 ? -1 : 1;
1237
1238     f_rate = floor( fabs(f_rate) / 0.1 + i_dir + 0.05 ) * 0.1;
1239
1240     if( f_rate < f_rate_min )
1241         f_rate = f_rate_min;
1242     else if( f_rate > f_rate_max )
1243         f_rate = f_rate_max;
1244     f_rate *= i_sign;
1245
1246     return f_rate;
1247 }
1248
1249 static void ClearChannels( intf_thread_t *p_intf, vout_thread_t *p_vout )
1250 {
1251     if( p_vout )
1252     {
1253         vout_FlushSubpictureChannel( p_vout, SPU_DEFAULT_CHANNEL );
1254         vout_FlushSubpictureChannel( p_vout, p_intf->p_sys->slider_chan );
1255     }
1256 }