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