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