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