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