]> git.sesse.net Git - vlc/blob - modules/gui/ncurses.c
66f88ebf713ca7f0fd1068098ec73af924999731
[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     if (p_sys->i_box_type == BOX_HELP)
823     {
824         /* Help box */
825         int l = 0;
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     else if (p_sys->i_box_type == BOX_INFO)
924     {
925         /* Info box */
926         int l = 0;
927         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Information "), p_sys->b_color);
928
929         if (p_input)
930         {
931             int i,j;
932             vlc_mutex_lock(&input_GetItem(p_input)->lock);
933             for(i = 0; i < input_GetItem(p_input)->i_categories; i++)
934             {
935                 info_category_t *p_category = input_GetItem(p_input)->pp_categories[i];
936                 if (y >= y_end) break;
937                 if (p_sys->b_color)
938                     wcolor_set(p_sys->w, C_CATEGORY, NULL);
939                 MainBoxWrite(p_intf, l++, 1, _("  [%s]"), p_category->psz_name);
940                 if (p_sys->b_color)
941                     wcolor_set(p_sys->w, C_DEFAULT, NULL);
942                 for(j = 0; j < p_category->i_infos; j++)
943                 {
944                     info_t *p_info = p_category->pp_infos[j];
945                     if (y >= y_end) break;
946                     MainBoxWrite(p_intf, l++, 1, _("      %s: %s"), p_info->psz_name, p_info->psz_value);
947                 }
948             }
949             vlc_mutex_unlock(&input_GetItem(p_input)->lock);
950         }
951         else
952             MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
953
954         p_sys->i_box_lines_total = l;
955         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
956             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
957
958         if (l - p_sys->i_box_start < p_sys->i_box_lines)
959             y += l - p_sys->i_box_start;
960         else
961             y += p_sys->i_box_lines;
962     }
963     else if (p_sys->i_box_type == BOX_META)
964     {
965         /* Meta data box */
966         int l = 0;
967
968         DrawBox(p_sys->w, y++, 0, h, COLS, _("Meta-information"),
969                  p_sys->b_color);
970
971         if (p_input)
972         {
973             input_item_t *p_item = input_GetItem(p_input);
974             vlc_mutex_lock(&p_item->lock);
975             for(int i=0; i<VLC_META_TYPE_COUNT; i++)
976             {
977                 const char *psz_meta = vlc_meta_Get(p_item->p_meta, i);
978                 if (!psz_meta || !*psz_meta)
979                     continue;
980
981                 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
982                 MainBoxWrite(p_intf, l++, 1, "  [%s]",
983                              vlc_meta_TypeToLocalizedString(i));
984                 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
985                 MainBoxWrite(p_intf, l++, 1, "      %s", psz_meta);
986             }
987             vlc_mutex_unlock(&p_item->lock);
988         }
989         else
990             MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
991
992         p_sys->i_box_lines_total = l;
993         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
994             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
995
996         y += __MIN(l - p_sys->i_box_start, p_sys->i_box_lines);
997     }
998 #if 0 /* Deprecated API */
999     else if (p_sys->i_box_type == BOX_LOG)
1000     {
1001         int i_line = 0;
1002         int i_stop;
1003         int i_start;
1004
1005         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Logs "), p_sys->b_color);
1006
1007
1008         i_start = p_intf->p_sys->p_sub->i_start;
1009
1010         vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1011         i_stop = *p_intf->p_sys->p_sub->pi_stop;
1012         vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1013
1014         for(;;)
1015         {
1016             static const char *ppsz_type[4] = { "", "error", "warning", "debug" };
1017             if (i_line >= h - 2)
1018             {
1019                 break;
1020             }
1021             i_stop--;
1022             i_line++;
1023             if (i_stop < 0) i_stop += VLC_MSG_QSIZE;
1024             if (i_stop == i_start)
1025             {
1026                 break;
1027             }
1028             if (p_sys->b_color)
1029                 wcolor_set(p_sys->w,
1030                     p_sys->p_sub->p_msg[i_stop].i_type + C_INFO,
1031                     NULL);
1032             mvnprintw(y + h-2-i_line, 1, COLS - 2, "   [%s] %s",
1033                       ppsz_type[p_sys->p_sub->p_msg[i_stop].i_type],
1034                       p_sys->p_sub->p_msg[i_stop].psz_msg);
1035             if (p_sys->b_color)
1036                 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1037         }
1038
1039         vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1040         p_intf->p_sys->p_sub->i_start = i_stop;
1041         vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1042         y = y_end;
1043     }
1044 #endif
1045     else if (p_sys->i_box_type == BOX_BROWSE)
1046     {
1047         /* Filebrowser box */
1048         int        i_start, i_stop;
1049         int        i_item;
1050         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Browse "), p_sys->b_color);
1051
1052         if (p_sys->i_box_bidx >= p_sys->i_dir_entries) p_sys->i_box_plidx = p_sys->i_dir_entries - 1;
1053         if (p_sys->i_box_bidx < 0) p_sys->i_box_bidx = 0;
1054
1055         if (p_sys->i_box_bidx < (h - 2)/2)
1056         {
1057             i_start = 0;
1058             i_stop = h - 2;
1059         }
1060         else if (p_sys->i_dir_entries - p_sys->i_box_bidx > (h - 2)/2)
1061         {
1062             i_start = p_sys->i_box_bidx - (h - 2)/2;
1063             i_stop = i_start + h - 2;
1064         }
1065         else
1066         {
1067             i_stop = p_sys->i_dir_entries;
1068             i_start = p_sys->i_dir_entries - (h - 2);
1069         }
1070         if (i_start < 0)
1071             i_start = 0;
1072         if (i_stop > p_sys->i_dir_entries)
1073             i_stop = p_sys->i_dir_entries;
1074
1075         for(i_item = i_start; i_item < i_stop; i_item++)
1076         {
1077             bool b_selected = (p_sys->i_box_bidx == i_item);
1078
1079             if (y >= y_end) break;
1080             if (b_selected)
1081                 attrset(A_REVERSE);
1082             if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1083                 wcolor_set(p_sys->w, C_FOLDER, NULL);
1084             mvnprintw(y++, 1, COLS - 2, " %c %s", p_sys->pp_dir_entries[i_item]->b_file == true ? ' ' : '+',
1085                             p_sys->pp_dir_entries[i_item]->psz_path);
1086             if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1087                 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1088
1089             if (b_selected)
1090                 attroff(A_REVERSE);
1091         }
1092
1093     }
1094     else if (p_sys->i_box_type == BOX_OBJECTS)
1095     {
1096         int l = 0;
1097         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Objects "), p_sys->b_color);
1098         DumpObject(p_intf, &l, VLC_OBJECT(p_intf->p_libvlc), 0);
1099
1100         p_sys->i_box_lines_total = l;
1101         if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1102             p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1103
1104         if (l - p_sys->i_box_start < p_sys->i_box_lines)
1105             y += l - p_sys->i_box_start;
1106         else
1107             y += p_sys->i_box_lines;
1108     }
1109     else if (p_sys->i_box_type == BOX_STATS)
1110     {
1111         DrawBox(p_sys->w, y++, 0, h, COLS, _(" Stats "), p_sys->b_color);
1112
1113         if (p_input)
1114         {
1115             input_item_t *p_item = input_GetItem(p_input);
1116             assert(p_item);
1117             vlc_mutex_lock(&p_item->lock);
1118             vlc_mutex_lock(&p_item->p_stats->lock);
1119
1120             int i_audio = 0;
1121             int i_video = 0;
1122             int i;
1123
1124             if (!p_item->i_es)
1125                 i_video = i_audio = 1;
1126             else
1127                 for(i = 0; i < p_item->i_es ; i++)
1128                 {
1129                     i_audio += (p_item->es[i]->i_cat == AUDIO_ES);
1130                     i_video += (p_item->es[i]->i_cat == VIDEO_ES);
1131                 }
1132
1133             int l = 0;
1134
1135 #define SHOW_ACS(x,c) \
1136     if (l >= p_sys->i_box_start && l - p_sys->i_box_start < p_sys->i_box_lines) \
1137         mvaddch(p_sys->i_box_y - p_sys->i_box_start + l, x, c)
1138
1139             /* Input */
1140             if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1141             MainBoxWrite(p_intf, l, 1, _("+-[Incoming]"));
1142             SHOW_ACS(1, ACS_ULCORNER);  SHOW_ACS(2, ACS_HLINE); l++;
1143             if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1144             MainBoxWrite(p_intf, l, 1, _("| input bytes read : %8.0f KiB"),
1145                     (float)(p_item->p_stats->i_read_bytes)/1024);
1146             SHOW_ACS(1, ACS_VLINE); l++;
1147             MainBoxWrite(p_intf, l, 1, _("| input bitrate    :   %6.0f kb/s"),
1148                     (float)(p_item->p_stats->f_input_bitrate)*8000);
1149             MainBoxWrite(p_intf, l, 1, _("| demux bytes read : %8.0f KiB"),
1150                     (float)(p_item->p_stats->i_demux_read_bytes)/1024);
1151             SHOW_ACS(1, ACS_VLINE); l++;
1152             MainBoxWrite(p_intf, l, 1, _("| demux bitrate    :   %6.0f kb/s"),
1153                     (float)(p_item->p_stats->f_demux_bitrate)*8000);
1154             SHOW_ACS(1, ACS_VLINE); l++;
1155             DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1156             SHOW_ACS(1, ACS_VLINE); l++;
1157
1158             /* Video */
1159             if (i_video)
1160             {
1161                 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1162                 MainBoxWrite(p_intf, l, 1, _("+-[Video Decoding]"));
1163                 SHOW_ACS(1, ACS_LTEE);  SHOW_ACS(2, ACS_HLINE); l++;
1164                 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1165                 MainBoxWrite(p_intf, l, 1, _("| video decoded    :    %"PRId64),
1166                         p_item->p_stats->i_decoded_video);
1167                 SHOW_ACS(1, ACS_VLINE); l++;
1168                 MainBoxWrite(p_intf, l, 1, _("| frames displayed :    %"PRId64),
1169                         p_item->p_stats->i_displayed_pictures);
1170                 SHOW_ACS(1, ACS_VLINE); l++;
1171                 MainBoxWrite(p_intf, l, 1, _("| frames lost      :    %"PRId64),
1172                         p_item->p_stats->i_lost_pictures);
1173                 SHOW_ACS(1, ACS_VLINE); l++;
1174                 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1175                 SHOW_ACS(1, ACS_VLINE); l++;
1176             }
1177             /* Audio*/
1178             if (i_audio)
1179             {
1180                 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1181                 MainBoxWrite(p_intf, l, 1, _("+-[Audio Decoding]"));
1182                 SHOW_ACS(1, ACS_LTEE);  SHOW_ACS(2, ACS_HLINE); l++;
1183                 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1184                 MainBoxWrite(p_intf, l, 1, _("| audio decoded    :    %"PRId64),
1185                         p_item->p_stats->i_decoded_audio);
1186                 SHOW_ACS(1, ACS_VLINE); l++;
1187                 MainBoxWrite(p_intf, l, 1, _("| buffers played   :    %"PRId64),
1188                         p_item->p_stats->i_played_abuffers);
1189                 SHOW_ACS(1, ACS_VLINE); l++;
1190                 MainBoxWrite(p_intf, l, 1, _("| buffers lost     :    %"PRId64),
1191                         p_item->p_stats->i_lost_abuffers);
1192                 SHOW_ACS(1, ACS_VLINE); l++;
1193                 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1194                 SHOW_ACS(1, ACS_VLINE); l++;
1195             }
1196             /* Sout */
1197             if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1198             MainBoxWrite(p_intf, l, 1, _("+-[Streaming]"));
1199             SHOW_ACS(1, ACS_LTEE);  SHOW_ACS(2, ACS_HLINE); l++;
1200             if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1201             MainBoxWrite(p_intf, l, 1, _("| packets sent     :    %5i"), p_item->p_stats->i_sent_packets);
1202             SHOW_ACS(1, ACS_VLINE); l++;
1203             MainBoxWrite(p_intf, l, 1, _("| bytes sent       : %8.0f KiB"),
1204                     (float)(p_item->p_stats->i_sent_bytes)/1024);
1205             SHOW_ACS(1, ACS_VLINE); l++;
1206             MainBoxWrite(p_intf, l, 1, _("\\ sending bitrate  :   %6.0f kb/s"),
1207                     (float)(p_item->p_stats->f_send_bitrate*8)*1000);
1208             SHOW_ACS(1, ACS_LLCORNER); l++;
1209             if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1210
1211 #undef SHOW_ACS
1212
1213             p_sys->i_box_lines_total = l;
1214             if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1215                 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1216
1217             if (l - p_sys->i_box_start < p_sys->i_box_lines)
1218                 y += l - p_sys->i_box_start;
1219             else
1220                 y += p_sys->i_box_lines;
1221
1222             vlc_mutex_unlock(&p_item->p_stats->lock);
1223             vlc_mutex_unlock(&p_item->lock);
1224
1225         }
1226     }
1227     else if (p_sys->i_box_type == BOX_PLAYLIST ||
1228                p_sys->i_box_type == BOX_SEARCH ||
1229                p_sys->i_box_type == BOX_OPEN )
1230     {
1231         /* Playlist box */
1232         int        i_start, i_stop, i_max = p_sys->i_plist_entries;
1233         int        i_item;
1234         char       *psz_title;
1235
1236         if (p_sys->category_view)
1237             psz_title = strdup(_(" Playlist (By category) "));
1238         else
1239             psz_title = strdup(_(" Playlist (All, one level) "));
1240
1241         DrawBox(p_sys->w, y++, 0, h, COLS, psz_title, p_sys->b_color);
1242         free(psz_title);
1243
1244         if (p_sys->b_need_update || !p_sys->pp_plist)
1245             PlaylistRebuild(p_intf);
1246         if (p_sys->b_box_plidx_follow)
1247             FindIndex(p_sys, p_playlist, false);
1248
1249         if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1250         if (p_sys->i_box_plidx >= i_max) p_sys->i_box_plidx = i_max - 1;
1251
1252         if (p_sys->i_box_plidx < (h - 2)/2)
1253         {
1254             i_start = 0;
1255             i_stop = h - 2;
1256         }
1257         else if (i_max - p_sys->i_box_plidx > (h - 2)/2)
1258         {
1259             i_start = p_sys->i_box_plidx - (h - 2)/2;
1260             i_stop = i_start + h - 2;
1261         }
1262         else
1263         {
1264             i_stop = i_max;
1265             i_start = i_max - (h - 2);
1266         }
1267         if (i_start < 0)
1268             i_start = 0;
1269         if (i_stop > i_max)
1270             i_stop = i_max;
1271
1272         for(i_item = i_start; i_item < i_stop; i_item++)
1273         {
1274             bool b_selected = (p_sys->i_box_plidx == i_item);
1275             playlist_item_t *p_item = p_sys->pp_plist[i_item]->p_item;
1276             playlist_item_t *p_node = p_sys->p_node;
1277             int c = ' ';
1278             input_thread_t *p_input2 = playlist_CurrentInput(p_playlist);
1279
1280             PL_LOCK;
1281             assert(p_item);
1282             playlist_item_t *p_current_playing_item = playlist_CurrentPlayingItem(p_playlist);
1283             if ((p_node && p_item->p_input == p_node->p_input) ||
1284                         (!p_node && p_input2 && p_current_playing_item &&
1285                           p_item->p_input == p_current_playing_item->p_input))
1286                 c = '*';
1287             else if (p_item == p_node || (p_item != p_node &&
1288                         PlaylistIsPlaying(p_playlist, p_item)))
1289                 c = '>';
1290             PL_UNLOCK;
1291
1292             if (p_input2)
1293                 vlc_object_release(p_input2);
1294
1295             if (y >= y_end) break;
1296             if (b_selected)
1297                 attrset(A_REVERSE);
1298             if (p_sys->b_color)
1299                 wcolor_set(p_sys->w, i_item % 3 + C_PLAYLIST_1, NULL);
1300             mvnprintw(y++, 1, COLS - 2, "%c%s", c,
1301                        p_sys->pp_plist[i_item]->psz_display);
1302             if (p_sys->b_color)
1303                 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1304             if (b_selected)
1305                 attroff(A_REVERSE);
1306         }
1307
1308     }
1309     else
1310         y++;
1311
1312     if (p_sys->i_box_type == BOX_SEARCH)
1313     {
1314         DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1315         mvnprintw(7, 1, COLS-2, _("Find: %s"), p_sys->psz_old_search ?
1316                   p_sys->psz_old_search : p_sys->psz_search_chain);
1317     }
1318     if (p_sys->i_box_type == BOX_OPEN)
1319     {
1320         DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1321         mvnprintw(7, 1, COLS-2, _("Open: %s"), p_sys->psz_open_chain);
1322     }
1323
1324     while (y < y_end)
1325         DrawEmptyLine(p_sys->w, y++, 1, COLS - 2);
1326
1327     refresh();
1328
1329     *t_last_refresh = time(0);
1330 }
1331
1332 static void ChangePosition(intf_thread_t *p_intf, float increment)
1333 {
1334     intf_sys_t     *p_sys = p_intf->p_sys;
1335     input_thread_t *p_input = p_sys->p_input;
1336     float pos;
1337
1338     if (!p_input || var_GetInteger(p_input, "state") != PLAYING_S)
1339         return;
1340
1341     pos = var_GetFloat(p_input, "position") + increment;
1342
1343     if (pos > 0.99) pos = 0.99;
1344     if (pos < 0.0)  pos = 0.0;
1345
1346     var_SetFloat(p_input, "position", pos);
1347 }
1348
1349 static inline int RemoveLastUTF8Entity(char *psz, int len)
1350 {
1351     while (len && ((psz[--len] & 0xc0) == 0x80));
1352                        /* UTF8 continuation byte */
1353
1354     psz[len] = '\0';
1355     return len;
1356 }
1357
1358 static char *GetDiscDevice(intf_thread_t *p_intf, const char *name)
1359 {
1360     static const struct { const char *s; size_t n; const char *v; } devs[] =
1361     {
1362         { "cdda://", 7, "cd-audio", },
1363         { "dvd://",  6, "dvd",      },
1364         { "vcd://",  6, "vcd",      },
1365     };
1366     char *device;
1367
1368     for (unsigned i = 0; i < sizeof devs / sizeof *devs; i++)
1369     {
1370         size_t n = devs[i].n;
1371         if (!strncmp(name, devs[i].s, n))
1372             switch(name[n])
1373             {
1374             case '\0':
1375             case '@':
1376                 return config_GetPsz(p_intf, devs[i].v);
1377             default:
1378                 /* Omit the beginning MRL-selector characters */
1379                 return strdup(name + n);
1380             }
1381     }
1382
1383     device = strdup(name);
1384
1385     if (device) /* Remove what we have after @ */
1386         device[strcspn(device, "@")] = '\0';
1387
1388     return device;
1389 }
1390
1391 static void Eject(intf_thread_t *p_intf)
1392 {
1393     char *psz_device, *psz_name;
1394     playlist_t * p_playlist = pl_Get(p_intf);
1395
1396     /* If there's a stream playing, we aren't allowed to eject ! */
1397     if (p_intf->p_sys->p_input)
1398         return;
1399
1400     PL_LOCK;
1401
1402     if (!playlist_CurrentPlayingItem(p_playlist))
1403     {
1404         PL_UNLOCK;
1405         return;
1406     }
1407
1408     psz_name = playlist_CurrentPlayingItem(p_playlist)->p_input->psz_name;
1409     psz_device = psz_name ? GetDiscDevice(p_intf, psz_name) : NULL;
1410
1411     PL_UNLOCK;
1412
1413     if (psz_device)
1414     {
1415         intf_Eject(p_intf, psz_device);
1416         free(psz_device);
1417     }
1418 }
1419
1420 static void PlayPause(intf_thread_t *p_intf)
1421 {
1422     input_thread_t *p_input = p_intf->p_sys->p_input;
1423
1424     if (p_input)
1425     {
1426         int64_t state = var_GetInteger( p_input, "state" );
1427         state = (state != PLAYING_S) ? PLAYING_S : PAUSE_S;
1428         var_SetInteger( p_input, "state", state );
1429     }
1430     else
1431         playlist_Play(pl_Get(p_intf));
1432 }
1433
1434 static inline void BoxSwitch(intf_sys_t *p_sys, int box)
1435 {
1436     p_sys->i_box_type = (p_sys->i_box_type == box) ? BOX_NONE : box;
1437 }
1438
1439 static bool HandlePlaylistKey(intf_thread_t *p_intf, int key)
1440 {
1441     bool b_box_plidx_follow = false;
1442     intf_sys_t *p_sys = p_intf->p_sys;
1443     playlist_t *p_playlist = pl_Get(p_intf);
1444     struct pl_item_t *p_pl_item;
1445
1446     switch(key)
1447     {
1448     /* Playlist Settings */
1449     case 'r': var_ToggleBool(p_playlist, "random"); return true;
1450     case 'l': var_ToggleBool(p_playlist, "loop");   return true;
1451     case 'R': var_ToggleBool(p_playlist, "repeat"); return true;
1452
1453     /* Playlist sort */
1454     case 'o':
1455     case 'O':
1456         playlist_RecursiveNodeSort(p_playlist, PlaylistGetRoot(p_intf),
1457                                     SORT_TITLE_NODES_FIRST,
1458                                     (key == 'o')? ORDER_NORMAL : ORDER_REVERSE);
1459         p_sys->b_need_update = true;
1460         return true;
1461
1462     /* Playlist view */
1463     case 'v':
1464         p_sys->category_view = !p_sys->category_view;
1465         p_sys->b_need_update = true;
1466         return true;
1467
1468     /* Playlist navigation */
1469 #ifdef __FreeBSD__
1470 /* workaround for FreeBSD + xterm:
1471 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1472     case KEY_SELECT:
1473 #endif
1474     case KEY_END:   p_sys->i_box_plidx = p_playlist->items.i_size - 1;  break;
1475     case KEY_HOME:  p_sys->i_box_plidx = 0;                             break;
1476     case KEY_UP:    p_sys->i_box_plidx--;                               break;
1477     case KEY_DOWN:  p_sys->i_box_plidx++;                               break;
1478     case KEY_PPAGE: p_sys->i_box_plidx -= p_sys->i_box_lines;           break;
1479     case KEY_NPAGE: p_sys->i_box_plidx += p_sys->i_box_lines;           break;
1480     case 'g':       FindIndex(p_sys, p_playlist, false);                break;
1481
1482     /* Deletion */
1483     case 'D':
1484     case KEY_BACKSPACE:
1485     case 0x7f:
1486     case KEY_DC:
1487     {
1488         playlist_item_t *p_item;
1489
1490         PL_LOCK;
1491         p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1492         if (p_item->i_children == -1)
1493             playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1494         else
1495             playlist_NodeDelete(p_playlist, p_item, true , false);
1496         PL_UNLOCK;
1497         p_sys->b_need_update = true;
1498         break;
1499     }
1500
1501     case KEY_ENTER:
1502     case '\r':
1503     case '\n':
1504         if (!(p_pl_item = p_sys->pp_plist[p_sys->i_box_plidx]))
1505             return false;
1506
1507         if (p_pl_item->p_item->i_children)
1508         {
1509             playlist_item_t *p_item, *p_parent = p_pl_item->p_item;
1510             if (p_parent->i_children == -1)
1511             {
1512                 p_item = p_parent;
1513
1514                 while (p_parent->p_parent)
1515                     p_parent = p_parent->p_parent;
1516             }
1517             else
1518             {
1519                 p_sys->p_node = p_parent;
1520                 p_item = NULL;
1521             }
1522
1523             playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked,
1524                               p_parent, p_item);
1525         }
1526         else
1527         {   /* We only want to set the current node */
1528             playlist_Stop(p_playlist);
1529             p_sys->p_node = p_pl_item->p_item;
1530         }
1531
1532         b_box_plidx_follow = true;
1533         break;
1534
1535     default:
1536         return false;
1537     }
1538
1539     if (p_sys->i_box_plidx > p_sys->i_plist_entries - 1)
1540         p_sys->i_box_plidx = p_sys->i_plist_entries - 1;
1541     if (p_sys->i_box_plidx < 0)
1542         p_sys->i_box_plidx = 0;
1543
1544     p_pl_item = p_sys->pp_plist[p_sys->i_box_plidx];
1545
1546     PL_LOCK;
1547     if (PlaylistIsPlaying(p_playlist, p_pl_item->p_item))
1548         b_box_plidx_follow = true;
1549     PL_UNLOCK;
1550     p_sys->b_box_plidx_follow = b_box_plidx_follow;
1551     return true;
1552 }
1553
1554 static bool HandleBrowseKey(intf_thread_t *p_intf, int key)
1555 {
1556     struct dir_entry_t *dir_entry;
1557     intf_sys_t *p_sys = p_intf->p_sys;
1558
1559     switch(key)
1560     {
1561     case '.':
1562         p_sys->b_show_hidden_files = !p_sys->b_show_hidden_files;
1563         ReadDir(p_intf);
1564         return true;
1565
1566     case KEY_ENTER:
1567     case '\r':
1568     case '\n':
1569     case ' ':
1570         dir_entry = p_sys->pp_dir_entries[p_sys->i_box_bidx];
1571
1572         if (!dir_entry->b_file && key != ' ')
1573         {
1574             char *current_dir = p_sys->psz_current_dir;
1575             if (asprintf(&p_sys->psz_current_dir, "%s/%s",
1576                           p_sys->psz_current_dir, dir_entry->psz_path) != -1)
1577             {
1578                 ReadDir(p_intf);
1579                 free(current_dir);
1580             }
1581             else
1582                 p_sys->psz_current_dir = current_dir;
1583
1584             return true;
1585         }
1586
1587         char* psz_uri;
1588         if (asprintf(&psz_uri, "%s://%s/%s",
1589                     dir_entry->b_file ? "file" : "directory",
1590                     p_sys->psz_current_dir, dir_entry->psz_path) == -1)
1591         {
1592             return false;
1593         }
1594
1595         playlist_t *p_playlist = pl_Get(p_intf);
1596         playlist_item_t *p_parent = p_sys->p_node;
1597         if (!p_parent)
1598         {
1599             playlist_item_t *p_item;
1600             PL_LOCK;
1601             p_item = playlist_CurrentPlayingItem(p_playlist);
1602             p_parent = p_item ? p_item->p_parent : NULL;
1603             PL_UNLOCK;
1604             if (!p_parent)
1605                 p_parent = p_playlist->p_local_onelevel;
1606         }
1607
1608         while (p_parent->p_parent && p_parent->p_parent->p_parent)
1609             p_parent = p_parent->p_parent;
1610
1611         input_item_t *p_input = p_playlist->p_local_onelevel->p_input;
1612         playlist_Add(p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
1613                       PLAYLIST_END, p_parent->p_input == p_input, false);
1614
1615         p_sys->i_box_type = BOX_PLAYLIST;
1616         free(psz_uri);
1617         return true;
1618
1619 #ifdef __FreeBSD__
1620     case KEY_SELECT:
1621 #endif
1622     case KEY_END:   p_sys->i_box_bidx = p_sys->i_dir_entries - 1;   break;
1623     case KEY_HOME:  p_sys->i_box_bidx = 0;                          break;
1624     case KEY_UP:    p_sys->i_box_bidx--;                            break;
1625     case KEY_DOWN:  p_sys->i_box_bidx++;                            break;
1626     case KEY_PPAGE: p_sys->i_box_bidx -= p_sys->i_box_lines;        break;
1627     case KEY_NPAGE: p_sys->i_box_bidx += p_sys->i_box_lines;        break;
1628
1629     default:
1630         return false;
1631     }
1632
1633     if (p_sys->i_box_bidx >= p_sys->i_dir_entries)
1634         p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
1635     if (p_sys->i_box_bidx < 0)
1636         p_sys->i_box_bidx = 0;
1637
1638     return true;
1639 }
1640
1641 static void InputNavigate(input_thread_t* p_input, const char *var)
1642 {
1643     if (p_input)
1644         var_TriggerCallback(p_input, var);
1645 }
1646
1647 static int HandleKey(intf_thread_t *p_intf)
1648 {
1649     intf_sys_t *p_sys = p_intf->p_sys;
1650     playlist_t *p_playlist = pl_Get(p_intf);
1651     int i_key = wgetch(p_sys->w);
1652
1653     if (i_key == -1)
1654         return 0;
1655
1656     if (p_sys->i_box_type == BOX_PLAYLIST)
1657     {
1658         if (HandlePlaylistKey(p_intf, i_key))
1659             return 1;
1660     }
1661     else if (p_sys->i_box_type == BOX_BROWSE)
1662     {
1663         if (HandleBrowseKey(p_intf, i_key))
1664             return 1;
1665     }
1666     else if (p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
1667              p_sys->i_box_type == BOX_META || p_sys->i_box_type == BOX_STATS ||
1668              p_sys->i_box_type == BOX_OBJECTS)
1669     {
1670         bool ret = true;
1671         switch(i_key)
1672         {
1673 #ifdef __FreeBSD__
1674         case KEY_SELECT:
1675 #endif
1676         case KEY_END:  p_sys->i_box_start = p_sys->i_box_lines_total - 1; break;
1677         case KEY_HOME: p_sys->i_box_start = 0;                            break;
1678         case KEY_UP:   p_sys->i_box_start--;                              break;
1679         case KEY_DOWN: p_sys->i_box_start++;                              break;
1680         case KEY_PPAGE:p_sys->i_box_start -= p_sys->i_box_lines;          break;
1681         case KEY_NPAGE:p_sys->i_box_start += p_sys->i_box_lines;          break;
1682
1683         default:
1684             ret = false;
1685             break;
1686         }
1687         if (ret)
1688         {
1689             if (p_sys->i_box_start < 0)
1690                 p_sys->i_box_start = 0;
1691             if (p_sys->i_box_start > p_sys->i_box_lines_total - 1)
1692                 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1693             return 1;
1694         }
1695     }
1696     else if (p_sys->i_box_type == BOX_NONE)
1697     {
1698         switch(i_key)
1699         {
1700 #ifdef __FreeBSD__
1701         case KEY_SELECT:
1702 #endif
1703         case KEY_END:   ChangePosition(p_intf, +.99);   return 1;
1704         case KEY_HOME:  ChangePosition(p_intf, -1.0);   return 1;
1705         case KEY_UP:    ChangePosition(p_intf, +0.05);  return 1;
1706         case KEY_DOWN:  ChangePosition(p_intf, -0.05);  return 1;
1707         }
1708     }
1709     else if (p_sys->i_box_type == BOX_SEARCH)
1710     {
1711         size_t i_chain_len = strlen(p_sys->psz_search_chain);
1712         switch(i_key)
1713         {
1714         case KEY_CLEAR:
1715         case 0x0c:      /* ^l */
1716             clear();
1717             return 1;
1718         case KEY_ENTER:
1719         case '\r':
1720         case '\n':
1721             if (i_chain_len)
1722                 p_sys->psz_old_search = strdup(p_sys->psz_search_chain);
1723             else if (p_sys->psz_old_search)
1724                 SearchPlaylist(p_sys, p_sys->psz_old_search);
1725             p_sys->i_box_type = BOX_PLAYLIST;
1726             return 1;
1727         case 0x1b: /* ESC */
1728             /* Alt+key combinations return 2 keys in the terminal keyboard:
1729              * ESC, and the 2nd key.
1730              * If some other key is available immediately (where immediately
1731              * means after wgetch() 1 second delay), that means that the
1732              * ESC key was not pressed.
1733              *
1734              * man 3X curs_getch says:
1735              *
1736              * Use of the escape key by a programmer for a single
1737              * character function is discouraged, as it will cause a delay
1738              * of up to one second while the keypad code looks for a
1739              * following function-key sequence.
1740              *
1741              */
1742             if (wgetch(p_sys->w) != ERR)
1743                 return 0;
1744             p_sys->i_box_plidx = p_sys->i_before_search;
1745             p_sys->i_box_type = BOX_PLAYLIST;
1746             return 1;
1747         case KEY_BACKSPACE:
1748         case 0x7f:
1749             RemoveLastUTF8Entity(p_sys->psz_search_chain, i_chain_len);
1750             break;
1751         default:
1752             if (i_chain_len + 1 < sizeof p_sys->psz_search_chain)
1753             {
1754                 p_sys->psz_search_chain[i_chain_len] = (char) i_key;
1755                 p_sys->psz_search_chain[i_chain_len + 1] = '\0';
1756             }
1757         }
1758         free(p_sys->psz_old_search);
1759         p_sys->psz_old_search = NULL;
1760         SearchPlaylist(p_sys, p_sys->psz_search_chain);
1761         return 1;
1762     }
1763     else if (p_sys->i_box_type == BOX_OPEN)
1764     {
1765         size_t i_chain_len = strlen(p_sys->psz_open_chain);
1766
1767         switch(i_key)
1768         {
1769         case KEY_CLEAR:
1770         case 0x0c:          /* ^l */
1771             clear();
1772             return 1;
1773         case KEY_ENTER:
1774         case '\r':
1775         case '\n':
1776             if (i_chain_len)
1777             {
1778                 playlist_item_t *p_parent = p_sys->p_node;
1779
1780                 PL_LOCK;
1781                 if (!p_parent)
1782                 p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
1783                 if (!p_parent)
1784                     p_parent = p_playlist->p_local_onelevel;
1785
1786                 while (p_parent->p_parent && p_parent->p_parent->p_parent)
1787                     p_parent = p_parent->p_parent;
1788                 PL_UNLOCK;
1789
1790                 playlist_Add(p_playlist, p_sys->psz_open_chain, NULL,
1791                               PLAYLIST_APPEND|PLAYLIST_GO, PLAYLIST_END,
1792                               p_parent->p_input ==
1793                                 p_playlist->p_local_onelevel->p_input
1794                               , false);
1795
1796                 p_sys->b_box_plidx_follow = true;
1797             }
1798             p_sys->i_box_type = BOX_PLAYLIST;
1799             return 1;
1800         case 0x1b:  /* ESC */
1801             if (wgetch(p_sys->w) != ERR)
1802                 return 0;
1803             p_sys->i_box_type = BOX_PLAYLIST;
1804             return 1;
1805         case KEY_BACKSPACE:
1806         case 0x7f:
1807             RemoveLastUTF8Entity(p_sys->psz_open_chain, i_chain_len);
1808             return 1;
1809         default:
1810             if (i_chain_len + 1 < sizeof p_sys->psz_open_chain)
1811             {
1812                 p_sys->psz_open_chain[i_chain_len] = (char) i_key;
1813                 p_sys->psz_open_chain[i_chain_len + 1] = '\0';
1814             }
1815         }
1816         return 1;
1817     }
1818
1819     /* Common keys */
1820     switch(i_key)
1821     {
1822     case 0x1b:  /* ESC */
1823         if (wgetch(p_sys->w) != ERR)
1824             return 0;
1825
1826     case 'q':
1827     case 'Q':
1828     case KEY_EXIT:
1829         libvlc_Quit(p_intf->p_libvlc);
1830         return 0;
1831
1832     case 'h':
1833     case 'H': BoxSwitch(p_sys, BOX_HELP);       return 1;
1834     case 'i': BoxSwitch(p_sys, BOX_INFO);       return 1;
1835     case 'm': BoxSwitch(p_sys, BOX_META);       return 1;
1836 //  case 'L': BoxSwitch(p_sys, BOX_LOG)         return 1;
1837     case 'P': BoxSwitch(p_sys, BOX_PLAYLIST);   return 1;
1838     case 'B': BoxSwitch(p_sys, BOX_BROWSE);     return 1;
1839     case 'x': BoxSwitch(p_sys, BOX_OBJECTS);    return 1;
1840     case 'S': BoxSwitch(p_sys, BOX_STATS);      return 1;
1841
1842     case '/': /* Search */
1843         p_sys->psz_search_chain[0] = '\0';
1844         p_sys->b_box_plidx_follow = false;
1845         p_sys->i_before_search = p_sys->i_box_plidx;
1846         p_sys->i_box_type = BOX_SEARCH;
1847         return 1;
1848
1849     case 'A': /* Open */
1850         p_sys->psz_open_chain[0] = '\0';
1851         p_sys->i_box_type = BOX_OPEN;
1852         return 1;
1853
1854     /* Navigation */
1855     case KEY_RIGHT: ChangePosition(p_intf, +0.01); return 1;
1856     case KEY_LEFT:  ChangePosition(p_intf, -0.01); return 1;
1857
1858     /* Common control */
1859     case 'f':
1860         if (p_sys->p_input)
1861         {
1862             vout_thread_t *p_vout = input_GetVout(p_sys->p_input);
1863             if (p_vout)
1864             {
1865                 bool fs = var_ToggleBool(p_playlist, "fullscreen");
1866                 var_SetBool(p_vout, "fullscreen", fs);
1867                 vlc_object_release(p_vout);
1868             }
1869         }
1870         return 0;
1871
1872     case ' ': PlayPause(p_intf);            return 1;
1873     case 's': playlist_Stop(p_playlist);    return 1;
1874     case 'e': Eject(p_intf);                return 1;
1875
1876     case '[': InputNavigate(p_sys->p_input, "prev-title");      return 1;
1877     case ']': InputNavigate(p_sys->p_input, "next-title");      return 1;
1878     case '<': InputNavigate(p_sys->p_input, "prev-chapter");    return 1;
1879     case '>': InputNavigate(p_sys->p_input, "next-chapter");    return 1;
1880
1881     case 'p': playlist_Prev(p_playlist);            goto lclear;
1882     case 'n': playlist_Next(p_playlist);            goto lclear;
1883     case 'a': aout_VolumeUp(p_playlist, 1, NULL);   goto lclear;
1884     case 'z': aout_VolumeDown(p_playlist, 1, NULL); goto lclear;
1885
1886 lclear:
1887     case 0x0c:  /* ^l */
1888     case KEY_CLEAR:     clear(); return 1;
1889     }
1890
1891     /* key not handled */
1892     return 0;
1893 }
1894
1895 /*****************************************************************************
1896  * Run: ncurses thread
1897  *****************************************************************************/
1898 static void Run(intf_thread_t *p_intf)
1899 {
1900     intf_sys_t    *p_sys = p_intf->p_sys;
1901     playlist_t    *p_playlist = pl_Get(p_intf);
1902     bool           force_redraw = false;
1903
1904     time_t t_last_refresh;
1905     int canc = vlc_savecancel();
1906
1907     PlaylistRebuild(p_intf);
1908     Redraw(p_intf, &t_last_refresh);
1909
1910     var_AddCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
1911     var_AddCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
1912
1913     while (vlc_object_alive(p_intf))
1914     {
1915         msleep(INTF_IDLE_SLEEP);
1916
1917         /* Update the input */
1918         if (!p_sys->p_input)
1919         {
1920             p_sys->p_input = playlist_CurrentInput(p_playlist);
1921             force_redraw = true;
1922         }
1923         else if (p_sys->p_input->b_dead)
1924         {
1925             vlc_object_release(p_sys->p_input);
1926             p_sys->p_input = NULL;
1927         }
1928
1929         PL_LOCK;
1930         if (p_sys->b_box_plidx_follow && playlist_CurrentPlayingItem(p_playlist))
1931             FindIndex(p_sys, p_playlist, true);
1932
1933         PL_UNLOCK;
1934
1935         while (HandleKey(p_intf))
1936             Redraw(p_intf, &t_last_refresh);
1937
1938         if (force_redraw)
1939         {
1940             clear();
1941             Redraw(p_intf, &t_last_refresh);
1942             force_redraw = false;
1943         }
1944
1945         if ((time(0) - t_last_refresh) >= 1)
1946             Redraw(p_intf, &t_last_refresh);
1947     }
1948     var_DelCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
1949     var_DelCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
1950     vlc_restorecancel(canc);
1951 }
1952
1953 /*****************************************************************************
1954  * Open: initialize and create window
1955  *****************************************************************************/
1956 static int Open(vlc_object_t *p_this)
1957 {
1958     intf_thread_t *p_intf = (intf_thread_t *)p_this;
1959     intf_sys_t    *p_sys  = p_intf->p_sys = calloc(1, sizeof(intf_sys_t));
1960     if (!p_sys)
1961         return VLC_ENOMEM;
1962
1963     p_sys->i_box_type = BOX_PLAYLIST;
1964     p_sys->b_box_plidx_follow = true;
1965 //  p_sys->p_sub = msg_Subscribe(p_intf);
1966     p_sys->b_color = var_CreateGetBool(p_intf, "color");
1967
1968     p_sys->category_view = true; //FIXME: switching back & forth is broken
1969
1970     p_sys->psz_current_dir = var_CreateGetString(p_intf, "browse-dir");
1971     if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
1972     {
1973         free(p_sys->psz_current_dir);
1974         p_sys->psz_current_dir = config_GetUserDir(VLC_HOME_DIR);
1975     }
1976
1977     p_sys->w = initscr();   /* Initialize the curses library */
1978
1979     if (p_sys->b_color)
1980         start_color_and_pairs(p_intf);
1981
1982     keypad(p_sys->w, TRUE); /* Don't do NL -> CR/NL */
1983     nonl();                 /* Take input chars one at a time */
1984     cbreak();               /* Don't echo */
1985     noecho();               /* Invisible cursor */
1986     curs_set(0);            /* Non blocking wgetch() */
1987     wtimeout(p_sys->w, 0);
1988     clear();
1989
1990     /* Stop printing errors to the console */
1991     freopen("/dev/null", "wb", stderr);
1992
1993     ReadDir(p_intf);
1994
1995     p_intf->pf_run = Run;
1996     return VLC_SUCCESS;
1997 }
1998
1999 /*****************************************************************************
2000  * Close: destroy interface window
2001  *****************************************************************************/
2002 static void Close(vlc_object_t *p_this)
2003 {
2004     intf_sys_t *p_sys = ((intf_thread_t*)p_this)->p_sys;
2005
2006     PlaylistDestroy(p_sys);
2007     DirsDestroy(p_sys);
2008
2009     free(p_sys->psz_current_dir);
2010     free(p_sys->psz_old_search);
2011
2012     if (p_sys->p_input)
2013         vlc_object_release(p_sys->p_input);
2014
2015     /* Close the ncurses interface */
2016     endwin();
2017
2018 //  msg_Unsubscribe(p_intf, p_sys->p_sub);
2019
2020     free(p_sys);
2021 }