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