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