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