]> git.sesse.net Git - vlc/blob - modules/codec/cmml/intf.c
b1940c0300ba2b87aa75026aee41868d4cf65f62
[vlc] / modules / codec / cmml / intf.c
1 /*****************************************************************************
2  * intf.c: interface for CMML annotations/hyperlinks
3  *****************************************************************************
4  * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
5  *                         Organisation (CSIRO) Australia
6  * Copyright (C) 2004 the VideoLAN team
7  *
8  * $Id$
9  *
10  * Authors: Andre Pang <Andre.Pang@csiro.au>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc/vlc.h>
35
36 #include <stdio.h>
37
38 #ifdef HAVE_UNISTD_H
39 #    include <unistd.h>
40 #endif
41
42 #include <vlc_codec.h>
43 #include <vlc_input.h>
44 #include <vlc_interface.h>
45 #include <vlc_vout.h>
46 #include <vlc_playlist.h>
47 #include <vlc_osd.h>
48
49 #include "vlc_keys.h"
50
51 #include "browser_open.h"
52 #include "history.h"
53 #include "xstrcat.h"
54 #include "xurl.h"
55
56 #undef  CMML_INTF_USE_TIMED_URIS
57
58 #undef  CMML_INTF_DEBUG
59 #undef  CMML_INTF_HISTORY_DEBUG
60
61 /*****************************************************************************
62  * intf_sys_t: description and status of interface
63  *****************************************************************************/
64 struct intf_sys_t
65 {
66     decoder_t *         p_cmml_decoder;
67     input_thread_t *    p_input;
68
69     vlc_bool_t          b_key_pressed;
70 };
71
72 struct navigation_history_t
73 {
74     int i_history_size;
75     int i_last_item;
76 };
77
78 /*****************************************************************************
79  * Local prototypes.
80  *****************************************************************************/
81 static int   InitThread                 ( intf_thread_t * );
82 static int   MouseEvent                 ( vlc_object_t *, char const *,
83                                           vlc_value_t, vlc_value_t, void * );
84 static int   KeyEvent                   ( vlc_object_t *, char const *,
85                                           vlc_value_t, vlc_value_t, void * );
86
87 static void  FollowAnchor               ( intf_thread_t * );
88 static void  GoBack                     ( intf_thread_t * );
89 static void  GoForward                  ( intf_thread_t * );
90
91 static int   FollowAnchorCallback       ( vlc_object_t *, char const *,
92                                           vlc_value_t, vlc_value_t, void * );
93 static int   GoBackCallback             ( vlc_object_t *, char const *,
94                                           vlc_value_t, vlc_value_t, void * );
95 static int   GoForwardCallback          ( vlc_object_t *, char const *,
96                                           vlc_value_t, vlc_value_t, void * );
97
98 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
99 static char *GetTimedURIFragmentForTime ( int );
100 static int   GetCurrentTimeInSeconds    ( input_thread_t * );
101 static int   DisplayAnchor              ( intf_thread_t *, vout_thread_t *,
102                                           char *, char * );
103 static int   DisplayPendingAnchor       ( intf_thread_t *, vout_thread_t * );
104 static history_t * GetHistory           ( playlist_t * );
105 static void  ReplacePlaylistItem        ( playlist_t *, char * );
106
107 /* Exported functions */
108 static void RunIntf        ( intf_thread_t *p_intf );
109
110 /*****************************************************************************
111  * OpenIntf: initialize CMML interface
112  *****************************************************************************/
113 int E_(OpenIntf) ( vlc_object_t *p_this )
114 {
115     intf_thread_t *p_intf = (intf_thread_t *)p_this;
116
117     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
118     if( p_intf->p_sys == NULL )
119     {
120         return( 1 );
121     };
122
123     p_intf->pf_run = RunIntf;
124
125     var_AddCallback( p_intf->p_libvlc, "key-pressed", KeyEvent, p_intf );
126     /* we also need to add the callback for "mouse-clicked", but do that later
127      * when we've found a p_vout */
128
129     var_Create( p_intf->p_libvlc, "browse-go-back", VLC_VAR_VOID );
130     var_AddCallback( p_intf->p_libvlc, "browse-go-back",
131                      GoBackCallback, p_intf );
132     var_Create( p_intf->p_libvlc, "browse-go-forward", VLC_VAR_VOID );
133     var_AddCallback( p_intf->p_libvlc, "browse-go-forward",
134                      GoForwardCallback, p_intf );
135     var_Create( p_intf->p_libvlc, "browse-follow-anchor", VLC_VAR_VOID );
136     var_AddCallback( p_intf->p_libvlc, "browse-follow-anchor",
137                      FollowAnchorCallback, p_intf );
138
139     return( 0 );
140 }
141
142 /*****************************************************************************
143  * CloseIntf: destroy dummy interface
144  *****************************************************************************/
145 void E_(CloseIntf) ( vlc_object_t *p_this )
146 {
147     intf_thread_t * p_intf = (intf_thread_t *)p_this;
148     vout_thread_t * p_vout;
149
150 #ifdef CMML_INTF_DEBUG
151     msg_Dbg( p_intf, "freeing CMML interface" );
152 #endif
153
154     /* erase the anchor text description from the video output if it exists */
155     p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
156     if( p_vout )
157     {
158         /* enable CMML as a subtitle track */
159         spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
160         vlc_object_release( p_vout );
161     }
162
163     var_DelCallback( p_intf->p_libvlc, "key-pressed", KeyEvent, p_intf );
164
165     vlc_object_release( p_intf->p_sys->p_cmml_decoder );
166
167     free( p_intf->p_sys );
168 }
169
170
171 /*****************************************************************************
172  * RunIntf: main loop
173  *****************************************************************************/
174 static void RunIntf( intf_thread_t *p_intf )
175 {
176     vout_thread_t * p_vout = NULL;
177
178     if( InitThread( p_intf ) < 0 )
179     {
180         msg_Err( p_intf, "can't initialize CMML interface" );
181         return;
182     }
183 #ifdef CMML_INTF_DEBUG
184     msg_Dbg( p_intf, "CMML intf initialized" );
185 #endif
186
187     /* if video output is dying, disassociate ourselves from it */
188     if( p_vout && p_vout->b_die )
189     {
190         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
191         vlc_object_release( p_vout );
192         p_vout = NULL;
193     }
194
195     /* Main loop */
196     while( !p_intf->b_die )
197     {
198
199         /* find a video output if we currently don't have one */
200         if( p_vout == NULL )
201         {
202             p_vout = vlc_object_find( p_intf->p_sys->p_input,
203                                       VLC_OBJECT_VOUT, FIND_CHILD );
204             if( p_vout )
205             {
206 #ifdef CMML_INTF_DEBUG
207                 msg_Dbg( p_intf, "found vout thread" );
208 #endif
209                 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
210             }
211         }
212
213         vlc_mutex_lock( &p_intf->change_lock );
214
215         /*
216          * keyboard event
217          */
218         if( p_intf->p_sys->b_key_pressed )
219         {
220             vlc_value_t val;
221             int i, i_action = -1;
222             struct hotkey *p_hotkeys = p_intf->p_libvlc->p_hotkeys;
223
224             /* Find action triggered by hotkey (if any) */
225             var_Get( p_intf->p_libvlc, "key-pressed", &val );
226
227             /* Acknowledge that we've handled the b_key_pressed event */
228             p_intf->p_sys->b_key_pressed = VLC_FALSE;
229
230 #ifdef CMML_INTF_DEBUG
231             msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
232 #endif
233
234             for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
235             {
236                 if( p_hotkeys[i].i_key == val.i_int )
237                     i_action = p_hotkeys[i].i_action;
238             }
239
240             /* What did the user do? */
241             if( i_action != -1 )
242             {
243                 switch( i_action )
244                 {
245                     case ACTIONID_NAV_ACTIVATE:
246                         FollowAnchor( p_intf );
247                         break;
248                     case ACTIONID_HISTORY_BACK:
249                         GoBack( p_intf );
250                         break;
251                     case ACTIONID_HISTORY_FORWARD:
252                         GoForward( p_intf );
253                         break;
254                     default:
255                         break;
256                 }
257             }
258         }
259
260         vlc_mutex_unlock( &p_intf->change_lock );
261
262         (void) DisplayPendingAnchor( p_intf, p_vout );
263
264         /* Wait a bit */
265         msleep( INTF_IDLE_SLEEP );
266     }
267
268     /* if we're here, the video output is dying: release the vout object */
269
270     if( p_vout )
271     {
272         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
273         vlc_object_release( p_vout );
274     }
275
276     vlc_object_release( p_intf->p_sys->p_input );
277 }
278
279 /*****************************************************************************
280  * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
281  * decoder and display it on screen
282  *****************************************************************************/
283 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
284 {
285     decoder_t *p_cmml_decoder;
286     char *psz_description = NULL;
287     char *psz_url = NULL;
288
289     intf_thread_t *p_primary_intf;
290     vlc_value_t val;
291
292     p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
293     if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
294             != VLC_SUCCESS )
295     {
296         return VLC_TRUE;
297     }
298
299     if( !val.p_address )
300         return VLC_TRUE;
301
302     psz_description = val.p_address;
303
304     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
305             == VLC_SUCCESS )
306     {
307         psz_url = val.p_address;
308     }
309
310     if( p_vout != NULL )
311     {
312         /* don't display anchor if main interface can display it */
313         p_primary_intf = vlc_object_find( p_intf->p_libvlc, VLC_OBJECT_INTF,
314                 FIND_CHILD );
315
316         if( p_primary_intf )
317         {
318             if( var_Get( p_primary_intf, "intf-displays-cmml-description", &val )
319                     == VLC_SUCCESS )
320             {
321                 if( val.b_bool == VLC_TRUE )
322                 {
323                     vlc_object_release( p_primary_intf );
324                     return VLC_TRUE;
325                 }
326             }
327
328             vlc_object_release( p_primary_intf );
329         }
330
331         /* display anchor as subtitle on-screen */
332         if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
333                 != VLC_SUCCESS )
334         {
335             /* text render unsuccessful: do nothing */
336             return VLC_FALSE;
337         }
338
339         /* text render successful: clear description */
340         val.p_address = NULL;
341         if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
342                 != VLC_SUCCESS )
343         {
344             msg_Dbg( p_intf,
345                      "reset of psz-current-anchor-description failed" );
346         }
347         free( psz_description );
348         psz_url = NULL;
349     }
350
351     return VLC_TRUE;
352 }
353
354
355 /*****************************************************************************
356  * InitThread:
357  *****************************************************************************/
358 static int InitThread( intf_thread_t * p_intf )
359 {
360     /* We might need some locking here */
361     if( !p_intf->b_die )
362     {
363         input_thread_t * p_input;
364         decoder_t *p_cmml_decoder;
365
366         p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
367         p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
368
369 #ifdef CMML_INTF_DEBUG
370         msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
371                  p_cmml_decoder, p_input );
372 #endif
373
374         /* Maybe the input just died */
375         if( p_input == NULL )
376         {
377             return VLC_EGENERIC;
378         }
379
380         vlc_mutex_lock( &p_intf->change_lock );
381
382         p_intf->p_sys->p_input = p_input;
383         p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
384
385         p_intf->p_sys->b_key_pressed = VLC_FALSE;
386
387         vlc_mutex_unlock( &p_intf->change_lock );
388
389         return VLC_SUCCESS;
390     }
391     else
392     {
393         return VLC_EGENERIC;
394     }
395 }
396
397 /*****************************************************************************
398  * MouseEvent: callback for mouse events
399  *****************************************************************************/
400 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
401                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
402 {
403     /* TODO: handle mouse clicks on the anchor text */
404
405     return VLC_SUCCESS;
406 }
407
408 /*****************************************************************************
409  * KeyEvent: callback for keyboard events
410  *****************************************************************************/
411 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
412                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
413 {
414     intf_thread_t *p_intf = (intf_thread_t *)p_data;
415     vlc_mutex_lock( &p_intf->change_lock );
416
417     p_intf->p_sys->b_key_pressed = VLC_TRUE;
418
419     vlc_mutex_unlock( &p_intf->change_lock );
420
421     return VLC_SUCCESS;
422 }
423
424 /*****************************************************************************
425  * FollowAnchor: follow the current anchor being displayed to the user
426  *****************************************************************************/
427 static void FollowAnchor ( intf_thread_t *p_intf )
428 {
429     intf_sys_t *p_sys;
430     decoder_t *p_cmml_decoder;
431     char *psz_url = NULL;
432     vlc_value_t val;
433
434     msg_Dbg( p_intf, "User followed anchor" );
435
436     p_sys = p_intf->p_sys;
437     p_cmml_decoder = p_sys->p_cmml_decoder;
438
439     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
440             VLC_SUCCESS )
441     {
442         if( val.p_address ) psz_url = val.p_address;
443     }
444
445 #ifdef CMML_INTF_DEBUG
446     msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
447 #endif
448
449     if( psz_url )
450     {
451         playlist_t *p_playlist;
452         playlist_item_t *p_current_item;
453         char *psz_uri_to_load;
454         mtime_t i_seconds;
455         vlc_value_t time;
456
457         p_playlist = (playlist_t *) vlc_object_find( p_intf,
458                 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
459         if ( !p_playlist )
460         {
461             msg_Warn( p_intf, "can't find playlist" );
462             return;
463         }
464
465         /* Get new URL */
466         p_current_item = p_playlist->status.p_item;
467         char *psz_uri = input_item_GetURI( p_current_item->p_input );
468 #ifdef CMML_INTF_DEBUG
469         msg_Dbg( p_intf, "Current playlist item URL is \"%s\"", psz_uri );
470 #endif
471
472         psz_uri_to_load = XURL_Concat( psz_uri, psz_url );
473         free( psz_uri );
474
475 #ifdef CMML_INTF_DEBUG
476         msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
477 #endif
478
479         if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
480         {
481             msg_Dbg( p_intf, "couldn't get time from current clip" );
482             time.i_time = 0;
483         }
484         i_seconds = time.i_time / 1000000;
485 #ifdef CMML_INTF_DEBUG
486         msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
487 #endif
488
489         /* TODO: we need a (much) more robust way of detecting whether
490          * the file's a media file ... */
491         if( strstr( psz_uri_to_load, ".anx" ) != NULL )
492         {
493             history_t *p_history = NULL;
494             history_item_t *p_history_item = NULL;
495             char *psz_timed_url;
496
497             p_history = GetHistory( p_playlist );
498
499             /* create history item */
500             psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
501             p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
502             free( psz_timed_url );
503
504             if( !p_history_item )
505             {
506                 msg_Warn( p_intf, "could not initialise history item" );
507             }
508             else
509             {
510 #ifdef CMML_INTF_DEBUG
511                 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
512 #endif
513                 history_PruneAndInsert( p_history, p_history_item );
514 #ifdef CMML_INTF_DEBUG
515                 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
516                          p_history_item, p_history_item->psz_uri );
517                 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
518 #endif
519             }
520
521             /* free current-anchor-url */
522             free( psz_url );
523             val.p_address = NULL;
524             if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
525                     VLC_SUCCESS )
526             {
527                 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
528             }
529
530             ReplacePlaylistItem( p_playlist, psz_uri_to_load );
531         }
532         else
533         {
534 #ifdef CMML_INTF_DEBUG
535             msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
536 #endif
537             (void) browser_Open( psz_url );
538             playlist_Control( p_playlist, PLAYLIST_PAUSE, VLC_TRUE, 0 );
539         }
540
541         free( psz_uri_to_load );
542
543         vlc_object_release( p_playlist );
544     }
545 }
546
547 static
548 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
549         playlist_item_t *p_current_item )
550 {
551 #ifdef CMML_INTF_USE_TIMED_URIS
552     char *psz_url = NULL;
553     char *psz_return_value = NULL;
554     char *psz_seconds = NULL;
555     int i_seconds;
556
557     char *psz_uri = input_item_GetURI( p_current_item->p_input );
558     psz_url = XURL_GetWithoutFragment( psz_uri );
559     free( psz_uri );
560
561     /* Get current time as a string */
562     if( XURL_IsFileURL( psz_url ) == VLC_TRUE )
563         psz_url = xstrcat( psz_url, "#" );
564     else
565         psz_url = xstrcat( psz_url, "?" );
566
567     /* jump back to 2 seconds before where we are now */
568     i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
569     psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
570     if( psz_seconds )
571     {
572         psz_url = xstrcat( psz_url, psz_seconds );
573         free( psz_seconds );
574         psz_return_value = psz_url;
575     }
576
577     return psz_return_value;
578 #else
579     void *p;
580
581     /* Suppress warning messages about unused functions */
582     p = GetTimedURIFragmentForTime; /* unused */
583     p = GetCurrentTimeInSeconds;    /* unused */
584
585     return input_item_GetURI( p_current_item->p_input );
586 #endif
587 }
588
589
590 /*
591  * Get the current time, rounded down to the nearest second
592  *
593  * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
594  */
595 static
596 int GetCurrentTimeInSeconds( input_thread_t *p_input )
597 {
598     vlc_value_t time;
599     mtime_t i_seconds;
600
601     var_Get( p_input, "time", &time );
602     i_seconds = time.i_time / 1000000;
603     return i_seconds;
604 }
605
606 static
607 char *GetTimedURIFragmentForTime( int seconds )
608 {
609     char *psz_time;
610
611     asprintf( &psz_time, "%d", seconds );
612     return psz_time;
613 }
614
615 static
616 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
617                     vlc_value_t oldval, vlc_value_t newval, void *p_data )
618 {
619     intf_thread_t *p_intf = (intf_thread_t *) p_data;
620     GoBack( p_intf );
621     return VLC_SUCCESS;
622 }
623
624 static
625 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
626                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
627 {
628     intf_thread_t *p_intf = (intf_thread_t *) p_data;
629     GoForward( p_intf );
630     return VLC_SUCCESS;
631 }
632
633 static
634 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
635                           vlc_value_t oldval, vlc_value_t newval,
636                           void *p_data )
637 {
638     intf_thread_t *p_intf = (intf_thread_t *) p_data;
639     FollowAnchor( p_intf );
640     return VLC_SUCCESS;
641 }
642
643 static
644 void GoBack( intf_thread_t *p_intf )
645 {
646     vlc_value_t history;
647     history_t *p_history = NULL;
648     history_item_t *p_history_item = NULL;
649     history_item_t *p_new_history_item = NULL;
650     playlist_t *p_playlist = NULL;
651     char *psz_timed_url = NULL;
652     playlist_item_t *p_current_item;
653
654 #ifdef CMML_INTF_DEBUG
655     msg_Dbg( p_intf, "Going back in navigation history" );
656 #endif
657
658     /* Find the playlist */
659     p_playlist = (playlist_t *) vlc_object_find( p_intf,
660             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
661     if ( !p_playlist )
662     {
663         msg_Warn( p_intf, "can't find playlist" );
664         return;
665     }
666
667     /* Retrieve navigation history from playlist */
668     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
669         !history.p_address )
670     {
671         /* History doesn't exist yet: ignore user's request */
672         msg_Warn( p_intf, "can't go back: no history exists yet" );
673         vlc_object_release( p_playlist );
674         return;
675     }
676
677     p_history = history.p_address;
678 #ifdef CMML_INTF_DEBUG
679     msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
680     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
681              p_history->p_xarray );
682 #endif
683
684     /* Check whether we can go back in the history */
685     if( history_CanGoBack( p_history ) == VLC_FALSE )
686     {
687         msg_Warn( p_intf, "can't go back: already at beginning of history" );
688         vlc_object_release( p_playlist );
689         return;
690     }
691
692     p_current_item = p_playlist->status.p_item;
693
694     /* Save the currently-playing media in a new history item */
695     psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
696     p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
697     free( psz_timed_url );
698
699     if( !p_new_history_item )
700     {
701 #ifdef CMML_INTF_DEBUG
702         msg_Dbg( p_intf, "back: could not initialise new history item" );
703 #endif
704         vlc_object_release( p_playlist );
705         return;
706     }
707
708     /* Go back in the history, saving the currently-playing item */
709     (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
710     p_history_item = history_Item( p_history );
711
712 #ifdef CMML_INTF_DEBUG
713     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
714     msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
715     msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
716 #endif
717
718     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
719     vlc_object_release( p_playlist );
720 }
721
722 static
723 void GoForward( intf_thread_t *p_intf )
724 {
725     vlc_value_t history;
726     history_t *p_history = NULL;
727     history_item_t *p_history_item = NULL;
728     history_item_t *p_new_history_item = NULL;
729     playlist_t *p_playlist = NULL;
730     playlist_item_t *p_current_item;
731
732 #ifdef CMML_INTF_DEBUG
733     msg_Dbg( p_intf, "Going forward in navigation history" );
734 #endif
735
736     /* Find the playlist */
737     p_playlist = (playlist_t *) vlc_object_find( p_intf,
738             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
739     if ( !p_playlist )
740     {
741         msg_Warn( p_intf, "can't find playlist" );
742         return;
743     }
744
745     /* Retrieve navigation history from playlist */
746     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
747         !history.p_address )
748     {
749         /* History doesn't exist yet: ignore user's request */
750         msg_Warn( p_intf, "can't go back: no history exists yet" );
751         vlc_object_release( p_playlist );
752         return;
753     }
754
755     p_history = history.p_address;
756 #ifdef CMML_INTF_DEBUG
757     msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
758     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
759              p_history->p_xarray );
760 #endif
761
762     /* Check whether we can go forward in the history */
763     if( history_CanGoForward( p_history ) == VLC_FALSE )
764     {
765         msg_Warn( p_intf, "can't go forward: already at end of history" );
766         vlc_object_release( p_playlist );
767         return;
768     }
769
770     /* Save the currently-playing media in a new history item */
771     p_new_history_item = malloc( sizeof(history_item_t) );
772     if( !p_new_history_item )
773     {
774 #ifdef CMML_INTF_DEBUG
775         msg_Dbg( p_intf, "forward: could not initialise new history item" );
776 #endif
777         vlc_object_release( p_playlist );
778         return;
779     }
780     p_current_item = p_playlist->status.p_item;
781     p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf,
782             p_current_item );
783     p_new_history_item->psz_name = p_new_history_item->psz_uri;
784
785     /* Go forward in the history, saving the currently-playing item */
786     (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
787     p_history_item = history_Item( p_history );
788
789 #ifdef CMML_INTF_DEBUG
790     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
791     msg_Dbg( p_intf, "got next history item: %p", p_history_item );
792     msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
793 #endif
794
795     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
796     vlc_object_release( p_playlist );
797 }
798
799 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
800 {
801     playlist_Stop( p_playlist );
802     (void) playlist_Add( p_playlist, psz_uri, psz_uri,
803                          PLAYLIST_INSERT /* FIXME: used to be PLAYLIST_REPLACE */, PLAYLIST_END|PLAYLIST_GO, VLC_TRUE /* FIXME: p_playlist->status.i_index */,
804                          VLC_FALSE);
805 }
806
807 /****************************************************************************
808  * DisplayAnchor: displays an anchor on the given video output
809  ****************************************************************************/
810 static int DisplayAnchor( intf_thread_t *p_intf,
811         vout_thread_t *p_vout,
812         char *psz_anchor_description,
813         char *psz_anchor_url )
814 {
815     int i_margin_h, i_margin_v;
816     mtime_t i_now;
817
818     i_margin_h = 0;
819     i_margin_v = 10;
820
821     i_now = mdate();
822
823     if( p_vout )
824     {
825         if( psz_anchor_url )
826         {
827             /* Should display subtitle underlined and in blue, but it looks
828              * like VLC doesn't implement any text styles yet.  D'oh! */
829             // p_style = &blue_with_underline;
830
831         }
832
833         /* TODO: p_subpicture doesn't have the proper i_x and i_y
834          * coordinates.  Need to look at the subpicture display system to
835          * work out why. */
836         if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
837                 psz_anchor_description, NULL, OSD_ALIGN_BOTTOM,
838                 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
839         {
840             /* Displayed successfully */
841         }
842         else
843         {
844             return VLC_EGENERIC;
845         }
846     }
847     else
848     {
849         msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
850         return VLC_EGENERIC;
851     }
852
853     return VLC_SUCCESS;
854 }
855
856 static history_t * GetHistory( playlist_t *p_playlist )
857 {
858     vlc_value_t val;
859     history_t *p_history = NULL;
860
861     if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
862     {
863         /* history doesn't exist yet: need to create it */
864         history_t *new_history = history_New();
865         val.p_address = new_history;
866         var_Create( p_playlist, "navigation-history",
867                 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
868         if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
869         {
870             msg_Warn( p_playlist, "could not initialise history" );
871         }
872         else
873         {
874             p_history = new_history;
875 #ifdef CMML_INTF_HISTORY_DEBUG
876             msg_Dbg( p_playlist, "nav history created at %p", new_history );
877             msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
878                      p_history->i_index, p_history->p_xarray );
879 #endif
880         }
881     }
882     else
883     {
884         p_history = val.p_address;
885 #ifdef CMML_INTF_HISTORY_DEBUG
886         msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
887 #endif
888     }
889
890     return p_history;
891 }
892