]> git.sesse.net Git - vlc/blob - modules/gui/ncurses.c
ncurses: simplify metadata printing
[vlc] / modules / gui / ncurses.c
1 /*****************************************************************************
2  * ncurses.c : NCurses interface for vlc
3  *****************************************************************************
4  * Copyright © 2001-2010 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 #include <vlc_fs.h>
60
61 #include <assert.h>
62
63 #ifdef HAVE_SYS_STAT_H
64 #   include <sys/stat.h>
65 #endif
66
67 #define SEARCH_CHAIN_SIZE 20
68 #define OPEN_CHAIN_SIZE 50
69
70 /*****************************************************************************
71  * Local prototypes.
72  *****************************************************************************/
73 static int  Open           (vlc_object_t *);
74 static void Close          (vlc_object_t *);
75
76 /*****************************************************************************
77  * Module descriptor
78  *****************************************************************************/
79
80 #define BROWSE_TEXT N_("Filebrowser starting point")
81 #define BROWSE_LONGTEXT N_(\
82     "This option allows you to specify the directory the ncurses filebrowser " \
83     "will show you initially.")
84
85 vlc_module_begin ()
86     set_shortname("Ncurses")
87     set_description(N_("Ncurses interface"))
88     set_capability("interface", 10)
89     set_category(CAT_INTERFACE)
90     set_subcategory(SUBCAT_INTERFACE_MAIN)
91     set_callbacks(Open, Close)
92     add_shortcut("curses")
93     add_directory("browse-dir", NULL, BROWSE_TEXT, BROWSE_LONGTEXT, false)
94 vlc_module_end ()
95
96 /*****************************************************************************
97  * intf_sys_t: description and status of ncurses interface
98  *****************************************************************************/
99 enum
100 {
101     BOX_NONE,
102     BOX_HELP,
103     BOX_INFO,
104 //  BOX_LOG,
105     BOX_PLAYLIST,
106     BOX_SEARCH,
107     BOX_OPEN,
108     BOX_BROWSE,
109     BOX_META,
110     BOX_OBJECTS,
111     BOX_STATS
112 };
113
114 enum
115 {
116     C_DEFAULT = 0,
117     C_TITLE,
118     C_PLAYLIST_1,
119     C_PLAYLIST_2,
120     C_PLAYLIST_3,
121     C_BOX,
122     C_STATUS,
123 #if 0
124     C_INFO,
125     C_ERROR,
126     C_WARNING,
127     C_DEBUG,
128 #endif
129     C_CATEGORY,
130     C_FOLDER,
131     /* XXX: new elements here ! */
132
133     C_MAX
134 };
135
136 /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
137 static const struct { short f; short b; } color_pairs[] =
138 {
139     /* element */       /* foreground*/ /* background*/
140     [C_TITLE]       = { COLOR_YELLOW,   COLOR_BLACK },
141
142     /* jamaican playlist, for rastafari sisters & brothers! */
143     [C_PLAYLIST_1]  = { COLOR_GREEN,    COLOR_BLACK },
144     [C_PLAYLIST_2]  = { COLOR_YELLOW,   COLOR_BLACK },
145     [C_PLAYLIST_3]  = { COLOR_RED,      COLOR_BLACK },
146
147     /* used in DrawBox() */
148     [C_BOX]         = { COLOR_CYAN,     COLOR_BLACK },
149     /* Source: State, Position, Volume, Chapters, etc...*/
150     [C_STATUS]      = { COLOR_BLUE,     COLOR_BLACK },
151
152 #if 0
153     /* VLC messages, keep the order from highest priority to lowest */
154     [C_INFO]        = { COLOR_BLACK,    COLOR_WHITE },
155     [C_ERROR]       = { COLOR_RED,      COLOR_BLACK },
156     [C_WARNING]     = { COLOR_YELLOW,   COLOR_BLACK },
157     [C_DEBUG]       = { COLOR_WHITE,    COLOR_BLACK },
158 #endif
159     /* Category title: help, info, metadata */
160     [C_CATEGORY]    = { COLOR_MAGENTA,  COLOR_BLACK },
161     /* Folder (BOX_BROWSE) */
162     [C_FOLDER]      = { COLOR_RED,      COLOR_BLACK },
163 };
164
165 struct dir_entry_t
166 {
167     bool        b_file;
168     char        *psz_path;
169 };
170
171 struct pl_item_t
172 {
173     playlist_item_t *p_item;
174     char            *psz_display;
175 };
176
177 struct intf_sys_t
178 {
179     input_thread_t *p_input;
180
181     bool            b_color;
182     bool            b_color_started;
183
184     float           f_slider;
185     float           f_slider_old;
186
187     WINDOW          *w;
188
189     int             i_box_type;
190     int             i_box_y;
191     int             i_box_lines;
192     int             i_box_lines_total;
193     int             i_box_start;
194
195     int             i_box_plidx;    /* Playlist index */
196     int             b_box_plidx_follow;
197     int             i_box_bidx;     /* browser index */
198
199     playlist_item_t *p_node;        /* current node */
200
201     int             b_box_cleared;
202
203 //  msg_subscription_t* p_sub;                  /* message bank subscription */
204
205     char            *psz_search_chain;          /* for playlist searching    */
206     char            *psz_old_search;            /* for searching next        */
207     int             i_before_search;
208
209     char            *psz_open_chain;
210 #ifndef HAVE_NCURSESW
211     char             psz_partial_keys[7];
212 #endif
213
214     char            *psz_current_dir;
215     int             i_dir_entries;
216     struct dir_entry_t  **pp_dir_entries;
217     bool            b_show_hidden_files;
218
219     bool            category_view;
220     struct pl_item_t    **pp_plist;
221     int             i_plist_entries;
222     bool            b_need_update;              /* for playlist view         */
223 };
224
225 /*****************************************************************************
226  * Directories
227  *****************************************************************************/
228
229 static void DirsDestroy(intf_sys_t *p_sys)
230 {
231     while (p_sys->i_dir_entries)
232     {
233         struct dir_entry_t *p_dir_entry;
234         p_dir_entry = p_sys->pp_dir_entries[--p_sys->i_dir_entries];
235         free(p_dir_entry->psz_path);
236         free(p_dir_entry);
237     }
238     free(p_sys->pp_dir_entries);
239     p_sys->pp_dir_entries = NULL;
240 }
241
242 static int comp_dir_entries(const void *pp_dir_entry1, const void *pp_dir_entry2)
243 {
244     struct dir_entry_t *p_dir_entry1 = *(struct dir_entry_t**)pp_dir_entry1;
245     struct dir_entry_t *p_dir_entry2 = *(struct dir_entry_t**)pp_dir_entry2;
246
247     if (p_dir_entry1->b_file == p_dir_entry2->b_file)
248         return strcasecmp(p_dir_entry1->psz_path, p_dir_entry2->psz_path);
249
250     return p_dir_entry1->b_file ? 1 : -1;
251 }
252
253 static void ReadDir(intf_thread_t *p_intf)
254 {
255     intf_sys_t *p_sys = p_intf->p_sys;
256     DIR *p_current_dir;
257
258     if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
259     {
260         msg_Dbg(p_intf, "no current dir set");
261         return;
262     }
263
264     char *psz_entry;
265
266     /* Open the dir */
267     p_current_dir = vlc_opendir(p_sys->psz_current_dir);
268
269     if (!p_current_dir)
270     {
271         /* something went bad, get out of here ! */
272         msg_Warn(p_intf, "cannot open directory `%s' (%m)",
273                   p_sys->psz_current_dir);
274         return;
275     }
276
277     /* Clean the old shit */
278     DirsDestroy(p_sys);
279
280     /* while we still have entries in the directory */
281     while ((psz_entry = vlc_readdir(p_current_dir)))
282     {
283 #if defined(S_ISDIR)
284         struct stat stat_data;
285 #endif
286         struct dir_entry_t *p_dir_entry;
287         char *psz_uri = NULL;
288
289         if (!p_sys->b_show_hidden_files)
290             if (*psz_entry == '.' && strcmp(psz_entry, ".."))
291                 goto next;
292
293         if (asprintf(&psz_uri, "%s/%s", p_sys->psz_current_dir, psz_entry) == -1)
294         {
295             psz_uri = NULL;
296             goto next;
297         }
298
299         if (!(p_dir_entry = malloc(sizeof *p_dir_entry)))
300             goto next;
301
302         p_dir_entry->b_file =
303 #if defined(S_ISDIR)
304             vlc_stat(psz_uri, &stat_data) || !S_ISDIR(stat_data.st_mode)
305 /*#elif defined(DT_DIR)
306             !(p_dir_content->d_type & DT_DIR)*/
307 #else
308             false
309 #endif
310         ;
311
312         p_dir_entry->psz_path = strdup(psz_entry);
313         INSERT_ELEM(p_sys->pp_dir_entries, p_sys->i_dir_entries,
314              p_sys->i_dir_entries, p_dir_entry);
315
316 next:
317         free(psz_uri);
318         free(psz_entry);
319     }
320
321     /* Sort */
322     qsort(p_sys->pp_dir_entries, p_sys->i_dir_entries,
323            sizeof(struct dir_entry_t*), &comp_dir_entries);
324
325     closedir(p_current_dir);
326 }
327
328 /*****************************************************************************
329  * Playlist
330  *****************************************************************************/
331
332 static void PlaylistDestroy(intf_sys_t *p_sys)
333 {
334     while (p_sys->i_plist_entries)
335     {
336         struct pl_item_t *p_pl_item = p_sys->pp_plist[--p_sys->i_plist_entries];
337         free(p_pl_item->psz_display);
338         free(p_pl_item);
339     }
340     free(p_sys->pp_plist);
341     p_sys->pp_plist = NULL;
342 }
343
344 static inline playlist_item_t *PlaylistGetRoot(intf_thread_t *p_intf)
345 {
346     playlist_t *p_playlist = pl_Get(p_intf);
347     return p_intf->p_sys->category_view ?
348         p_playlist->p_root_category :
349         p_playlist->p_root_onelevel;
350 }
351
352 static bool PlaylistAddChild(intf_sys_t *p_sys, playlist_item_t *p_child,
353                              const char *c, const char d)
354 {
355     int ret;
356     char *psz_name = input_item_GetTitleFbName(p_child->p_input);
357     struct pl_item_t *p_pl_item = malloc(sizeof *p_pl_item);
358
359     if (!psz_name || !p_pl_item)
360         goto error;
361
362     p_pl_item->p_item = p_child;
363
364     if (c && *c)
365         ret = asprintf(&p_pl_item->psz_display, "%s%c-%s", c, d, psz_name);
366     else
367         ret = asprintf(&p_pl_item->psz_display, " %s", psz_name);
368
369     free(psz_name);
370     psz_name = NULL;
371
372     if (ret == -1)
373         goto error;
374
375     INSERT_ELEM(p_sys->pp_plist, p_sys->i_plist_entries,
376                  p_sys->i_plist_entries, p_pl_item);
377
378     return true;
379
380 error:
381     free(psz_name);
382     free(p_pl_item);
383     return false;
384 }
385
386 static void PlaylistAddNode(intf_sys_t *p_sys, playlist_item_t *p_node,
387                             const char *c)
388 {
389     for(int k = 0; k < p_node->i_children; k++)
390     {
391         playlist_item_t *p_child = p_node->pp_children[k];
392         char d = k == p_node->i_children - 1 ? '`' : '|';
393         if(!PlaylistAddChild(p_sys, p_child, c, d))
394             return;
395
396         if (p_child->i_children <= 0)
397             continue;
398
399         if (*c)
400         {
401             char *psz_tmp;
402             if (asprintf(&psz_tmp, "%s%c ", c,
403                      k == p_node->i_children - 1 ? ' ' : '|') == -1)
404                 return;
405             PlaylistAddNode(p_sys, p_child, psz_tmp);
406             free(psz_tmp);
407         }
408         else
409             PlaylistAddNode(p_sys, p_child, " ");
410     }
411 }
412
413 static void PlaylistRebuild(intf_thread_t *p_intf)
414 {
415     intf_sys_t *p_sys = p_intf->p_sys;
416     playlist_t *p_playlist = pl_Get(p_intf);
417
418     PL_LOCK;
419
420     PlaylistDestroy(p_sys);
421     PlaylistAddNode(p_sys, PlaylistGetRoot(p_intf), "");
422     p_sys->b_need_update = false;
423
424     PL_UNLOCK;
425 }
426
427 static int PlaylistChanged(vlc_object_t *p_this, const char *psz_variable,
428                             vlc_value_t oval, vlc_value_t nval, void *param)
429 {
430     VLC_UNUSED(p_this); VLC_UNUSED(psz_variable);
431     VLC_UNUSED(oval); VLC_UNUSED(nval);
432
433     intf_thread_t *p_intf   = (intf_thread_t *)param;
434     intf_sys_t *p_sys       = p_intf->p_sys;
435     playlist_item_t *p_node = playlist_CurrentPlayingItem(pl_Get(p_intf));
436
437     p_sys->b_need_update = true;
438     p_sys->p_node = p_node ? p_node->p_parent : NULL;
439
440     return VLC_SUCCESS;
441 }
442
443 /* Playlist suxx */
444 /* This function have to be called with the playlist locked */
445 static inline bool PlaylistIsPlaying(playlist_t *p_playlist,
446                                      playlist_item_t *p_item)
447 {
448     playlist_item_t *p_played_item = playlist_CurrentPlayingItem(p_playlist);
449     return p_item                && p_played_item
450         && p_item->p_input       && p_played_item->p_input
451         && p_item->p_input->i_id == p_played_item->p_input->i_id;
452 }
453
454 static int SubSearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring,
455                               int i_start, int i_stop)
456 {
457     for(int i = i_start + 1; i < i_stop; i++)
458         if (strcasestr(p_sys->pp_plist[i]->psz_display, psz_searchstring))
459             return i;
460
461     return -1;
462 }
463
464 static void SearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring)
465 {
466     int i_item, i_first = p_sys->i_before_search;
467
468     if (i_first < 0)
469         i_first = 0;
470
471     if (!psz_searchstring || !*psz_searchstring)
472     {
473         p_sys->i_box_plidx = p_sys->i_before_search;
474         return;
475     }
476
477     i_item = SubSearchPlaylist(p_sys, psz_searchstring, i_first + 1,
478                                p_sys->i_plist_entries);
479     if (i_item < 0)
480         i_item = SubSearchPlaylist(p_sys, psz_searchstring, 0, i_first);
481
482     if (i_item > 0)
483         p_sys->i_box_plidx = i_item;
484 }
485
486 static inline bool IsIndex(intf_sys_t *p_sys, playlist_t *p_playlist, int i)
487 {
488     playlist_item_t *p_item = p_sys->pp_plist[i]->p_item;
489     return (p_item->i_children == 0 && p_item == p_sys->p_node) ||
490             PlaylistIsPlaying(p_playlist, p_item);
491 }
492
493 static void FindIndex(intf_sys_t *p_sys, playlist_t *p_playlist, bool locked)
494 {
495     int plidx = p_sys->i_box_plidx;
496     if (!locked)
497         PL_LOCK;
498
499     if (plidx < 0 || plidx >= p_sys->i_plist_entries ||
500         !IsIndex(p_sys, p_playlist, plidx))
501     {
502         for(int i = 0; i < p_sys->i_plist_entries; i++)
503             if (IsIndex(p_sys, p_playlist, i))
504             {
505                 p_sys->i_box_plidx = i;
506                 break;
507             }
508     }
509
510     if (!locked)
511         PL_UNLOCK;
512 }
513
514 /****************************************************************************
515  * Drawing
516  ****************************************************************************/
517
518 static void start_color_and_pairs(intf_thread_t *p_intf)
519 {
520     assert(p_intf->p_sys->b_color && !p_intf->p_sys->b_color_started);
521
522     if (!has_colors())
523     {
524         p_intf->p_sys->b_color = false;
525         msg_Warn(p_intf, "Terminal doesn't support colors");
526         return;
527     }
528
529     start_color();
530     for(int i = C_DEFAULT + 1; i < C_MAX; i++)
531         init_pair(i, color_pairs[i].f, color_pairs[i].b);
532
533     /* untested, in all my terminals, !can_change_color() --funman */
534     if (can_change_color())
535         init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
536
537     p_intf->p_sys->b_color_started = true;
538 }
539
540 static void DrawBox(WINDOW *win, int y, int x, int h, int w, const char *title, bool b_color)
541 {
542     int i_len;
543
544     if (w <= 3 || h <= 2)
545         return;
546
547     if (b_color)
548         wcolor_set(win, C_BOX, NULL);
549     if (!title) title = "";
550     i_len = strlen(title);
551
552     if (i_len > w - 2)
553         i_len = w - 2;
554
555     mvwaddch(win, y, x,    ACS_ULCORNER);
556     mvwhline(win, y, x+1,  ACS_HLINE, (w-i_len-2)/2);
557     mvwprintw(win,y, x+1+(w-i_len-2)/2, "%s", title);
558     mvwhline(win, y, x+(w-i_len)/2+i_len,  ACS_HLINE, w - 1 - ((w-i_len)/2+i_len));
559     mvwaddch(win, y, x+w-1,ACS_URCORNER);
560
561     for(int i = 0; i < h-2; i++)
562     {
563         mvwaddch(win, y+i+1, x,     ACS_VLINE);
564         mvwaddch(win, y+i+1, x+w-1, ACS_VLINE);
565     }
566
567     mvwaddch(win, y+h-1, x,     ACS_LLCORNER);
568     mvwhline(win, y+h-1, x+1,   ACS_HLINE, w - 2);
569     mvwaddch(win, y+h-1, x+w-1, ACS_LRCORNER);
570     if (b_color)
571         wcolor_set(win, C_DEFAULT, NULL);
572 }
573
574 static void DrawEmptyLine(WINDOW *win, int y, int x, int w)
575 {
576     if (w <= 0) return;
577
578     mvwhline(win, y, x, ' ', w);
579 }
580
581 static void DrawLine(WINDOW *win, int y, int x, int w)
582 {
583     if (w <= 0) return;
584
585     attrset(A_REVERSE);
586     mvwhline(win, y, x, ' ', w);
587     attroff(A_REVERSE);
588 }
589
590 static void mvnprintw(int y, int x, int w, const char *p_fmt, ...)
591 {
592     va_list  vl_args;
593     char    *p_buf;
594     int      i_len;
595
596     if (w <= 0)
597         return;
598
599     va_start(vl_args, p_fmt);
600     if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
601         return;
602     va_end(vl_args);
603
604     i_len = strlen(p_buf);
605
606 #ifdef HAVE_NCURSESW
607     wchar_t psz_wide[i_len + 1];
608
609     EnsureUTF8(p_buf);
610     size_t i_char_len = mbstowcs(psz_wide, p_buf, i_len);
611
612     size_t i_width; /* number of columns */
613
614     if (i_char_len == (size_t)-1) /* an invalid character was encountered */
615     {
616         free(p_buf);
617         return;
618     }
619
620     i_width = wcswidth(psz_wide, i_char_len);
621     if (i_width == (size_t)-1)
622     {
623         /* a non printable character was encountered */
624         i_width = 0;
625         for(unsigned i = 0 ; i < i_char_len ; i++)
626         {
627             int i_cwidth = wcwidth(psz_wide[i]);
628             if (i_cwidth != -1)
629                 i_width += i_cwidth;
630         }
631     }
632
633     if (i_width <= (size_t)w)
634     {
635         mvprintw(y, x, "%s", p_buf);
636         mvhline(y, x + i_width, ' ', w - i_width);
637         free(p_buf);
638         return;
639     }
640
641     int i_total_width = 0;
642     int i = 0;
643     while (i_total_width < w)
644     {
645         i_total_width += wcwidth(psz_wide[i]);
646         if (w > 7 && i_total_width >= w/2)
647         {
648             psz_wide[i  ] = '.';
649             psz_wide[i+1] = '.';
650             i_total_width -= wcwidth(psz_wide[i]) - 2;
651             if (i > 0)
652             {
653                 /* we require this check only if at least one character
654                  * 4 or more columns wide exists (which i doubt) */
655                 psz_wide[i-1] = '.';
656                 i_total_width -= wcwidth(psz_wide[i-1]) - 1;
657             }
658
659             /* find the widest string */
660             int j, i_2nd_width = 0;
661             for(j = i_char_len - 1; i_2nd_width < w - i_total_width; j--)
662                 i_2nd_width += wcwidth(psz_wide[j]);
663
664             /* we already have i_total_width columns filled, and we can't
665              * have more than w columns */
666             if (i_2nd_width > w - i_total_width)
667                 j++;
668
669             wmemmove(&psz_wide[i+2], &psz_wide[j+1], i_char_len - j - 1);
670             psz_wide[i + 2 + i_char_len - j - 1] = '\0';
671             break;
672         }
673         i++;
674     }
675     if (w <= 7) /* we don't add the '...' else we lose too much chars */
676         psz_wide[i] = '\0';
677
678     size_t i_wlen = wcslen(psz_wide) * 6 + 1; /* worst case */
679     char psz_ellipsized[i_wlen];
680     wcstombs(psz_ellipsized, psz_wide, i_wlen);
681     mvprintw(y, x, "%s", psz_ellipsized);
682
683 #else
684     if (i_len > w)
685     {
686         int i_cut = i_len - w;
687         int x1 = i_len/2 - i_cut/2;
688         int x2 = x1 + i_cut;
689
690         if (i_len > x2)
691             memmove(&p_buf[x1], &p_buf[x2], i_len - x2);
692
693         p_buf[w] = '\0';
694         if (w > 7)
695         {
696             p_buf[w/2-1] = '.';
697             p_buf[w/2  ] = '.';
698             p_buf[w/2+1] = '.';
699         }
700     }
701
702     char *psz_local = ToLocale(p_buf);
703     mvprintw(y, x, "%s", psz_local);
704
705     if (i_len > w)
706         mvhline(y, x + i_len, ' ', w - i_len);
707
708     LocaleFree(psz_local);
709 #endif
710
711     free(p_buf);
712 }
713
714 static void MainBoxWrite(intf_thread_t *p_intf, int l, int x, const char *p_fmt, ...)
715 {
716     intf_sys_t  *p_sys = p_intf->p_sys;
717     va_list      vl_args;
718     char        *p_buf;
719
720     if (l < p_sys->i_box_start || l - p_sys->i_box_start >= p_sys->i_box_lines)
721         return;
722
723     va_start(vl_args, p_fmt);
724     if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
725         return;
726     va_end(vl_args);
727
728     mvnprintw(p_sys->i_box_y + l - p_sys->i_box_start, x, COLS - x - 1, "%s", p_buf);
729     free(p_buf);
730 }
731
732 static void DumpObject(intf_thread_t *p_intf, int *l, vlc_object_t *p_obj, int i_level)
733 {
734     char *psz_name = vlc_object_get_name(p_obj);
735     if (psz_name)
736     {
737         MainBoxWrite(p_intf, (*l)++, 1 + 2 * i_level, "%s \"%s\" (%p)",
738                 p_obj->psz_object_type, psz_name, p_obj);
739         free(psz_name);
740     }
741     else
742         MainBoxWrite(p_intf, (*l)++, 1 + 2 * i_level, "%s (%o)",
743                 p_obj->psz_object_type, p_obj);
744
745     vlc_list_t *list = vlc_list_children(p_obj);
746     for(int i = 0; i < list->i_count ; i++)
747     {
748         MainBoxWrite(p_intf, *l, 1 + 2 * i_level,
749             i == list->i_count - 1 ? "`-" : "|-");
750         DumpObject(p_intf, l, list->p_values[i].p_object, i_level + 1);
751     }
752     vlc_list_release(list);
753 }
754
755 static void Redraw(intf_thread_t *p_intf, time_t *t_last_refresh)
756 {
757     intf_sys_t     *p_sys = p_intf->p_sys;
758     input_thread_t *p_input = p_sys->p_input;
759     playlist_t     *p_playlist = pl_Get(p_intf);
760     int y = 0;
761     int h;
762     int y_end;
763
764     /* Title */
765     attrset(A_REVERSE);
766     int i_len = sizeof "VLC media player "PACKAGE_VERSION - 1;
767     int mid = (COLS - i_len) / 2;
768     if (mid < 0)
769         mid = 0;
770     int i_size = (COLS > i_len + 1) ? COLS : i_len + 1;
771     char psz_title[i_size];
772     memset(psz_title, ' ', mid);
773     if (p_sys->b_color)
774         wcolor_set(p_sys->w, C_TITLE, NULL);
775     strlcpy(&psz_title[mid], "VLC media player "PACKAGE_VERSION, i_size);
776     mvnprintw(y, 0, COLS, "%s", psz_title);
777     attroff(A_REVERSE);
778     y += 2;
779
780     if (p_sys->b_color)
781         wcolor_set(p_sys->w, C_STATUS, NULL);
782
783     /* Infos */
784     char *psz_state;
785     if (asprintf(&psz_state, "%s%s%s",
786             var_GetBool(p_playlist, "repeat") ? _("[Repeat] ") : "",
787             var_GetBool(p_playlist, "random") ? _("[Random] ") : "",
788             var_GetBool(p_playlist, "loop") ? _("[Loop]") : "") == -1)
789         psz_state = NULL;
790
791     if (p_input && !p_input->b_dead)
792     {
793         char buf1[MSTRTIME_MAX_SIZE];
794         char buf2[MSTRTIME_MAX_SIZE];
795         vlc_value_t val;
796
797         /* Source */
798         char *psz_uri = input_item_GetURI(input_GetItem(p_input));
799         mvnprintw(y++, 0, COLS, _(" Source   : %s"), psz_uri);
800         free(psz_uri);
801
802         /* State */
803         var_Get(p_input, "state", &val);
804         if (val.i_int == PLAYING_S)
805             mvnprintw(y++, 0, COLS, _(" State    : Playing %s"), psz_state);
806         else if (val.i_int == OPENING_S)
807             mvnprintw(y++, 0, COLS, _(" State    : Opening/Connecting %s"), psz_state);
808         else if (val.i_int == PAUSE_S)
809             mvnprintw(y++, 0, COLS, _(" State    : Paused %s"), psz_state);
810
811         if (val.i_int == INIT_S || val.i_int == END_S)
812             y += 2;
813         else
814         {
815             audio_volume_t i_volume;
816
817             /* Position */
818             var_Get(p_input, "time", &val);
819             secstotimestr(buf1, val.i_time / CLOCK_FREQ);
820
821             var_Get(p_input, "length", &val);
822             secstotimestr(buf2, val.i_time / CLOCK_FREQ);
823
824             mvnprintw(y++, 0, COLS, _(" Position : %s/%s (%.2f%%)"), buf1, buf2, p_sys->f_slider);
825
826             /* Volume */
827             aout_VolumeGet(p_playlist, &i_volume);
828             mvnprintw(y++, 0, COLS, _(" Volume   : %i%%"), i_volume*200/AOUT_VOLUME_MAX);
829
830             /* Title */
831             if (!var_Get(p_input, "title", &val))
832             {
833                 int i_title_count = var_CountChoices(p_input, "title");
834                 if (i_title_count > 0)
835                     mvnprintw(y++, 0, COLS, _(" Title    : %"PRId64"/%d"),
836                                val.i_int, i_title_count);
837             }
838
839             /* Chapter */
840             if (!var_Get(p_input, "chapter", &val))
841             {
842                 int i_chapter_count = var_CountChoices(p_input, "chapter");
843                 if (i_chapter_count > 0)
844                     mvnprintw(y++, 0, COLS, _(" Chapter  : %"PRId64"/%d"),
845                                val.i_int, i_chapter_count);
846             }
847         }
848     }
849     else
850     {
851         mvnprintw(y++, 0, COLS, _(" Source: <no current item> %s"), psz_state);
852         DrawEmptyLine(p_sys->w, y++, 0, COLS);
853         mvnprintw(y++, 0, COLS, _(" [ h for help ]"));
854         DrawEmptyLine(p_sys->w, y++, 0, COLS);
855     }
856     free(psz_state);
857     if (p_sys->b_color)
858         wcolor_set(p_sys->w, C_DEFAULT, NULL);
859
860     DrawBox(p_sys->w, y, 0, 3, COLS, "", p_sys->b_color);
861     DrawEmptyLine(p_sys->w, y+1, 1, COLS-2);
862     DrawLine(p_sys->w, y+1, 1, (int)(p_intf->p_sys->f_slider/100.0 * (COLS -2)));
863     y += 3;
864
865     p_sys->i_box_y = y + 1;
866     p_sys->i_box_lines = LINES - y - 2;
867
868     h = LINES - y;
869     y_end = y + h - 1;
870
871     if (p_sys->i_box_type == BOX_HELP)
872     {
873         /* Help box */
874         int l = 0;
875         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Help "), p_sys->b_color);
876
877         if (p_sys->b_color)
878             wcolor_set(p_sys->w, C_CATEGORY, NULL);
879         MainBoxWrite(p_intf, l++, 1, _("[Display]"));
880         if (p_sys->b_color)
881             wcolor_set(p_sys->w, C_DEFAULT, NULL);
882         MainBoxWrite(p_intf, l++, 1, _("     h,H         Show/Hide help box"));
883         MainBoxWrite(p_intf, l++, 1, _("     i           Show/Hide info box"));
884         MainBoxWrite(p_intf, l++, 1, _("     m           Show/Hide metadata box"));
885 //      MainBoxWrite(p_intf, l++, 1, _("     L           Show/Hide messages box"));
886         MainBoxWrite(p_intf, l++, 1, _("     P           Show/Hide playlist box"));
887         MainBoxWrite(p_intf, l++, 1, _("     B           Show/Hide filebrowser"));
888         MainBoxWrite(p_intf, l++, 1, _("     x           Show/Hide objects box"));
889         MainBoxWrite(p_intf, l++, 1, _("     S           Show/Hide statistics box"));
890         MainBoxWrite(p_intf, l++, 1, _("     c           Switch color on/off"));
891         MainBoxWrite(p_intf, l++, 1, _("     Esc         Close Add/Search entry"));
892         MainBoxWrite(p_intf, l++, 1, "");
893
894         if (p_sys->b_color)
895             wcolor_set(p_sys->w, C_CATEGORY, NULL);
896         MainBoxWrite(p_intf, l++, 1, _("[Global]"));
897         if (p_sys->b_color)
898             wcolor_set(p_sys->w, C_DEFAULT, NULL);
899         MainBoxWrite(p_intf, l++, 1, _("     q, Q, Esc   Quit"));
900         MainBoxWrite(p_intf, l++, 1, _("     s           Stop"));
901         MainBoxWrite(p_intf, l++, 1, _("     <space>     Pause/Play"));
902         MainBoxWrite(p_intf, l++, 1, _("     f           Toggle Fullscreen"));
903         MainBoxWrite(p_intf, l++, 1, _("     n, p        Next/Previous playlist item"));
904         MainBoxWrite(p_intf, l++, 1, _("     [, ]        Next/Previous title"));
905         MainBoxWrite(p_intf, l++, 1, _("     <, >        Next/Previous chapter"));
906         MainBoxWrite(p_intf, l++, 1, _("     <right>     Seek +1%%"));
907         MainBoxWrite(p_intf, l++, 1, _("     <left>      Seek -1%%"));
908         MainBoxWrite(p_intf, l++, 1, _("     a           Volume Up"));
909         MainBoxWrite(p_intf, l++, 1, _("     z           Volume Down"));
910         MainBoxWrite(p_intf, l++, 1, "");
911
912         if (p_sys->b_color)
913             wcolor_set(p_sys->w, C_CATEGORY, NULL);
914         MainBoxWrite(p_intf, l++, 1, _("[Playlist]"));
915         if (p_sys->b_color)
916             wcolor_set(p_sys->w, C_DEFAULT, NULL);
917         MainBoxWrite(p_intf, l++, 1, _("     r           Toggle Random playing"));
918         MainBoxWrite(p_intf, l++, 1, _("     l           Toggle Loop Playlist"));
919         MainBoxWrite(p_intf, l++, 1, _("     R           Toggle Repeat item"));
920         MainBoxWrite(p_intf, l++, 1, _("     o           Order Playlist by title"));
921         MainBoxWrite(p_intf, l++, 1, _("     O           Reverse order Playlist by title"));
922         MainBoxWrite(p_intf, l++, 1, _("     g           Go to the current playing item"));
923         MainBoxWrite(p_intf, l++, 1, _("     /           Look for an item"));
924         MainBoxWrite(p_intf, l++, 1, _("     A           Add an entry"));
925         MainBoxWrite(p_intf, l++, 1, _("     D, <del>    Delete an entry"));
926         MainBoxWrite(p_intf, l++, 1, _("     <backspace> Delete an entry"));
927         MainBoxWrite(p_intf, l++, 1, _("     e           Eject (if stopped)"));
928         MainBoxWrite(p_intf, l++, 1, "");
929
930         if (p_sys->b_color)
931             wcolor_set(p_sys->w, C_CATEGORY, NULL);
932         MainBoxWrite(p_intf, l++, 1, _("[Filebrowser]"));
933         if (p_sys->b_color)
934             wcolor_set(p_sys->w, C_DEFAULT, NULL);
935         MainBoxWrite(p_intf, l++, 1, _("     <enter>     Add the selected file to the playlist"));
936         MainBoxWrite(p_intf, l++, 1, _("     <space>     Add the selected directory to the playlist"));
937         MainBoxWrite(p_intf, l++, 1, _("     .           Show/Hide hidden files"));
938         MainBoxWrite(p_intf, l++, 1, "");
939
940         if (p_sys->b_color)
941             wcolor_set(p_sys->w, C_CATEGORY, NULL);
942         MainBoxWrite(p_intf, l++, 1, _("[Boxes]"));
943         if (p_sys->b_color)
944             wcolor_set(p_sys->w, C_DEFAULT, NULL);
945         MainBoxWrite(p_intf, l++, 1, _("     <up>,<down>     Navigate through the box line by line"));
946         MainBoxWrite(p_intf, l++, 1, _("     <pgup>,<pgdown> Navigate through the box page by page"));
947         MainBoxWrite(p_intf, l++, 1, "");
948
949         if (p_sys->b_color)
950             wcolor_set(p_sys->w, C_CATEGORY, NULL);
951         MainBoxWrite(p_intf, l++, 1, _("[Player]"));
952         if (p_sys->b_color)
953             wcolor_set(p_sys->w, C_DEFAULT, NULL);
954         MainBoxWrite(p_intf, l++, 1, _("     <up>,<down>     Seek +/-5%%"));
955         MainBoxWrite(p_intf, l++, 1, "");
956
957         if (p_sys->b_color)
958             wcolor_set(p_sys->w, C_CATEGORY, NULL);
959         MainBoxWrite(p_intf, l++, 1, _("[Miscellaneous]"));
960         if (p_sys->b_color)
961             wcolor_set(p_sys->w, C_DEFAULT, NULL);
962         MainBoxWrite(p_intf, l++, 1, _("     Ctrl-l          Refresh the screen"));
963
964         p_sys->i_box_lines_total = l;
965         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
966             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
967
968         if (l - p_sys->i_box_start < p_sys->i_box_lines)
969             y += l - p_sys->i_box_start;
970         else
971             y += p_sys->i_box_lines;
972     }
973     else if (p_sys->i_box_type == BOX_INFO)
974     {
975         /* Info box */
976         int l = 0;
977         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Information "), p_sys->b_color);
978
979         if (p_input)
980         {
981             int i,j;
982             vlc_mutex_lock(&input_GetItem(p_input)->lock);
983             for(i = 0; i < input_GetItem(p_input)->i_categories; i++)
984             {
985                 info_category_t *p_category = input_GetItem(p_input)->pp_categories[i];
986                 if (y >= y_end) break;
987                 if (p_sys->b_color)
988                     wcolor_set(p_sys->w, C_CATEGORY, NULL);
989                 MainBoxWrite(p_intf, l++, 1, _("  [%s]"), p_category->psz_name);
990                 if (p_sys->b_color)
991                     wcolor_set(p_sys->w, C_DEFAULT, NULL);
992                 for(j = 0; j < p_category->i_infos; j++)
993                 {
994                     info_t *p_info = p_category->pp_infos[j];
995                     if (y >= y_end) break;
996                     MainBoxWrite(p_intf, l++, 1, _("      %s: %s"), p_info->psz_name, p_info->psz_value);
997                 }
998             }
999             vlc_mutex_unlock(&input_GetItem(p_input)->lock);
1000         }
1001         else
1002             MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
1003
1004         p_sys->i_box_lines_total = l;
1005         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1006             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1007
1008         if (l - p_sys->i_box_start < p_sys->i_box_lines)
1009             y += l - p_sys->i_box_start;
1010         else
1011             y += p_sys->i_box_lines;
1012     }
1013     else if (p_sys->i_box_type == BOX_META)
1014     {
1015         /* Meta data box */
1016         int l = 0;
1017
1018         DrawBox(p_sys->w, y++, 0, h, COLS, _("Meta-information"),
1019                  p_sys->b_color);
1020
1021         if (p_input)
1022         {
1023             input_item_t *p_item = input_GetItem(p_input);
1024             vlc_mutex_lock(&p_item->lock);
1025             for(int i=0; i<VLC_META_TYPE_COUNT; i++)
1026             {
1027                 const char *psz_meta = vlc_meta_Get(p_item->p_meta, i);
1028                 if (!psz_meta || !*psz_meta)
1029                     continue;
1030
1031                 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1032                 MainBoxWrite(p_intf, l++, 1, "  [%s]",
1033                              vlc_meta_TypeToLocalizedString(i));
1034                 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1035                 MainBoxWrite(p_intf, l++, 1, "      %s", psz_meta);
1036             }
1037             vlc_mutex_unlock(&p_item->lock);
1038         }
1039         else
1040             MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
1041
1042         p_sys->i_box_lines_total = l;
1043         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1044             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1045
1046         y += __MIN(l - p_sys->i_box_start, p_sys->i_box_lines);
1047     }
1048 #if 0 /* Deprecated API */
1049     else if (p_sys->i_box_type == BOX_LOG)
1050     {
1051         int i_line = 0;
1052         int i_stop;
1053         int i_start;
1054
1055         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Logs "), p_sys->b_color);
1056
1057
1058         i_start = p_intf->p_sys->p_sub->i_start;
1059
1060         vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1061         i_stop = *p_intf->p_sys->p_sub->pi_stop;
1062         vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1063
1064         for(;;)
1065         {
1066             static const char *ppsz_type[4] = { "", "error", "warning", "debug" };
1067             if (i_line >= h - 2)
1068             {
1069                 break;
1070             }
1071             i_stop--;
1072             i_line++;
1073             if (i_stop < 0) i_stop += VLC_MSG_QSIZE;
1074             if (i_stop == i_start)
1075             {
1076                 break;
1077             }
1078             if (p_sys->b_color)
1079                 wcolor_set(p_sys->w,
1080                     p_sys->p_sub->p_msg[i_stop].i_type + C_INFO,
1081                     NULL);
1082             mvnprintw(y + h-2-i_line, 1, COLS - 2, "   [%s] %s",
1083                       ppsz_type[p_sys->p_sub->p_msg[i_stop].i_type],
1084                       p_sys->p_sub->p_msg[i_stop].psz_msg);
1085             if (p_sys->b_color)
1086                 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1087         }
1088
1089         vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1090         p_intf->p_sys->p_sub->i_start = i_stop;
1091         vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1092         y = y_end;
1093     }
1094 #endif
1095     else if (p_sys->i_box_type == BOX_BROWSE)
1096     {
1097         /* Filebrowser box */
1098         int        i_start, i_stop;
1099         int        i_item;
1100         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Browse "), p_sys->b_color);
1101
1102         if (p_sys->i_box_bidx >= p_sys->i_dir_entries) p_sys->i_box_plidx = p_sys->i_dir_entries - 1;
1103         if (p_sys->i_box_bidx < 0) p_sys->i_box_bidx = 0;
1104
1105         if (p_sys->i_box_bidx < (h - 2)/2)
1106         {
1107             i_start = 0;
1108             i_stop = h - 2;
1109         }
1110         else if (p_sys->i_dir_entries - p_sys->i_box_bidx > (h - 2)/2)
1111         {
1112             i_start = p_sys->i_box_bidx - (h - 2)/2;
1113             i_stop = i_start + h - 2;
1114         }
1115         else
1116         {
1117             i_stop = p_sys->i_dir_entries;
1118             i_start = p_sys->i_dir_entries - (h - 2);
1119         }
1120         if (i_start < 0)
1121             i_start = 0;
1122         if (i_stop > p_sys->i_dir_entries)
1123             i_stop = p_sys->i_dir_entries;
1124
1125         for(i_item = i_start; i_item < i_stop; i_item++)
1126         {
1127             bool b_selected = (p_sys->i_box_bidx == i_item);
1128
1129             if (y >= y_end) break;
1130             if (b_selected)
1131                 attrset(A_REVERSE);
1132             if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1133                 wcolor_set(p_sys->w, C_FOLDER, NULL);
1134             mvnprintw(y++, 1, COLS - 2, " %c %s", p_sys->pp_dir_entries[i_item]->b_file == true ? ' ' : '+',
1135                             p_sys->pp_dir_entries[i_item]->psz_path);
1136             if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1137                 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1138
1139             if (b_selected)
1140                 attroff(A_REVERSE);
1141         }
1142
1143     }
1144     else if (p_sys->i_box_type == BOX_OBJECTS)
1145     {
1146         int l = 0;
1147         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Objects "), p_sys->b_color);
1148         DumpObject(p_intf, &l, VLC_OBJECT(p_intf->p_libvlc), 0);
1149
1150         p_sys->i_box_lines_total = l;
1151         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1152             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1153
1154         if (l - p_sys->i_box_start < p_sys->i_box_lines)
1155             y += l - p_sys->i_box_start;
1156         else
1157             y += p_sys->i_box_lines;
1158     }
1159     else if (p_sys->i_box_type == BOX_STATS)
1160     {
1161         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Stats "), p_sys->b_color);
1162
1163         if (p_input)
1164         {
1165             input_item_t *p_item = input_GetItem(p_input);
1166             assert(p_item);
1167             vlc_mutex_lock(&p_item->lock);
1168             vlc_mutex_lock(&p_item->p_stats->lock);
1169
1170             int i_audio = 0;
1171             int i_video = 0;
1172             int i;
1173
1174             if (!p_item->i_es)
1175                 i_video = i_audio = 1;
1176             else
1177                 for(i = 0; i < p_item->i_es ; i++)
1178                 {
1179                     i_audio += (p_item->es[i]->i_cat == AUDIO_ES);
1180                     i_video += (p_item->es[i]->i_cat == VIDEO_ES);
1181                 }
1182
1183             int l = 0;
1184
1185 #define SHOW_ACS(x,c) \
1186     if (l >= p_sys->i_box_start && l - p_sys->i_box_start < p_sys->i_box_lines) \
1187         mvaddch(p_sys->i_box_y - p_sys->i_box_start + l, x, c)
1188
1189             /* Input */
1190             if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1191             MainBoxWrite(p_intf, l, 1, _("+-[Incoming]"));
1192             SHOW_ACS(1, ACS_ULCORNER);  SHOW_ACS(2, ACS_HLINE); l++;
1193             if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1194             MainBoxWrite(p_intf, l, 1, _("| input bytes read : %8.0f KiB"),
1195                     (float)(p_item->p_stats->i_read_bytes)/1024);
1196             SHOW_ACS(1, ACS_VLINE); l++;
1197             MainBoxWrite(p_intf, l, 1, _("| input bitrate    :   %6.0f kb/s"),
1198                     (float)(p_item->p_stats->f_input_bitrate)*8000);
1199             MainBoxWrite(p_intf, l, 1, _("| demux bytes read : %8.0f KiB"),
1200                     (float)(p_item->p_stats->i_demux_read_bytes)/1024);
1201             SHOW_ACS(1, ACS_VLINE); l++;
1202             MainBoxWrite(p_intf, l, 1, _("| demux bitrate    :   %6.0f kb/s"),
1203                     (float)(p_item->p_stats->f_demux_bitrate)*8000);
1204             SHOW_ACS(1, ACS_VLINE); l++;
1205             DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1206             SHOW_ACS(1, ACS_VLINE); l++;
1207
1208             /* Video */
1209             if (i_video)
1210             {
1211                 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1212                 MainBoxWrite(p_intf, l, 1, _("+-[Video Decoding]"));
1213                 SHOW_ACS(1, ACS_LTEE);  SHOW_ACS(2, ACS_HLINE); l++;
1214                 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1215                 MainBoxWrite(p_intf, l, 1, _("| video decoded    :    %"PRId64),
1216                         p_item->p_stats->i_decoded_video);
1217                 SHOW_ACS(1, ACS_VLINE); l++;
1218                 MainBoxWrite(p_intf, l, 1, _("| frames displayed :    %"PRId64),
1219                         p_item->p_stats->i_displayed_pictures);
1220                 SHOW_ACS(1, ACS_VLINE); l++;
1221                 MainBoxWrite(p_intf, l, 1, _("| frames lost      :    %"PRId64),
1222                         p_item->p_stats->i_lost_pictures);
1223                 SHOW_ACS(1, ACS_VLINE); l++;
1224                 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1225                 SHOW_ACS(1, ACS_VLINE); l++;
1226             }
1227             /* Audio*/
1228             if (i_audio)
1229             {
1230                 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1231                 MainBoxWrite(p_intf, l, 1, _("+-[Audio Decoding]"));
1232                 SHOW_ACS(1, ACS_LTEE);  SHOW_ACS(2, ACS_HLINE); l++;
1233                 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1234                 MainBoxWrite(p_intf, l, 1, _("| audio decoded    :    %"PRId64),
1235                         p_item->p_stats->i_decoded_audio);
1236                 SHOW_ACS(1, ACS_VLINE); l++;
1237                 MainBoxWrite(p_intf, l, 1, _("| buffers played   :    %"PRId64),
1238                         p_item->p_stats->i_played_abuffers);
1239                 SHOW_ACS(1, ACS_VLINE); l++;
1240                 MainBoxWrite(p_intf, l, 1, _("| buffers lost     :    %"PRId64),
1241                         p_item->p_stats->i_lost_abuffers);
1242                 SHOW_ACS(1, ACS_VLINE); l++;
1243                 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1244                 SHOW_ACS(1, ACS_VLINE); l++;
1245             }
1246             /* Sout */
1247             if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1248             MainBoxWrite(p_intf, l, 1, _("+-[Streaming]"));
1249             SHOW_ACS(1, ACS_LTEE);  SHOW_ACS(2, ACS_HLINE); l++;
1250             if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1251             MainBoxWrite(p_intf, l, 1, _("| packets sent     :    %5i"), p_item->p_stats->i_sent_packets);
1252             SHOW_ACS(1, ACS_VLINE); l++;
1253             MainBoxWrite(p_intf, l, 1, _("| bytes sent       : %8.0f KiB"),
1254                     (float)(p_item->p_stats->i_sent_bytes)/1024);
1255             SHOW_ACS(1, ACS_VLINE); l++;
1256             MainBoxWrite(p_intf, l, 1, _("\\ sending bitrate  :   %6.0f kb/s"),
1257                     (float)(p_item->p_stats->f_send_bitrate*8)*1000);
1258             SHOW_ACS(1, ACS_LLCORNER); l++;
1259             if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1260
1261 #undef SHOW_ACS
1262
1263             p_sys->i_box_lines_total = l;
1264             if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1265                 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1266
1267             if (l - p_sys->i_box_start < p_sys->i_box_lines)
1268                 y += l - p_sys->i_box_start;
1269             else
1270                 y += p_sys->i_box_lines;
1271
1272             vlc_mutex_unlock(&p_item->p_stats->lock);
1273             vlc_mutex_unlock(&p_item->lock);
1274
1275         }
1276     }
1277     else if (p_sys->i_box_type == BOX_PLAYLIST ||
1278                p_sys->i_box_type == BOX_SEARCH ||
1279                p_sys->i_box_type == BOX_OPEN )
1280     {
1281         /* Playlist box */
1282         int        i_start, i_stop, i_max = p_sys->i_plist_entries;
1283         int        i_item;
1284         char       *psz_title;
1285
1286         if (p_sys->category_view)
1287             psz_title = strdup(_(" Playlist (By category) "));
1288         else
1289             psz_title = strdup(_(" Playlist (All, one level) "));
1290
1291         DrawBox(p_sys->w, y++, 0, h, COLS, psz_title, p_sys->b_color);
1292         free(psz_title);
1293
1294         if (p_sys->b_need_update || !p_sys->pp_plist)
1295             PlaylistRebuild(p_intf);
1296         if (p_sys->b_box_plidx_follow)
1297             FindIndex(p_sys, p_playlist, false);
1298
1299         if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1300         if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1301         if (p_sys->i_box_plidx >= i_max) p_sys->i_box_plidx = i_max - 1;
1302
1303         if (p_sys->i_box_plidx < (h - 2)/2)
1304         {
1305             i_start = 0;
1306             i_stop = h - 2;
1307         }
1308         else if (i_max - p_sys->i_box_plidx > (h - 2)/2)
1309         {
1310             i_start = p_sys->i_box_plidx - (h - 2)/2;
1311             i_stop = i_start + h - 2;
1312         }
1313         else
1314         {
1315             i_stop = i_max;
1316             i_start = i_max - (h - 2);
1317         }
1318         if (i_start < 0)
1319             i_start = 0;
1320         if (i_stop > i_max)
1321             i_stop = i_max;
1322
1323         for(i_item = i_start; i_item < i_stop; i_item++)
1324         {
1325             bool b_selected = (p_sys->i_box_plidx == i_item);
1326             playlist_item_t *p_item = p_sys->pp_plist[i_item]->p_item;
1327             playlist_item_t *p_node = p_sys->p_node;
1328             int c = ' ';
1329             input_thread_t *p_input2 = playlist_CurrentInput(p_playlist);
1330
1331             PL_LOCK;
1332             assert(p_item);
1333             playlist_item_t *p_current_playing_item = playlist_CurrentPlayingItem(p_playlist);
1334             if ((p_node && p_item->p_input == p_node->p_input) ||
1335                         (!p_node && p_input2 && p_current_playing_item &&
1336                           p_item->p_input == p_current_playing_item->p_input))
1337                 c = '*';
1338             else if (p_item == p_node || (p_item != p_node &&
1339                         PlaylistIsPlaying(p_playlist, p_item)))
1340                 c = '>';
1341             PL_UNLOCK;
1342
1343             if (p_input2)
1344                 vlc_object_release(p_input2);
1345
1346             if (y >= y_end) break;
1347             if (b_selected)
1348                 attrset(A_REVERSE);
1349             if (p_sys->b_color)
1350                 wcolor_set(p_sys->w, i_item % 3 + C_PLAYLIST_1, NULL);
1351             mvnprintw(y++, 1, COLS - 2, "%c%s", c,
1352                        p_sys->pp_plist[i_item]->psz_display);
1353             if (p_sys->b_color)
1354                 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1355             if (b_selected)
1356                 attroff(A_REVERSE);
1357         }
1358
1359     }
1360     else
1361         y++;
1362
1363     if (p_sys->i_box_type == BOX_SEARCH)
1364     {
1365         DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1366         if (p_sys->psz_search_chain)
1367         {
1368             if (!*p_sys->psz_search_chain && p_sys->psz_old_search)
1369                 /* Searching next entry */
1370                 mvnprintw(7, 1, COLS-2, _("Find: %s"), p_sys->psz_old_search);
1371             else
1372                 mvnprintw(7, 1, COLS-2, _("Find: %s"), p_sys->psz_search_chain);
1373         }
1374     }
1375     if (p_sys->i_box_type == BOX_OPEN && p_sys->psz_open_chain)
1376     {
1377         DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1378         mvnprintw(7, 1, COLS-2, _("Open: %s"), p_sys->psz_open_chain);
1379     }
1380
1381     while (y < y_end)
1382         DrawEmptyLine(p_sys->w, y++, 1, COLS - 2);
1383
1384     refresh();
1385
1386     *t_last_refresh = time(0);
1387 }
1388
1389 static void ManageSlider(intf_thread_t *p_intf)
1390 {
1391     intf_sys_t     *p_sys = p_intf->p_sys;
1392     input_thread_t *p_input = p_sys->p_input;
1393     float pos;
1394
1395     if (!p_input || var_GetInteger(p_input, "state") != PLAYING_S)
1396         return;
1397
1398     pos = var_GetFloat(p_input, "position");
1399     if (p_sys->f_slider == p_sys->f_slider_old)
1400         p_sys->f_slider = p_sys->f_slider_old = 100. * pos;
1401     else
1402     {
1403         p_sys->f_slider_old = p_sys->f_slider;
1404
1405         pos = p_sys->f_slider / 100.;
1406         var_SetFloat(p_input, "position", pos);
1407     }
1408 }
1409
1410 #ifndef HAVE_NCURSESW
1411 static char *KeyToUTF8(int i_key, char *psz_part)
1412 {
1413     char *psz_utf8;
1414     int len = strlen(psz_part);
1415     if (len == 6)
1416     {
1417         /* overflow error - should not happen */
1418         memset(psz_part, 0, 6);
1419         return NULL;
1420     }
1421
1422     psz_part[len] = (char)i_key;
1423
1424     psz_utf8 = FromLocaleDup(psz_part);
1425
1426     /* Ugly check for incomplete bytes sequences
1427      * (in case of non-UTF8 multibyte local encoding) */
1428     char *psz;
1429     for(psz = psz_utf8; *psz; psz++)
1430         if ((*psz == '?') && (*psz_utf8 != '?'))
1431         {
1432             /* incomplete bytes sequence detected
1433              * (VLC core inserted dummy question marks) */
1434             free(psz_utf8);
1435             return NULL;
1436         }
1437
1438     /* Check for incomplete UTF8 bytes sequence */
1439     if (!EnsureUTF8(psz_utf8))
1440     {
1441         free(psz_utf8);
1442         return NULL;
1443     }
1444
1445     memset(psz_part, 0, 6);
1446     return psz_utf8;
1447 }
1448 #endif
1449
1450 static inline int RemoveLastUTF8Entity(char *psz, int len)
1451 {
1452     while (len && ((psz[--len] & 0xc0) == 0x80));
1453                        /* UTF8 continuation byte */
1454
1455     psz[len] = '\0';
1456     return len;
1457 }
1458
1459 static char *GetDiscDevice(intf_thread_t *p_intf, const char *name)
1460 {
1461     static const struct { const char *s; size_t n; const char *v; } devs[] =
1462     {
1463         { "cdda://", 7, "cd-audio", },
1464         { "dvd://",  6, "dvd",      },
1465         { "vcd://",  6, "vcd",      },
1466     };
1467     char *device;
1468
1469     for (unsigned i = 0; i < sizeof devs / sizeof *devs; i++)
1470     {
1471         size_t n = devs[i].n;
1472         if (!strncmp(name, devs[i].s, n))
1473             switch(name[n])
1474             {
1475             case '\0':
1476             case '@':
1477                 return config_GetPsz(p_intf, devs[i].v);
1478             default:
1479                 /* Omit the beginning MRL-selector characters */
1480                 return strdup(name + n);
1481             }
1482     }
1483
1484     device = strdup(name);
1485
1486     if (device) /* Remove what we have after @ */
1487         device[strcspn(device, "@")] = '\0';
1488
1489     return device;
1490 }
1491
1492 static void Eject(intf_thread_t *p_intf)
1493 {
1494     char *psz_device, *psz_name;
1495     playlist_t * p_playlist = pl_Get(p_intf);
1496
1497     /* If there's a stream playing, we aren't allowed to eject ! */
1498     if (p_intf->p_sys->p_input)
1499         return;
1500
1501     PL_LOCK;
1502
1503     if (!playlist_CurrentPlayingItem(p_playlist))
1504     {
1505         PL_UNLOCK;
1506         return;
1507     }
1508
1509     psz_name = playlist_CurrentPlayingItem(p_playlist)->p_input->psz_name;
1510
1511     if (psz_name)
1512         psz_device = GetDiscDevice(p_intf, psz_name);
1513
1514     PL_UNLOCK;
1515
1516     if (psz_device)
1517     {
1518         intf_Eject(p_intf, psz_device);
1519         free(psz_device);
1520     }
1521 }
1522
1523 static void PlayPause(intf_thread_t *p_intf)
1524 {
1525     input_thread_t *p_input = p_intf->p_sys->p_input;
1526
1527     if (p_input)
1528     {
1529         int64_t state = var_GetInteger( p_input, "state" );
1530         state = (state != PLAYING_S) ? PLAYING_S : PAUSE_S;
1531         var_SetInteger( p_input, "state", state );
1532     }
1533     else
1534         playlist_Play(pl_Get(p_intf));
1535 }
1536
1537 static int HandleKey(intf_thread_t *p_intf)
1538 {
1539     intf_sys_t *p_sys = p_intf->p_sys;
1540     playlist_t *p_playlist = pl_Get(p_intf);
1541     int i_key = wgetch(p_sys->w);
1542
1543     if (i_key == -1)
1544         return 0;
1545
1546     if (p_sys->i_box_type == BOX_PLAYLIST)
1547     {
1548         int b_ret = true;
1549         bool b_box_plidx_follow = false;
1550
1551         switch(i_key)
1552         {
1553             /* Playlist Settings */
1554             case 'r':
1555                 var_ToggleBool(p_playlist, "random");
1556                 return 1;
1557             case 'l':
1558                 var_ToggleBool(p_playlist, "loop");
1559                 return 1;
1560             case 'R':
1561                 var_ToggleBool(p_playlist, "repeat");
1562                 return 1;
1563
1564             /* Playlist sort */
1565             case 'o':
1566                 playlist_RecursiveNodeSort(p_playlist,
1567                                             PlaylistGetRoot(p_intf),
1568                                             SORT_TITLE_NODES_FIRST, ORDER_NORMAL);
1569                 p_sys->b_need_update = true;
1570                 return 1;
1571             case 'O':
1572                 playlist_RecursiveNodeSort(p_playlist,
1573                                             PlaylistGetRoot(p_intf),
1574                                             SORT_TITLE_NODES_FIRST, ORDER_REVERSE);
1575                 p_sys->b_need_update = true;
1576                 return 1;
1577
1578             /* Playlist view */
1579             case 'v':
1580                 p_sys->category_view = !p_sys->category_view;
1581                 PlaylistRebuild(p_intf);
1582                 return 1;
1583
1584             /* Playlist navigation */
1585             case 'g':
1586                 FindIndex(p_sys, p_playlist, false);
1587                 break;
1588             case KEY_HOME:
1589                 p_sys->i_box_plidx = 0;
1590                 break;
1591 #ifdef __FreeBSD__
1592 /* workaround for FreeBSD + xterm:
1593  * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1594             case KEY_SELECT:
1595 #endif
1596             case KEY_END:
1597                 p_sys->i_box_plidx = p_playlist->items.i_size - 1;
1598                 break;
1599             case KEY_UP:
1600                 p_sys->i_box_plidx--;
1601                 break;
1602             case KEY_DOWN:
1603                 p_sys->i_box_plidx++;
1604                 break;
1605             case KEY_PPAGE:
1606                 p_sys->i_box_plidx -= p_sys->i_box_lines;
1607                 break;
1608             case KEY_NPAGE:
1609                 p_sys->i_box_plidx += p_sys->i_box_lines;
1610                 break;
1611             case 'D':
1612             case KEY_BACKSPACE:
1613             case 0x7f:
1614             case KEY_DC:
1615             {
1616                 playlist_item_t *p_item;
1617
1618                 PL_LOCK;
1619                 p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1620                 if (p_item->i_children == -1)
1621                     playlist_DeleteFromInput(p_playlist,
1622                                               p_item->p_input, pl_Locked);
1623                 else
1624                     playlist_NodeDelete(p_playlist, p_item, true , false);
1625                 PL_UNLOCK;
1626                 PlaylistRebuild(p_intf);
1627                 break;
1628             }
1629
1630             case KEY_ENTER:
1631             case '\r':
1632             case '\n':
1633                 if (!p_sys->pp_plist[p_sys->i_box_plidx])
1634                 {
1635                     b_ret = false;
1636                     break;
1637                 }
1638                 if (p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
1639                         == -1)
1640                 {
1641                     playlist_item_t *p_item, *p_parent;
1642                     p_item = p_parent =
1643                             p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1644
1645                     if (!p_parent)
1646                         p_parent = p_playlist->p_root_onelevel;
1647                     while (p_parent->p_parent)
1648                         p_parent = p_parent->p_parent;
1649                     playlist_Control(p_playlist, PLAYLIST_VIEWPLAY,
1650                                       pl_Unlocked, p_parent, p_item);
1651                 }
1652                 else if (p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
1653                         == 0)
1654                 {   /* We only want to set the current node */
1655                     playlist_Stop(p_playlist);
1656                     p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1657                 }
1658                 else
1659                 {
1660                     p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1661                     playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked,
1662                         p_sys->pp_plist[p_sys->i_box_plidx]->p_item, NULL);
1663                 }
1664                 b_box_plidx_follow = true;
1665                 break;
1666             default:
1667                 b_ret = false;
1668                 break;
1669         }
1670
1671         if (b_ret)
1672         {
1673             int i_max = p_sys->i_plist_entries;
1674             if (p_sys->i_box_plidx >= i_max) p_sys->i_box_plidx = i_max - 1;
1675             if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1676
1677             PL_LOCK;
1678             if (PlaylistIsPlaying(p_playlist,
1679                                    p_sys->pp_plist[p_sys->i_box_plidx]->p_item))
1680                 b_box_plidx_follow = true;
1681             PL_UNLOCK;
1682             p_sys->b_box_plidx_follow = b_box_plidx_follow;
1683             return 1;
1684         }
1685     }
1686     else if (p_sys->i_box_type == BOX_BROWSE)
1687     {
1688         bool b_ret = true;
1689         /* Browser navigation */
1690         switch(i_key)
1691         {
1692             case KEY_HOME:
1693                 p_sys->i_box_bidx = 0;
1694                 break;
1695 #ifdef __FreeBSD__
1696             case KEY_SELECT:
1697 #endif
1698             case KEY_END:
1699                 p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
1700                 break;
1701             case KEY_UP:
1702                 p_sys->i_box_bidx--;
1703                 break;
1704             case KEY_DOWN:
1705                 p_sys->i_box_bidx++;
1706                 break;
1707             case KEY_PPAGE:
1708                 p_sys->i_box_bidx -= p_sys->i_box_lines;
1709                 break;
1710             case KEY_NPAGE:
1711                 p_sys->i_box_bidx += p_sys->i_box_lines;
1712                 break;
1713             case '.': /* Toggle show hidden files */
1714                 p_sys->b_show_hidden_files = (p_sys->b_show_hidden_files ==
1715                     true ? false : true);
1716                 ReadDir(p_intf);
1717                 break;
1718
1719             case KEY_ENTER:
1720             case '\r':
1721             case '\n':
1722             case ' ':
1723                 if (p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ')
1724                 {
1725                     char* psz_uri;
1726                     if (asprintf(&psz_uri, "%s://%s/%s",
1727                         p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file ?
1728                             "file" : "directory",
1729                         p_sys->psz_current_dir,
1730                         p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path
1731                        ) == -1)
1732                     {
1733                         psz_uri = NULL;
1734                     }
1735
1736                     playlist_item_t *p_parent = p_sys->p_node;
1737                     if (!p_parent)
1738                     {
1739                         PL_LOCK;
1740                         p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
1741                         PL_UNLOCK;
1742                         if (!p_parent)
1743                             p_parent = p_playlist->p_local_onelevel;
1744                     }
1745
1746                     while (p_parent->p_parent && p_parent->p_parent->p_parent)
1747                         p_parent = p_parent->p_parent;
1748
1749                     playlist_Add(p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
1750                                   PLAYLIST_END,
1751                                   p_parent->p_input ==
1752                                     p_playlist->p_local_onelevel->p_input
1753                                   , false);
1754
1755                     p_sys->i_box_type = BOX_PLAYLIST;
1756                     free(psz_uri);
1757                 }
1758                 else
1759                 {
1760                     if (asprintf(&(p_sys->psz_current_dir), "%s/%s", p_sys->psz_current_dir,
1761                                   p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path) != -1)
1762                         ReadDir(p_intf);
1763                 }
1764                 break;
1765             default:
1766                 b_ret = false;
1767                 break;
1768         }
1769         if (b_ret)
1770         {
1771             if (p_sys->i_box_bidx >= p_sys->i_dir_entries) p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
1772             if (p_sys->i_box_bidx < 0) p_sys->i_box_bidx = 0;
1773             return 1;
1774         }
1775     }
1776     else if (p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
1777              p_sys->i_box_type == BOX_META || p_sys->i_box_type == BOX_STATS ||
1778              p_sys->i_box_type == BOX_OBJECTS)
1779     {
1780         switch(i_key)
1781         {
1782             case KEY_HOME:
1783                 p_sys->i_box_start = 0;
1784                 return 1;
1785 #ifdef __FreeBSD__
1786             case KEY_SELECT:
1787 #endif
1788             case KEY_END:
1789                 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1790                 return 1;
1791             case KEY_UP:
1792                 if (p_sys->i_box_start > 0) p_sys->i_box_start--;
1793                 return 1;
1794             case KEY_DOWN:
1795                 if (p_sys->i_box_start < p_sys->i_box_lines_total - 1)
1796                     p_sys->i_box_start++;
1797                 return 1;
1798             case KEY_PPAGE:
1799                 p_sys->i_box_start -= p_sys->i_box_lines;
1800                 if (p_sys->i_box_start < 0) p_sys->i_box_start = 0;
1801                 return 1;
1802             case KEY_NPAGE:
1803                 p_sys->i_box_start += p_sys->i_box_lines;
1804                 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1805                     p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1806                 return 1;
1807             default:
1808                 break;
1809         }
1810     }
1811     else if (p_sys->i_box_type == BOX_NONE)
1812     {
1813         switch(i_key)
1814         {
1815             case KEY_HOME:
1816                 p_sys->f_slider = 0;
1817                 ManageSlider(p_intf);
1818                 return 1;
1819 #ifdef __FreeBSD__
1820             case KEY_SELECT:
1821 #endif
1822             case KEY_END:
1823                 p_sys->f_slider = 99.9;
1824                 ManageSlider(p_intf);
1825                 return 1;
1826             case KEY_UP:
1827                 p_sys->f_slider += 5.0;
1828                 if (p_sys->f_slider >= 99.0) p_sys->f_slider = 99.0;
1829                 ManageSlider(p_intf);
1830                 return 1;
1831             case KEY_DOWN:
1832                 p_sys->f_slider -= 5.0;
1833                 if (p_sys->f_slider < 0.0) p_sys->f_slider = 0.0;
1834                 ManageSlider(p_intf);
1835                 return 1;
1836
1837             default:
1838                 break;
1839         }
1840     }
1841     else if (p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain)
1842     {
1843         int i_chain_len = strlen(p_sys->psz_search_chain);
1844         switch(i_key)
1845         {
1846             case KEY_CLEAR:
1847             case 0x0c:      /* ^l */
1848                 clear();
1849                 return 1;
1850             case KEY_ENTER:
1851             case '\r':
1852             case '\n':
1853                 if (i_chain_len > 0)
1854                     p_sys->psz_old_search = strdup(p_sys->psz_search_chain);
1855                 else if (p_sys->psz_old_search)
1856                     SearchPlaylist(p_sys, p_sys->psz_old_search);
1857                 p_sys->i_box_type = BOX_PLAYLIST;
1858                 return 1;
1859             case 0x1b: /* ESC */
1860                 /* Alt+key combinations return 2 keys in the terminal keyboard:
1861                  * ESC, and the 2nd key.
1862                  * If some other key is available immediately (where immediately
1863                  * means after wgetch() 1 second delay), that means that the
1864                  * ESC key was not pressed.
1865                  *
1866                  * man 3X curs_getch says:
1867                  *
1868                  * Use of the escape key by a programmer for a single
1869                  * character function is discouraged, as it will cause a delay
1870                  * of up to one second while the keypad code looks for a
1871                  * following function-key sequence.
1872                  *
1873                  */
1874                 if (wgetch(p_sys->w) != ERR)
1875                     return 0;
1876                 p_sys->i_box_plidx = p_sys->i_before_search;
1877                 p_sys->i_box_type = BOX_PLAYLIST;
1878                 return 1;
1879             case KEY_BACKSPACE:
1880             case 0x7f:
1881                 RemoveLastUTF8Entity(p_sys->psz_search_chain, i_chain_len);
1882                 break;
1883             default:
1884             {
1885 #ifdef HAVE_NCURSESW
1886                 if (i_chain_len + 1 < SEARCH_CHAIN_SIZE)
1887                 {
1888                     p_sys->psz_search_chain[i_chain_len] = (char) i_key;
1889                     p_sys->psz_search_chain[i_chain_len + 1] = '\0';
1890                 }
1891 #else
1892                 char *psz_utf8 = KeyToUTF8(i_key, p_sys->psz_partial_keys);
1893
1894                 if (psz_utf8)
1895                 {
1896                     if (i_chain_len + strlen(psz_utf8) < SEARCH_CHAIN_SIZE)
1897                         strcpy(p_sys->psz_search_chain + i_chain_len, psz_utf8);
1898                     free(psz_utf8);
1899                 }
1900 #endif
1901                 break;
1902             }
1903         }
1904         free(p_sys->psz_old_search);
1905         p_sys->psz_old_search = NULL;
1906         SearchPlaylist(p_sys, p_sys->psz_search_chain);
1907         return 1;
1908     }
1909     else if (p_sys->i_box_type == BOX_OPEN && p_sys->psz_open_chain)
1910     {
1911         int i_chain_len = strlen(p_sys->psz_open_chain);
1912
1913         switch(i_key)
1914         {
1915             case KEY_CLEAR:
1916             case 0x0c:          /* ^l */
1917                 clear();
1918                 return 1;
1919             case KEY_ENTER:
1920             case '\r':
1921             case '\n':
1922                 if (i_chain_len > 0)
1923                 {
1924                     playlist_item_t *p_parent = p_sys->p_node;
1925
1926                     PL_LOCK;
1927                     if (!p_parent)
1928                     p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
1929                     if (!p_parent)
1930                         p_parent = p_playlist->p_local_onelevel;
1931
1932                     while (p_parent->p_parent && p_parent->p_parent->p_parent)
1933                         p_parent = p_parent->p_parent;
1934                     PL_UNLOCK;
1935
1936                     playlist_Add(p_playlist, p_sys->psz_open_chain, NULL,
1937                                   PLAYLIST_APPEND|PLAYLIST_GO, PLAYLIST_END,
1938                                   p_parent->p_input ==
1939                                     p_playlist->p_local_onelevel->p_input
1940                                   , false);
1941
1942                     p_sys->b_box_plidx_follow = true;
1943                 }
1944                 p_sys->i_box_type = BOX_PLAYLIST;
1945                 return 1;
1946             case 0x1b:  /* ESC */
1947                 if (wgetch(p_sys->w) != ERR)
1948                     return 0;
1949                 p_sys->i_box_type = BOX_PLAYLIST;
1950                 return 1;
1951             case KEY_BACKSPACE:
1952             case 0x7f:
1953                 RemoveLastUTF8Entity(p_sys->psz_open_chain, i_chain_len);
1954                 return 1;
1955             default:
1956             {
1957 #ifdef HAVE_NCURSESW
1958                 if (i_chain_len + 1 < OPEN_CHAIN_SIZE)
1959                 {
1960                     p_sys->psz_open_chain[i_chain_len] = (char) i_key;
1961                     p_sys->psz_open_chain[i_chain_len + 1] = '\0';
1962                 }
1963 #else
1964                 char *psz_utf8 = KeyToUTF8(i_key, p_sys->psz_partial_keys);
1965
1966                 if (psz_utf8)
1967                 {
1968                     if (i_chain_len + strlen(psz_utf8) < OPEN_CHAIN_SIZE)
1969                         strcpy(p_sys->psz_open_chain + i_chain_len, psz_utf8);
1970                     free(psz_utf8);
1971                 }
1972 #endif
1973             }
1974         }
1975         return 1;
1976     }
1977
1978
1979     /* Common keys */
1980     switch(i_key)
1981     {
1982         case 0x1b:  /* ESC */
1983             if (wgetch(p_sys->w) != ERR)
1984                 return 0;
1985         case 'q':
1986         case 'Q':
1987         case KEY_EXIT:
1988             libvlc_Quit(p_intf->p_libvlc);
1989             return 0;
1990
1991         /* Box switching */
1992         case 'i':
1993             if (p_sys->i_box_type == BOX_INFO)
1994                 p_sys->i_box_type = BOX_NONE;
1995             else
1996                 p_sys->i_box_type = BOX_INFO;
1997             p_sys->i_box_lines_total = 0;
1998             break;
1999         case 'm':
2000             if (p_sys->i_box_type == BOX_META)
2001                 p_sys->i_box_type = BOX_NONE;
2002             else
2003                 p_sys->i_box_type = BOX_META;
2004             p_sys->i_box_lines_total = 0;
2005             break;
2006 #if 0
2007         case 'L':
2008             if (p_sys->i_box_type == BOX_LOG)
2009                 p_sys->i_box_type = BOX_NONE;
2010             else
2011                 p_sys->i_box_type = BOX_LOG;
2012             break;
2013 #endif
2014         case 'P':
2015             if (p_sys->i_box_type == BOX_PLAYLIST)
2016                 p_sys->i_box_type = BOX_NONE;
2017             else
2018                 p_sys->i_box_type = BOX_PLAYLIST;
2019             break;
2020         case 'B':
2021             if (p_sys->i_box_type == BOX_BROWSE)
2022                 p_sys->i_box_type = BOX_NONE;
2023             else
2024                 p_sys->i_box_type = BOX_BROWSE;
2025             break;
2026         case 'x':
2027             if (p_sys->i_box_type == BOX_OBJECTS)
2028                 p_sys->i_box_type = BOX_NONE;
2029             else
2030                 p_sys->i_box_type = BOX_OBJECTS;
2031             break;
2032         case 'S':
2033             if (p_sys->i_box_type == BOX_STATS)
2034                 p_sys->i_box_type = BOX_NONE;
2035             else
2036                 p_sys->i_box_type = BOX_STATS;
2037             break;
2038         case 'c':
2039             p_sys->b_color = !p_sys->b_color;
2040             if (p_sys->b_color && !p_sys->b_color_started)
2041                 start_color_and_pairs(p_intf);
2042             break;
2043         case 'h':
2044         case 'H':
2045             if (p_sys->i_box_type == BOX_HELP)
2046                 p_sys->i_box_type = BOX_NONE;
2047             else
2048                 p_sys->i_box_type = BOX_HELP;
2049             p_sys->i_box_lines_total = 0;
2050             break;
2051         case '/':
2052             if (p_sys->i_box_type != BOX_SEARCH && p_sys->psz_search_chain)
2053             {
2054                 p_sys->psz_search_chain[0] = '\0';
2055                 p_sys->b_box_plidx_follow = false;
2056                 p_sys->i_before_search = p_sys->i_box_plidx;
2057                 p_sys->i_box_type = BOX_SEARCH;
2058             }
2059             break;
2060         case 'A': /* Open */
2061             if (p_sys->i_box_type != BOX_OPEN && p_sys->psz_open_chain)
2062             {
2063                 p_sys->psz_open_chain[0] = '\0';
2064                 p_sys->i_box_type = BOX_OPEN;
2065             }
2066             break;
2067
2068         /* Navigation */
2069         case KEY_RIGHT:
2070             p_sys->f_slider += 1.0;
2071             if (p_sys->f_slider > 99.9) p_sys->f_slider = 99.9;
2072             ManageSlider(p_intf);
2073             break;
2074
2075         case KEY_LEFT:
2076             p_sys->f_slider -= 1.0;
2077             if (p_sys->f_slider < 0.0) p_sys->f_slider = 0.0;
2078             ManageSlider(p_intf);
2079             break;
2080
2081         /* Common control */
2082         case 'f':
2083             if (p_intf->p_sys->p_input)
2084             {
2085                 vout_thread_t *p_vout = input_GetVout(p_intf->p_sys->p_input);
2086                 if (p_vout)
2087                 {
2088                     bool fs = var_ToggleBool(p_playlist, "fullscreen");
2089                     var_SetBool(p_vout, "fullscreen", fs);
2090                     vlc_object_release(p_vout);
2091                 }
2092             }
2093             return 0;
2094
2095         case ' ':
2096             PlayPause(p_intf);
2097             break;
2098
2099         case 's':
2100             playlist_Stop(p_playlist);
2101             break;
2102
2103         case 'e':
2104             Eject(p_intf);
2105             break;
2106
2107         case '[':
2108             if (p_sys->p_input)
2109                 var_TriggerCallback(p_sys->p_input, "prev-title");
2110             break;
2111
2112         case ']':
2113             if (p_sys->p_input)
2114                 var_TriggerCallback(p_sys->p_input, "next-title");
2115             break;
2116
2117         case '<':
2118             if (p_sys->p_input)
2119                 var_TriggerCallback(p_sys->p_input, "prev-chapter");
2120             break;
2121
2122         case '>':
2123             if (p_sys->p_input)
2124                 var_TriggerCallback(p_sys->p_input, "next-chapter");
2125             break;
2126
2127         case 'p':
2128             playlist_Prev(p_playlist);
2129             clear();
2130             break;
2131
2132         case 'n':
2133             playlist_Next(p_playlist);
2134             clear();
2135             break;
2136
2137         case 'a':
2138             aout_VolumeUp(p_playlist, 1, NULL);
2139             clear();
2140             break;
2141
2142         case 'z':
2143             aout_VolumeDown(p_playlist, 1, NULL);
2144             clear();
2145             break;
2146
2147         /*
2148          * ^l should clear and redraw the screen
2149          */
2150         case KEY_CLEAR:
2151         case 0x0c:          /* ^l */
2152             clear();
2153             break;
2154
2155         default:
2156             return 0;
2157     }
2158
2159     return 1;
2160 }
2161
2162 /*****************************************************************************
2163  * Run: ncurses thread
2164  *****************************************************************************/
2165 static void Run(intf_thread_t *p_intf)
2166 {
2167     intf_sys_t    *p_sys = p_intf->p_sys;
2168     playlist_t    *p_playlist = pl_Get(p_intf);
2169
2170     time_t t_last_refresh;
2171     int canc = vlc_savecancel();
2172
2173     PlaylistRebuild(p_intf);
2174     Redraw(p_intf, &t_last_refresh);
2175
2176     var_AddCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
2177     var_AddCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
2178
2179     while (vlc_object_alive(p_intf))
2180     {
2181         msleep(INTF_IDLE_SLEEP);
2182
2183         /* Update the input */
2184         if (!p_sys->p_input)
2185             p_sys->p_input = playlist_CurrentInput(p_playlist);
2186         else if (p_sys->p_input->b_dead)
2187         {
2188             vlc_object_release(p_sys->p_input);
2189             p_sys->p_input = NULL;
2190             p_sys->f_slider = p_sys->f_slider_old = 0.0;
2191             p_sys->b_box_cleared = false;
2192         }
2193
2194         PL_LOCK;
2195         if (p_sys->b_box_plidx_follow && playlist_CurrentPlayingItem(p_playlist))
2196             FindIndex(p_sys, p_playlist, true);
2197
2198         PL_UNLOCK;
2199
2200         while (HandleKey(p_intf))
2201             Redraw(p_intf, &t_last_refresh);
2202
2203         /* Hack */
2204         if (p_sys->f_slider > 0.0001 && !p_sys->b_box_cleared)
2205         {
2206             clear();
2207             Redraw(p_intf, &t_last_refresh);
2208             p_sys->b_box_cleared = true;
2209         }
2210
2211         if ((time(0) - t_last_refresh) >= 1)
2212         {
2213             ManageSlider(p_intf);
2214             Redraw(p_intf, &t_last_refresh);
2215         }
2216     }
2217     var_DelCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
2218     var_DelCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
2219     vlc_restorecancel(canc);
2220 }
2221
2222 /*****************************************************************************
2223  * Open: initialize and create window
2224  *****************************************************************************/
2225 static int Open(vlc_object_t *p_this)
2226 {
2227     intf_thread_t *p_intf = (intf_thread_t *)p_this;
2228     intf_sys_t    *p_sys  = p_intf->p_sys = calloc(1, sizeof(intf_sys_t));
2229     if (!p_sys)
2230         return VLC_ENOMEM;
2231
2232     p_sys->f_slider = 0.0;
2233     p_sys->f_slider_old = 0.0;
2234     p_sys->i_box_type = BOX_PLAYLIST;
2235     p_sys->b_box_plidx_follow = true;
2236 //  p_sys->p_sub = msg_Subscribe(p_intf);
2237     p_sys->b_color = var_CreateGetBool(p_intf, "color");
2238
2239     p_sys->category_view = true; //FIXME: switching back & forth is broken
2240     p_sys->psz_search_chain = malloc(SEARCH_CHAIN_SIZE + 1);
2241     p_sys->psz_open_chain =   malloc(OPEN_CHAIN_SIZE + 1);
2242
2243     p_sys->psz_current_dir = var_CreateGetString(p_intf, "browse-dir");
2244     if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
2245     {
2246         free(p_sys->psz_current_dir);
2247         p_sys->psz_current_dir = config_GetUserDir(VLC_HOME_DIR);
2248     }
2249
2250     p_sys->w = initscr();   /* Initialize the curses library */
2251
2252     if (p_sys->b_color)
2253         start_color_and_pairs(p_intf);
2254
2255     keypad(p_sys->w, TRUE); /* Don't do NL -> CR/NL */
2256     nonl();                 /* Take input chars one at a time */
2257     cbreak();               /* Don't echo */
2258     noecho();               /* Invisible cursor */
2259     curs_set(0);            /* Non blocking wgetch() */
2260     wtimeout(p_sys->w, 0);
2261     clear();
2262
2263     /* Stop printing errors to the console */
2264     freopen("/dev/null", "wb", stderr);
2265
2266     ReadDir(p_intf);
2267
2268     p_intf->pf_run = Run;
2269     return VLC_SUCCESS;
2270 }
2271
2272 /*****************************************************************************
2273  * Close: destroy interface window
2274  *****************************************************************************/
2275 static void Close(vlc_object_t *p_this)
2276 {
2277     intf_sys_t *p_sys = ((intf_thread_t*)p_this)->p_sys;
2278
2279     PlaylistDestroy(p_sys);
2280     DirsDestroy(p_sys);
2281
2282     free(p_sys->psz_current_dir);
2283     free(p_sys->psz_search_chain);
2284     free(p_sys->psz_old_search);
2285     free(p_sys->psz_open_chain);
2286
2287     if (p_sys->p_input)
2288         vlc_object_release(p_sys->p_input);
2289
2290     /* Close the ncurses interface */
2291     endwin();
2292
2293 //  msg_Unsubscribe(p_intf, p_sys->p_sub);
2294
2295     free(p_sys);
2296 }