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