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