root/modules/gui/ncurses.c

Revision d228fdd28ef03b992c97af16cb842acd7c879831, 85.7 kB (checked in by Pierre d'Herbemont <pdherbemont@videolan.org>, 1 week ago)

playlist: Move more members as private.

  • Property mode set to 100644
Line 
1 /*****************************************************************************
2  * ncurses.c : NCurses interface for vlc
3  *****************************************************************************
4  * Copyright © 2001-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Sam Hocevar <sam@zoy.org>
8  *          Laurent Aimar <fenrir@via.ecp.fr>
9  *          Yoann Peronneau <yoann@videolan.org>
10  *          Derk-Jan Hartman <hartman at videolan dot org>
11  *          Rafaël Carré <funman@videolanorg>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26  *****************************************************************************/
27
28 /*
29  * Note that when we use wide characters (and link with libncursesw),
30  * we assume that an UTF8 locale is used (or compatible, such as ASCII).
31  * Other characters encodings are not supported.
32  */
33
34 /*****************************************************************************
35  * Preamble
36  *****************************************************************************/
37 #ifdef HAVE_CONFIG_H
38 # include "config.h"
39 #endif
40
41 #include <vlc_common.h>
42 #include <vlc_plugin.h>
43
44 #ifdef HAVE_NCURSESW
45 #   define _XOPEN_SOURCE_EXTENDED 1
46 #   include <wchar.h>
47 #endif
48
49 #include <ncurses.h>
50
51 #include <vlc_interface.h>
52 #include <vlc_vout.h>
53 #include <vlc_aout.h>
54 #include <vlc_charset.h>
55 #include <vlc_input.h>
56 #include <vlc_es.h>
57 #include <vlc_playlist.h>
58 #include <vlc_meta.h>
59
60 #include <assert.h>
61
62 #ifdef HAVE_SYS_STAT_H
63 #   include <sys/stat.h>
64 #endif
65 #if (!defined( WIN32 ) || defined(__MINGW32__))
66 /* Mingw has its own version of dirent */
67 #   include <dirent.h>
68 #endif
69
70 #ifdef HAVE_CDDAX
71 #define CDDA_MRL "cddax://"
72 #else
73 #define CDDA_MRL "cdda://"
74 #endif
75
76 #ifdef HAVE_VCDX
77 #define VCD_MRL "vcdx://"
78 #else
79 #define VCD_MRL "vcd://"
80 #endif
81
82 #define SEARCH_CHAIN_SIZE 20
83 #define OPEN_CHAIN_SIZE 50
84
85 /*****************************************************************************
86  * Local prototypes.
87  *****************************************************************************/
88 static int  Open           ( vlc_object_t * );
89 static void Close          ( vlc_object_t * );
90
91 static void Run            ( intf_thread_t * );
92 static void PlayPause      ( intf_thread_t * );
93 static void Eject          ( intf_thread_t * );
94
95 static int  HandleKey      ( intf_thread_t *, int );
96 static void Redraw         ( intf_thread_t *, time_t * );
97
98 static playlist_item_t *PlaylistGetRoot( intf_thread_t * );
99 static void PlaylistRebuild( intf_thread_t * );
100 static void PlaylistAddNode( intf_thread_t *, playlist_item_t *, int, const char *);
101 static void PlaylistDestroy( intf_thread_t * );
102 static int  PlaylistChanged( vlc_object_t *, const char *, vlc_value_t,
103                              vlc_value_t, void * );
104 static inline bool PlaylistIsPlaying( intf_thread_t *,
105                                             playlist_item_t * );
106 static void FindIndex      ( intf_thread_t * );
107 static void SearchPlaylist ( intf_thread_t *, char * );
108 static int  SubSearchPlaylist( intf_thread_t *, char *, int, int );
109
110 static void ManageSlider   ( intf_thread_t * );
111 static void ReadDir        ( intf_thread_t * );
112
113 static void start_color_and_pairs ( intf_thread_t * );
114
115 /*****************************************************************************
116  * Module descriptor
117  *****************************************************************************/
118
119 #define BROWSE_TEXT N_("Filebrowser starting point")
120 #define BROWSE_LONGTEXT N_( \
121     "This option allows you to specify the directory the ncurses filebrowser " \
122     "will show you initially.")
123
124 vlc_module_begin();
125     set_shortname( "Ncurses" );
126     set_description( N_("Ncurses interface") );
127     set_capability( "interface", 10 );
128     set_category( CAT_INTERFACE );
129     set_subcategory( SUBCAT_INTERFACE_MAIN );
130     set_callbacks( Open, Close );
131     add_shortcut( "curses" );
132     add_directory( "browse-dir", NULL, NULL, BROWSE_TEXT, BROWSE_LONGTEXT, false );
133 vlc_module_end();
134
135 /*****************************************************************************
136  * intf_sys_t: description and status of ncurses interface
137  *****************************************************************************/
138 enum
139 {
140     BOX_NONE,
141     BOX_HELP,
142     BOX_INFO,
143     BOX_LOG,
144     BOX_PLAYLIST,
145     BOX_SEARCH,
146     BOX_OPEN,
147     BOX_BROWSE,
148     BOX_META,
149     BOX_OBJECTS,
150     BOX_STATS
151 };
152 enum
153 {
154     C_DEFAULT = 0,
155     C_TITLE,
156     C_PLAYLIST_1,
157     C_PLAYLIST_2,
158     C_PLAYLIST_3,
159     C_BOX,
160     C_STATUS,
161     C_INFO,
162     C_ERROR,
163     C_WARNING,
164     C_DEBUG,
165     C_CATEGORY,
166     C_FOLDER
167 };
168 enum
169 {
170     VIEW_CATEGORY,
171     VIEW_ONELEVEL
172 };
173 struct dir_entry_t
174 {
175     bool  b_file;
176     char        *psz_path;
177 };
178 struct pl_item_t
179 {
180     playlist_item_t *p_item;
181     char            *psz_display;
182 };
183 struct intf_sys_t
184 {
185     input_thread_t *p_input;
186     playlist_t     *p_playlist;
187
188     bool      b_color;
189     bool      b_color_started;
190
191     float           f_slider;
192     float           f_slider_old;
193
194     WINDOW          *w;
195
196     int             i_box_type;
197     int             i_box_y;
198     int             i_box_lines;
199     int             i_box_lines_total;
200     int             i_box_start;
201
202     int             i_box_plidx;    /* Playlist index */
203     int             b_box_plidx_follow;
204     int             i_box_bidx;     /* browser index */
205
206     playlist_item_t *p_node;        /* current node */
207
208     int             b_box_cleared;
209
210     msg_subscription_t* p_sub;                  /* message bank subscription */
211
212     char            *psz_search_chain;          /* for playlist searching    */
213     char            *psz_old_search;            /* for searching next        */
214     int             i_before_search;
215
216     char            *psz_open_chain;
217 #ifndef HAVE_NCURSESW
218     char             psz_partial_keys[7];
219 #endif
220
221     char            *psz_current_dir;
222     int             i_dir_entries;
223     struct dir_entry_t  **pp_dir_entries;
224     bool      b_show_hidden_files;
225
226     int             i_current_view;             /* playlist view             */
227     struct pl_item_t    **pp_plist;
228     int             i_plist_entries;
229     bool      b_need_update;              /* for playlist view         */
230
231     int             i_verbose;                  /* stores verbosity level    */
232 };
233
234 static void DrawBox( WINDOW *win, int y, int x, int h, int w, const char *title, bool b_color );
235 static void DrawLine( WINDOW *win, int y, int x, int w );
236 static void DrawEmptyLine( WINDOW *win, int y, int x, int w );
237
238 /*****************************************************************************
239  * Open: initialize and create window
240  *****************************************************************************/
241 static int Open( vlc_object_t *p_this )
242 {
243     intf_thread_t *p_intf = (intf_thread_t *)p_this;
244     intf_sys_t    *p_sys;
245     vlc_value_t    val;
246
247     /* Allocate instance and initialize some members */
248     p_sys = p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
249     if( !p_sys )
250         return VLC_ENOMEM;
251     p_sys->p_node = NULL;
252     p_sys->p_input = NULL;
253     p_sys->f_slider = 0.0;
254     p_sys->f_slider_old = 0.0;
255     p_sys->i_box_type = BOX_PLAYLIST;
256     p_sys->i_box_lines = 0;
257     p_sys->i_box_start= 0;
258     p_sys->i_box_lines_total = 0;
259     p_sys->b_box_plidx_follow = true;
260     p_sys->b_box_cleared = false;
261     p_sys->i_box_plidx = 0;
262     p_sys->i_box_bidx = 0;
263     p_sys->p_sub = msg_Subscribe( p_intf );
264     p_sys->b_color = var_CreateGetBool( p_intf, "color" );
265     p_sys->b_color_started = false;
266
267 #ifndef HAVE_NCURSESW
268     memset( p_sys->psz_partial_keys, 0, sizeof( p_sys->psz_partial_keys ) );
269 #endif
270
271     /* Initialize the curses library */
272     p_sys->w = initscr();
273
274     if( p_sys->b_color )
275         start_color_and_pairs( p_intf );
276
277     keypad( p_sys->w, TRUE );
278     /* Don't do NL -> CR/NL */
279     nonl();
280     /* Take input chars one at a time */
281     cbreak();
282     /* Don't echo */
283     noecho();
284     /* Invisible cursor */
285     curs_set( 0 );
286     /* Non blocking wgetch() */
287     wtimeout( p_sys->w, 0 );
288
289     clear();
290
291     /* exported function */
292     p_intf->pf_run = Run;
293
294     /* Remember verbosity level */
295     var_Get( p_intf->p_libvlc, "verbose", &val );
296     p_sys->i_verbose = val.i_int;
297     /* Set quiet mode */
298     val.i_int = -1;
299     var_Set( p_intf->p_libvlc, "verbose", val );
300
301     /* Set defaul playlist view */
302     p_sys->i_current_view = VIEW_CATEGORY;
303     p_sys->pp_plist = NULL;
304     p_sys->i_plist_entries = 0;
305     p_sys->b_need_update = false;
306
307     /* Initialize search chain */
308     p_sys->psz_search_chain = (char *)malloc( SEARCH_CHAIN_SIZE + 1 );
309     p_sys->psz_old_search = NULL;
310     p_sys->i_before_search = 0;
311
312     /* Initialize open chain */
313     p_sys->psz_open_chain = (char *)malloc( OPEN_CHAIN_SIZE + 1 );
314
315     /* Initialize browser options */
316     var_Create( p_intf, "browse-dir", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
317     var_Get( p_intf, "browse-dir", &val);
318
319     if( val.psz_string && *val.psz_string )
320     {
321         p_sys->psz_current_dir = strdup( val.psz_string );
322     }
323     else
324     {
325         p_sys->psz_current_dir = strdup( config_GetHomeDir() );
326     }
327
328     free( val.psz_string );
329
330     p_sys->i_dir_entries = 0;
331     p_sys->pp_dir_entries = NULL;
332     p_sys->b_show_hidden_files = false;
333     ReadDir( p_intf );
334
335     return VLC_SUCCESS;
336 }
337
338 /*****************************************************************************
339  * Close: destroy interface window
340  *****************************************************************************/
341 static void Close( vlc_object_t *p_this )
342 {
343     intf_thread_t *p_intf = (intf_thread_t *)p_this;
344     intf_sys_t    *p_sys = p_intf->p_sys;
345
346     PlaylistDestroy( p_intf );
347
348     while( p_sys->i_dir_entries )
349     {
350         struct dir_entry_t *p_dir_entry = p_sys->pp_dir_entries[0];
351         free( p_dir_entry->psz_path );
352         REMOVE_ELEM( p_sys->pp_dir_entries, p_sys->i_dir_entries, 0 );
353         free( p_dir_entry );
354     }
355     p_sys->pp_dir_entries = NULL;
356
357     free( p_sys->psz_current_dir );
358     free( p_sys->psz_search_chain );
359     free( p_sys->psz_old_search );
360     free( p_sys->psz_open_chain );
361
362     if( p_sys->p_input )
363     {
364         vlc_object_release( p_sys->p_input );
365     }
366     pl_Release( p_intf );
367
368     /* Close the ncurses interface */
369     endwin();
370
371     msg_Unsubscribe( p_intf, p_sys->p_sub );
372
373     /* Restores initial verbose setting */
374     vlc_value_t val;
375     val.i_int = p_sys->i_verbose;
376     var_Set( p_intf->p_libvlc, "verbose", val );
377
378     /* Destroy structure */
379     free( p_sys );
380 }
381
382 /*****************************************************************************
383  * Run: ncurses thread
384  *****************************************************************************/
385 static void Run( intf_thread_t *p_intf )
386 {
387     intf_sys_t    *p_sys = p_intf->p_sys;
388     playlist_t    *p_playlist = pl_Hold( p_intf );
389     p_sys->p_playlist = p_playlist;
390
391     int i_key;
392     time_t t_last_refresh;
393     int canc = vlc_savecancel();
394
395     /*
396      * force drawing the interface for the first time
397      */
398     t_last_refresh = ( time( 0 ) - 1);
399     /*
400      * force building of the playlist array
401      */
402     PlaylistRebuild( p_intf );
403     var_AddCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
404     var_AddCallback( p_playlist, "item-append", PlaylistChanged, p_intf );
405     var_AddCallback( p_playlist, "item-change", PlaylistChanged, p_intf );
406
407     while( vlc_object_alive( p_intf ) )
408     {
409         msleep( INTF_IDLE_SLEEP );
410
411         /* Update the input */
412         PL_LOCK;
413         if( p_sys->p_input == NULL )
414         {
415             p_sys->p_input = playlist_CurrentInput( p_playlist );
416         }
417         else if( p_sys->p_input->b_dead )
418         {
419             vlc_object_release( p_sys->p_input );
420             p_sys->p_input = NULL;
421             p_sys->f_slider = p_sys->f_slider_old = 0.0;
422             p_sys->b_box_cleared = false;
423         }
424         PL_UNLOCK;
425
426         if( p_sys->b_box_plidx_follow && playlist_CurrentPlayingItem(p_playlist) )
427         {
428             FindIndex( p_intf );
429         }
430
431         while( ( i_key = wgetch( p_sys->w ) ) != -1 )
432         {
433             /*
434              * HandleKey returns 1 if the screen needs to be redrawn
435              */
436             if( HandleKey( p_intf, i_key ) )
437             {
438                 Redraw( p_intf, &t_last_refresh );
439             }
440         }
441         /* Hack */
442         if( p_sys->f_slider > 0.0001 && !p_sys->b_box_cleared )
443         {
444             clear();
445             Redraw( p_intf, &t_last_refresh );
446             p_sys->b_box_cleared = true;
447         }
448
449         /*
450          * redraw the screen every second
451          */
452         if( (time(0) - t_last_refresh) >= 1 )
453         {
454             ManageSlider( p_intf );
455             Redraw( p_intf, &t_last_refresh );
456         }
457     }
458     var_DelCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
459     var_DelCallback( p_playlist, "item-append", PlaylistChanged, p_intf );
460     var_DelCallback( p_playlist, "item-change", PlaylistChanged, p_intf );
461     vlc_restorecancel( canc );
462 }
463
464 /* following functions are local */
465 static void start_color_and_pairs( intf_thread_t *p_intf )
466 {
467     assert( p_intf->p_sys->b_color && !p_intf->p_sys->b_color_started );
468
469     if( !has_colors() )
470     {
471         p_intf->p_sys->b_color = false;
472         msg_Warn( p_intf, "Terminal doesn't support colors" );
473         return;
474     }
475
476     start_color();
477
478     /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
479
480     /* untested, in all my terminals, !can_change_color() --funman */
481     if( can_change_color() )
482         init_color( COLOR_YELLOW, 960, 500, 0 ); /* YELLOW -> ORANGE */
483
484     /* title */
485     init_pair( C_TITLE, COLOR_YELLOW, COLOR_BLACK );
486
487     /* jamaican playlist */
488     init_pair( C_PLAYLIST_1, COLOR_GREEN, COLOR_BLACK );
489     init_pair( C_PLAYLIST_2, COLOR_YELLOW, COLOR_BLACK );
490     init_pair( C_PLAYLIST_3, COLOR_RED, COLOR_BLACK );
491
492     /* used in DrawBox() */
493     init_pair( C_BOX, COLOR_CYAN, COLOR_BLACK );
494     /* Source, State, Position, Volume, Chapters, etc...*/
495     init_pair( C_STATUS, COLOR_BLUE, COLOR_BLACK );
496
497     /* VLC messages, keep the order from highest priority to lowest */
498
499     /* infos */
500     init_pair( C_INFO, COLOR_BLACK, COLOR_WHITE );
501     /* errors */
502     init_pair( C_ERROR, COLOR_RED, COLOR_BLACK );
503     /* warnings */
504     init_pair( C_WARNING, COLOR_YELLOW, COLOR_BLACK );
505 /* debug */
506     init_pair( C_DEBUG, COLOR_WHITE, COLOR_BLACK );
507
508     /* Category title (help, info, metadata) */
509     init_pair( C_CATEGORY, COLOR_MAGENTA, COLOR_BLACK );
510
511     /* Folder (BOX_BROWSE) */
512     init_pair( C_FOLDER, COLOR_RED, COLOR_BLACK );
513
514     p_intf->p_sys->b_color_started = true;
515 }
516
517 #ifndef HAVE_NCURSESW
518 static char *KeyToUTF8( int i_key, char *psz_part )
519 {
520     char *psz_utf8;
521     int len = strlen( psz_part );
522     if( len == 6 )
523     {
524         /* overflow error - should not happen */
525         memset( psz_part, 0, 6 );
526         return NULL;
527     }
528
529     psz_part[len] = (char)i_key;
530
531     psz_utf8 = FromLocaleDup( psz_part );
532
533     /* Ugly check for incomplete bytes sequences
534      * (in case of non-UTF8 multibyte local encoding) */
535     char *psz;
536     for( psz = psz_utf8; *psz; psz++ )
537         if( ( *psz == '?' ) && ( *psz_utf8 != '?' ) )
538         {
539             /* incomplete bytes sequence detected
540              * (VLC core inserted dummy question marks) */
541             free( psz_utf8 );
542             return NULL;
543         }
544
545     /* Check for incomplete UTF8 bytes sequence */
546     if( EnsureUTF8( psz_utf8 ) == NULL )
547     {
548         free( psz_utf8 );
549         return NULL;
550     }
551
552     memset( psz_part, 0, 6 );
553     return psz_utf8;
554 }
555 #endif
556
557 static inline int RemoveLastUTF8Entity( char *psz, int len )
558 {
559     while( len && ( (psz[--len] & 0xc0) == 0x80 ) );
560                        /* UTF8 continuation byte */
561
562     psz[len] = '\0';
563     return len;
564 }
565
566 static int HandleKey( intf_thread_t *p_intf, int i_key )
567 {
568     intf_sys_t *p_sys = p_intf->p_sys;
569     vlc_value_t val;
570
571     #define ReturnTrue \
572     do { \
573     vlc_object_release( p_playlist ); \
574     return 1; \
575     } while(0)
576
577     #define ReturnFalse \
578     do { \
579     vlc_object_release( p_playlist ); \
580     return 0; \
581     } while(0)
582
583     playlist_t *p_playlist = pl_Hold( p_intf );
584
585     if( p_sys->i_box_type == BOX_PLAYLIST )
586     {
587         int b_ret = true;
588         bool b_box_plidx_follow = false;
589
590         switch( i_key )
591         {
592             vlc_value_t val;
593             /* Playlist Settings */
594             case 'r':
595                 var_Get( p_playlist, "random", &val );
596                 val.b_bool = !val.b_bool;
597                 var_Set( p_playlist, "random", val );
598                 ReturnTrue;
599             case 'l':
600                 var_Get( p_playlist, "loop", &val );
601                 val.b_bool = !val.b_bool;
602                 var_Set( p_playlist, "loop", val );
603                 ReturnTrue;
604             case 'R':
605                 var_Get( p_playlist, "repeat", &val );
606                 val.b_bool = !val.b_bool;
607                 var_Set( p_playlist, "repeat", val );
608                 ReturnTrue;
609
610             /* Playlist sort */
611             case 'o':
612                 playlist_RecursiveNodeSort( p_playlist,
613                                             PlaylistGetRoot( p_intf ),
614                                             SORT_TITLE_NODES_FIRST, ORDER_NORMAL );
615                 p_sys->b_need_update = true;
616                 ReturnTrue;
617             case 'O':
618                 playlist_RecursiveNodeSort( p_playlist,
619                                             PlaylistGetRoot( p_intf ),
620                                             SORT_TITLE_NODES_FIRST, ORDER_REVERSE );
621                 p_sys->b_need_update = true;
622                 ReturnTrue;
623
624             /* Playlist view */
625             case 'v':
626                 switch( p_sys->i_current_view )
627                 {
628                     case VIEW_CATEGORY:
629                         p_sys->i_current_view = VIEW_ONELEVEL;
630                         break;
631                     default:
632                         p_sys->i_current_view = VIEW_CATEGORY;
633                 }
634                 //p_sys->b_need_update = true;
635                 PlaylistRebuild( p_intf );
636                 ReturnTrue;
637
638             /* Playlist navigation */
639             case 'g':
640                 FindIndex( p_intf );
641                 break;
642             case KEY_HOME:
643                 p_sys->i_box_plidx = 0;
644                 break;
645 #ifdef __FreeBSD__
646 /* workaround for FreeBSD + xterm:
647  * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
648             case KEY_SELECT:
649 #endif
650             case KEY_END:
651                 p_sys->i_box_plidx = p_playlist->items.i_size - 1;
652                 break;
653             case KEY_UP:
654                 p_sys->i_box_plidx--;
655                 break;
656             case KEY_DOWN:
657                 p_sys->i_box_plidx++;
658                 break;
659             case KEY_PPAGE:
660                 p_sys->i_box_plidx -= p_sys->i_box_lines;
661                 break;
662             case KEY_NPAGE:
663                 p_sys->i_box_plidx += p_sys->i_box_lines;
664                 break;
665             case 'D':
666             case KEY_BACKSPACE:
667             case 0x7f:
668             case KEY_DC:
669             {
670                 playlist_item_t *p_item;
671
672                 PL_LOCK;
673                 p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
674                 if( p_item->i_children == -1 )
675                 {
676                     playlist_DeleteFromInput( p_playlist,
677                                               p_item->p_input->i_id, pl_Locked );
678                 }
679                 else
680                 {
681                     playlist_NodeDelete( p_playlist, p_item,
682                                          true , false );
683                 }
684                 PL_UNLOCK;
685                 PlaylistRebuild( p_intf );
686                 break;
687             }
688
689             case KEY_ENTER:
690             case '\r':
691             case '\n':
692                 if( !p_sys->pp_plist[p_sys->i_box_plidx] )
693                 {
694                     b_ret = false;
695                     break;
696                 }
697                 if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
698                         == -1 )
699                 {
700                     playlist_item_t *p_item, *p_parent;
701                     p_item = p_parent =
702                             p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
703
704                     if( !p_parent )
705                         p_parent = p_playlist->p_root_onelevel;
706                     while( p_parent->p_parent )
707                         p_parent = p_parent->p_parent;
708                     playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
709                                       pl_Unlocked, p_parent, p_item );
710                 }
711                 else if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
712                         == 0 )
713                 {   /* We only want to set the current node */
714                     playlist_Stop( p_playlist );
715                     p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
716                 }
717                 else
718                 {
719                     p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
720                     playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked,
721                         p_sys->pp_plist[p_sys->i_box_plidx]->p_item, NULL );
722                 }
723                 b_box_plidx_follow = true;
724                 break;
725             default:
726                 b_ret = false;
727                 break;
728         }
729
730         if( b_ret )
731         {
732             int i_max = p_sys->i_plist_entries;
733             if( p_sys->i_box_plidx >= i_max ) p_sys->i_box_plidx = i_max - 1;
734             if( p_sys->i_box_plidx < 0 ) p_sys->i_box_plidx = 0;
735             if( PlaylistIsPlaying( p_intf,
736                     p_sys->pp_plist[p_sys->i_box_plidx]->p_item ) )
737                 b_box_plidx_follow = true;
738             p_sys->b_box_plidx_follow = b_box_plidx_follow;
739             ReturnTrue;
740         }
741     }
742     if( p_sys->i_box_type == BOX_BROWSE )
743     {
744         bool b_ret = true;
745         /* Browser navigation */
746         switch( i_key )
747         {
748             case KEY_HOME:
749                 p_sys->i_box_bidx = 0;
750                 break;
751 #ifdef __FreeBSD__
752             case KEY_SELECT:
753 #endif
754             case KEY_END:
755                 p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
756                 break;
757             case KEY_UP:
758                 p_sys->i_box_bidx--;
759                 break;
760             case KEY_DOWN:
761                 p_sys->i_box_bidx++;
762                 break;
763             case KEY_PPAGE:
764                 p_sys->i_box_bidx -= p_sys->i_box_lines;
765                 break;
766             case KEY_NPAGE:
767                 p_sys->i_box_bidx += p_sys->i_box_lines;
768                 break;
769             case '.': /* Toggle show hidden files */
770                 p_sys->b_show_hidden_files = ( p_sys->b_show_hidden_files ==
771                     true ? false : true );
772                 ReadDir( p_intf );
773                 break;
774
775             case KEY_ENTER:
776             case '\r':
777             case '\n':
778             case ' ':
779                 if( p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ' )
780                 {
781                     int i_size_entry = strlen( "directory://" ) +
782                                        strlen( p_sys->psz_current_dir ) +
783                                        strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
784                     char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);
785
786                     sprintf( psz_uri, "directory://%s/%s", p_sys->psz_current_dir, p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path );
787
788                     playlist_item_t *p_parent = p_sys->p_node;
789                     if( !p_parent )
790                     p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
791                     if( !p_parent )
792                         p_parent = p_playlist->p_local_onelevel;
793
794                     while( p_parent->p_parent && p_parent->p_parent->p_parent )
795                         p_parent = p_parent->p_parent;
796
797                     playlist_Add( p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
798                                   PLAYLIST_END,
799                                   p_parent->p_input ==
800                                     p_playlist->p_local_onelevel->p_input
801                                   , false );
802
803                     p_sys->i_box_type = BOX_PLAYLIST;
804                     free( psz_uri );
805                 }
806                 else
807                 {
808                     int i_size_entry = strlen( p_sys->psz_current_dir ) +
809                                        strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
810                     char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);
811
812                     sprintf( psz_uri, "%s/%s", p_sys->psz_current_dir, p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path );
813
814                     p_sys->psz_current_dir = strdup( psz_uri );
815                     ReadDir( p_intf );
816                     free( psz_uri );
817                 }
818                 break;
819             default:
820                 b_ret = false;
821                 break;
822         }
823         if( b_ret )
824         {
825             if( p_sys->i_box_bidx >= p_sys->i_dir_entries ) p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
826             if( p_sys->i_box_bidx < 0 ) p_sys->i_box_bidx = 0;
827             ReturnTrue;
828         }
829     }
830     else if( p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
831              p_sys->i_box_type == BOX_META || p_sys->i_box_type == BOX_STATS ||
832              p_sys->i_box_type == BOX_OBJECTS )
833     {
834         switch( i_key )
835         {
836             case KEY_HOME:
837                 p_sys->i_box_start = 0;
838                 ReturnTrue;
839 #ifdef __FreeBSD__
840             case KEY_SELECT:
841 #endif
842             case KEY_END:
843                 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
844                 ReturnTrue;
845             case KEY_UP:
846                 if( p_sys->i_box_start > 0 ) p_sys->i_box_start--;
847                 ReturnTrue;
848             case KEY_DOWN:
849                 if( p_sys->i_box_start < p_sys->i_box_lines_total - 1 )
850                 {
851                     p_sys->i_box_start++;
852                 }
853                 ReturnTrue;
854             case KEY_PPAGE:
855                 p_sys->i_box_start -= p_sys->i_box_lines;
856                 if( p_sys->i_box_start < 0 ) p_sys->i_box_start = 0;
857                 ReturnTrue;
858             case KEY_NPAGE:
859                 p_sys->i_box_start += p_sys->i_box_lines;
860                 if( p_sys->i_box_start >= p_sys->i_box_lines_total )
861                 {
862                     p_sys->i_box_start = p_sys->i_box_lines_total - 1;
863                 }
864                 ReturnTrue;
865             default:
866                 break;
867         }
868     }
869     else if( p_sys->i_box_type == BOX_NONE )
870     {
871         switch( i_key )
872         {
873             case KEY_HOME:
874                 p_sys->f_slider = 0;
875                 ManageSlider( p_intf );
876                 ReturnTrue;
877 #ifdef __FreeBSD__
878             case KEY_SELECT:
879 #endif
880             case KEY_END:
881                 p_sys->f_slider = 99.9;
882                 ManageSlider( p_intf );
883                 ReturnTrue;
884             case KEY_UP:
885                 p_sys->f_slider += 5.0;
<