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