1 /*****************************************************************************
2 * ncurses.c : NCurses interface for vlc
3 *****************************************************************************
4 * Copyright © 2001-2010 the VideoLAN team
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>
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.
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.
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 *****************************************************************************/
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.
34 /*****************************************************************************
36 *****************************************************************************/
41 #include <vlc_common.h>
42 #include <vlc_plugin.h>
45 # define _XOPEN_SOURCE_EXTENDED 1
51 #include <vlc_interface.h>
54 #include <vlc_charset.h>
55 #include <vlc_input.h>
57 #include <vlc_playlist.h>
63 #ifdef HAVE_SYS_STAT_H
64 # include <sys/stat.h>
67 #define SEARCH_CHAIN_SIZE 20
68 #define OPEN_CHAIN_SIZE 50
70 /*****************************************************************************
72 *****************************************************************************/
73 static int Open (vlc_object_t *);
74 static void Close (vlc_object_t *);
76 /*****************************************************************************
78 *****************************************************************************/
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.")
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)
96 /*****************************************************************************
97 * intf_sys_t: description and status of ncurses interface
98 *****************************************************************************/
131 /* XXX: new elements here ! */
136 /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
137 static const struct { short f; short b; } color_pairs[] =
139 /* element */ /* foreground*/ /* background*/
140 [C_TITLE] = { COLOR_YELLOW, COLOR_BLACK },
142 /* jamaican playlist, for rastafari sisters & brothers! */
143 [C_PLAYLIST_1] = { COLOR_GREEN, COLOR_BLACK },
144 [C_PLAYLIST_2] = { COLOR_YELLOW, COLOR_BLACK },
145 [C_PLAYLIST_3] = { COLOR_RED, COLOR_BLACK },
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 },
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 },
159 /* Category title: help, info, metadata */
160 [C_CATEGORY] = { COLOR_MAGENTA, COLOR_BLACK },
161 /* Folder (BOX_BROWSE) */
162 [C_FOLDER] = { COLOR_RED, COLOR_BLACK },
173 playlist_item_t *p_item;
179 input_thread_t *p_input;
182 bool b_color_started;
192 int i_box_lines_total;
195 int i_box_plidx; /* Playlist index */
196 int b_box_plidx_follow;
197 int i_box_bidx; /* browser index */
199 playlist_item_t *p_node; /* current node */
203 // msg_subscription_t* p_sub; /* message bank subscription */
205 char *psz_search_chain; /* for playlist searching */
206 char *psz_old_search; /* for searching next */
209 char *psz_open_chain;
210 #ifndef HAVE_NCURSESW
211 char psz_partial_keys[7];
214 char *psz_current_dir;
216 struct dir_entry_t **pp_dir_entries;
217 bool b_show_hidden_files;
220 struct pl_item_t **pp_plist;
222 bool b_need_update; /* for playlist view */
225 /*****************************************************************************
227 *****************************************************************************/
229 static void DirsDestroy(intf_sys_t *p_sys)
231 while (p_sys->i_dir_entries)
233 struct dir_entry_t *p_dir_entry;
234 p_dir_entry = p_sys->pp_dir_entries[--p_sys->i_dir_entries];
235 free(p_dir_entry->psz_path);
238 free(p_sys->pp_dir_entries);
239 p_sys->pp_dir_entries = NULL;
242 static int comp_dir_entries(const void *pp_dir_entry1, const void *pp_dir_entry2)
244 struct dir_entry_t *p_dir_entry1 = *(struct dir_entry_t**)pp_dir_entry1;
245 struct dir_entry_t *p_dir_entry2 = *(struct dir_entry_t**)pp_dir_entry2;
247 if (p_dir_entry1->b_file == p_dir_entry2->b_file)
248 return strcasecmp(p_dir_entry1->psz_path, p_dir_entry2->psz_path);
250 return p_dir_entry1->b_file ? 1 : -1;
253 static void ReadDir(intf_thread_t *p_intf)
255 intf_sys_t *p_sys = p_intf->p_sys;
258 if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
260 msg_Dbg(p_intf, "no current dir set");
267 p_current_dir = vlc_opendir(p_sys->psz_current_dir);
271 /* something went bad, get out of here ! */
272 msg_Warn(p_intf, "cannot open directory `%s' (%m)",
273 p_sys->psz_current_dir);
277 /* Clean the old shit */
280 /* while we still have entries in the directory */
281 while ((psz_entry = vlc_readdir(p_current_dir)))
284 struct stat stat_data;
286 struct dir_entry_t *p_dir_entry;
287 char *psz_uri = NULL;
289 if (!p_sys->b_show_hidden_files)
290 if (*psz_entry == '.' && strcmp(psz_entry, ".."))
293 if (asprintf(&psz_uri, "%s/%s", p_sys->psz_current_dir, psz_entry) == -1)
299 if (!(p_dir_entry = malloc(sizeof *p_dir_entry)))
302 p_dir_entry->b_file =
304 vlc_stat(psz_uri, &stat_data) || !S_ISDIR(stat_data.st_mode)
305 /*#elif defined(DT_DIR)
306 !(p_dir_content->d_type & DT_DIR)*/
312 p_dir_entry->psz_path = strdup(psz_entry);
313 INSERT_ELEM(p_sys->pp_dir_entries, p_sys->i_dir_entries,
314 p_sys->i_dir_entries, p_dir_entry);
322 qsort(p_sys->pp_dir_entries, p_sys->i_dir_entries,
323 sizeof(struct dir_entry_t*), &comp_dir_entries);
325 closedir(p_current_dir);
328 /*****************************************************************************
330 *****************************************************************************/
332 static void PlaylistDestroy(intf_sys_t *p_sys)
334 while (p_sys->i_plist_entries)
336 struct pl_item_t *p_pl_item = p_sys->pp_plist[--p_sys->i_plist_entries];
337 free(p_pl_item->psz_display);
340 free(p_sys->pp_plist);
341 p_sys->pp_plist = NULL;
344 static inline playlist_item_t *PlaylistGetRoot(intf_thread_t *p_intf)
346 playlist_t *p_playlist = pl_Get(p_intf);
347 return p_intf->p_sys->category_view ?
348 p_playlist->p_root_category :
349 p_playlist->p_root_onelevel;
352 static bool PlaylistAddChild(intf_sys_t *p_sys, playlist_item_t *p_child,
353 const char *c, const char d)
356 char *psz_name = input_item_GetTitleFbName(p_child->p_input);
357 struct pl_item_t *p_pl_item = malloc(sizeof *p_pl_item);
359 if (!psz_name || !p_pl_item)
362 p_pl_item->p_item = p_child;
365 ret = asprintf(&p_pl_item->psz_display, "%s%c-%s", c, d, psz_name);
367 ret = asprintf(&p_pl_item->psz_display, " %s", psz_name);
375 INSERT_ELEM(p_sys->pp_plist, p_sys->i_plist_entries,
376 p_sys->i_plist_entries, p_pl_item);
386 static void PlaylistAddNode(intf_sys_t *p_sys, playlist_item_t *p_node,
389 for(int k = 0; k < p_node->i_children; k++)
391 playlist_item_t *p_child = p_node->pp_children[k];
392 char d = k == p_node->i_children - 1 ? '`' : '|';
393 if(!PlaylistAddChild(p_sys, p_child, c, d))
396 if (p_child->i_children <= 0)
402 if (asprintf(&psz_tmp, "%s%c ", c,
403 k == p_node->i_children - 1 ? ' ' : '|') == -1)
405 PlaylistAddNode(p_sys, p_child, psz_tmp);
409 PlaylistAddNode(p_sys, p_child, " ");
413 static void PlaylistRebuild(intf_thread_t *p_intf)
415 intf_sys_t *p_sys = p_intf->p_sys;
416 playlist_t *p_playlist = pl_Get(p_intf);
420 PlaylistDestroy(p_sys);
421 PlaylistAddNode(p_sys, PlaylistGetRoot(p_intf), "");
422 p_sys->b_need_update = false;
427 static int PlaylistChanged(vlc_object_t *p_this, const char *psz_variable,
428 vlc_value_t oval, vlc_value_t nval, void *param)
430 VLC_UNUSED(p_this); VLC_UNUSED(psz_variable);
431 VLC_UNUSED(oval); VLC_UNUSED(nval);
433 intf_thread_t *p_intf = (intf_thread_t *)param;
434 intf_sys_t *p_sys = p_intf->p_sys;
435 playlist_item_t *p_node = playlist_CurrentPlayingItem(pl_Get(p_intf));
437 p_sys->b_need_update = true;
438 p_sys->p_node = p_node ? p_node->p_parent : NULL;
444 /* This function have to be called with the playlist locked */
445 static inline bool PlaylistIsPlaying(playlist_t *p_playlist,
446 playlist_item_t *p_item)
448 playlist_item_t *p_played_item = playlist_CurrentPlayingItem(p_playlist);
449 return p_item && p_played_item
450 && p_item->p_input && p_played_item->p_input
451 && p_item->p_input->i_id == p_played_item->p_input->i_id;
454 static int SubSearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring,
455 int i_start, int i_stop)
457 for(int i = i_start + 1; i < i_stop; i++)
458 if (strcasestr(p_sys->pp_plist[i]->psz_display, psz_searchstring))
464 static void SearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring)
466 int i_item, i_first = p_sys->i_before_search;
471 if (!psz_searchstring || !*psz_searchstring)
473 p_sys->i_box_plidx = p_sys->i_before_search;
477 i_item = SubSearchPlaylist(p_sys, psz_searchstring, i_first + 1,
478 p_sys->i_plist_entries);
480 i_item = SubSearchPlaylist(p_sys, psz_searchstring, 0, i_first);
483 p_sys->i_box_plidx = i_item;
486 static inline bool IsIndex(intf_sys_t *p_sys, playlist_t *p_playlist, int i)
488 playlist_item_t *p_item = p_sys->pp_plist[i]->p_item;
489 return (p_item->i_children == 0 && p_item == p_sys->p_node) ||
490 PlaylistIsPlaying(p_playlist, p_item);
493 static void FindIndex(intf_sys_t *p_sys, playlist_t *p_playlist, bool locked)
495 int plidx = p_sys->i_box_plidx;
499 if (plidx < 0 || plidx >= p_sys->i_plist_entries ||
500 !IsIndex(p_sys, p_playlist, plidx))
502 for(int i = 0; i < p_sys->i_plist_entries; i++)
503 if (IsIndex(p_sys, p_playlist, i))
505 p_sys->i_box_plidx = i;
514 /****************************************************************************
516 ****************************************************************************/
518 static void start_color_and_pairs(intf_thread_t *p_intf)
520 assert(p_intf->p_sys->b_color && !p_intf->p_sys->b_color_started);
524 p_intf->p_sys->b_color = false;
525 msg_Warn(p_intf, "Terminal doesn't support colors");
530 for(int i = C_DEFAULT + 1; i < C_MAX; i++)
531 init_pair(i, color_pairs[i].f, color_pairs[i].b);
533 /* untested, in all my terminals, !can_change_color() --funman */
534 if (can_change_color())
535 init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
537 p_intf->p_sys->b_color_started = true;
540 static void DrawBox(WINDOW *win, int y, int x, int h, int w, const char *title, bool b_color)
544 if (w <= 3 || h <= 2)
548 wcolor_set(win, C_BOX, NULL);
549 if (!title) title = "";
550 i_len = strlen(title);
555 mvwaddch(win, y, x, ACS_ULCORNER);
556 mvwhline(win, y, x+1, ACS_HLINE, (w-i_len-2)/2);
557 mvwprintw(win,y, x+1+(w-i_len-2)/2, "%s", title);
558 mvwhline(win, y, x+(w-i_len)/2+i_len, ACS_HLINE, w - 1 - ((w-i_len)/2+i_len));
559 mvwaddch(win, y, x+w-1,ACS_URCORNER);
561 for(int i = 0; i < h-2; i++)
563 mvwaddch(win, y+i+1, x, ACS_VLINE);
564 mvwaddch(win, y+i+1, x+w-1, ACS_VLINE);
567 mvwaddch(win, y+h-1, x, ACS_LLCORNER);
568 mvwhline(win, y+h-1, x+1, ACS_HLINE, w - 2);
569 mvwaddch(win, y+h-1, x+w-1, ACS_LRCORNER);
571 wcolor_set(win, C_DEFAULT, NULL);
574 static void DrawEmptyLine(WINDOW *win, int y, int x, int w)
578 mvwhline(win, y, x, ' ', w);
581 static void DrawLine(WINDOW *win, int y, int x, int w)
586 mvwhline(win, y, x, ' ', w);
590 static void mvnprintw(int y, int x, int w, const char *p_fmt, ...)
599 va_start(vl_args, p_fmt);
600 if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
604 i_len = strlen(p_buf);
607 wchar_t psz_wide[i_len + 1];
610 size_t i_char_len = mbstowcs(psz_wide, p_buf, i_len);
612 size_t i_width; /* number of columns */
614 if (i_char_len == (size_t)-1) /* an invalid character was encountered */
620 i_width = wcswidth(psz_wide, i_char_len);
621 if (i_width == (size_t)-1)
623 /* a non printable character was encountered */
625 for(unsigned i = 0 ; i < i_char_len ; i++)
627 int i_cwidth = wcwidth(psz_wide[i]);
633 if (i_width <= (size_t)w)
635 mvprintw(y, x, "%s", p_buf);
636 mvhline(y, x + i_width, ' ', w - i_width);
641 int i_total_width = 0;
643 while (i_total_width < w)
645 i_total_width += wcwidth(psz_wide[i]);
646 if (w > 7 && i_total_width >= w/2)
650 i_total_width -= wcwidth(psz_wide[i]) - 2;
653 /* we require this check only if at least one character
654 * 4 or more columns wide exists (which i doubt) */
656 i_total_width -= wcwidth(psz_wide[i-1]) - 1;
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]);
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)
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';
675 if (w <= 7) /* we don't add the '...' else we lose too much chars */
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);
686 int i_cut = i_len - w;
687 int x1 = i_len/2 - i_cut/2;
691 memmove(&p_buf[x1], &p_buf[x2], i_len - x2);
702 char *psz_local = ToLocale(p_buf);
703 mvprintw(y, x, "%s", psz_local);
706 mvhline(y, x + i_len, ' ', w - i_len);
708 LocaleFree(psz_local);
714 static void MainBoxWrite(intf_thread_t *p_intf, int l, int x, const char *p_fmt, ...)
716 intf_sys_t *p_sys = p_intf->p_sys;
720 if (l < p_sys->i_box_start || l - p_sys->i_box_start >= p_sys->i_box_lines)
723 va_start(vl_args, p_fmt);
724 if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
728 mvnprintw(p_sys->i_box_y + l - p_sys->i_box_start, x, COLS - x - 1, "%s", p_buf);
732 static void DumpObject(intf_thread_t *p_intf, int *l, vlc_object_t *p_obj, int i_level)
734 char *psz_name = vlc_object_get_name(p_obj);
737 MainBoxWrite(p_intf, (*l)++, 1 + 2 * i_level, "%s \"%s\" (%p)",
738 p_obj->psz_object_type, psz_name, p_obj);
742 MainBoxWrite(p_intf, (*l)++, 1 + 2 * i_level, "%s (%o)",
743 p_obj->psz_object_type, p_obj);
745 vlc_list_t *list = vlc_list_children(p_obj);
746 for(int i = 0; i < list->i_count ; i++)
748 MainBoxWrite(p_intf, *l, 1 + 2 * i_level,
749 i == list->i_count - 1 ? "`-" : "|-");
750 DumpObject(p_intf, l, list->p_values[i].p_object, i_level + 1);
752 vlc_list_release(list);
755 static void Redraw(intf_thread_t *p_intf, time_t *t_last_refresh)
757 intf_sys_t *p_sys = p_intf->p_sys;
758 input_thread_t *p_input = p_sys->p_input;
759 playlist_t *p_playlist = pl_Get(p_intf);
766 int i_len = sizeof "VLC media player "PACKAGE_VERSION - 1;
767 int mid = (COLS - i_len) / 2;
770 int i_size = (COLS > i_len + 1) ? COLS : i_len + 1;
771 char psz_title[i_size];
772 memset(psz_title, ' ', mid);
774 wcolor_set(p_sys->w, C_TITLE, NULL);
775 strlcpy(&psz_title[mid], "VLC media player "PACKAGE_VERSION, i_size);
776 mvnprintw(y, 0, COLS, "%s", psz_title);
781 wcolor_set(p_sys->w, C_STATUS, NULL);
785 if (asprintf(&psz_state, "%s%s%s",
786 var_GetBool(p_playlist, "repeat") ? _("[Repeat] ") : "",
787 var_GetBool(p_playlist, "random") ? _("[Random] ") : "",
788 var_GetBool(p_playlist, "loop") ? _("[Loop]") : "") == -1)
791 if (p_input && !p_input->b_dead)
793 char buf1[MSTRTIME_MAX_SIZE];
794 char buf2[MSTRTIME_MAX_SIZE];
798 char *psz_uri = input_item_GetURI(input_GetItem(p_input));
799 mvnprintw(y++, 0, COLS, _(" Source : %s"), psz_uri);
803 var_Get(p_input, "state", &val);
804 if (val.i_int == PLAYING_S)
805 mvnprintw(y++, 0, COLS, _(" State : Playing %s"), psz_state);
806 else if (val.i_int == OPENING_S)
807 mvnprintw(y++, 0, COLS, _(" State : Opening/Connecting %s"), psz_state);
808 else if (val.i_int == PAUSE_S)
809 mvnprintw(y++, 0, COLS, _(" State : Paused %s"), psz_state);
811 if (val.i_int == INIT_S || val.i_int == END_S)
815 audio_volume_t i_volume;
818 var_Get(p_input, "time", &val);
819 secstotimestr(buf1, val.i_time / CLOCK_FREQ);
821 var_Get(p_input, "length", &val);
822 secstotimestr(buf2, val.i_time / CLOCK_FREQ);
824 mvnprintw(y++, 0, COLS, _(" Position : %s/%s (%.2f%%)"), buf1, buf2, p_sys->f_slider);
827 aout_VolumeGet(p_playlist, &i_volume);
828 mvnprintw(y++, 0, COLS, _(" Volume : %i%%"), i_volume*200/AOUT_VOLUME_MAX);
831 if (!var_Get(p_input, "title", &val))
833 int i_title_count = var_CountChoices(p_input, "title");
834 if (i_title_count > 0)
835 mvnprintw(y++, 0, COLS, _(" Title : %"PRId64"/%d"),
836 val.i_int, i_title_count);
840 if (!var_Get(p_input, "chapter", &val))
842 int i_chapter_count = var_CountChoices(p_input, "chapter");
843 if (i_chapter_count > 0)
844 mvnprintw(y++, 0, COLS, _(" Chapter : %"PRId64"/%d"),
845 val.i_int, i_chapter_count);
851 mvnprintw(y++, 0, COLS, _(" Source: <no current item> %s"), psz_state);
852 DrawEmptyLine(p_sys->w, y++, 0, COLS);
853 mvnprintw(y++, 0, COLS, _(" [ h for help ]"));
854 DrawEmptyLine(p_sys->w, y++, 0, COLS);
858 wcolor_set(p_sys->w, C_DEFAULT, NULL);
860 DrawBox(p_sys->w, y, 0, 3, COLS, "", p_sys->b_color);
861 DrawEmptyLine(p_sys->w, y+1, 1, COLS-2);
862 DrawLine(p_sys->w, y+1, 1, (int)(p_intf->p_sys->f_slider/100.0 * (COLS -2)));
865 p_sys->i_box_y = y + 1;
866 p_sys->i_box_lines = LINES - y - 2;
871 if (p_sys->i_box_type == BOX_HELP)
875 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Help "), p_sys->b_color);
878 wcolor_set(p_sys->w, C_CATEGORY, NULL);
879 MainBoxWrite(p_intf, l++, 1, _("[Display]"));
881 wcolor_set(p_sys->w, C_DEFAULT, NULL);
882 MainBoxWrite(p_intf, l++, 1, _(" h,H Show/Hide help box"));
883 MainBoxWrite(p_intf, l++, 1, _(" i Show/Hide info box"));
884 MainBoxWrite(p_intf, l++, 1, _(" m Show/Hide metadata box"));
885 // MainBoxWrite(p_intf, l++, 1, _(" L Show/Hide messages box"));
886 MainBoxWrite(p_intf, l++, 1, _(" P Show/Hide playlist box"));
887 MainBoxWrite(p_intf, l++, 1, _(" B Show/Hide filebrowser"));
888 MainBoxWrite(p_intf, l++, 1, _(" x Show/Hide objects box"));
889 MainBoxWrite(p_intf, l++, 1, _(" S Show/Hide statistics box"));
890 MainBoxWrite(p_intf, l++, 1, _(" c Switch color on/off"));
891 MainBoxWrite(p_intf, l++, 1, _(" Esc Close Add/Search entry"));
892 MainBoxWrite(p_intf, l++, 1, "");
895 wcolor_set(p_sys->w, C_CATEGORY, NULL);
896 MainBoxWrite(p_intf, l++, 1, _("[Global]"));
898 wcolor_set(p_sys->w, C_DEFAULT, NULL);
899 MainBoxWrite(p_intf, l++, 1, _(" q, Q, Esc Quit"));
900 MainBoxWrite(p_intf, l++, 1, _(" s Stop"));
901 MainBoxWrite(p_intf, l++, 1, _(" <space> Pause/Play"));
902 MainBoxWrite(p_intf, l++, 1, _(" f Toggle Fullscreen"));
903 MainBoxWrite(p_intf, l++, 1, _(" n, p Next/Previous playlist item"));
904 MainBoxWrite(p_intf, l++, 1, _(" [, ] Next/Previous title"));
905 MainBoxWrite(p_intf, l++, 1, _(" <, > Next/Previous chapter"));
906 MainBoxWrite(p_intf, l++, 1, _(" <right> Seek +1%%"));
907 MainBoxWrite(p_intf, l++, 1, _(" <left> Seek -1%%"));
908 MainBoxWrite(p_intf, l++, 1, _(" a Volume Up"));
909 MainBoxWrite(p_intf, l++, 1, _(" z Volume Down"));
910 MainBoxWrite(p_intf, l++, 1, "");
913 wcolor_set(p_sys->w, C_CATEGORY, NULL);
914 MainBoxWrite(p_intf, l++, 1, _("[Playlist]"));
916 wcolor_set(p_sys->w, C_DEFAULT, NULL);
917 MainBoxWrite(p_intf, l++, 1, _(" r Toggle Random playing"));
918 MainBoxWrite(p_intf, l++, 1, _(" l Toggle Loop Playlist"));
919 MainBoxWrite(p_intf, l++, 1, _(" R Toggle Repeat item"));
920 MainBoxWrite(p_intf, l++, 1, _(" o Order Playlist by title"));
921 MainBoxWrite(p_intf, l++, 1, _(" O Reverse order Playlist by title"));
922 MainBoxWrite(p_intf, l++, 1, _(" g Go to the current playing item"));
923 MainBoxWrite(p_intf, l++, 1, _(" / Look for an item"));
924 MainBoxWrite(p_intf, l++, 1, _(" A Add an entry"));
925 MainBoxWrite(p_intf, l++, 1, _(" D, <del> Delete an entry"));
926 MainBoxWrite(p_intf, l++, 1, _(" <backspace> Delete an entry"));
927 MainBoxWrite(p_intf, l++, 1, _(" e Eject (if stopped)"));
928 MainBoxWrite(p_intf, l++, 1, "");
931 wcolor_set(p_sys->w, C_CATEGORY, NULL);
932 MainBoxWrite(p_intf, l++, 1, _("[Filebrowser]"));
934 wcolor_set(p_sys->w, C_DEFAULT, NULL);
935 MainBoxWrite(p_intf, l++, 1, _(" <enter> Add the selected file to the playlist"));
936 MainBoxWrite(p_intf, l++, 1, _(" <space> Add the selected directory to the playlist"));
937 MainBoxWrite(p_intf, l++, 1, _(" . Show/Hide hidden files"));
938 MainBoxWrite(p_intf, l++, 1, "");
941 wcolor_set(p_sys->w, C_CATEGORY, NULL);
942 MainBoxWrite(p_intf, l++, 1, _("[Boxes]"));
944 wcolor_set(p_sys->w, C_DEFAULT, NULL);
945 MainBoxWrite(p_intf, l++, 1, _(" <up>,<down> Navigate through the box line by line"));
946 MainBoxWrite(p_intf, l++, 1, _(" <pgup>,<pgdown> Navigate through the box page by page"));
947 MainBoxWrite(p_intf, l++, 1, "");
950 wcolor_set(p_sys->w, C_CATEGORY, NULL);
951 MainBoxWrite(p_intf, l++, 1, _("[Player]"));
953 wcolor_set(p_sys->w, C_DEFAULT, NULL);
954 MainBoxWrite(p_intf, l++, 1, _(" <up>,<down> Seek +/-5%%"));
955 MainBoxWrite(p_intf, l++, 1, "");
958 wcolor_set(p_sys->w, C_CATEGORY, NULL);
959 MainBoxWrite(p_intf, l++, 1, _("[Miscellaneous]"));
961 wcolor_set(p_sys->w, C_DEFAULT, NULL);
962 MainBoxWrite(p_intf, l++, 1, _(" Ctrl-l Refresh the screen"));
964 p_sys->i_box_lines_total = l;
965 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
966 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
968 if (l - p_sys->i_box_start < p_sys->i_box_lines)
969 y += l - p_sys->i_box_start;
971 y += p_sys->i_box_lines;
973 else if (p_sys->i_box_type == BOX_INFO)
977 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Information "), p_sys->b_color);
982 vlc_mutex_lock(&input_GetItem(p_input)->lock);
983 for(i = 0; i < input_GetItem(p_input)->i_categories; i++)
985 info_category_t *p_category = input_GetItem(p_input)->pp_categories[i];
986 if (y >= y_end) break;
988 wcolor_set(p_sys->w, C_CATEGORY, NULL);
989 MainBoxWrite(p_intf, l++, 1, _(" [%s]"), p_category->psz_name);
991 wcolor_set(p_sys->w, C_DEFAULT, NULL);
992 for(j = 0; j < p_category->i_infos; j++)
994 info_t *p_info = p_category->pp_infos[j];
995 if (y >= y_end) break;
996 MainBoxWrite(p_intf, l++, 1, _(" %s: %s"), p_info->psz_name, p_info->psz_value);
999 vlc_mutex_unlock(&input_GetItem(p_input)->lock);
1002 MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
1004 p_sys->i_box_lines_total = l;
1005 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1006 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1008 if (l - p_sys->i_box_start < p_sys->i_box_lines)
1009 y += l - p_sys->i_box_start;
1011 y += p_sys->i_box_lines;
1013 else if (p_sys->i_box_type == BOX_META)
1018 DrawBox(p_sys->w, y++, 0, h, COLS, _("Meta-information"),
1023 input_item_t *p_item = input_GetItem(p_input);
1024 vlc_mutex_lock(&p_item->lock);
1025 for(int i=0; i<VLC_META_TYPE_COUNT; i++)
1027 const char *psz_meta = vlc_meta_Get(p_item->p_meta, i);
1028 if (!psz_meta || !*psz_meta)
1031 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1032 MainBoxWrite(p_intf, l++, 1, " [%s]",
1033 vlc_meta_TypeToLocalizedString(i));
1034 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1035 MainBoxWrite(p_intf, l++, 1, " %s", psz_meta);
1037 vlc_mutex_unlock(&p_item->lock);
1040 MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
1042 p_sys->i_box_lines_total = l;
1043 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1044 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1046 y += __MIN(l - p_sys->i_box_start, p_sys->i_box_lines);
1048 #if 0 /* Deprecated API */
1049 else if (p_sys->i_box_type == BOX_LOG)
1055 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Logs "), p_sys->b_color);
1058 i_start = p_intf->p_sys->p_sub->i_start;
1060 vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1061 i_stop = *p_intf->p_sys->p_sub->pi_stop;
1062 vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1066 static const char *ppsz_type[4] = { "", "error", "warning", "debug" };
1067 if (i_line >= h - 2)
1073 if (i_stop < 0) i_stop += VLC_MSG_QSIZE;
1074 if (i_stop == i_start)
1079 wcolor_set(p_sys->w,
1080 p_sys->p_sub->p_msg[i_stop].i_type + C_INFO,
1082 mvnprintw(y + h-2-i_line, 1, COLS - 2, " [%s] %s",
1083 ppsz_type[p_sys->p_sub->p_msg[i_stop].i_type],
1084 p_sys->p_sub->p_msg[i_stop].psz_msg);
1086 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1089 vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1090 p_intf->p_sys->p_sub->i_start = i_stop;
1091 vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1095 else if (p_sys->i_box_type == BOX_BROWSE)
1097 /* Filebrowser box */
1098 int i_start, i_stop;
1100 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Browse "), p_sys->b_color);
1102 if (p_sys->i_box_bidx >= p_sys->i_dir_entries) p_sys->i_box_plidx = p_sys->i_dir_entries - 1;
1103 if (p_sys->i_box_bidx < 0) p_sys->i_box_bidx = 0;
1105 if (p_sys->i_box_bidx < (h - 2)/2)
1110 else if (p_sys->i_dir_entries - p_sys->i_box_bidx > (h - 2)/2)
1112 i_start = p_sys->i_box_bidx - (h - 2)/2;
1113 i_stop = i_start + h - 2;
1117 i_stop = p_sys->i_dir_entries;
1118 i_start = p_sys->i_dir_entries - (h - 2);
1122 if (i_stop > p_sys->i_dir_entries)
1123 i_stop = p_sys->i_dir_entries;
1125 for(i_item = i_start; i_item < i_stop; i_item++)
1127 bool b_selected = (p_sys->i_box_bidx == i_item);
1129 if (y >= y_end) break;
1132 if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1133 wcolor_set(p_sys->w, C_FOLDER, NULL);
1134 mvnprintw(y++, 1, COLS - 2, " %c %s", p_sys->pp_dir_entries[i_item]->b_file == true ? ' ' : '+',
1135 p_sys->pp_dir_entries[i_item]->psz_path);
1136 if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1137 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1144 else if (p_sys->i_box_type == BOX_OBJECTS)
1147 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Objects "), p_sys->b_color);
1148 DumpObject(p_intf, &l, VLC_OBJECT(p_intf->p_libvlc), 0);
1150 p_sys->i_box_lines_total = l;
1151 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1152 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1154 if (l - p_sys->i_box_start < p_sys->i_box_lines)
1155 y += l - p_sys->i_box_start;
1157 y += p_sys->i_box_lines;
1159 else if (p_sys->i_box_type == BOX_STATS)
1161 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Stats "), p_sys->b_color);
1165 input_item_t *p_item = input_GetItem(p_input);
1167 vlc_mutex_lock(&p_item->lock);
1168 vlc_mutex_lock(&p_item->p_stats->lock);
1175 i_video = i_audio = 1;
1177 for(i = 0; i < p_item->i_es ; i++)
1179 i_audio += (p_item->es[i]->i_cat == AUDIO_ES);
1180 i_video += (p_item->es[i]->i_cat == VIDEO_ES);
1185 #define SHOW_ACS(x,c) \
1186 if (l >= p_sys->i_box_start && l - p_sys->i_box_start < p_sys->i_box_lines) \
1187 mvaddch(p_sys->i_box_y - p_sys->i_box_start + l, x, c)
1190 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1191 MainBoxWrite(p_intf, l, 1, _("+-[Incoming]"));
1192 SHOW_ACS(1, ACS_ULCORNER); SHOW_ACS(2, ACS_HLINE); l++;
1193 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1194 MainBoxWrite(p_intf, l, 1, _("| input bytes read : %8.0f KiB"),
1195 (float)(p_item->p_stats->i_read_bytes)/1024);
1196 SHOW_ACS(1, ACS_VLINE); l++;
1197 MainBoxWrite(p_intf, l, 1, _("| input bitrate : %6.0f kb/s"),
1198 (float)(p_item->p_stats->f_input_bitrate)*8000);
1199 MainBoxWrite(p_intf, l, 1, _("| demux bytes read : %8.0f KiB"),
1200 (float)(p_item->p_stats->i_demux_read_bytes)/1024);
1201 SHOW_ACS(1, ACS_VLINE); l++;
1202 MainBoxWrite(p_intf, l, 1, _("| demux bitrate : %6.0f kb/s"),
1203 (float)(p_item->p_stats->f_demux_bitrate)*8000);
1204 SHOW_ACS(1, ACS_VLINE); l++;
1205 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1206 SHOW_ACS(1, ACS_VLINE); l++;
1211 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1212 MainBoxWrite(p_intf, l, 1, _("+-[Video Decoding]"));
1213 SHOW_ACS(1, ACS_LTEE); SHOW_ACS(2, ACS_HLINE); l++;
1214 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1215 MainBoxWrite(p_intf, l, 1, _("| video decoded : %"PRId64),
1216 p_item->p_stats->i_decoded_video);
1217 SHOW_ACS(1, ACS_VLINE); l++;
1218 MainBoxWrite(p_intf, l, 1, _("| frames displayed : %"PRId64),
1219 p_item->p_stats->i_displayed_pictures);
1220 SHOW_ACS(1, ACS_VLINE); l++;
1221 MainBoxWrite(p_intf, l, 1, _("| frames lost : %"PRId64),
1222 p_item->p_stats->i_lost_pictures);
1223 SHOW_ACS(1, ACS_VLINE); l++;
1224 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1225 SHOW_ACS(1, ACS_VLINE); l++;
1230 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1231 MainBoxWrite(p_intf, l, 1, _("+-[Audio Decoding]"));
1232 SHOW_ACS(1, ACS_LTEE); SHOW_ACS(2, ACS_HLINE); l++;
1233 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1234 MainBoxWrite(p_intf, l, 1, _("| audio decoded : %"PRId64),
1235 p_item->p_stats->i_decoded_audio);
1236 SHOW_ACS(1, ACS_VLINE); l++;
1237 MainBoxWrite(p_intf, l, 1, _("| buffers played : %"PRId64),
1238 p_item->p_stats->i_played_abuffers);
1239 SHOW_ACS(1, ACS_VLINE); l++;
1240 MainBoxWrite(p_intf, l, 1, _("| buffers lost : %"PRId64),
1241 p_item->p_stats->i_lost_abuffers);
1242 SHOW_ACS(1, ACS_VLINE); l++;
1243 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1244 SHOW_ACS(1, ACS_VLINE); l++;
1247 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1248 MainBoxWrite(p_intf, l, 1, _("+-[Streaming]"));
1249 SHOW_ACS(1, ACS_LTEE); 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, _("| packets sent : %5i"), p_item->p_stats->i_sent_packets);
1252 SHOW_ACS(1, ACS_VLINE); l++;
1253 MainBoxWrite(p_intf, l, 1, _("| bytes sent : %8.0f KiB"),
1254 (float)(p_item->p_stats->i_sent_bytes)/1024);
1255 SHOW_ACS(1, ACS_VLINE); l++;
1256 MainBoxWrite(p_intf, l, 1, _("\\ sending bitrate : %6.0f kb/s"),
1257 (float)(p_item->p_stats->f_send_bitrate*8)*1000);
1258 SHOW_ACS(1, ACS_LLCORNER); l++;
1259 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1263 p_sys->i_box_lines_total = l;
1264 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1265 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1267 if (l - p_sys->i_box_start < p_sys->i_box_lines)
1268 y += l - p_sys->i_box_start;
1270 y += p_sys->i_box_lines;
1272 vlc_mutex_unlock(&p_item->p_stats->lock);
1273 vlc_mutex_unlock(&p_item->lock);
1277 else if (p_sys->i_box_type == BOX_PLAYLIST ||
1278 p_sys->i_box_type == BOX_SEARCH ||
1279 p_sys->i_box_type == BOX_OPEN )
1282 int i_start, i_stop, i_max = p_sys->i_plist_entries;
1286 if (p_sys->category_view)
1287 psz_title = strdup(_(" Playlist (By category) "));
1289 psz_title = strdup(_(" Playlist (All, one level) "));
1291 DrawBox(p_sys->w, y++, 0, h, COLS, psz_title, p_sys->b_color);
1294 if (p_sys->b_need_update || !p_sys->pp_plist)
1295 PlaylistRebuild(p_intf);
1296 if (p_sys->b_box_plidx_follow)
1297 FindIndex(p_sys, p_playlist, false);
1299 if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1300 if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1301 if (p_sys->i_box_plidx >= i_max) p_sys->i_box_plidx = i_max - 1;
1303 if (p_sys->i_box_plidx < (h - 2)/2)
1308 else if (i_max - p_sys->i_box_plidx > (h - 2)/2)
1310 i_start = p_sys->i_box_plidx - (h - 2)/2;
1311 i_stop = i_start + h - 2;
1316 i_start = i_max - (h - 2);
1323 for(i_item = i_start; i_item < i_stop; i_item++)
1325 bool b_selected = (p_sys->i_box_plidx == i_item);
1326 playlist_item_t *p_item = p_sys->pp_plist[i_item]->p_item;
1327 playlist_item_t *p_node = p_sys->p_node;
1329 input_thread_t *p_input2 = playlist_CurrentInput(p_playlist);
1333 playlist_item_t *p_current_playing_item = playlist_CurrentPlayingItem(p_playlist);
1334 if ((p_node && p_item->p_input == p_node->p_input) ||
1335 (!p_node && p_input2 && p_current_playing_item &&
1336 p_item->p_input == p_current_playing_item->p_input))
1338 else if (p_item == p_node || (p_item != p_node &&
1339 PlaylistIsPlaying(p_playlist, p_item)))
1344 vlc_object_release(p_input2);
1346 if (y >= y_end) break;
1350 wcolor_set(p_sys->w, i_item % 3 + C_PLAYLIST_1, NULL);
1351 mvnprintw(y++, 1, COLS - 2, "%c%s", c,
1352 p_sys->pp_plist[i_item]->psz_display);
1354 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1363 if (p_sys->i_box_type == BOX_SEARCH)
1365 DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1366 if (p_sys->psz_search_chain)
1368 if (!*p_sys->psz_search_chain && p_sys->psz_old_search)
1369 /* Searching next entry */
1370 mvnprintw(7, 1, COLS-2, _("Find: %s"), p_sys->psz_old_search);
1372 mvnprintw(7, 1, COLS-2, _("Find: %s"), p_sys->psz_search_chain);
1375 if (p_sys->i_box_type == BOX_OPEN && p_sys->psz_open_chain)
1377 DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1378 mvnprintw(7, 1, COLS-2, _("Open: %s"), p_sys->psz_open_chain);
1382 DrawEmptyLine(p_sys->w, y++, 1, COLS - 2);
1386 *t_last_refresh = time(0);
1389 static void ManageSlider(intf_thread_t *p_intf)
1391 intf_sys_t *p_sys = p_intf->p_sys;
1392 input_thread_t *p_input = p_sys->p_input;
1395 if (!p_input || var_GetInteger(p_input, "state") != PLAYING_S)
1398 pos = var_GetFloat(p_input, "position");
1399 if (p_sys->f_slider == p_sys->f_slider_old)
1400 p_sys->f_slider = p_sys->f_slider_old = 100. * pos;
1403 p_sys->f_slider_old = p_sys->f_slider;
1405 pos = p_sys->f_slider / 100.;
1406 var_SetFloat(p_input, "position", pos);
1410 #ifndef HAVE_NCURSESW
1411 static char *KeyToUTF8(int i_key, char *psz_part)
1414 int len = strlen(psz_part);
1417 /* overflow error - should not happen */
1418 memset(psz_part, 0, 6);
1422 psz_part[len] = (char)i_key;
1424 psz_utf8 = FromLocaleDup(psz_part);
1426 /* Ugly check for incomplete bytes sequences
1427 * (in case of non-UTF8 multibyte local encoding) */
1429 for(psz = psz_utf8; *psz; psz++)
1430 if ((*psz == '?') && (*psz_utf8 != '?'))
1432 /* incomplete bytes sequence detected
1433 * (VLC core inserted dummy question marks) */
1438 /* Check for incomplete UTF8 bytes sequence */
1439 if (!EnsureUTF8(psz_utf8))
1445 memset(psz_part, 0, 6);
1450 static inline int RemoveLastUTF8Entity(char *psz, int len)
1452 while (len && ((psz[--len] & 0xc0) == 0x80));
1453 /* UTF8 continuation byte */
1459 static char *GetDiscDevice(intf_thread_t *p_intf, const char *name)
1461 static const struct { const char *s; size_t n; const char *v; } devs[] =
1463 { "cdda://", 7, "cd-audio", },
1464 { "dvd://", 6, "dvd", },
1465 { "vcd://", 6, "vcd", },
1469 for (unsigned i = 0; i < sizeof devs / sizeof *devs; i++)
1471 size_t n = devs[i].n;
1472 if (!strncmp(name, devs[i].s, n))
1477 return config_GetPsz(p_intf, devs[i].v);
1479 /* Omit the beginning MRL-selector characters */
1480 return strdup(name + n);
1484 device = strdup(name);
1486 if (device) /* Remove what we have after @ */
1487 device[strcspn(device, "@")] = '\0';
1492 static void Eject(intf_thread_t *p_intf)
1494 char *psz_device, *psz_name;
1495 playlist_t * p_playlist = pl_Get(p_intf);
1497 /* If there's a stream playing, we aren't allowed to eject ! */
1498 if (p_intf->p_sys->p_input)
1503 if (!playlist_CurrentPlayingItem(p_playlist))
1509 psz_name = playlist_CurrentPlayingItem(p_playlist)->p_input->psz_name;
1512 psz_device = GetDiscDevice(p_intf, psz_name);
1518 intf_Eject(p_intf, psz_device);
1523 static void PlayPause(intf_thread_t *p_intf)
1525 input_thread_t *p_input = p_intf->p_sys->p_input;
1529 int64_t state = var_GetInteger( p_input, "state" );
1530 state = (state != PLAYING_S) ? PLAYING_S : PAUSE_S;
1531 var_SetInteger( p_input, "state", state );
1534 playlist_Play(pl_Get(p_intf));
1537 static int HandleKey(intf_thread_t *p_intf)
1539 intf_sys_t *p_sys = p_intf->p_sys;
1540 playlist_t *p_playlist = pl_Get(p_intf);
1541 int i_key = wgetch(p_sys->w);
1546 if (p_sys->i_box_type == BOX_PLAYLIST)
1549 bool b_box_plidx_follow = false;
1553 /* Playlist Settings */
1555 var_ToggleBool(p_playlist, "random");
1558 var_ToggleBool(p_playlist, "loop");
1561 var_ToggleBool(p_playlist, "repeat");
1566 playlist_RecursiveNodeSort(p_playlist,
1567 PlaylistGetRoot(p_intf),
1568 SORT_TITLE_NODES_FIRST, ORDER_NORMAL);
1569 p_sys->b_need_update = true;
1572 playlist_RecursiveNodeSort(p_playlist,
1573 PlaylistGetRoot(p_intf),
1574 SORT_TITLE_NODES_FIRST, ORDER_REVERSE);
1575 p_sys->b_need_update = true;
1580 p_sys->category_view = !p_sys->category_view;
1581 PlaylistRebuild(p_intf);
1584 /* Playlist navigation */
1586 FindIndex(p_sys, p_playlist, false);
1589 p_sys->i_box_plidx = 0;
1592 /* workaround for FreeBSD + xterm:
1593 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1597 p_sys->i_box_plidx = p_playlist->items.i_size - 1;
1600 p_sys->i_box_plidx--;
1603 p_sys->i_box_plidx++;
1606 p_sys->i_box_plidx -= p_sys->i_box_lines;
1609 p_sys->i_box_plidx += p_sys->i_box_lines;
1616 playlist_item_t *p_item;
1619 p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1620 if (p_item->i_children == -1)
1621 playlist_DeleteFromInput(p_playlist,
1622 p_item->p_input, pl_Locked);
1624 playlist_NodeDelete(p_playlist, p_item, true , false);
1626 PlaylistRebuild(p_intf);
1633 if (!p_sys->pp_plist[p_sys->i_box_plidx])
1638 if (p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
1641 playlist_item_t *p_item, *p_parent;
1643 p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1646 p_parent = p_playlist->p_root_onelevel;
1647 while (p_parent->p_parent)
1648 p_parent = p_parent->p_parent;
1649 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY,
1650 pl_Unlocked, p_parent, p_item);
1652 else if (p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
1654 { /* We only want to set the current node */
1655 playlist_Stop(p_playlist);
1656 p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1660 p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1661 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked,
1662 p_sys->pp_plist[p_sys->i_box_plidx]->p_item, NULL);
1664 b_box_plidx_follow = true;
1673 int i_max = p_sys->i_plist_entries;
1674 if (p_sys->i_box_plidx >= i_max) p_sys->i_box_plidx = i_max - 1;
1675 if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1678 if (PlaylistIsPlaying(p_playlist,
1679 p_sys->pp_plist[p_sys->i_box_plidx]->p_item))
1680 b_box_plidx_follow = true;
1682 p_sys->b_box_plidx_follow = b_box_plidx_follow;
1686 else if (p_sys->i_box_type == BOX_BROWSE)
1689 /* Browser navigation */
1693 p_sys->i_box_bidx = 0;
1699 p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
1702 p_sys->i_box_bidx--;
1705 p_sys->i_box_bidx++;
1708 p_sys->i_box_bidx -= p_sys->i_box_lines;
1711 p_sys->i_box_bidx += p_sys->i_box_lines;
1713 case '.': /* Toggle show hidden files */
1714 p_sys->b_show_hidden_files = (p_sys->b_show_hidden_files ==
1715 true ? false : true);
1723 if (p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ')
1726 if (asprintf(&psz_uri, "%s://%s/%s",
1727 p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file ?
1728 "file" : "directory",
1729 p_sys->psz_current_dir,
1730 p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path
1736 playlist_item_t *p_parent = p_sys->p_node;
1740 p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
1743 p_parent = p_playlist->p_local_onelevel;
1746 while (p_parent->p_parent && p_parent->p_parent->p_parent)
1747 p_parent = p_parent->p_parent;
1749 playlist_Add(p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
1751 p_parent->p_input ==
1752 p_playlist->p_local_onelevel->p_input
1755 p_sys->i_box_type = BOX_PLAYLIST;
1760 if (asprintf(&(p_sys->psz_current_dir), "%s/%s", p_sys->psz_current_dir,
1761 p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path) != -1)
1771 if (p_sys->i_box_bidx >= p_sys->i_dir_entries) p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
1772 if (p_sys->i_box_bidx < 0) p_sys->i_box_bidx = 0;
1776 else if (p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
1777 p_sys->i_box_type == BOX_META || p_sys->i_box_type == BOX_STATS ||
1778 p_sys->i_box_type == BOX_OBJECTS)
1783 p_sys->i_box_start = 0;
1789 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1792 if (p_sys->i_box_start > 0) p_sys->i_box_start--;
1795 if (p_sys->i_box_start < p_sys->i_box_lines_total - 1)
1796 p_sys->i_box_start++;
1799 p_sys->i_box_start -= p_sys->i_box_lines;
1800 if (p_sys->i_box_start < 0) p_sys->i_box_start = 0;
1803 p_sys->i_box_start += p_sys->i_box_lines;
1804 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1805 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1811 else if (p_sys->i_box_type == BOX_NONE)
1816 p_sys->f_slider = 0;
1817 ManageSlider(p_intf);
1823 p_sys->f_slider = 99.9;
1824 ManageSlider(p_intf);
1827 p_sys->f_slider += 5.0;
1828 if (p_sys->f_slider >= 99.0) p_sys->f_slider = 99.0;
1829 ManageSlider(p_intf);
1832 p_sys->f_slider -= 5.0;
1833 if (p_sys->f_slider < 0.0) p_sys->f_slider = 0.0;
1834 ManageSlider(p_intf);
1841 else if (p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain)
1843 int i_chain_len = strlen(p_sys->psz_search_chain);
1853 if (i_chain_len > 0)
1854 p_sys->psz_old_search = strdup(p_sys->psz_search_chain);
1855 else if (p_sys->psz_old_search)
1856 SearchPlaylist(p_sys, p_sys->psz_old_search);
1857 p_sys->i_box_type = BOX_PLAYLIST;
1859 case 0x1b: /* ESC */
1860 /* Alt+key combinations return 2 keys in the terminal keyboard:
1861 * ESC, and the 2nd key.
1862 * If some other key is available immediately (where immediately
1863 * means after wgetch() 1 second delay), that means that the
1864 * ESC key was not pressed.
1866 * man 3X curs_getch says:
1868 * Use of the escape key by a programmer for a single
1869 * character function is discouraged, as it will cause a delay
1870 * of up to one second while the keypad code looks for a
1871 * following function-key sequence.
1874 if (wgetch(p_sys->w) != ERR)
1876 p_sys->i_box_plidx = p_sys->i_before_search;
1877 p_sys->i_box_type = BOX_PLAYLIST;
1881 RemoveLastUTF8Entity(p_sys->psz_search_chain, i_chain_len);
1885 #ifdef HAVE_NCURSESW
1886 if (i_chain_len + 1 < SEARCH_CHAIN_SIZE)
1888 p_sys->psz_search_chain[i_chain_len] = (char) i_key;
1889 p_sys->psz_search_chain[i_chain_len + 1] = '\0';
1892 char *psz_utf8 = KeyToUTF8(i_key, p_sys->psz_partial_keys);
1896 if (i_chain_len + strlen(psz_utf8) < SEARCH_CHAIN_SIZE)
1897 strcpy(p_sys->psz_search_chain + i_chain_len, psz_utf8);
1904 free(p_sys->psz_old_search);
1905 p_sys->psz_old_search = NULL;
1906 SearchPlaylist(p_sys, p_sys->psz_search_chain);
1909 else if (p_sys->i_box_type == BOX_OPEN && p_sys->psz_open_chain)
1911 int i_chain_len = strlen(p_sys->psz_open_chain);
1922 if (i_chain_len > 0)
1924 playlist_item_t *p_parent = p_sys->p_node;
1928 p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
1930 p_parent = p_playlist->p_local_onelevel;
1932 while (p_parent->p_parent && p_parent->p_parent->p_parent)
1933 p_parent = p_parent->p_parent;
1936 playlist_Add(p_playlist, p_sys->psz_open_chain, NULL,
1937 PLAYLIST_APPEND|PLAYLIST_GO, PLAYLIST_END,
1938 p_parent->p_input ==
1939 p_playlist->p_local_onelevel->p_input
1942 p_sys->b_box_plidx_follow = true;
1944 p_sys->i_box_type = BOX_PLAYLIST;
1946 case 0x1b: /* ESC */
1947 if (wgetch(p_sys->w) != ERR)
1949 p_sys->i_box_type = BOX_PLAYLIST;
1953 RemoveLastUTF8Entity(p_sys->psz_open_chain, i_chain_len);
1957 #ifdef HAVE_NCURSESW
1958 if (i_chain_len + 1 < OPEN_CHAIN_SIZE)
1960 p_sys->psz_open_chain[i_chain_len] = (char) i_key;
1961 p_sys->psz_open_chain[i_chain_len + 1] = '\0';
1964 char *psz_utf8 = KeyToUTF8(i_key, p_sys->psz_partial_keys);
1968 if (i_chain_len + strlen(psz_utf8) < OPEN_CHAIN_SIZE)
1969 strcpy(p_sys->psz_open_chain + i_chain_len, psz_utf8);
1982 case 0x1b: /* ESC */
1983 if (wgetch(p_sys->w) != ERR)
1988 libvlc_Quit(p_intf->p_libvlc);
1993 if (p_sys->i_box_type == BOX_INFO)
1994 p_sys->i_box_type = BOX_NONE;
1996 p_sys->i_box_type = BOX_INFO;
1997 p_sys->i_box_lines_total = 0;
2000 if (p_sys->i_box_type == BOX_META)
2001 p_sys->i_box_type = BOX_NONE;
2003 p_sys->i_box_type = BOX_META;
2004 p_sys->i_box_lines_total = 0;
2008 if (p_sys->i_box_type == BOX_LOG)
2009 p_sys->i_box_type = BOX_NONE;
2011 p_sys->i_box_type = BOX_LOG;
2015 if (p_sys->i_box_type == BOX_PLAYLIST)
2016 p_sys->i_box_type = BOX_NONE;
2018 p_sys->i_box_type = BOX_PLAYLIST;
2021 if (p_sys->i_box_type == BOX_BROWSE)
2022 p_sys->i_box_type = BOX_NONE;
2024 p_sys->i_box_type = BOX_BROWSE;
2027 if (p_sys->i_box_type == BOX_OBJECTS)
2028 p_sys->i_box_type = BOX_NONE;
2030 p_sys->i_box_type = BOX_OBJECTS;
2033 if (p_sys->i_box_type == BOX_STATS)
2034 p_sys->i_box_type = BOX_NONE;
2036 p_sys->i_box_type = BOX_STATS;
2039 p_sys->b_color = !p_sys->b_color;
2040 if (p_sys->b_color && !p_sys->b_color_started)
2041 start_color_and_pairs(p_intf);
2045 if (p_sys->i_box_type == BOX_HELP)
2046 p_sys->i_box_type = BOX_NONE;
2048 p_sys->i_box_type = BOX_HELP;
2049 p_sys->i_box_lines_total = 0;
2052 if (p_sys->i_box_type != BOX_SEARCH && p_sys->psz_search_chain)
2054 p_sys->psz_search_chain[0] = '\0';
2055 p_sys->b_box_plidx_follow = false;
2056 p_sys->i_before_search = p_sys->i_box_plidx;
2057 p_sys->i_box_type = BOX_SEARCH;
2060 case 'A': /* Open */
2061 if (p_sys->i_box_type != BOX_OPEN && p_sys->psz_open_chain)
2063 p_sys->psz_open_chain[0] = '\0';
2064 p_sys->i_box_type = BOX_OPEN;
2070 p_sys->f_slider += 1.0;
2071 if (p_sys->f_slider > 99.9) p_sys->f_slider = 99.9;
2072 ManageSlider(p_intf);
2076 p_sys->f_slider -= 1.0;
2077 if (p_sys->f_slider < 0.0) p_sys->f_slider = 0.0;
2078 ManageSlider(p_intf);
2081 /* Common control */
2083 if (p_intf->p_sys->p_input)
2085 vout_thread_t *p_vout = input_GetVout(p_intf->p_sys->p_input);
2088 bool fs = var_ToggleBool(p_playlist, "fullscreen");
2089 var_SetBool(p_vout, "fullscreen", fs);
2090 vlc_object_release(p_vout);
2100 playlist_Stop(p_playlist);
2109 var_TriggerCallback(p_sys->p_input, "prev-title");
2114 var_TriggerCallback(p_sys->p_input, "next-title");
2119 var_TriggerCallback(p_sys->p_input, "prev-chapter");
2124 var_TriggerCallback(p_sys->p_input, "next-chapter");
2128 playlist_Prev(p_playlist);
2133 playlist_Next(p_playlist);
2138 aout_VolumeUp(p_playlist, 1, NULL);
2143 aout_VolumeDown(p_playlist, 1, NULL);
2148 * ^l should clear and redraw the screen
2162 /*****************************************************************************
2163 * Run: ncurses thread
2164 *****************************************************************************/
2165 static void Run(intf_thread_t *p_intf)
2167 intf_sys_t *p_sys = p_intf->p_sys;
2168 playlist_t *p_playlist = pl_Get(p_intf);
2170 time_t t_last_refresh;
2171 int canc = vlc_savecancel();
2173 PlaylistRebuild(p_intf);
2174 Redraw(p_intf, &t_last_refresh);
2176 var_AddCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
2177 var_AddCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
2179 while (vlc_object_alive(p_intf))
2181 msleep(INTF_IDLE_SLEEP);
2183 /* Update the input */
2184 if (!p_sys->p_input)
2185 p_sys->p_input = playlist_CurrentInput(p_playlist);
2186 else if (p_sys->p_input->b_dead)
2188 vlc_object_release(p_sys->p_input);
2189 p_sys->p_input = NULL;
2190 p_sys->f_slider = p_sys->f_slider_old = 0.0;
2191 p_sys->b_box_cleared = false;
2195 if (p_sys->b_box_plidx_follow && playlist_CurrentPlayingItem(p_playlist))
2196 FindIndex(p_sys, p_playlist, true);
2200 while (HandleKey(p_intf))
2201 Redraw(p_intf, &t_last_refresh);
2204 if (p_sys->f_slider > 0.0001 && !p_sys->b_box_cleared)
2207 Redraw(p_intf, &t_last_refresh);
2208 p_sys->b_box_cleared = true;
2211 if ((time(0) - t_last_refresh) >= 1)
2213 ManageSlider(p_intf);
2214 Redraw(p_intf, &t_last_refresh);
2217 var_DelCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
2218 var_DelCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
2219 vlc_restorecancel(canc);
2222 /*****************************************************************************
2223 * Open: initialize and create window
2224 *****************************************************************************/
2225 static int Open(vlc_object_t *p_this)
2227 intf_thread_t *p_intf = (intf_thread_t *)p_this;
2228 intf_sys_t *p_sys = p_intf->p_sys = calloc(1, sizeof(intf_sys_t));
2232 p_sys->f_slider = 0.0;
2233 p_sys->f_slider_old = 0.0;
2234 p_sys->i_box_type = BOX_PLAYLIST;
2235 p_sys->b_box_plidx_follow = true;
2236 // p_sys->p_sub = msg_Subscribe(p_intf);
2237 p_sys->b_color = var_CreateGetBool(p_intf, "color");
2239 p_sys->category_view = true; //FIXME: switching back & forth is broken
2240 p_sys->psz_search_chain = malloc(SEARCH_CHAIN_SIZE + 1);
2241 p_sys->psz_open_chain = malloc(OPEN_CHAIN_SIZE + 1);
2243 p_sys->psz_current_dir = var_CreateGetString(p_intf, "browse-dir");
2244 if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
2246 free(p_sys->psz_current_dir);
2247 p_sys->psz_current_dir = config_GetUserDir(VLC_HOME_DIR);
2250 p_sys->w = initscr(); /* Initialize the curses library */
2253 start_color_and_pairs(p_intf);
2255 keypad(p_sys->w, TRUE); /* Don't do NL -> CR/NL */
2256 nonl(); /* Take input chars one at a time */
2257 cbreak(); /* Don't echo */
2258 noecho(); /* Invisible cursor */
2259 curs_set(0); /* Non blocking wgetch() */
2260 wtimeout(p_sys->w, 0);
2263 /* Stop printing errors to the console */
2264 freopen("/dev/null", "wb", stderr);
2268 p_intf->pf_run = Run;
2272 /*****************************************************************************
2273 * Close: destroy interface window
2274 *****************************************************************************/
2275 static void Close(vlc_object_t *p_this)
2277 intf_sys_t *p_sys = ((intf_thread_t*)p_this)->p_sys;
2279 PlaylistDestroy(p_sys);
2282 free(p_sys->psz_current_dir);
2283 free(p_sys->psz_search_chain);
2284 free(p_sys->psz_old_search);
2285 free(p_sys->psz_open_chain);
2288 vlc_object_release(p_sys->p_input);
2290 /* Close the ncurses interface */
2293 // msg_Unsubscribe(p_intf, p_sys->p_sub);