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