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