1 /*****************************************************************************
2  * hotkeys.c: Hotkey handling for vlc
3  *****************************************************************************
4  * Copyright (C) 2005-2009 the VideoLAN team
5  * $Id: 91ebde82c1a9018b5f73257024bada7a12328118 $
6  *
7  * Authors: Sigmund Augdal Helberg <dnumgis@videolan.org>
8  *          Jean-Paul Saman <jpsaman #_at_# m2x.nl>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32 
33 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_interface.h>
37 #include <vlc_input.h>
38 #include <vlc_aout.h>
39 #include <vlc_viewpoint.h>
40 #include <vlc_vout_osd.h>
41 #include <vlc_playlist.h>
42 #include <vlc_actions.h>
43 #include "math.h"
44 
45 #include <assert.h>
46 
47 /*****************************************************************************
48  * intf_sys_t: description and status of FB interface
49  *****************************************************************************/
50 struct intf_sys_t
51 {
52     vlc_mutex_t         lock;
53     vout_thread_t      *p_vout;
54     input_thread_t     *p_input;
55     int slider_chan;
56 
57     /*subtitle_delaybookmarks: placeholder for storing subtitle sync timestamps*/
58     struct
59     {
60         int64_t i_time_subtitle;
61         int64_t i_time_audio;
62     } subtitle_delaybookmarks;
63 
64     struct
65     {
66         bool b_can_change;
67         bool b_button_pressed;
68         int x, y;
69     } vrnav;
70 };
71 
72 /*****************************************************************************
73  * Local prototypes
74  *****************************************************************************/
75 static int  Open    ( vlc_object_t * );
76 static void Close   ( vlc_object_t * );
77 static int  ActionEvent( vlc_object_t *, char const *,
78                          vlc_value_t, vlc_value_t, void * );
79 static void PlayBookmark( intf_thread_t *, int );
80 static void SetBookmark ( intf_thread_t *, int );
81 static void DisplayPosition( vout_thread_t *, int,  input_thread_t * );
82 static void DisplayVolume( vout_thread_t *, int, float );
83 static void DisplayRate ( vout_thread_t *, float );
84 static float AdjustRateFine( vlc_object_t *, const int );
85 static void ClearChannels  ( vout_thread_t *, int );
86 
87 #define DisplayMessage(vout, ...) \
88     do { \
89         if (vout) \
90             vout_OSDMessage(vout, VOUT_SPU_CHANNEL_OSD, __VA_ARGS__); \
91     } while(0)
92 #define DisplayIcon(vout, icon) \
93     do { if(vout) vout_OSDIcon(vout, VOUT_SPU_CHANNEL_OSD, icon); } while(0)
94 
95 /*****************************************************************************
96  * Module descriptor
97  *****************************************************************************/
98 
99 vlc_module_begin ()
100     set_shortname( N_("Hotkeys") )
101     set_description( N_("Hotkeys management interface") )
102     set_capability( "interface", 0 )
set_callbacks(Open,Close)103     set_callbacks( Open, Close )
104     set_category( CAT_INTERFACE )
105     set_subcategory( SUBCAT_INTERFACE_HOTKEYS )
106 
107 vlc_module_end ()
108 
109 static int MovedEvent( vlc_object_t *p_this, char const *psz_var,
110                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
111 {
112     intf_thread_t *p_intf = (intf_thread_t *)p_data;
113     intf_sys_t    *p_sys = p_intf->p_sys;
114 
115     (void) p_this; (void) psz_var; (void) oldval;
116 
117     if( p_sys->vrnav.b_button_pressed )
118     {
119         int i_horizontal = newval.coords.x - p_sys->vrnav.x;
120         int i_vertical   = newval.coords.y - p_sys->vrnav.y;
121 
122         vlc_viewpoint_t viewpoint = {
123             .yaw   = -i_horizontal * 0.05f,
124             .pitch = -i_vertical   * 0.05f,
125         };
126 
127         input_UpdateViewpoint( p_sys->p_input, &viewpoint, false );
128 
129         p_sys->vrnav.x = newval.coords.x;
130         p_sys->vrnav.y = newval.coords.y;
131     }
132 
133     return VLC_SUCCESS;
134 }
135 
ViewpointMovedEvent(vlc_object_t * p_this,char const * psz_var,vlc_value_t oldval,vlc_value_t newval,void * p_data)136 static int ViewpointMovedEvent( vlc_object_t *p_this, char const *psz_var,
137                                 vlc_value_t oldval, vlc_value_t newval,
138                                 void *p_data )
139 {
140     intf_thread_t *p_intf = (intf_thread_t *)p_data;
141     intf_sys_t    *p_sys = p_intf->p_sys;
142 
143     (void) p_this; (void) psz_var; (void) oldval;
144 
145     input_UpdateViewpoint( p_sys->p_input, newval.p_address, false );
146 
147     return VLC_SUCCESS;
148 }
149 
ButtonEvent(vlc_object_t * p_this,char const * psz_var,vlc_value_t oldval,vlc_value_t newval,void * p_data)150 static int ButtonEvent( vlc_object_t *p_this, char const *psz_var,
151                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
152 {
153     intf_thread_t *p_intf = p_data;
154     intf_sys_t *p_sys = p_intf->p_sys;
155 
156     (void) psz_var; (void) oldval;
157 
158     if( newval.i_int & 0x01 )
159     {
160         if( !p_sys->vrnav.b_button_pressed )
161         {
162             p_sys->vrnav.b_button_pressed = true;
163             var_GetCoords( p_this, "mouse-moved",
164                            &p_sys->vrnav.x, &p_sys->vrnav.y );
165         }
166     }
167     else
168         p_sys->vrnav.b_button_pressed = false;
169 
170     return VLC_SUCCESS;
171 }
172 
ChangeVout(intf_thread_t * p_intf,vout_thread_t * p_vout)173 static void ChangeVout( intf_thread_t *p_intf, vout_thread_t *p_vout )
174 {
175     intf_sys_t *p_sys = p_intf->p_sys;
176 
177     int slider_chan;
178     bool b_vrnav_can_change;
179     if( p_vout != NULL )
180     {
181         slider_chan = vout_RegisterSubpictureChannel( p_vout );
182         b_vrnav_can_change = var_GetBool( p_vout, "viewpoint-changeable" );
183     }
184 
185     vlc_mutex_lock( &p_sys->lock );
186     vout_thread_t *p_old_vout = p_sys->p_vout;
187     bool b_vrnav_could_change = p_sys->vrnav.b_can_change;
188     p_sys->p_vout = p_vout;
189     if( p_vout != NULL )
190     {
191         p_sys->slider_chan = slider_chan;
192         p_sys->vrnav.b_can_change = b_vrnav_can_change;
193     }
194     else
195         p_sys->vrnav.b_can_change = false;
196     vlc_mutex_unlock( &p_sys->lock );
197 
198     if( p_old_vout != NULL )
199     {
200         if( b_vrnav_could_change )
201         {
202             var_DelCallback( p_old_vout, "mouse-moved", MovedEvent,
203                              p_intf );
204             var_DelCallback( p_old_vout, "mouse-button-down", ButtonEvent,
205                              p_intf );
206             var_DelCallback( p_old_vout, "viewpoint-moved", ViewpointMovedEvent,
207                              p_intf );
208         }
209         vlc_object_release( p_old_vout );
210     }
211 
212     if( p_sys->vrnav.b_can_change )
213     {
214         assert( p_sys->p_vout != NULL );
215         var_AddCallback( p_sys->p_vout, "mouse-moved", MovedEvent,
216                          p_intf );
217         var_AddCallback( p_sys->p_vout, "mouse-button-down", ButtonEvent,
218                          p_intf );
219         var_AddCallback( p_sys->p_vout, "viewpoint-moved", ViewpointMovedEvent,
220                          p_intf );
221     }
222 }
223 
InputEvent(vlc_object_t * p_this,char const * psz_var,vlc_value_t oldval,vlc_value_t val,void * p_data)224 static int InputEvent( vlc_object_t *p_this, char const *psz_var,
225                        vlc_value_t oldval, vlc_value_t val, void *p_data )
226 {
227     input_thread_t *p_input = (input_thread_t *)p_this;
228     intf_thread_t *p_intf = p_data;
229 
230     (void) psz_var; (void) oldval;
231 
232     if( val.i_int == INPUT_EVENT_VOUT )
233         ChangeVout( p_intf, input_GetVout( p_input ) );
234 
235     return VLC_SUCCESS;
236 }
237 
ChangeInput(intf_thread_t * p_intf,input_thread_t * p_input)238 static void ChangeInput( intf_thread_t *p_intf, input_thread_t *p_input )
239 {
240     intf_sys_t *p_sys = p_intf->p_sys;
241 
242     input_thread_t *p_old_input = p_sys->p_input;
243     vout_thread_t *p_old_vout = NULL;
244     if( p_old_input != NULL )
245     {
246         /* First, remove callbacks from previous input. It's safe to access it
247          * unlocked, since it's written from this thread */
248         var_DelCallback( p_old_input, "intf-event", InputEvent, p_intf );
249 
250         p_old_vout = p_sys->p_vout;
251         /* Remove mouse events before setting new input, since callbacks may
252          * access it */
253         if( p_old_vout != NULL && p_sys->vrnav.b_can_change )
254         {
255             var_DelCallback( p_old_vout, "mouse-moved", MovedEvent,
256                              p_intf );
257             var_DelCallback( p_old_vout, "mouse-button-down", ButtonEvent,
258                              p_intf );
259             var_DelCallback( p_old_vout, "viewpoint-moved", ViewpointMovedEvent,
260                              p_intf );
261         }
262     }
263 
264     /* Replace input and vout locked */
265     vlc_mutex_lock( &p_sys->lock );
266     p_sys->p_input = p_input ? vlc_object_hold( p_input ) : NULL;
267     p_sys->p_vout = NULL;
268     p_sys->vrnav.b_can_change = false;
269     vlc_mutex_unlock( &p_sys->lock );
270 
271     /* Release old input and vout objects unlocked */
272     if( p_old_input != NULL )
273     {
274         if( p_old_vout != NULL )
275             vlc_object_release( p_old_vout );
276         vlc_object_release( p_old_input );
277     }
278 
279     /* Register input events */
280     if( p_input != NULL )
281         var_AddCallback( p_input, "intf-event", InputEvent, p_intf );
282 }
283 
PlaylistEvent(vlc_object_t * p_this,char const * psz_var,vlc_value_t oldval,vlc_value_t val,void * p_data)284 static int PlaylistEvent( vlc_object_t *p_this, char const *psz_var,
285                           vlc_value_t oldval, vlc_value_t val, void *p_data )
286 {
287     intf_thread_t *p_intf = p_data;
288 
289     (void) p_this; (void) psz_var; (void) oldval;
290 
291     ChangeInput( p_intf, val.p_address );
292 
293     return VLC_SUCCESS;
294 }
295 
296 /*****************************************************************************
297  * Open: initialize interface
298  *****************************************************************************/
Open(vlc_object_t * p_this)299 static int Open( vlc_object_t *p_this )
300 {
301     intf_thread_t *p_intf = (intf_thread_t *)p_this;
302     intf_sys_t *p_sys;
303     p_sys = malloc( sizeof( intf_sys_t ) );
304     if( !p_sys )
305         return VLC_ENOMEM;
306 
307     p_intf->p_sys = p_sys;
308 
309     p_sys->p_vout = NULL;
310     p_sys->p_input = NULL;
311     p_sys->vrnav.b_can_change = false;
312     p_sys->vrnav.b_button_pressed = false;
313     p_sys->subtitle_delaybookmarks.i_time_audio = 0;
314     p_sys->subtitle_delaybookmarks.i_time_subtitle = 0;
315 
316     vlc_mutex_init( &p_sys->lock );
317 
318     var_AddCallback( p_intf->obj.libvlc, "key-action", ActionEvent, p_intf );
319 
320     var_AddCallback( pl_Get(p_intf), "input-current", PlaylistEvent, p_intf );
321 
322     return VLC_SUCCESS;
323 }
324 
325 /*****************************************************************************
326  * Close: destroy interface
327  *****************************************************************************/
Close(vlc_object_t * p_this)328 static void Close( vlc_object_t *p_this )
329 {
330     intf_thread_t *p_intf = (intf_thread_t *)p_this;
331     intf_sys_t *p_sys = p_intf->p_sys;
332 
333     var_DelCallback( pl_Get(p_intf), "input-current", PlaylistEvent, p_intf );
334 
335     var_DelCallback( p_intf->obj.libvlc, "key-action", ActionEvent, p_intf );
336 
337     ChangeInput( p_intf, NULL );
338 
339     vlc_mutex_destroy( &p_sys->lock );
340 
341     /* Destroy structure */
342     free( p_sys );
343 }
344 
PutAction(intf_thread_t * p_intf,input_thread_t * p_input,vout_thread_t * p_vout,int slider_chan,bool b_vrnav,int i_action)345 static int PutAction( intf_thread_t *p_intf, input_thread_t *p_input,
346                       vout_thread_t *p_vout, int slider_chan, bool b_vrnav,
347                       int i_action )
348 {
349 #define DO_ACTION(x) PutAction( p_intf, p_input, p_vout, slider_chan, b_vrnav, x)
350     intf_sys_t *p_sys = p_intf->p_sys;
351     playlist_t *p_playlist = pl_Get( p_intf );
352 
353     /* Quit */
354     switch( i_action )
355     {
356         /* Libvlc / interface actions */
357         case ACTIONID_QUIT:
358             libvlc_Quit( p_intf->obj.libvlc );
359 
360             ClearChannels( p_vout, slider_chan );
361             DisplayMessage( p_vout, _( "Quit" ) );
362             break;
363 
364         case ACTIONID_INTF_TOGGLE_FSC:
365         case ACTIONID_INTF_HIDE:
366             var_TriggerCallback( p_intf->obj.libvlc, "intf-toggle-fscontrol" );
367             break;
368         case ACTIONID_INTF_BOSS:
369             var_TriggerCallback( p_intf->obj.libvlc, "intf-boss" );
370             break;
371         case ACTIONID_INTF_POPUP_MENU:
372             var_TriggerCallback( p_intf->obj.libvlc, "intf-popupmenu" );
373             break;
374 
375         /* Playlist actions (including audio) */
376         case ACTIONID_LOOP:
377         {
378             /* Toggle Normal -> Loop -> Repeat -> Normal ... */
379             const char *mode;
380             if( var_GetBool( p_playlist, "repeat" ) )
381             {
382                 var_SetBool( p_playlist, "repeat", false );
383                 mode = N_("Off");
384             }
385             else
386             if( var_GetBool( p_playlist, "loop" ) )
387             { /* FIXME: this is not atomic, we should use a real tristate */
388                 var_SetBool( p_playlist, "loop", false );
389                 var_SetBool( p_playlist, "repeat", true );
390                 mode = N_("One");
391             }
392             else
393             {
394                 var_SetBool( p_playlist, "loop", true );
395                 mode = N_("All");
396             }
397             DisplayMessage( p_vout, _("Loop: %s"), vlc_gettext(mode) );
398             break;
399         }
400 
401         case ACTIONID_RANDOM:
402         {
403             const bool state = var_ToggleBool( p_playlist, "random" );
404             DisplayMessage( p_vout, _("Random: %s"),
405                             vlc_gettext( state ? N_("On") : N_("Off") ) );
406             break;
407         }
408 
409         case ACTIONID_NEXT:
410             DisplayMessage( p_vout, _("Next") );
411             playlist_Next( p_playlist );
412             break;
413         case ACTIONID_PREV:
414             DisplayMessage( p_vout, _("Previous") );
415             playlist_Prev( p_playlist );
416             break;
417 
418         case ACTIONID_STOP:
419             playlist_Stop( p_playlist );
420             break;
421 
422         case ACTIONID_RATE_NORMAL:
423             var_SetFloat( p_playlist, "rate", 1.f );
424             DisplayRate( p_vout, 1.f );
425             break;
426         case ACTIONID_FASTER:
427             var_TriggerCallback( p_playlist, "rate-faster" );
428             DisplayRate( p_vout, var_GetFloat( p_playlist, "rate" ) );
429             break;
430         case ACTIONID_SLOWER:
431             var_TriggerCallback( p_playlist, "rate-slower" );
432             DisplayRate( p_vout, var_GetFloat( p_playlist, "rate" ) );
433             break;
434         case ACTIONID_RATE_FASTER_FINE:
435         case ACTIONID_RATE_SLOWER_FINE:
436         {
437             const int i_dir = i_action == ACTIONID_RATE_FASTER_FINE ? 1 : -1;
438             float rate = AdjustRateFine( VLC_OBJECT(p_playlist), i_dir );
439 
440             var_SetFloat( p_playlist, "rate", rate );
441             DisplayRate( p_vout, rate );
442             break;
443         }
444 
445         case ACTIONID_PLAY_BOOKMARK1:
446         case ACTIONID_PLAY_BOOKMARK2:
447         case ACTIONID_PLAY_BOOKMARK3:
448         case ACTIONID_PLAY_BOOKMARK4:
449         case ACTIONID_PLAY_BOOKMARK5:
450         case ACTIONID_PLAY_BOOKMARK6:
451         case ACTIONID_PLAY_BOOKMARK7:
452         case ACTIONID_PLAY_BOOKMARK8:
453         case ACTIONID_PLAY_BOOKMARK9:
454         case ACTIONID_PLAY_BOOKMARK10:
455             PlayBookmark( p_intf, i_action - ACTIONID_PLAY_BOOKMARK1 + 1 );
456             break;
457 
458         case ACTIONID_SET_BOOKMARK1:
459         case ACTIONID_SET_BOOKMARK2:
460         case ACTIONID_SET_BOOKMARK3:
461         case ACTIONID_SET_BOOKMARK4:
462         case ACTIONID_SET_BOOKMARK5:
463         case ACTIONID_SET_BOOKMARK6:
464         case ACTIONID_SET_BOOKMARK7:
465         case ACTIONID_SET_BOOKMARK8:
466         case ACTIONID_SET_BOOKMARK9:
467         case ACTIONID_SET_BOOKMARK10:
468             SetBookmark( p_intf, i_action - ACTIONID_SET_BOOKMARK1 + 1 );
469             break;
470         case ACTIONID_PLAY_CLEAR:
471             playlist_Clear( p_playlist, pl_Unlocked );
472             break;
473         case ACTIONID_VOL_UP:
474         {
475             float vol;
476             if( playlist_VolumeUp( p_playlist, 1, &vol ) == 0 )
477                 DisplayVolume( p_vout, slider_chan, vol );
478             break;
479         }
480         case ACTIONID_VOL_DOWN:
481         {
482             float vol;
483             if( playlist_VolumeDown( p_playlist, 1, &vol ) == 0 )
484                 DisplayVolume( p_vout, slider_chan, vol );
485             break;
486         }
487         case ACTIONID_VOL_MUTE:
488         {
489             int mute = playlist_MuteGet( p_playlist );
490             if( mute < 0 )
491                 break;
492             mute = !mute;
493             if( playlist_MuteSet( p_playlist, mute ) )
494                 break;
495 
496             float vol = playlist_VolumeGet( p_playlist );
497             if( mute || vol == 0.f )
498             {
499                 ClearChannels( p_vout, slider_chan );
500                 DisplayIcon( p_vout, OSD_MUTE_ICON );
501             }
502             else
503                 DisplayVolume( p_vout, slider_chan, vol );
504             break;
505         }
506 
507         case ACTIONID_AUDIODEVICE_CYCLE:
508         {
509             audio_output_t *p_aout = playlist_GetAout( p_playlist );
510             if( p_aout == NULL )
511                 break;
512 
513             char **ids, **names;
514             int n = aout_DevicesList( p_aout, &ids, &names );
515             if( n == -1 )
516                 break;
517 
518             char *dev = aout_DeviceGet( p_aout );
519             const char *devstr = (dev != NULL) ? dev : "";
520 
521             int idx = 0;
522             for( int i = 0; i < n; i++ )
523             {
524                 if( !strcmp(devstr, ids[i]) )
525                     idx = (i + 1) % n;
526             }
527             free( dev );
528 
529             if( !aout_DeviceSet( p_aout, ids[idx] ) )
530                 DisplayMessage( p_vout, _("Audio Device: %s"), names[idx] );
531             vlc_object_release( p_aout );
532 
533             for( int i = 0; i < n; i++ )
534             {
535                 free( ids[i] );
536                 free( names[i] );
537             }
538             free( ids );
539             free( names );
540             break;
541         }
542 
543         /* Playlist + input actions */
544         case ACTIONID_PLAY_PAUSE:
545             if( p_input )
546             {
547                 ClearChannels( p_vout, slider_chan );
548 
549                 int state = var_GetInteger( p_input, "state" );
550                 DisplayIcon( p_vout, state != PAUSE_S ? OSD_PAUSE_ICON : OSD_PLAY_ICON );
551             }
552             playlist_TogglePause( p_playlist );
553             break;
554 
555         case ACTIONID_PLAY:
556             if( p_input && var_GetFloat( p_input, "rate" ) != 1.f )
557                 /* Return to normal speed */
558                 var_SetFloat( p_input, "rate", 1.f );
559             else
560             {
561                 ClearChannels( p_vout, slider_chan );
562                 DisplayIcon( p_vout, OSD_PLAY_ICON );
563                 playlist_Play( p_playlist );
564             }
565             break;
566 
567         /* Playlist + video output actions */
568         case ACTIONID_WALLPAPER:
569         {
570             bool wp = var_ToggleBool( p_playlist, "video-wallpaper" );
571             if( p_vout )
572                 var_SetBool( p_vout, "video-wallpaper", wp );
573             break;
574         }
575 
576         /* Input actions */
577         case ACTIONID_PAUSE:
578             if( p_input && var_GetInteger( p_input, "state" ) != PAUSE_S )
579             {
580                 ClearChannels( p_vout, slider_chan );
581                 DisplayIcon( p_vout, OSD_PAUSE_ICON );
582                 var_SetInteger( p_input, "state", PAUSE_S );
583             }
584             break;
585 
586         case ACTIONID_RECORD:
587             if( p_input && var_GetBool( p_input, "can-record" ) )
588             {
589                 const bool on = var_ToggleBool( p_input, "record" );
590                 DisplayMessage( p_vout, vlc_gettext(on
591                                    ? N_("Recording") : N_("Recording done")) );
592             }
593             break;
594 
595         case ACTIONID_FRAME_NEXT:
596             if( p_input )
597             {
598                 var_TriggerCallback( p_input, "frame-next" );
599                 DisplayMessage( p_vout, _("Next frame") );
600             }
601             break;
602 
603         case ACTIONID_SUBSYNC_MARKAUDIO:
604         {
605             p_sys->subtitle_delaybookmarks.i_time_audio = mdate();
606             DisplayMessage( p_vout, _("Sub sync: bookmarked audio time"));
607             break;
608         }
609         case ACTIONID_SUBSYNC_MARKSUB:
610             if( p_input )
611             {
612                 vlc_value_t val, list, list2;
613                 int i_count;
614                 var_Get( p_input, "spu-es", &val );
615 
616                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
617                             &list, &list2 );
618                 i_count = list.p_list->i_count;
619                 if( i_count < 1 || val.i_int < 0 )
620                 {
621                     DisplayMessage( p_vout, _("No active subtitle") );
622                     var_FreeList( &list, &list2 );
623                     break;
624                 }
625                 p_sys->subtitle_delaybookmarks.i_time_subtitle = mdate();
626                 DisplayMessage( p_vout,
627                                 _("Sub sync: bookmarked subtitle time"));
628                 var_FreeList( &list, &list2 );
629             }
630             break;
631         case ACTIONID_SUBSYNC_APPLY:
632         {
633             /* Warning! Can yield a pause in the playback.
634              * For example, the following succession of actions will yield a 5 second delay :
635              * - Pressing Shift-H (ACTIONID_SUBSYNC_MARKAUDIO)
636              * - wait 5 second
637              * - Press Shift-J (ACTIONID_SUBSYNC_MARKSUB)
638              * - Press Shift-K (ACTIONID_SUBSYNC_APPLY)
639              * --> 5 seconds pause
640              * This is due to var_SetTime() (and ultimately UpdatePtsDelay())
641              * which causes the video to pause for an equivalent duration
642              * (This problem is also present in the "Track synchronization" window) */
643             if ( p_input )
644             {
645                 if ( (p_sys->subtitle_delaybookmarks.i_time_audio == 0) || (p_sys->subtitle_delaybookmarks.i_time_subtitle == 0) )
646                 {
647                     DisplayMessage( p_vout, _( "Sub sync: set bookmarks first!" ) );
648                 }
649                 else
650                 {
651                     int64_t i_current_subdelay = var_GetInteger( p_input, "spu-delay" );
652                     int64_t i_additional_subdelay = p_sys->subtitle_delaybookmarks.i_time_audio - p_sys->subtitle_delaybookmarks.i_time_subtitle;
653                     int64_t i_total_subdelay = i_current_subdelay + i_additional_subdelay;
654                     var_SetInteger( p_input, "spu-delay", i_total_subdelay);
655                     ClearChannels( p_vout, slider_chan );
656                     DisplayMessage( p_vout, _( "Sub sync: corrected %i ms (total delay = %i ms)" ),
657                                             (int)(i_additional_subdelay / 1000),
658                                             (int)(i_total_subdelay / 1000) );
659                     p_sys->subtitle_delaybookmarks.i_time_audio = 0;
660                     p_sys->subtitle_delaybookmarks.i_time_subtitle = 0;
661                 }
662             }
663             break;
664         }
665         case ACTIONID_SUBSYNC_RESET:
666         {
667             var_SetInteger( p_input, "spu-delay", 0);
668             ClearChannels( p_vout, slider_chan );
669             DisplayMessage( p_vout, _( "Sub sync: delay reset" ) );
670             p_sys->subtitle_delaybookmarks.i_time_audio = 0;
671             p_sys->subtitle_delaybookmarks.i_time_subtitle = 0;
672             break;
673         }
674 
675         case ACTIONID_SUBDELAY_DOWN:
676         case ACTIONID_SUBDELAY_UP:
677         {
678             int diff = (i_action == ACTIONID_SUBDELAY_UP) ? 50000 : -50000;
679             if( p_input )
680             {
681                 vlc_value_t val, list, list2;
682                 int i_count;
683                 var_Get( p_input, "spu-es", &val );
684 
685                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
686                             &list, &list2 );
687                 i_count = list.p_list->i_count;
688                 if( i_count < 1 || val.i_int < 0 )
689                 {
690                     DisplayMessage( p_vout, _("No active subtitle") );
691                     var_FreeList( &list, &list2 );
692                     break;
693                 }
694                 int64_t i_delay = var_GetInteger( p_input, "spu-delay" ) + diff;
695 
696                 var_SetInteger( p_input, "spu-delay", i_delay );
697                 ClearChannels( p_vout, slider_chan );
698                 DisplayMessage( p_vout, _( "Subtitle delay %i ms" ),
699                                 (int)(i_delay/1000) );
700                 var_FreeList( &list, &list2 );
701             }
702             break;
703         }
704         case ACTIONID_AUDIODELAY_DOWN:
705         case ACTIONID_AUDIODELAY_UP:
706         {
707             int diff = (i_action == ACTIONID_AUDIODELAY_UP) ? 50000 : -50000;
708             if( p_input )
709             {
710                 int64_t i_delay = var_GetInteger( p_input, "audio-delay" )
711                                   + diff;
712 
713                 var_SetInteger( p_input, "audio-delay", i_delay );
714                 ClearChannels( p_vout, slider_chan );
715                 DisplayMessage( p_vout, _( "Audio delay %i ms" ),
716                                  (int)(i_delay/1000) );
717             }
718             break;
719         }
720 
721         case ACTIONID_AUDIO_TRACK:
722             if( p_input )
723             {
724                 vlc_value_t val, list, list2;
725                 int i_count, i;
726                 var_Get( p_input, "audio-es", &val );
727                 var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES,
728                             &list, &list2 );
729                 i_count = list.p_list->i_count;
730                 if( i_count > 1 )
731                 {
732                     for( i = 0; i < i_count; i++ )
733                     {
734                         if( val.i_int == list.p_list->p_values[i].i_int )
735                         {
736                             break;
737                         }
738                     }
739                     /* value of audio-es was not in choices list */
740                     if( i == i_count )
741                     {
742                         msg_Warn( p_input,
743                                   "invalid current audio track, selecting 0" );
744                         i = 0;
745                     }
746                     else if( i == i_count - 1 )
747                         i = 1;
748                     else
749                         i++;
750                     var_Set( p_input, "audio-es", list.p_list->p_values[i] );
751                     DisplayMessage( p_vout, _("Audio track: %s"),
752                                     list2.p_list->p_values[i].psz_string );
753                 }
754                 var_FreeList( &list, &list2 );
755             }
756             break;
757 
758         case ACTIONID_SUBTITLE_TRACK:
759         case ACTIONID_SUBTITLE_REVERSE_TRACK:
760             if( p_input )
761             {
762                 vlc_value_t val, list, list2;
763                 int i_count, i;
764                 var_Get( p_input, "spu-es", &val );
765 
766                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
767                             &list, &list2 );
768                 i_count = list.p_list->i_count;
769                 if( i_count <= 1 )
770                 {
771                     DisplayMessage( p_vout, _("Subtitle track: %s"),
772                                     _("N/A") );
773                     var_FreeList( &list, &list2 );
774                     break;
775                 }
776                 for( i = 0; i < i_count; i++ )
777                 {
778                     if( val.i_int == list.p_list->p_values[i].i_int )
779                     {
780                         break;
781                     }
782                 }
783                 /* value of spu-es was not in choices list */
784                 if( i == i_count )
785                 {
786                     msg_Warn( p_input,
787                               "invalid current subtitle track, selecting 0" );
788                     i = 0;
789                 }
790                 else if ((i == i_count - 1) && (i_action == ACTIONID_SUBTITLE_TRACK))
791                     i = 0;
792                 else if ((i == 0) && (i_action == ACTIONID_SUBTITLE_REVERSE_TRACK))
793                     i = i_count - 1;
794                 else
795                     i = (i_action == ACTIONID_SUBTITLE_TRACK) ? i+1 : i-1;
796                 var_SetInteger( p_input, "spu-es", list.p_list->p_values[i].i_int );
797                 DisplayMessage( p_vout, _("Subtitle track: %s"),
798                                 list2.p_list->p_values[i].psz_string );
799                 var_FreeList( &list, &list2 );
800             }
801             break;
802         case ACTIONID_SUBTITLE_TOGGLE:
803             if( p_input )
804             {
805                 vlc_value_t list, list2;
806                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
807                             &list, &list2 );
808                 int i_count = list.p_list->i_count;
809                 if( i_count <= 1 )
810                 {
811                     DisplayMessage( p_vout, _("Subtitle track: %s"),
812                                     _("N/A") );
813                     var_FreeList( &list, &list2 );
814                     break;
815                 }
816 
817                 int i_cur_id = var_GetInteger( p_input, "spu-es" );
818                 int i_new_id;
819                 if( i_cur_id == -1 )
820                 {
821                     /* subtitles were disabled: restore the saved track id */
822                     i_new_id = var_GetInteger( p_input, "spu-choice" );
823                     if( i_new_id != -1 )
824                         var_SetInteger( p_input, "spu-choice", -1 );
825                 }
826                 else
827                 {
828                     /* subtitles were enabled: save the track id and disable */
829                     i_new_id = -1;
830                     var_SetInteger( p_input, "spu-choice", i_cur_id );
831                 }
832 
833                 int i_new_index = 1; /* select first track by default */
834                 /* if subtitles were disabled with no saved id, use the first track */
835                 if( i_cur_id != -1 || i_new_id != -1 )
836                 {
837                     for( int i = 0; i < i_count; ++i )
838                     {
839                         if( i_new_id == list.p_list->p_values[i].i_int )
840                         {
841                             i_new_index = i;
842                             break;
843                         }
844                     }
845                 }
846                 var_SetInteger( p_input, "spu-es", list.p_list->p_values[i_new_index].i_int );
847                 DisplayMessage( p_vout, _("Subtitle track: %s"),
848                                 list2.p_list->p_values[i_new_index].psz_string );
849                 var_FreeList( &list, &list2 );
850             }
851             break;
852         case ACTIONID_PROGRAM_SID_NEXT:
853         case ACTIONID_PROGRAM_SID_PREV:
854             if( p_input )
855             {
856                 vlc_value_t val, list, list2;
857                 int i_count, i;
858                 var_Get( p_input, "program", &val );
859 
860                 var_Change( p_input, "program", VLC_VAR_GETCHOICES,
861                             &list, &list2 );
862                 i_count = list.p_list->i_count;
863                 if( i_count <= 1 )
864                 {
865                     DisplayMessage( p_vout, _("Program Service ID: %s"),
866                                     _("N/A") );
867                     var_FreeList( &list, &list2 );
868                     break;
869                 }
870                 for( i = 0; i < i_count; i++ )
871                 {
872                     if( val.i_int == list.p_list->p_values[i].i_int )
873                     {
874                         break;
875                     }
876                 }
877                 /* value of program sid was not in choices list */
878                 if( i == i_count )
879                 {
880                     msg_Warn( p_input,
881                               "invalid current program SID, selecting 0" );
882                     i = 0;
883                 }
884                 else if( i_action == ACTIONID_PROGRAM_SID_NEXT ) {
885                     if( i == i_count - 1 )
886                         i = 0;
887                     else
888                         i++;
889                     }
890                 else { /* ACTIONID_PROGRAM_SID_PREV */
891                     if( i == 0 )
892                         i = i_count - 1;
893                     else
894                         i--;
895                     }
896                 var_Set( p_input, "program", list.p_list->p_values[i] );
897                 DisplayMessage( p_vout, _("Program Service ID: %s"),
898                                 list2.p_list->p_values[i].psz_string );
899                 var_FreeList( &list, &list2 );
900             }
901             break;
902 
903         case ACTIONID_JUMP_BACKWARD_EXTRASHORT:
904         case ACTIONID_JUMP_FORWARD_EXTRASHORT:
905         case ACTIONID_JUMP_BACKWARD_SHORT:
906         case ACTIONID_JUMP_FORWARD_SHORT:
907         case ACTIONID_JUMP_BACKWARD_MEDIUM:
908         case ACTIONID_JUMP_FORWARD_MEDIUM:
909         case ACTIONID_JUMP_BACKWARD_LONG:
910         case ACTIONID_JUMP_FORWARD_LONG:
911         {
912             if( p_input == NULL || !var_GetBool( p_input, "can-seek" ) )
913                 break;
914 
915             const char *varname;
916             int sign = +1;
917             switch( i_action )
918             {
919                 case ACTIONID_JUMP_BACKWARD_EXTRASHORT:
920                     sign = -1;
921                     /* fall through */
922                 case ACTIONID_JUMP_FORWARD_EXTRASHORT:
923                     varname = "extrashort-jump-size";
924                     break;
925                 case ACTIONID_JUMP_BACKWARD_SHORT:
926                     sign = -1;
927                     /* fall through */
928                 case ACTIONID_JUMP_FORWARD_SHORT:
929                     varname = "short-jump-size";
930                     break;
931                 case ACTIONID_JUMP_BACKWARD_MEDIUM:
932                     sign = -1;
933                     /* fall through */
934                 case ACTIONID_JUMP_FORWARD_MEDIUM:
935                     varname = "medium-jump-size";
936                     break;
937                 case ACTIONID_JUMP_BACKWARD_LONG:
938                     sign = -1;
939                     /* fall through */
940                 case ACTIONID_JUMP_FORWARD_LONG:
941                     varname = "long-jump-size";
942                     break;
943             }
944 
945             mtime_t it = var_InheritInteger( p_input, varname );
946             if( it < 0 )
947                 break;
948             var_SetInteger( p_input, "time-offset", it * sign * CLOCK_FREQ );
949             DisplayPosition( p_vout, slider_chan, p_input );
950             break;
951         }
952 
953         /* Input navigation */
954         case ACTIONID_TITLE_PREV:
955             if( p_input )
956                 var_TriggerCallback( p_input, "prev-title" );
957             break;
958         case ACTIONID_TITLE_NEXT:
959             if( p_input )
960                 var_TriggerCallback( p_input, "next-title" );
961             break;
962         case ACTIONID_CHAPTER_PREV:
963             if( p_input )
964                 var_TriggerCallback( p_input, "prev-chapter" );
965             break;
966         case ACTIONID_CHAPTER_NEXT:
967             if( p_input )
968                 var_TriggerCallback( p_input, "next-chapter" );
969             break;
970         case ACTIONID_DISC_MENU:
971             if( p_input )
972                 var_SetInteger( p_input, "title  0", 2 );
973             break;
974         case ACTIONID_NAV_ACTIVATE:
975             if( p_input )
976                 input_Control( p_input, INPUT_NAV_ACTIVATE, NULL );
977             break;
978         case ACTIONID_NAV_UP:
979             if( p_input )
980                 input_Control( p_input, INPUT_NAV_UP, NULL );
981             break;
982         case ACTIONID_NAV_DOWN:
983             if( p_input )
984                 input_Control( p_input, INPUT_NAV_DOWN, NULL );
985             break;
986         case ACTIONID_NAV_LEFT:
987             if( p_input )
988                 input_Control( p_input, INPUT_NAV_LEFT, NULL );
989             break;
990         case ACTIONID_NAV_RIGHT:
991             if( p_input )
992                 input_Control( p_input, INPUT_NAV_RIGHT, NULL );
993             break;
994 
995         /* Video Output actions */
996         case ACTIONID_SNAPSHOT:
997             if( p_vout )
998                 var_TriggerCallback( p_vout, "video-snapshot" );
999             break;
1000 
1001         case ACTIONID_TOGGLE_FULLSCREEN:
1002         {
1003             if( p_vout )
1004             {
1005                 bool fs = var_ToggleBool( p_vout, "fullscreen" );
1006                 var_SetBool( p_playlist, "fullscreen", fs );
1007             }
1008             else
1009                 var_ToggleBool( p_playlist, "fullscreen" );
1010             break;
1011         }
1012 
1013         case ACTIONID_LEAVE_FULLSCREEN:
1014             if( p_vout )
1015                 var_SetBool( p_vout, "fullscreen", false );
1016             var_SetBool( p_playlist, "fullscreen", false );
1017             break;
1018 
1019         case ACTIONID_ASPECT_RATIO:
1020             if( p_vout )
1021             {
1022                 vlc_value_t val={0}, val_list, text_list;
1023                 var_Get( p_vout, "aspect-ratio", &val );
1024                 if( var_Change( p_vout, "aspect-ratio", VLC_VAR_GETCHOICES,
1025                                 &val_list, &text_list ) >= 0 )
1026                 {
1027                     int i;
1028                     for( i = 0; i < val_list.p_list->i_count; i++ )
1029                     {
1030                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
1031                                      val.psz_string ) )
1032                         {
1033                             i++;
1034                             break;
1035                         }
1036                     }
1037                     if( i == val_list.p_list->i_count ) i = 0;
1038                     var_SetString( p_vout, "aspect-ratio",
1039                                    val_list.p_list->p_values[i].psz_string );
1040                     DisplayMessage( p_vout, _("Aspect ratio: %s"),
1041                                     text_list.p_list->p_values[i].psz_string );
1042 
1043                     var_FreeList( &val_list, &text_list );
1044                 }
1045                 free( val.psz_string );
1046             }
1047             break;
1048 
1049         case ACTIONID_CROP:
1050             if( p_vout )
1051             {
1052                 vlc_value_t val={0}, val_list, text_list;
1053                 var_Get( p_vout, "crop", &val );
1054                 if( var_Change( p_vout, "crop", VLC_VAR_GETCHOICES,
1055                                 &val_list, &text_list ) >= 0 )
1056                 {
1057                     int i;
1058                     for( i = 0; i < val_list.p_list->i_count; i++ )
1059                     {
1060                         if( !strcmp( val_list.p_list->p_values[i].psz_string,
1061                                      val.psz_string ) )
1062                         {
1063                             i++;
1064                             break;
1065                         }
1066                     }
1067                     if( i == val_list.p_list->i_count ) i = 0;
1068                     var_SetString( p_vout, "crop",
1069                                    val_list.p_list->p_values[i].psz_string );
1070                     DisplayMessage( p_vout, _("Crop: %s"),
1071                                     text_list.p_list->p_values[i].psz_string );
1072 
1073                     var_FreeList( &val_list, &text_list );
1074                 }
1075                 free( val.psz_string );
1076             }
1077             break;
1078         case ACTIONID_CROP_TOP:
1079             if( p_vout )
1080                 var_IncInteger( p_vout, "crop-top" );
1081             break;
1082         case ACTIONID_UNCROP_TOP:
1083             if( p_vout )
1084                 var_DecInteger( p_vout, "crop-top" );
1085             break;
1086         case ACTIONID_CROP_BOTTOM:
1087             if( p_vout )
1088                 var_IncInteger( p_vout, "crop-bottom" );
1089             break;
1090         case ACTIONID_UNCROP_BOTTOM:
1091             if( p_vout )
1092                 var_DecInteger( p_vout, "crop-bottom" );
1093             break;
1094         case ACTIONID_CROP_LEFT:
1095             if( p_vout )
1096                 var_IncInteger( p_vout, "crop-left" );
1097             break;
1098         case ACTIONID_UNCROP_LEFT:
1099             if( p_vout )
1100                 var_DecInteger( p_vout, "crop-left" );
1101             break;
1102         case ACTIONID_CROP_RIGHT:
1103             if( p_vout )
1104                 var_IncInteger( p_vout, "crop-right" );
1105             break;
1106         case ACTIONID_UNCROP_RIGHT:
1107             if( p_vout )
1108                 var_DecInteger( p_vout, "crop-right" );
1109             break;
1110 
1111         case ACTIONID_VIEWPOINT_FOV_IN:
1112             if( p_vout )
1113                 input_UpdateViewpoint( p_input,
1114                                        &(vlc_viewpoint_t) { .fov = -1.f },
1115                                        false );
1116             break;
1117         case ACTIONID_VIEWPOINT_FOV_OUT:
1118             if( p_vout )
1119                 input_UpdateViewpoint( p_input,
1120                                        &(vlc_viewpoint_t) { .fov = 1.f },
1121                                        false );
1122             break;
1123 
1124         case ACTIONID_VIEWPOINT_ROLL_CLOCK:
1125             if( p_vout )
1126                 input_UpdateViewpoint( p_input,
1127                                        &(vlc_viewpoint_t) { .roll = -1.f },
1128                                        false );
1129             break;
1130         case ACTIONID_VIEWPOINT_ROLL_ANTICLOCK:
1131             if( p_vout )
1132                 input_UpdateViewpoint( p_input,
1133                                        &(vlc_viewpoint_t) { .roll = 1.f },
1134                                        false );
1135             break;
1136 
1137          case ACTIONID_TOGGLE_AUTOSCALE:
1138             if( p_vout )
1139             {
1140                 float f_scalefactor = var_GetFloat( p_vout, "zoom" );
1141                 if ( f_scalefactor != 1.f )
1142                 {
1143                     var_SetFloat( p_vout, "zoom", 1.f );
1144                     DisplayMessage( p_vout, _("Zooming reset") );
1145                 }
1146                 else
1147                 {
1148                     bool b_autoscale = !var_GetBool( p_vout, "autoscale" );
1149                     var_SetBool( p_vout, "autoscale", b_autoscale );
1150                     if( b_autoscale )
1151                         DisplayMessage( p_vout, _("Scaled to screen") );
1152                     else
1153                         DisplayMessage( p_vout, _("Original Size") );
1154                 }
1155             }
1156             break;
1157         case ACTIONID_SCALE_UP:
1158             if( p_vout )
1159             {
1160                float f_scalefactor = var_GetFloat( p_vout, "zoom" );
1161 
1162                if( f_scalefactor < 10.f )
1163                    f_scalefactor += .1f;
1164                var_SetFloat( p_vout, "zoom", f_scalefactor );
1165             }
1166             break;
1167         case ACTIONID_SCALE_DOWN:
1168             if( p_vout )
1169             {
1170                float f_scalefactor = var_GetFloat( p_vout, "zoom" );
1171 
1172                if( f_scalefactor > .3f )
1173                    f_scalefactor -= .1f;
1174                var_SetFloat( p_vout, "zoom", f_scalefactor );
1175             }
1176             break;
1177 
1178         case ACTIONID_ZOOM_QUARTER:
1179         case ACTIONID_ZOOM_HALF:
1180         case ACTIONID_ZOOM_ORIGINAL:
1181         case ACTIONID_ZOOM_DOUBLE:
1182             if( p_vout )
1183             {
1184                 float f;
1185                 switch( i_action )
1186                 {
1187                     case ACTIONID_ZOOM_QUARTER:  f = 0.25; break;
1188                     case ACTIONID_ZOOM_HALF:     f = 0.5;  break;
1189                     case ACTIONID_ZOOM_ORIGINAL: f = 1.;   break;
1190                      /*case ACTIONID_ZOOM_DOUBLE:*/
1191                     default:                     f = 2.;   break;
1192                 }
1193                 var_SetFloat( p_vout, "zoom", f );
1194             }
1195             break;
1196         case ACTIONID_ZOOM:
1197         case ACTIONID_UNZOOM:
1198             if( p_vout )
1199             {
1200                 vlc_value_t val={0}, val_list, text_list;
1201                 var_Get( p_vout, "zoom", &val );
1202                 if( var_Change( p_vout, "zoom", VLC_VAR_GETCHOICES,
1203                                 &val_list, &text_list ) >= 0 )
1204                 {
1205                     int i;
1206                     for( i = 0; i < val_list.p_list->i_count; i++ )
1207                     {
1208                         if( val_list.p_list->p_values[i].f_float
1209                            == val.f_float )
1210                         {
1211                             if( i_action == ACTIONID_ZOOM )
1212                                 i++;
1213                             else /* ACTIONID_UNZOOM */
1214                                 i--;
1215                             break;
1216                         }
1217                     }
1218                     if( i == val_list.p_list->i_count ) i = 0;
1219                     if( i == -1 ) i = val_list.p_list->i_count-1;
1220                     var_SetFloat( p_vout, "zoom",
1221                                   val_list.p_list->p_values[i].f_float );
1222                     DisplayMessage( p_vout, _("Zoom mode: %s"),
1223                                     text_list.p_list->p_values[i].psz_string );
1224 
1225                     var_FreeList( &val_list, &text_list );
1226                 }
1227             }
1228             break;
1229 
1230         case ACTIONID_DEINTERLACE:
1231             if( p_vout )
1232             {
1233                 int i_deinterlace = var_GetInteger( p_vout, "deinterlace" );
1234                 if( i_deinterlace != 0 )
1235                 {
1236                     var_SetInteger( p_vout, "deinterlace", 0 );
1237                     DisplayMessage( p_vout, _("Deinterlace off") );
1238                 }
1239                 else
1240                 {
1241                     var_SetInteger( p_vout, "deinterlace", 1 );
1242 
1243                     char *psz_mode = var_GetString( p_vout, "deinterlace-mode" );
1244                     vlc_value_t vlist, tlist;
1245                     if( psz_mode && !var_Change( p_vout, "deinterlace-mode", VLC_VAR_GETCHOICES, &vlist, &tlist ) )
1246                     {
1247                         const char *psz_text = NULL;
1248                         for( int i = 0; i < vlist.p_list->i_count; i++ )
1249                         {
1250                             if( !strcmp( vlist.p_list->p_values[i].psz_string, psz_mode ) )
1251                             {
1252                                 psz_text = tlist.p_list->p_values[i].psz_string;
1253                                 break;
1254                             }
1255                         }
1256                         DisplayMessage( p_vout, "%s (%s)", _("Deinterlace on"),
1257                                         psz_text ? psz_text : psz_mode );
1258 
1259                         var_FreeList( &vlist, &tlist );
1260                     }
1261                     free( psz_mode );
1262                 }
1263             }
1264             break;
1265         case ACTIONID_DEINTERLACE_MODE:
1266             if( p_vout )
1267             {
1268                 char *psz_mode = var_GetString( p_vout, "deinterlace-mode" );
1269                 vlc_value_t vlist, tlist;
1270                 if( psz_mode && !var_Change( p_vout, "deinterlace-mode", VLC_VAR_GETCHOICES, &vlist, &tlist ))
1271                 {
1272                     const char *psz_text = NULL;
1273                     int i;
1274                     for( i = 0; i < vlist.p_list->i_count; i++ )
1275                     {
1276                         if( !strcmp( vlist.p_list->p_values[i].psz_string, psz_mode ) )
1277                         {
1278                             i++;
1279                             break;
1280                         }
1281                     }
1282                     if( i == vlist.p_list->i_count ) i = 0;
1283                     psz_text = tlist.p_list->p_values[i].psz_string;
1284                     var_SetString( p_vout, "deinterlace-mode", vlist.p_list->p_values[i].psz_string );
1285 
1286                     int i_deinterlace = var_GetInteger( p_vout, "deinterlace" );
1287                     if( i_deinterlace != 0 )
1288                     {
1289                       DisplayMessage( p_vout, "%s (%s)", _("Deinterlace on"),
1290                                       psz_text ? psz_text : psz_mode );
1291                     }
1292                     else
1293                     {
1294                       DisplayMessage( p_vout, "%s (%s)", _("Deinterlace off"),
1295                                       psz_text ? psz_text : psz_mode );
1296                     }
1297 
1298                     var_FreeList( &vlist, &tlist );
1299                 }
1300                 free( psz_mode );
1301             }
1302             break;
1303 
1304         case ACTIONID_SUBPOS_DOWN:
1305         case ACTIONID_SUBPOS_UP:
1306         {
1307             if( p_input )
1308             {
1309                 vlc_value_t val, list, list2;
1310                 int i_count;
1311                 var_Get( p_input, "spu-es", &val );
1312 
1313                 var_Change( p_input, "spu-es", VLC_VAR_GETCHOICES,
1314                             &list, &list2 );
1315                 i_count = list.p_list->i_count;
1316                 if( i_count < 1 || val.i_int < 0 )
1317                 {
1318                     DisplayMessage( p_vout,
1319                                     _("Subtitle position: no active subtitle") );
1320                     var_FreeList( &list, &list2 );
1321                     break;
1322                 }
1323 
1324                 int i_pos;
1325                 if( i_action == ACTIONID_SUBPOS_DOWN )
1326                     i_pos = var_DecInteger( p_vout, "sub-margin" );
1327                 else
1328                     i_pos = var_IncInteger( p_vout, "sub-margin" );
1329 
1330                 ClearChannels( p_vout, slider_chan );
1331                 DisplayMessage( p_vout, _( "Subtitle position %d px" ), i_pos );
1332                 var_FreeList( &list, &list2 );
1333             }
1334             break;
1335         }
1336 
1337         case ACTIONID_SUBTITLE_TEXT_SCALE_DOWN:
1338         case ACTIONID_SUBTITLE_TEXT_SCALE_UP:
1339         case ACTIONID_SUBTITLE_TEXT_SCALE_NORMAL:
1340             if( p_vout )
1341             {
1342                 int i_scale;
1343                 if( i_action == ACTIONID_SUBTITLE_TEXT_SCALE_NORMAL )
1344                 {
1345                     i_scale = 100;
1346                 }
1347                 else
1348                 {
1349                     i_scale = var_GetInteger( p_playlist, "sub-text-scale" );
1350                     unsigned increment = ((i_scale > 100 ? i_scale - 100 : 100 - i_scale) / 25) <= 1 ? 10 : 25;
1351                     i_scale += ((i_action == ACTIONID_SUBTITLE_TEXT_SCALE_UP) ? 1 : -1) * increment;
1352                     i_scale -= i_scale % increment;
1353                     i_scale = VLC_CLIP( i_scale, 25, 500 );
1354                 }
1355                 var_SetInteger( p_playlist, "sub-text-scale", i_scale );
1356                 DisplayMessage( p_vout, _( "Subtitle text scale %d%%" ), i_scale );
1357             }
1358             break;
1359 
1360         /* Input + video output */
1361         case ACTIONID_POSITION:
1362             if( p_vout && vout_OSDEpg( p_vout, input_GetItem( p_input ) ) )
1363                 DisplayPosition( p_vout, slider_chan, p_input );
1364             break;
1365 
1366         case ACTIONID_COMBO_VOL_FOV_UP:
1367             if( b_vrnav )
1368                 DO_ACTION( ACTIONID_VIEWPOINT_FOV_IN );
1369             else
1370                 DO_ACTION( ACTIONID_VOL_UP );
1371             break;
1372         case ACTIONID_COMBO_VOL_FOV_DOWN:
1373             if( b_vrnav )
1374                 DO_ACTION( ACTIONID_VIEWPOINT_FOV_OUT );
1375             else
1376                 DO_ACTION( ACTIONID_VOL_DOWN );
1377             break;
1378     }
1379 
1380     return VLC_SUCCESS;
1381 }
1382 
1383 /*****************************************************************************
1384  * ActionEvent: callback for hotkey actions
1385  *****************************************************************************/
ActionEvent(vlc_object_t * libvlc,char const * psz_var,vlc_value_t oldval,vlc_value_t newval,void * p_data)1386 static int ActionEvent( vlc_object_t *libvlc, char const *psz_var,
1387                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
1388 {
1389     intf_thread_t *p_intf = (intf_thread_t *)p_data;
1390     intf_sys_t *p_sys = p_intf->p_sys;
1391 
1392     (void)libvlc;
1393     (void)psz_var;
1394     (void)oldval;
1395 
1396     vlc_mutex_lock( &p_intf->p_sys->lock );
1397     input_thread_t *p_input = p_sys->p_input ? vlc_object_hold( p_sys->p_input )
1398                                              : NULL;
1399     vout_thread_t *p_vout = p_sys->p_vout ? vlc_object_hold( p_sys->p_vout )
1400                                           : NULL;
1401     int slider_chan = p_sys->slider_chan;
1402     bool b_vrnav = p_sys->vrnav.b_can_change;
1403     vlc_mutex_unlock( &p_intf->p_sys->lock );
1404 
1405     int i_ret = PutAction( p_intf, p_input, p_vout, slider_chan, b_vrnav,
1406                            newval.i_int );
1407 
1408     if( p_input != NULL )
1409         vlc_object_release( p_input );
1410     if( p_vout != NULL )
1411         vlc_object_release( p_vout );
1412 
1413     return i_ret;
1414 }
1415 
PlayBookmark(intf_thread_t * p_intf,int i_num)1416 static void PlayBookmark( intf_thread_t *p_intf, int i_num )
1417 {
1418     char *psz_bookmark_name;
1419     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
1420         return;
1421 
1422     playlist_t *p_playlist = pl_Get( p_intf );
1423     char *psz_bookmark = var_CreateGetString( p_intf, psz_bookmark_name );
1424 
1425     PL_LOCK;
1426     FOREACH_ARRAY( playlist_item_t *p_item, p_playlist->items )
1427         char *psz_uri = input_item_GetURI( p_item->p_input );
1428         if( !strcmp( psz_bookmark, psz_uri ) )
1429         {
1430             free( psz_uri );
1431             playlist_ViewPlay( p_playlist, NULL, p_item );
1432             break;
1433         }
1434         else
1435             free( psz_uri );
1436     FOREACH_END();
1437     PL_UNLOCK;
1438 
1439     free( psz_bookmark );
1440     free( psz_bookmark_name );
1441 }
1442 
SetBookmark(intf_thread_t * p_intf,int i_num)1443 static void SetBookmark( intf_thread_t *p_intf, int i_num )
1444 {
1445     char *psz_bookmark_name;
1446     char *psz_uri = NULL;
1447     if( asprintf( &psz_bookmark_name, "bookmark%i", i_num ) == -1 )
1448         return;
1449 
1450     playlist_t *p_playlist = pl_Get( p_intf );
1451     var_Create( p_intf, psz_bookmark_name,
1452                 VLC_VAR_STRING|VLC_VAR_DOINHERIT );
1453 
1454     PL_LOCK;
1455     playlist_item_t * p_item = playlist_CurrentPlayingItem( p_playlist );
1456     if( p_item ) psz_uri = input_item_GetURI( p_item->p_input );
1457     PL_UNLOCK;
1458 
1459     if( p_item )
1460     {
1461         config_PutPsz( p_intf, psz_bookmark_name, psz_uri);
1462         msg_Info( p_intf, "setting playlist bookmark %i to %s", i_num, psz_uri);
1463     }
1464 
1465     free( psz_uri );
1466     free( psz_bookmark_name );
1467 }
1468 
DisplayPosition(vout_thread_t * p_vout,int slider_chan,input_thread_t * p_input)1469 static void DisplayPosition( vout_thread_t *p_vout, int slider_chan,
1470                              input_thread_t *p_input )
1471 {
1472     char psz_duration[MSTRTIME_MAX_SIZE];
1473     char psz_time[MSTRTIME_MAX_SIZE];
1474 
1475     if( p_vout == NULL ) return;
1476 
1477     ClearChannels( p_vout, slider_chan );
1478 
1479     int64_t t = var_GetInteger( p_input, "time" ) / CLOCK_FREQ;
1480     int64_t l = var_GetInteger( p_input, "length" ) / CLOCK_FREQ;
1481 
1482     secstotimestr( psz_time, t );
1483 
1484     if( l > 0 )
1485     {
1486         secstotimestr( psz_duration, l );
1487         DisplayMessage( p_vout, "%s / %s", psz_time, psz_duration );
1488     }
1489     else if( t > 0 )
1490     {
1491         DisplayMessage( p_vout, "%s", psz_time );
1492     }
1493 
1494     if( var_GetBool( p_vout, "fullscreen" ) )
1495     {
1496         vlc_value_t pos;
1497         var_Get( p_input, "position", &pos );
1498         vout_OSDSlider( p_vout, slider_chan,
1499                         pos.f_float * 100, OSD_HOR_SLIDER );
1500     }
1501 }
1502 
DisplayVolume(vout_thread_t * p_vout,int slider_chan,float vol)1503 static void DisplayVolume( vout_thread_t *p_vout, int slider_chan, float vol )
1504 {
1505     if( p_vout == NULL )
1506         return;
1507     ClearChannels( p_vout, slider_chan );
1508 
1509     if( var_GetBool( p_vout, "fullscreen" ) )
1510         vout_OSDSlider( p_vout, slider_chan,
1511                         lroundf(vol * 100.f), OSD_VERT_SLIDER );
1512     DisplayMessage( p_vout, _( "Volume %ld%%" ), lroundf(vol * 100.f) );
1513 }
1514 
DisplayRate(vout_thread_t * p_vout,float f_rate)1515 static void DisplayRate( vout_thread_t *p_vout, float f_rate )
1516 {
1517     DisplayMessage( p_vout, _("Speed: %.2fx"), (double) f_rate );
1518 }
1519 
AdjustRateFine(vlc_object_t * p_obj,const int i_dir)1520 static float AdjustRateFine( vlc_object_t *p_obj, const int i_dir )
1521 {
1522     const float f_rate_min = (float)INPUT_RATE_DEFAULT / INPUT_RATE_MAX;
1523     const float f_rate_max = (float)INPUT_RATE_DEFAULT / INPUT_RATE_MIN;
1524     float f_rate = var_GetFloat( p_obj, "rate" );
1525 
1526     int i_sign = f_rate < 0 ? -1 : 1;
1527 
1528     f_rate = floor( fabs(f_rate) / 0.1 + i_dir + 0.05 ) * 0.1;
1529 
1530     if( f_rate < f_rate_min )
1531         f_rate = f_rate_min;
1532     else if( f_rate > f_rate_max )
1533         f_rate = f_rate_max;
1534     f_rate *= i_sign;
1535 
1536     return f_rate;
1537 }
1538 
ClearChannels(vout_thread_t * p_vout,int slider_chan)1539 static void ClearChannels( vout_thread_t *p_vout, int slider_chan )
1540 {
1541     if( p_vout )
1542     {
1543         vout_FlushSubpictureChannel( p_vout, VOUT_SPU_CHANNEL_OSD );
1544         vout_FlushSubpictureChannel( p_vout, slider_chan );
1545     }
1546 }
1547