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