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