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