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 *****************************************************************************/
28 /* UTF8 locale is required */
30 /*****************************************************************************
32 *****************************************************************************/
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
40 #define _XOPEN_SOURCE_EXTENDED 1
45 #include <vlc_interface.h>
48 #include <vlc_charset.h>
49 #include <vlc_input.h>
51 #include <vlc_playlist.h>
57 #ifdef HAVE_SYS_STAT_H
58 # include <sys/stat.h>
61 /*****************************************************************************
63 *****************************************************************************/
64 static int Open (vlc_object_t *);
65 static void Close (vlc_object_t *);
67 /*****************************************************************************
69 *****************************************************************************/
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.")
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)
87 /*****************************************************************************
88 * intf_sys_t: description and status of ncurses interface
89 *****************************************************************************/
122 /* XXX: new elements here ! */
127 /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
128 static const struct { short f; short b; } color_pairs[] =
130 /* element */ /* foreground*/ /* background*/
131 [C_TITLE] = { COLOR_YELLOW, COLOR_BLACK },
133 /* jamaican playlist, for rastafari sisters & brothers! */
134 [C_PLAYLIST_1] = { COLOR_GREEN, COLOR_BLACK },
135 [C_PLAYLIST_2] = { COLOR_YELLOW, COLOR_BLACK },
136 [C_PLAYLIST_3] = { COLOR_RED, COLOR_BLACK },
138 /* used in DrawBox() */
139 [C_BOX] = { COLOR_CYAN, COLOR_BLACK },
140 /* Source: State, Position, Volume, Chapters, etc...*/
141 [C_STATUS] = { COLOR_BLUE, COLOR_BLACK },
144 /* VLC messages, keep the order from highest priority to lowest */
145 [C_INFO] = { COLOR_BLACK, COLOR_WHITE },
146 [C_ERROR] = { COLOR_RED, COLOR_BLACK },
147 [C_WARNING] = { COLOR_YELLOW, COLOR_BLACK },
148 [C_DEBUG] = { COLOR_WHITE, COLOR_BLACK },
150 /* Category title: help, info, metadata */
151 [C_CATEGORY] = { COLOR_MAGENTA, COLOR_BLACK },
152 /* Folder (BOX_BROWSE) */
153 [C_FOLDER] = { COLOR_RED, COLOR_BLACK },
164 playlist_item_t *p_item;
170 input_thread_t *p_input;
179 int i_box_lines_total;
182 int i_box_plidx; /* Playlist index */
183 int b_box_plidx_follow;
184 int i_box_bidx; /* browser index */
186 playlist_item_t *p_node; /* current node */
188 // msg_subscription_t* p_sub; /* message bank subscription */
190 char psz_search_chain[20];
191 char *psz_old_search;
194 char psz_open_chain[50];
196 char *psz_current_dir;
198 struct dir_entry_t **pp_dir_entries;
199 bool b_show_hidden_files;
202 struct pl_item_t **pp_plist;
204 bool b_need_update; /* for playlist view */
207 /*****************************************************************************
209 *****************************************************************************/
211 static void DirsDestroy(intf_sys_t *p_sys)
213 while (p_sys->i_dir_entries)
215 struct dir_entry_t *p_dir_entry;
216 p_dir_entry = p_sys->pp_dir_entries[--p_sys->i_dir_entries];
217 free(p_dir_entry->psz_path);
220 free(p_sys->pp_dir_entries);
221 p_sys->pp_dir_entries = NULL;
224 static int comp_dir_entries(const void *pp_dir_entry1, const void *pp_dir_entry2)
226 struct dir_entry_t *p_dir_entry1 = *(struct dir_entry_t**)pp_dir_entry1;
227 struct dir_entry_t *p_dir_entry2 = *(struct dir_entry_t**)pp_dir_entry2;
229 if (p_dir_entry1->b_file == p_dir_entry2->b_file)
230 return strcasecmp(p_dir_entry1->psz_path, p_dir_entry2->psz_path);
232 return p_dir_entry1->b_file ? 1 : -1;
235 static bool IsFile(const char *current_dir, const char *entry)
242 if (asprintf(&uri, "%s/%s", current_dir, entry) != -1)
244 ret = vlc_stat(uri, &st) || !S_ISDIR(st.st_mode);
251 static void ReadDir(intf_thread_t *p_intf)
253 intf_sys_t *p_sys = p_intf->p_sys;
256 if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
258 msg_Dbg(p_intf, "no current dir set");
265 p_current_dir = vlc_opendir(p_sys->psz_current_dir);
269 /* something went bad, get out of here ! */
270 msg_Warn(p_intf, "cannot open directory `%s' (%m)",
271 p_sys->psz_current_dir);
275 /* Clean the old shit */
278 /* while we still have entries in the directory */
279 while ((psz_entry = vlc_readdir(p_current_dir)))
281 struct dir_entry_t *p_dir_entry;
283 if (!p_sys->b_show_hidden_files)
284 if (*psz_entry == '.' && strcmp(psz_entry, ".."))
287 if (!(p_dir_entry = malloc(sizeof *p_dir_entry)))
290 p_dir_entry->b_file = IsFile(p_sys->psz_current_dir, psz_entry);
291 p_dir_entry->psz_path = strdup(psz_entry);
292 INSERT_ELEM(p_sys->pp_dir_entries, p_sys->i_dir_entries,
293 p_sys->i_dir_entries, p_dir_entry);
300 qsort(p_sys->pp_dir_entries, p_sys->i_dir_entries,
301 sizeof(struct dir_entry_t*), &comp_dir_entries);
303 closedir(p_current_dir);
306 /*****************************************************************************
308 *****************************************************************************/
310 static void PlaylistDestroy(intf_sys_t *p_sys)
312 while (p_sys->i_plist_entries)
314 struct pl_item_t *p_pl_item = p_sys->pp_plist[--p_sys->i_plist_entries];
315 free(p_pl_item->psz_display);
318 free(p_sys->pp_plist);
319 p_sys->pp_plist = NULL;
322 static inline playlist_item_t *PlaylistGetRoot(intf_thread_t *p_intf)
324 playlist_t *p_playlist = pl_Get(p_intf);
325 return p_intf->p_sys->category_view ?
326 p_playlist->p_root_category :
327 p_playlist->p_root_onelevel;
330 static bool PlaylistAddChild(intf_sys_t *p_sys, playlist_item_t *p_child,
331 const char *c, const char d)
334 char *psz_name = input_item_GetTitleFbName(p_child->p_input);
335 struct pl_item_t *p_pl_item = malloc(sizeof *p_pl_item);
337 if (!psz_name || !p_pl_item)
340 p_pl_item->p_item = p_child;
343 ret = asprintf(&p_pl_item->psz_display, "%s%c-%s", c, d, psz_name);
345 ret = asprintf(&p_pl_item->psz_display, " %s", psz_name);
353 INSERT_ELEM(p_sys->pp_plist, p_sys->i_plist_entries,
354 p_sys->i_plist_entries, p_pl_item);
364 static void PlaylistAddNode(intf_sys_t *p_sys, playlist_item_t *p_node,
367 for(int k = 0; k < p_node->i_children; k++)
369 playlist_item_t *p_child = p_node->pp_children[k];
370 char d = k == p_node->i_children - 1 ? '`' : '|';
371 if(!PlaylistAddChild(p_sys, p_child, c, d))
374 if (p_child->i_children <= 0)
380 if (asprintf(&psz_tmp, "%s%c ", c,
381 k == p_node->i_children - 1 ? ' ' : '|') == -1)
383 PlaylistAddNode(p_sys, p_child, psz_tmp);
387 PlaylistAddNode(p_sys, p_child, " ");
391 static void PlaylistRebuild(intf_thread_t *p_intf)
393 intf_sys_t *p_sys = p_intf->p_sys;
394 playlist_t *p_playlist = pl_Get(p_intf);
398 PlaylistDestroy(p_sys);
399 PlaylistAddNode(p_sys, PlaylistGetRoot(p_intf), "");
400 p_sys->b_need_update = false;
405 static int PlaylistChanged(vlc_object_t *p_this, const char *psz_variable,
406 vlc_value_t oval, vlc_value_t nval, void *param)
408 VLC_UNUSED(p_this); VLC_UNUSED(psz_variable);
409 VLC_UNUSED(oval); VLC_UNUSED(nval);
411 intf_thread_t *p_intf = (intf_thread_t *)param;
412 intf_sys_t *p_sys = p_intf->p_sys;
413 playlist_item_t *p_node = playlist_CurrentPlayingItem(pl_Get(p_intf));
415 p_sys->b_need_update = true;
416 p_sys->p_node = p_node ? p_node->p_parent : NULL;
422 /* This function have to be called with the playlist locked */
423 static inline bool PlaylistIsPlaying(playlist_t *p_playlist,
424 playlist_item_t *p_item)
426 playlist_item_t *p_played_item = playlist_CurrentPlayingItem(p_playlist);
427 return p_item && p_played_item
428 && p_item->p_input && p_played_item->p_input
429 && p_item->p_input->i_id == p_played_item->p_input->i_id;
432 static int SubSearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring,
433 int i_start, int i_stop)
435 for(int i = i_start + 1; i < i_stop; i++)
436 if (strcasestr(p_sys->pp_plist[i]->psz_display, psz_searchstring))
442 static void SearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring)
444 int i_item, i_first = p_sys->i_before_search;
449 if (!psz_searchstring || !*psz_searchstring)
451 p_sys->i_box_plidx = p_sys->i_before_search;
455 i_item = SubSearchPlaylist(p_sys, psz_searchstring, i_first + 1,
456 p_sys->i_plist_entries);
458 i_item = SubSearchPlaylist(p_sys, psz_searchstring, 0, i_first);
461 p_sys->i_box_plidx = i_item;
464 static inline bool IsIndex(intf_sys_t *p_sys, playlist_t *p_playlist, int i)
466 playlist_item_t *p_item = p_sys->pp_plist[i]->p_item;
467 return (p_item->i_children == 0 && p_item == p_sys->p_node) ||
468 PlaylistIsPlaying(p_playlist, p_item);
471 static void FindIndex(intf_sys_t *p_sys, playlist_t *p_playlist, bool locked)
473 int plidx = p_sys->i_box_plidx;
477 if (plidx < 0 || plidx >= p_sys->i_plist_entries ||
478 !IsIndex(p_sys, p_playlist, plidx))
480 for(int i = 0; i < p_sys->i_plist_entries; i++)
481 if (IsIndex(p_sys, p_playlist, i))
483 p_sys->i_box_plidx = i;
492 /****************************************************************************
494 ****************************************************************************/
496 static void start_color_and_pairs(intf_thread_t *p_intf)
500 p_intf->p_sys->b_color = false;
501 msg_Warn(p_intf, "Terminal doesn't support colors");
506 for(int i = C_DEFAULT + 1; i < C_MAX; i++)
507 init_pair(i, color_pairs[i].f, color_pairs[i].b);
509 /* untested, in all my terminals, !can_change_color() --funman */
510 if (can_change_color())
511 init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
514 static void DrawBox(WINDOW *win, int y, int x, int h, int w, const char *title, bool b_color)
518 if (w <= 3 || h <= 2)
522 wcolor_set(win, C_BOX, NULL);
523 if (!title) title = "";
524 i_len = strlen(title);
529 mvwaddch(win, y, x, ACS_ULCORNER);
530 mvwhline(win, y, x+1, ACS_HLINE, (w-i_len-2)/2);
531 mvwprintw(win,y, x+1+(w-i_len-2)/2, "%s", title);
532 mvwhline(win, y, x+(w-i_len)/2+i_len, ACS_HLINE, w - 1 - ((w-i_len)/2+i_len));
533 mvwaddch(win, y, x+w-1,ACS_URCORNER);
535 for(int i = 0; i < h-2; i++)
537 mvwaddch(win, y+i+1, x, ACS_VLINE);
538 mvwaddch(win, y+i+1, x+w-1, ACS_VLINE);
541 mvwaddch(win, y+h-1, x, ACS_LLCORNER);
542 mvwhline(win, y+h-1, x+1, ACS_HLINE, w - 2);
543 mvwaddch(win, y+h-1, x+w-1, ACS_LRCORNER);
545 wcolor_set(win, C_DEFAULT, NULL);
548 static void DrawEmptyLine(WINDOW *win, int y, int x, int w)
552 mvwhline(win, y, x, ' ', w);
555 static void DrawLine(WINDOW *win, int y, int x, int w)
560 mvwhline(win, y, x, ' ', w);
564 static void mvnprintw(int y, int x, int w, const char *p_fmt, ...)
573 va_start(vl_args, p_fmt);
574 if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
578 i_len = strlen(p_buf);
580 wchar_t psz_wide[i_len + 1];
583 size_t i_char_len = mbstowcs(psz_wide, p_buf, i_len);
585 size_t i_width; /* number of columns */
587 if (i_char_len == (size_t)-1) /* an invalid character was encountered */
593 i_width = wcswidth(psz_wide, i_char_len);
594 if (i_width == (size_t)-1)
596 /* a non printable character was encountered */
598 for(unsigned i = 0 ; i < i_char_len ; i++)
600 int i_cwidth = wcwidth(psz_wide[i]);
606 if (i_width <= (size_t)w)
608 mvprintw(y, x, "%s", p_buf);
609 mvhline(y, x + i_width, ' ', w - i_width);
614 int i_total_width = 0;
616 while (i_total_width < w)
618 i_total_width += wcwidth(psz_wide[i]);
619 if (w > 7 && i_total_width >= w/2)
623 i_total_width -= wcwidth(psz_wide[i]) - 2;
626 /* we require this check only if at least one character
627 * 4 or more columns wide exists (which i doubt) */
629 i_total_width -= wcwidth(psz_wide[i-1]) - 1;
632 /* find the widest string */
633 int j, i_2nd_width = 0;
634 for(j = i_char_len - 1; i_2nd_width < w - i_total_width; j--)
635 i_2nd_width += wcwidth(psz_wide[j]);
637 /* we already have i_total_width columns filled, and we can't
638 * have more than w columns */
639 if (i_2nd_width > w - i_total_width)
642 wmemmove(&psz_wide[i+2], &psz_wide[j+1], i_char_len - j - 1);
643 psz_wide[i + 2 + i_char_len - j - 1] = '\0';
648 if (w <= 7) /* we don't add the '...' else we lose too much chars */
651 size_t i_wlen = wcslen(psz_wide) * 6 + 1; /* worst case */
652 char psz_ellipsized[i_wlen];
653 wcstombs(psz_ellipsized, psz_wide, i_wlen);
654 mvprintw(y, x, "%s", psz_ellipsized);
659 static void MainBoxWrite(intf_thread_t *p_intf, int l, int x, const char *p_fmt, ...)
661 intf_sys_t *p_sys = p_intf->p_sys;
665 if (l < p_sys->i_box_start || l - p_sys->i_box_start >= p_sys->i_box_lines)
668 va_start(vl_args, p_fmt);
669 if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
673 mvnprintw(p_sys->i_box_y + l - p_sys->i_box_start, x, COLS - x - 1, "%s", p_buf);
677 static void DumpObject(intf_thread_t *p_intf, int *l, vlc_object_t *p_obj, int i_level)
679 char *psz_name = vlc_object_get_name(p_obj);
682 MainBoxWrite(p_intf, (*l)++, 1 + 2 * i_level, "%s \"%s\" (%p)",
683 p_obj->psz_object_type, psz_name, p_obj);
687 MainBoxWrite(p_intf, (*l)++, 1 + 2 * i_level, "%s (%o)",
688 p_obj->psz_object_type, p_obj);
690 vlc_list_t *list = vlc_list_children(p_obj);
691 for(int i = 0; i < list->i_count ; i++)
693 MainBoxWrite(p_intf, *l, 1 + 2 * i_level,
694 i == list->i_count - 1 ? "`-" : "|-");
695 DumpObject(p_intf, l, list->p_values[i].p_object, i_level + 1);
697 vlc_list_release(list);
700 static void Redraw(intf_thread_t *p_intf, time_t *t_last_refresh)
702 intf_sys_t *p_sys = p_intf->p_sys;
703 input_thread_t *p_input = p_sys->p_input;
704 playlist_t *p_playlist = pl_Get(p_intf);
711 int i_len = sizeof "VLC media player "PACKAGE_VERSION - 1;
712 int mid = (COLS - i_len) / 2;
715 int i_size = (COLS > i_len + 1) ? COLS : i_len + 1;
716 char psz_title[i_size];
717 memset(psz_title, ' ', mid);
719 wcolor_set(p_sys->w, C_TITLE, NULL);
720 strlcpy(&psz_title[mid], "VLC media player "PACKAGE_VERSION, i_size);
721 mvnprintw(y, 0, COLS, "%s", psz_title);
726 wcolor_set(p_sys->w, C_STATUS, NULL);
730 if (asprintf(&psz_state, "%s%s%s",
731 var_GetBool(p_playlist, "repeat") ? _("[Repeat] ") : "",
732 var_GetBool(p_playlist, "random") ? _("[Random] ") : "",
733 var_GetBool(p_playlist, "loop") ? _("[Loop]") : "") == -1)
736 if (p_input && !p_input->b_dead)
738 char buf1[MSTRTIME_MAX_SIZE];
739 char buf2[MSTRTIME_MAX_SIZE];
743 char *psz_uri = input_item_GetURI(input_GetItem(p_input));
744 mvnprintw(y++, 0, COLS, _(" Source : %s"), psz_uri);
748 var_Get(p_input, "state", &val);
749 if (val.i_int == PLAYING_S)
750 mvnprintw(y++, 0, COLS, _(" State : Playing %s"), psz_state);
751 else if (val.i_int == OPENING_S)
752 mvnprintw(y++, 0, COLS, _(" State : Opening/Connecting %s"), psz_state);
753 else if (val.i_int == PAUSE_S)
754 mvnprintw(y++, 0, COLS, _(" State : Paused %s"), psz_state);
756 if (val.i_int == INIT_S || val.i_int == END_S)
760 audio_volume_t i_volume;
763 var_Get(p_input, "time", &val);
764 secstotimestr(buf1, val.i_time / CLOCK_FREQ);
766 var_Get(p_input, "length", &val);
767 secstotimestr(buf2, val.i_time / CLOCK_FREQ);
769 mvnprintw(y++, 0, COLS, _(" Position : %s/%s"), buf1, buf2);
772 aout_VolumeGet(p_playlist, &i_volume);
773 mvnprintw(y++, 0, COLS, _(" Volume : %i%%"), i_volume*200/AOUT_VOLUME_MAX);
776 if (!var_Get(p_input, "title", &val))
778 int i_title_count = var_CountChoices(p_input, "title");
779 if (i_title_count > 0)
780 mvnprintw(y++, 0, COLS, _(" Title : %"PRId64"/%d"),
781 val.i_int, i_title_count);
785 if (!var_Get(p_input, "chapter", &val))
787 int i_chapter_count = var_CountChoices(p_input, "chapter");
788 if (i_chapter_count > 0)
789 mvnprintw(y++, 0, COLS, _(" Chapter : %"PRId64"/%d"),
790 val.i_int, i_chapter_count);
796 mvnprintw(y++, 0, COLS, _(" Source: <no current item> %s"), psz_state);
797 DrawEmptyLine(p_sys->w, y++, 0, COLS);
798 mvnprintw(y++, 0, COLS, _(" [ h for help ]"));
799 DrawEmptyLine(p_sys->w, y++, 0, COLS);
803 wcolor_set(p_sys->w, C_DEFAULT, NULL);
805 DrawBox(p_sys->w, y, 0, 3, COLS, "", p_sys->b_color);
806 DrawEmptyLine(p_sys->w, y+1, 1, COLS-2);
808 if (p_input && var_GetInteger(p_input, "state") == PLAYING_S)
810 float pos = var_GetFloat(p_input, "position");
811 DrawLine(p_sys->w, y+1, 1, (int)(pos * (COLS-2)));
816 p_sys->i_box_y = y + 1;
817 p_sys->i_box_lines = LINES - y - 2;
822 if (p_sys->i_box_type == BOX_HELP)
826 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Help "), p_sys->b_color);
829 wcolor_set(p_sys->w, C_CATEGORY, NULL);
830 MainBoxWrite(p_intf, l++, 1, _("[Display]"));
832 wcolor_set(p_sys->w, C_DEFAULT, NULL);
833 MainBoxWrite(p_intf, l++, 1, _(" h,H Show/Hide help box"));
834 MainBoxWrite(p_intf, l++, 1, _(" i Show/Hide info box"));
835 MainBoxWrite(p_intf, l++, 1, _(" m Show/Hide metadata box"));
836 // MainBoxWrite(p_intf, l++, 1, _(" L Show/Hide messages box"));
837 MainBoxWrite(p_intf, l++, 1, _(" P Show/Hide playlist box"));
838 MainBoxWrite(p_intf, l++, 1, _(" B Show/Hide filebrowser"));
839 MainBoxWrite(p_intf, l++, 1, _(" x Show/Hide objects box"));
840 MainBoxWrite(p_intf, l++, 1, _(" S Show/Hide statistics box"));
841 MainBoxWrite(p_intf, l++, 1, _(" Esc Close Add/Search entry"));
842 MainBoxWrite(p_intf, l++, 1, "");
845 wcolor_set(p_sys->w, C_CATEGORY, NULL);
846 MainBoxWrite(p_intf, l++, 1, _("[Global]"));
848 wcolor_set(p_sys->w, C_DEFAULT, NULL);
849 MainBoxWrite(p_intf, l++, 1, _(" q, Q, Esc Quit"));
850 MainBoxWrite(p_intf, l++, 1, _(" s Stop"));
851 MainBoxWrite(p_intf, l++, 1, _(" <space> Pause/Play"));
852 MainBoxWrite(p_intf, l++, 1, _(" f Toggle Fullscreen"));
853 MainBoxWrite(p_intf, l++, 1, _(" n, p Next/Previous playlist item"));
854 MainBoxWrite(p_intf, l++, 1, _(" [, ] Next/Previous title"));
855 MainBoxWrite(p_intf, l++, 1, _(" <, > Next/Previous chapter"));
856 MainBoxWrite(p_intf, l++, 1, _(" <right> Seek +1%%"));
857 MainBoxWrite(p_intf, l++, 1, _(" <left> Seek -1%%"));
858 MainBoxWrite(p_intf, l++, 1, _(" a Volume Up"));
859 MainBoxWrite(p_intf, l++, 1, _(" z Volume Down"));
860 MainBoxWrite(p_intf, l++, 1, "");
863 wcolor_set(p_sys->w, C_CATEGORY, NULL);
864 MainBoxWrite(p_intf, l++, 1, _("[Playlist]"));
866 wcolor_set(p_sys->w, C_DEFAULT, NULL);
867 MainBoxWrite(p_intf, l++, 1, _(" r Toggle Random playing"));
868 MainBoxWrite(p_intf, l++, 1, _(" l Toggle Loop Playlist"));
869 MainBoxWrite(p_intf, l++, 1, _(" R Toggle Repeat item"));
870 MainBoxWrite(p_intf, l++, 1, _(" o Order Playlist by title"));
871 MainBoxWrite(p_intf, l++, 1, _(" O Reverse order Playlist by title"));
872 MainBoxWrite(p_intf, l++, 1, _(" g Go to the current playing item"));
873 MainBoxWrite(p_intf, l++, 1, _(" / Look for an item"));
874 MainBoxWrite(p_intf, l++, 1, _(" A Add an entry"));
875 MainBoxWrite(p_intf, l++, 1, _(" D, <del> Delete an entry"));
876 MainBoxWrite(p_intf, l++, 1, _(" <backspace> Delete an entry"));
877 MainBoxWrite(p_intf, l++, 1, _(" e Eject (if stopped)"));
878 MainBoxWrite(p_intf, l++, 1, "");
881 wcolor_set(p_sys->w, C_CATEGORY, NULL);
882 MainBoxWrite(p_intf, l++, 1, _("[Filebrowser]"));
884 wcolor_set(p_sys->w, C_DEFAULT, NULL);
885 MainBoxWrite(p_intf, l++, 1, _(" <enter> Add the selected file to the playlist"));
886 MainBoxWrite(p_intf, l++, 1, _(" <space> Add the selected directory to the playlist"));
887 MainBoxWrite(p_intf, l++, 1, _(" . Show/Hide hidden files"));
888 MainBoxWrite(p_intf, l++, 1, "");
891 wcolor_set(p_sys->w, C_CATEGORY, NULL);
892 MainBoxWrite(p_intf, l++, 1, _("[Boxes]"));
894 wcolor_set(p_sys->w, C_DEFAULT, NULL);
895 MainBoxWrite(p_intf, l++, 1, _(" <up>,<down> Navigate through the box line by line"));
896 MainBoxWrite(p_intf, l++, 1, _(" <pgup>,<pgdown> Navigate through the box page by page"));
897 MainBoxWrite(p_intf, l++, 1, "");
900 wcolor_set(p_sys->w, C_CATEGORY, NULL);
901 MainBoxWrite(p_intf, l++, 1, _("[Player]"));
903 wcolor_set(p_sys->w, C_DEFAULT, NULL);
904 MainBoxWrite(p_intf, l++, 1, _(" <up>,<down> Seek +/-5%%"));
905 MainBoxWrite(p_intf, l++, 1, "");
908 wcolor_set(p_sys->w, C_CATEGORY, NULL);
909 MainBoxWrite(p_intf, l++, 1, _("[Miscellaneous]"));
911 wcolor_set(p_sys->w, C_DEFAULT, NULL);
912 MainBoxWrite(p_intf, l++, 1, _(" Ctrl-l Refresh the screen"));
914 p_sys->i_box_lines_total = l;
915 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
916 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
918 if (l - p_sys->i_box_start < p_sys->i_box_lines)
919 y += l - p_sys->i_box_start;
921 y += p_sys->i_box_lines;
923 else if (p_sys->i_box_type == BOX_INFO)
927 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Information "), p_sys->b_color);
932 vlc_mutex_lock(&input_GetItem(p_input)->lock);
933 for(i = 0; i < input_GetItem(p_input)->i_categories; i++)
935 info_category_t *p_category = input_GetItem(p_input)->pp_categories[i];
936 if (y >= y_end) break;
938 wcolor_set(p_sys->w, C_CATEGORY, NULL);
939 MainBoxWrite(p_intf, l++, 1, _(" [%s]"), p_category->psz_name);
941 wcolor_set(p_sys->w, C_DEFAULT, NULL);
942 for(j = 0; j < p_category->i_infos; j++)
944 info_t *p_info = p_category->pp_infos[j];
945 if (y >= y_end) break;
946 MainBoxWrite(p_intf, l++, 1, _(" %s: %s"), p_info->psz_name, p_info->psz_value);
949 vlc_mutex_unlock(&input_GetItem(p_input)->lock);
952 MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
954 p_sys->i_box_lines_total = l;
955 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
956 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
958 if (l - p_sys->i_box_start < p_sys->i_box_lines)
959 y += l - p_sys->i_box_start;
961 y += p_sys->i_box_lines;
963 else if (p_sys->i_box_type == BOX_META)
968 DrawBox(p_sys->w, y++, 0, h, COLS, _("Meta-information"),
973 input_item_t *p_item = input_GetItem(p_input);
974 vlc_mutex_lock(&p_item->lock);
975 for(int i=0; i<VLC_META_TYPE_COUNT; i++)
977 const char *psz_meta = vlc_meta_Get(p_item->p_meta, i);
978 if (!psz_meta || !*psz_meta)
981 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
982 MainBoxWrite(p_intf, l++, 1, " [%s]",
983 vlc_meta_TypeToLocalizedString(i));
984 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
985 MainBoxWrite(p_intf, l++, 1, " %s", psz_meta);
987 vlc_mutex_unlock(&p_item->lock);
990 MainBoxWrite(p_intf, l++, 1, _("No item currently playing"));
992 p_sys->i_box_lines_total = l;
993 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
994 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
996 y += __MIN(l - p_sys->i_box_start, p_sys->i_box_lines);
998 #if 0 /* Deprecated API */
999 else if (p_sys->i_box_type == BOX_LOG)
1005 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Logs "), p_sys->b_color);
1008 i_start = p_intf->p_sys->p_sub->i_start;
1010 vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1011 i_stop = *p_intf->p_sys->p_sub->pi_stop;
1012 vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1016 static const char *ppsz_type[4] = { "", "error", "warning", "debug" };
1017 if (i_line >= h - 2)
1023 if (i_stop < 0) i_stop += VLC_MSG_QSIZE;
1024 if (i_stop == i_start)
1029 wcolor_set(p_sys->w,
1030 p_sys->p_sub->p_msg[i_stop].i_type + C_INFO,
1032 mvnprintw(y + h-2-i_line, 1, COLS - 2, " [%s] %s",
1033 ppsz_type[p_sys->p_sub->p_msg[i_stop].i_type],
1034 p_sys->p_sub->p_msg[i_stop].psz_msg);
1036 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1039 vlc_mutex_lock(p_intf->p_sys->p_sub->p_lock);
1040 p_intf->p_sys->p_sub->i_start = i_stop;
1041 vlc_mutex_unlock(p_intf->p_sys->p_sub->p_lock);
1045 else if (p_sys->i_box_type == BOX_BROWSE)
1047 /* Filebrowser box */
1048 int i_start, i_stop;
1050 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Browse "), p_sys->b_color);
1052 if (p_sys->i_box_bidx >= p_sys->i_dir_entries) p_sys->i_box_plidx = p_sys->i_dir_entries - 1;
1053 if (p_sys->i_box_bidx < 0) p_sys->i_box_bidx = 0;
1055 if (p_sys->i_box_bidx < (h - 2)/2)
1060 else if (p_sys->i_dir_entries - p_sys->i_box_bidx > (h - 2)/2)
1062 i_start = p_sys->i_box_bidx - (h - 2)/2;
1063 i_stop = i_start + h - 2;
1067 i_stop = p_sys->i_dir_entries;
1068 i_start = p_sys->i_dir_entries - (h - 2);
1072 if (i_stop > p_sys->i_dir_entries)
1073 i_stop = p_sys->i_dir_entries;
1075 for(i_item = i_start; i_item < i_stop; i_item++)
1077 bool b_selected = (p_sys->i_box_bidx == i_item);
1079 if (y >= y_end) break;
1082 if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1083 wcolor_set(p_sys->w, C_FOLDER, NULL);
1084 mvnprintw(y++, 1, COLS - 2, " %c %s", p_sys->pp_dir_entries[i_item]->b_file == true ? ' ' : '+',
1085 p_sys->pp_dir_entries[i_item]->psz_path);
1086 if (p_sys->b_color && !p_sys->pp_dir_entries[i_item]->b_file)
1087 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1094 else if (p_sys->i_box_type == BOX_OBJECTS)
1097 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Objects "), p_sys->b_color);
1098 DumpObject(p_intf, &l, VLC_OBJECT(p_intf->p_libvlc), 0);
1100 p_sys->i_box_lines_total = l;
1101 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1102 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1104 if (l - p_sys->i_box_start < p_sys->i_box_lines)
1105 y += l - p_sys->i_box_start;
1107 y += p_sys->i_box_lines;
1109 else if (p_sys->i_box_type == BOX_STATS)
1111 DrawBox(p_sys->w, y++, 0, h, COLS, _(" Stats "), p_sys->b_color);
1115 input_item_t *p_item = input_GetItem(p_input);
1117 vlc_mutex_lock(&p_item->lock);
1118 vlc_mutex_lock(&p_item->p_stats->lock);
1125 i_video = i_audio = 1;
1127 for(i = 0; i < p_item->i_es ; i++)
1129 i_audio += (p_item->es[i]->i_cat == AUDIO_ES);
1130 i_video += (p_item->es[i]->i_cat == VIDEO_ES);
1135 #define SHOW_ACS(x,c) \
1136 if (l >= p_sys->i_box_start && l - p_sys->i_box_start < p_sys->i_box_lines) \
1137 mvaddch(p_sys->i_box_y - p_sys->i_box_start + l, x, c)
1140 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1141 MainBoxWrite(p_intf, l, 1, _("+-[Incoming]"));
1142 SHOW_ACS(1, ACS_ULCORNER); SHOW_ACS(2, ACS_HLINE); l++;
1143 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1144 MainBoxWrite(p_intf, l, 1, _("| input bytes read : %8.0f KiB"),
1145 (float)(p_item->p_stats->i_read_bytes)/1024);
1146 SHOW_ACS(1, ACS_VLINE); l++;
1147 MainBoxWrite(p_intf, l, 1, _("| input bitrate : %6.0f kb/s"),
1148 (float)(p_item->p_stats->f_input_bitrate)*8000);
1149 MainBoxWrite(p_intf, l, 1, _("| demux bytes read : %8.0f KiB"),
1150 (float)(p_item->p_stats->i_demux_read_bytes)/1024);
1151 SHOW_ACS(1, ACS_VLINE); l++;
1152 MainBoxWrite(p_intf, l, 1, _("| demux bitrate : %6.0f kb/s"),
1153 (float)(p_item->p_stats->f_demux_bitrate)*8000);
1154 SHOW_ACS(1, ACS_VLINE); l++;
1155 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1156 SHOW_ACS(1, ACS_VLINE); l++;
1161 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1162 MainBoxWrite(p_intf, l, 1, _("+-[Video Decoding]"));
1163 SHOW_ACS(1, ACS_LTEE); SHOW_ACS(2, ACS_HLINE); l++;
1164 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1165 MainBoxWrite(p_intf, l, 1, _("| video decoded : %"PRId64),
1166 p_item->p_stats->i_decoded_video);
1167 SHOW_ACS(1, ACS_VLINE); l++;
1168 MainBoxWrite(p_intf, l, 1, _("| frames displayed : %"PRId64),
1169 p_item->p_stats->i_displayed_pictures);
1170 SHOW_ACS(1, ACS_VLINE); l++;
1171 MainBoxWrite(p_intf, l, 1, _("| frames lost : %"PRId64),
1172 p_item->p_stats->i_lost_pictures);
1173 SHOW_ACS(1, ACS_VLINE); l++;
1174 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1175 SHOW_ACS(1, ACS_VLINE); l++;
1180 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1181 MainBoxWrite(p_intf, l, 1, _("+-[Audio Decoding]"));
1182 SHOW_ACS(1, ACS_LTEE); SHOW_ACS(2, ACS_HLINE); l++;
1183 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1184 MainBoxWrite(p_intf, l, 1, _("| audio decoded : %"PRId64),
1185 p_item->p_stats->i_decoded_audio);
1186 SHOW_ACS(1, ACS_VLINE); l++;
1187 MainBoxWrite(p_intf, l, 1, _("| buffers played : %"PRId64),
1188 p_item->p_stats->i_played_abuffers);
1189 SHOW_ACS(1, ACS_VLINE); l++;
1190 MainBoxWrite(p_intf, l, 1, _("| buffers lost : %"PRId64),
1191 p_item->p_stats->i_lost_abuffers);
1192 SHOW_ACS(1, ACS_VLINE); l++;
1193 DrawEmptyLine(p_sys->w, p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2);
1194 SHOW_ACS(1, ACS_VLINE); l++;
1197 if (p_sys->b_color) wcolor_set(p_sys->w, C_CATEGORY, NULL);
1198 MainBoxWrite(p_intf, l, 1, _("+-[Streaming]"));
1199 SHOW_ACS(1, ACS_LTEE); SHOW_ACS(2, ACS_HLINE); l++;
1200 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1201 MainBoxWrite(p_intf, l, 1, _("| packets sent : %5i"), p_item->p_stats->i_sent_packets);
1202 SHOW_ACS(1, ACS_VLINE); l++;
1203 MainBoxWrite(p_intf, l, 1, _("| bytes sent : %8.0f KiB"),
1204 (float)(p_item->p_stats->i_sent_bytes)/1024);
1205 SHOW_ACS(1, ACS_VLINE); l++;
1206 MainBoxWrite(p_intf, l, 1, _("\\ sending bitrate : %6.0f kb/s"),
1207 (float)(p_item->p_stats->f_send_bitrate*8)*1000);
1208 SHOW_ACS(1, ACS_LLCORNER); l++;
1209 if (p_sys->b_color) wcolor_set(p_sys->w, C_DEFAULT, NULL);
1213 p_sys->i_box_lines_total = l;
1214 if (p_sys->i_box_start >= p_sys->i_box_lines_total)
1215 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1217 if (l - p_sys->i_box_start < p_sys->i_box_lines)
1218 y += l - p_sys->i_box_start;
1220 y += p_sys->i_box_lines;
1222 vlc_mutex_unlock(&p_item->p_stats->lock);
1223 vlc_mutex_unlock(&p_item->lock);
1227 else if (p_sys->i_box_type == BOX_PLAYLIST ||
1228 p_sys->i_box_type == BOX_SEARCH ||
1229 p_sys->i_box_type == BOX_OPEN )
1232 int i_start, i_stop, i_max = p_sys->i_plist_entries;
1236 if (p_sys->category_view)
1237 psz_title = strdup(_(" Playlist (By category) "));
1239 psz_title = strdup(_(" Playlist (All, one level) "));
1241 DrawBox(p_sys->w, y++, 0, h, COLS, psz_title, p_sys->b_color);
1244 if (p_sys->b_need_update || !p_sys->pp_plist)
1245 PlaylistRebuild(p_intf);
1246 if (p_sys->b_box_plidx_follow)
1247 FindIndex(p_sys, p_playlist, false);
1249 if (p_sys->i_box_plidx < 0) p_sys->i_box_plidx = 0;
1250 if (p_sys->i_box_plidx >= i_max) p_sys->i_box_plidx = i_max - 1;
1252 if (p_sys->i_box_plidx < (h - 2)/2)
1257 else if (i_max - p_sys->i_box_plidx > (h - 2)/2)
1259 i_start = p_sys->i_box_plidx - (h - 2)/2;
1260 i_stop = i_start + h - 2;
1265 i_start = i_max - (h - 2);
1272 for(i_item = i_start; i_item < i_stop; i_item++)
1274 bool b_selected = (p_sys->i_box_plidx == i_item);
1275 playlist_item_t *p_item = p_sys->pp_plist[i_item]->p_item;
1276 playlist_item_t *p_node = p_sys->p_node;
1278 input_thread_t *p_input2 = playlist_CurrentInput(p_playlist);
1282 playlist_item_t *p_current_playing_item = playlist_CurrentPlayingItem(p_playlist);
1283 if ((p_node && p_item->p_input == p_node->p_input) ||
1284 (!p_node && p_input2 && p_current_playing_item &&
1285 p_item->p_input == p_current_playing_item->p_input))
1287 else if (p_item == p_node || (p_item != p_node &&
1288 PlaylistIsPlaying(p_playlist, p_item)))
1293 vlc_object_release(p_input2);
1295 if (y >= y_end) break;
1299 wcolor_set(p_sys->w, i_item % 3 + C_PLAYLIST_1, NULL);
1300 mvnprintw(y++, 1, COLS - 2, "%c%s", c,
1301 p_sys->pp_plist[i_item]->psz_display);
1303 wcolor_set(p_sys->w, C_DEFAULT, NULL);
1312 if (p_sys->i_box_type == BOX_SEARCH)
1314 DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1315 mvnprintw(7, 1, COLS-2, _("Find: %s"), p_sys->psz_old_search ?
1316 p_sys->psz_old_search : p_sys->psz_search_chain);
1318 if (p_sys->i_box_type == BOX_OPEN)
1320 DrawEmptyLine(p_sys->w, 7, 1, COLS-2);
1321 mvnprintw(7, 1, COLS-2, _("Open: %s"), p_sys->psz_open_chain);
1325 DrawEmptyLine(p_sys->w, y++, 1, COLS - 2);
1329 *t_last_refresh = time(0);
1332 static void ChangePosition(intf_thread_t *p_intf, float increment)
1334 intf_sys_t *p_sys = p_intf->p_sys;
1335 input_thread_t *p_input = p_sys->p_input;
1338 if (!p_input || var_GetInteger(p_input, "state") != PLAYING_S)
1341 pos = var_GetFloat(p_input, "position") + increment;
1343 if (pos > 0.99) pos = 0.99;
1344 if (pos < 0.0) pos = 0.0;
1346 var_SetFloat(p_input, "position", pos);
1349 static inline int RemoveLastUTF8Entity(char *psz, int len)
1351 while (len && ((psz[--len] & 0xc0) == 0x80));
1352 /* UTF8 continuation byte */
1358 static char *GetDiscDevice(intf_thread_t *p_intf, const char *name)
1360 static const struct { const char *s; size_t n; const char *v; } devs[] =
1362 { "cdda://", 7, "cd-audio", },
1363 { "dvd://", 6, "dvd", },
1364 { "vcd://", 6, "vcd", },
1368 for (unsigned i = 0; i < sizeof devs / sizeof *devs; i++)
1370 size_t n = devs[i].n;
1371 if (!strncmp(name, devs[i].s, n))
1376 return config_GetPsz(p_intf, devs[i].v);
1378 /* Omit the beginning MRL-selector characters */
1379 return strdup(name + n);
1383 device = strdup(name);
1385 if (device) /* Remove what we have after @ */
1386 device[strcspn(device, "@")] = '\0';
1391 static void Eject(intf_thread_t *p_intf)
1393 char *psz_device, *psz_name;
1394 playlist_t * p_playlist = pl_Get(p_intf);
1396 /* If there's a stream playing, we aren't allowed to eject ! */
1397 if (p_intf->p_sys->p_input)
1402 if (!playlist_CurrentPlayingItem(p_playlist))
1408 psz_name = playlist_CurrentPlayingItem(p_playlist)->p_input->psz_name;
1409 psz_device = psz_name ? GetDiscDevice(p_intf, psz_name) : NULL;
1415 intf_Eject(p_intf, psz_device);
1420 static void PlayPause(intf_thread_t *p_intf)
1422 input_thread_t *p_input = p_intf->p_sys->p_input;
1426 int64_t state = var_GetInteger( p_input, "state" );
1427 state = (state != PLAYING_S) ? PLAYING_S : PAUSE_S;
1428 var_SetInteger( p_input, "state", state );
1431 playlist_Play(pl_Get(p_intf));
1434 static inline void BoxSwitch(intf_sys_t *p_sys, int box)
1436 p_sys->i_box_type = (p_sys->i_box_type == box) ? BOX_NONE : box;
1439 static bool HandlePlaylistKey(intf_thread_t *p_intf, int key)
1441 bool b_box_plidx_follow = false;
1442 intf_sys_t *p_sys = p_intf->p_sys;
1443 playlist_t *p_playlist = pl_Get(p_intf);
1444 struct pl_item_t *p_pl_item;
1448 /* Playlist Settings */
1449 case 'r': var_ToggleBool(p_playlist, "random"); return true;
1450 case 'l': var_ToggleBool(p_playlist, "loop"); return true;
1451 case 'R': var_ToggleBool(p_playlist, "repeat"); return true;
1456 playlist_RecursiveNodeSort(p_playlist, PlaylistGetRoot(p_intf),
1457 SORT_TITLE_NODES_FIRST,
1458 (key == 'o')? ORDER_NORMAL : ORDER_REVERSE);
1459 p_sys->b_need_update = true;
1464 p_sys->category_view = !p_sys->category_view;
1465 p_sys->b_need_update = true;
1468 /* Playlist navigation */
1470 /* workaround for FreeBSD + xterm:
1471 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1474 case KEY_END: p_sys->i_box_plidx = p_playlist->items.i_size - 1; break;
1475 case KEY_HOME: p_sys->i_box_plidx = 0; break;
1476 case KEY_UP: p_sys->i_box_plidx--; break;
1477 case KEY_DOWN: p_sys->i_box_plidx++; break;
1478 case KEY_PPAGE: p_sys->i_box_plidx -= p_sys->i_box_lines; break;
1479 case KEY_NPAGE: p_sys->i_box_plidx += p_sys->i_box_lines; break;
1480 case 'g': FindIndex(p_sys, p_playlist, false); break;
1488 playlist_item_t *p_item;
1491 p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
1492 if (p_item->i_children == -1)
1493 playlist_DeleteFromInput(p_playlist, p_item->p_input, pl_Locked);
1495 playlist_NodeDelete(p_playlist, p_item, true , false);
1497 p_sys->b_need_update = true;
1504 if (!(p_pl_item = p_sys->pp_plist[p_sys->i_box_plidx]))
1507 if (p_pl_item->p_item->i_children)
1509 playlist_item_t *p_item, *p_parent = p_pl_item->p_item;
1510 if (p_parent->i_children == -1)
1514 while (p_parent->p_parent)
1515 p_parent = p_parent->p_parent;
1519 p_sys->p_node = p_parent;
1523 playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Unlocked,
1527 { /* We only want to set the current node */
1528 playlist_Stop(p_playlist);
1529 p_sys->p_node = p_pl_item->p_item;
1532 b_box_plidx_follow = true;
1539 if (p_sys->i_box_plidx > p_sys->i_plist_entries - 1)
1540 p_sys->i_box_plidx = p_sys->i_plist_entries - 1;
1541 if (p_sys->i_box_plidx < 0)
1542 p_sys->i_box_plidx = 0;
1544 p_pl_item = p_sys->pp_plist[p_sys->i_box_plidx];
1547 if (PlaylistIsPlaying(p_playlist, p_pl_item->p_item))
1548 b_box_plidx_follow = true;
1550 p_sys->b_box_plidx_follow = b_box_plidx_follow;
1554 static bool HandleBrowseKey(intf_thread_t *p_intf, int key)
1556 struct dir_entry_t *dir_entry;
1557 intf_sys_t *p_sys = p_intf->p_sys;
1562 p_sys->b_show_hidden_files = !p_sys->b_show_hidden_files;
1570 dir_entry = p_sys->pp_dir_entries[p_sys->i_box_bidx];
1572 if (!dir_entry->b_file && key != ' ')
1574 char *current_dir = p_sys->psz_current_dir;
1575 if (asprintf(&p_sys->psz_current_dir, "%s/%s",
1576 p_sys->psz_current_dir, dir_entry->psz_path) != -1)
1582 p_sys->psz_current_dir = current_dir;
1588 if (asprintf(&psz_uri, "%s://%s/%s",
1589 dir_entry->b_file ? "file" : "directory",
1590 p_sys->psz_current_dir, dir_entry->psz_path) == -1)
1595 playlist_t *p_playlist = pl_Get(p_intf);
1596 playlist_item_t *p_parent = p_sys->p_node;
1599 playlist_item_t *p_item;
1601 p_item = playlist_CurrentPlayingItem(p_playlist);
1602 p_parent = p_item ? p_item->p_parent : NULL;
1605 p_parent = p_playlist->p_local_onelevel;
1608 while (p_parent->p_parent && p_parent->p_parent->p_parent)
1609 p_parent = p_parent->p_parent;
1611 input_item_t *p_input = p_playlist->p_local_onelevel->p_input;
1612 playlist_Add(p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
1613 PLAYLIST_END, p_parent->p_input == p_input, false);
1615 p_sys->i_box_type = BOX_PLAYLIST;
1622 case KEY_END: p_sys->i_box_bidx = p_sys->i_dir_entries - 1; break;
1623 case KEY_HOME: p_sys->i_box_bidx = 0; break;
1624 case KEY_UP: p_sys->i_box_bidx--; break;
1625 case KEY_DOWN: p_sys->i_box_bidx++; break;
1626 case KEY_PPAGE: p_sys->i_box_bidx -= p_sys->i_box_lines; break;
1627 case KEY_NPAGE: p_sys->i_box_bidx += p_sys->i_box_lines; break;
1633 if (p_sys->i_box_bidx >= p_sys->i_dir_entries)
1634 p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
1635 if (p_sys->i_box_bidx < 0)
1636 p_sys->i_box_bidx = 0;
1641 static void InputNavigate(input_thread_t* p_input, const char *var)
1644 var_TriggerCallback(p_input, var);
1647 static int HandleKey(intf_thread_t *p_intf)
1649 intf_sys_t *p_sys = p_intf->p_sys;
1650 playlist_t *p_playlist = pl_Get(p_intf);
1651 int i_key = wgetch(p_sys->w);
1656 if (p_sys->i_box_type == BOX_PLAYLIST)
1658 if (HandlePlaylistKey(p_intf, i_key))
1661 else if (p_sys->i_box_type == BOX_BROWSE)
1663 if (HandleBrowseKey(p_intf, i_key))
1666 else if (p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
1667 p_sys->i_box_type == BOX_META || p_sys->i_box_type == BOX_STATS ||
1668 p_sys->i_box_type == BOX_OBJECTS)
1676 case KEY_END: p_sys->i_box_start = p_sys->i_box_lines_total - 1; break;
1677 case KEY_HOME: p_sys->i_box_start = 0; break;
1678 case KEY_UP: p_sys->i_box_start--; break;
1679 case KEY_DOWN: p_sys->i_box_start++; break;
1680 case KEY_PPAGE:p_sys->i_box_start -= p_sys->i_box_lines; break;
1681 case KEY_NPAGE:p_sys->i_box_start += p_sys->i_box_lines; break;
1689 if (p_sys->i_box_start < 0)
1690 p_sys->i_box_start = 0;
1691 if (p_sys->i_box_start > p_sys->i_box_lines_total - 1)
1692 p_sys->i_box_start = p_sys->i_box_lines_total - 1;
1696 else if (p_sys->i_box_type == BOX_NONE)
1703 case KEY_END: ChangePosition(p_intf, +.99); return 1;
1704 case KEY_HOME: ChangePosition(p_intf, -1.0); return 1;
1705 case KEY_UP: ChangePosition(p_intf, +0.05); return 1;
1706 case KEY_DOWN: ChangePosition(p_intf, -0.05); return 1;
1709 else if (p_sys->i_box_type == BOX_SEARCH)
1711 size_t i_chain_len = strlen(p_sys->psz_search_chain);
1722 p_sys->psz_old_search = strdup(p_sys->psz_search_chain);
1723 else if (p_sys->psz_old_search)
1724 SearchPlaylist(p_sys, p_sys->psz_old_search);
1725 p_sys->i_box_type = BOX_PLAYLIST;
1727 case 0x1b: /* ESC */
1728 /* Alt+key combinations return 2 keys in the terminal keyboard:
1729 * ESC, and the 2nd key.
1730 * If some other key is available immediately (where immediately
1731 * means after wgetch() 1 second delay), that means that the
1732 * ESC key was not pressed.
1734 * man 3X curs_getch says:
1736 * Use of the escape key by a programmer for a single
1737 * character function is discouraged, as it will cause a delay
1738 * of up to one second while the keypad code looks for a
1739 * following function-key sequence.
1742 if (wgetch(p_sys->w) != ERR)
1744 p_sys->i_box_plidx = p_sys->i_before_search;
1745 p_sys->i_box_type = BOX_PLAYLIST;
1749 RemoveLastUTF8Entity(p_sys->psz_search_chain, i_chain_len);
1752 if (i_chain_len + 1 < sizeof p_sys->psz_search_chain)
1754 p_sys->psz_search_chain[i_chain_len] = (char) i_key;
1755 p_sys->psz_search_chain[i_chain_len + 1] = '\0';
1758 free(p_sys->psz_old_search);
1759 p_sys->psz_old_search = NULL;
1760 SearchPlaylist(p_sys, p_sys->psz_search_chain);
1763 else if (p_sys->i_box_type == BOX_OPEN)
1765 size_t i_chain_len = strlen(p_sys->psz_open_chain);
1778 playlist_item_t *p_parent = p_sys->p_node;
1782 p_parent = playlist_CurrentPlayingItem(p_playlist) ? playlist_CurrentPlayingItem(p_playlist)->p_parent : NULL;
1784 p_parent = p_playlist->p_local_onelevel;
1786 while (p_parent->p_parent && p_parent->p_parent->p_parent)
1787 p_parent = p_parent->p_parent;
1790 playlist_Add(p_playlist, p_sys->psz_open_chain, NULL,
1791 PLAYLIST_APPEND|PLAYLIST_GO, PLAYLIST_END,
1792 p_parent->p_input ==
1793 p_playlist->p_local_onelevel->p_input
1796 p_sys->b_box_plidx_follow = true;
1798 p_sys->i_box_type = BOX_PLAYLIST;
1800 case 0x1b: /* ESC */
1801 if (wgetch(p_sys->w) != ERR)
1803 p_sys->i_box_type = BOX_PLAYLIST;
1807 RemoveLastUTF8Entity(p_sys->psz_open_chain, i_chain_len);
1810 if (i_chain_len + 1 < sizeof p_sys->psz_open_chain)
1812 p_sys->psz_open_chain[i_chain_len] = (char) i_key;
1813 p_sys->psz_open_chain[i_chain_len + 1] = '\0';
1822 case 0x1b: /* ESC */
1823 if (wgetch(p_sys->w) != ERR)
1829 libvlc_Quit(p_intf->p_libvlc);
1833 case 'H': BoxSwitch(p_sys, BOX_HELP); return 1;
1834 case 'i': BoxSwitch(p_sys, BOX_INFO); return 1;
1835 case 'm': BoxSwitch(p_sys, BOX_META); return 1;
1836 // case 'L': BoxSwitch(p_sys, BOX_LOG) return 1;
1837 case 'P': BoxSwitch(p_sys, BOX_PLAYLIST); return 1;
1838 case 'B': BoxSwitch(p_sys, BOX_BROWSE); return 1;
1839 case 'x': BoxSwitch(p_sys, BOX_OBJECTS); return 1;
1840 case 'S': BoxSwitch(p_sys, BOX_STATS); return 1;
1842 case '/': /* Search */
1843 p_sys->psz_search_chain[0] = '\0';
1844 p_sys->b_box_plidx_follow = false;
1845 p_sys->i_before_search = p_sys->i_box_plidx;
1846 p_sys->i_box_type = BOX_SEARCH;
1849 case 'A': /* Open */
1850 p_sys->psz_open_chain[0] = '\0';
1851 p_sys->i_box_type = BOX_OPEN;
1855 case KEY_RIGHT: ChangePosition(p_intf, +0.01); return 1;
1856 case KEY_LEFT: ChangePosition(p_intf, -0.01); return 1;
1858 /* Common control */
1862 vout_thread_t *p_vout = input_GetVout(p_sys->p_input);
1865 bool fs = var_ToggleBool(p_playlist, "fullscreen");
1866 var_SetBool(p_vout, "fullscreen", fs);
1867 vlc_object_release(p_vout);
1872 case ' ': PlayPause(p_intf); return 1;
1873 case 's': playlist_Stop(p_playlist); return 1;
1874 case 'e': Eject(p_intf); return 1;
1876 case '[': InputNavigate(p_sys->p_input, "prev-title"); return 1;
1877 case ']': InputNavigate(p_sys->p_input, "next-title"); return 1;
1878 case '<': InputNavigate(p_sys->p_input, "prev-chapter"); return 1;
1879 case '>': InputNavigate(p_sys->p_input, "next-chapter"); return 1;
1881 case 'p': playlist_Prev(p_playlist); goto lclear;
1882 case 'n': playlist_Next(p_playlist); goto lclear;
1883 case 'a': aout_VolumeUp(p_playlist, 1, NULL); goto lclear;
1884 case 'z': aout_VolumeDown(p_playlist, 1, NULL); goto lclear;
1888 case KEY_CLEAR: clear(); return 1;
1891 /* key not handled */
1895 /*****************************************************************************
1896 * Run: ncurses thread
1897 *****************************************************************************/
1898 static void Run(intf_thread_t *p_intf)
1900 intf_sys_t *p_sys = p_intf->p_sys;
1901 playlist_t *p_playlist = pl_Get(p_intf);
1902 bool force_redraw = false;
1904 time_t t_last_refresh;
1905 int canc = vlc_savecancel();
1907 PlaylistRebuild(p_intf);
1908 Redraw(p_intf, &t_last_refresh);
1910 var_AddCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
1911 var_AddCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
1913 while (vlc_object_alive(p_intf))
1915 msleep(INTF_IDLE_SLEEP);
1917 /* Update the input */
1918 if (!p_sys->p_input)
1920 p_sys->p_input = playlist_CurrentInput(p_playlist);
1921 force_redraw = true;
1923 else if (p_sys->p_input->b_dead)
1925 vlc_object_release(p_sys->p_input);
1926 p_sys->p_input = NULL;
1930 if (p_sys->b_box_plidx_follow && playlist_CurrentPlayingItem(p_playlist))
1931 FindIndex(p_sys, p_playlist, true);
1935 while (HandleKey(p_intf))
1936 Redraw(p_intf, &t_last_refresh);
1941 Redraw(p_intf, &t_last_refresh);
1942 force_redraw = false;
1945 if ((time(0) - t_last_refresh) >= 1)
1946 Redraw(p_intf, &t_last_refresh);
1948 var_DelCallback(p_playlist, "intf-change", PlaylistChanged, p_intf);
1949 var_DelCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf);
1950 vlc_restorecancel(canc);
1953 /*****************************************************************************
1954 * Open: initialize and create window
1955 *****************************************************************************/
1956 static int Open(vlc_object_t *p_this)
1958 intf_thread_t *p_intf = (intf_thread_t *)p_this;
1959 intf_sys_t *p_sys = p_intf->p_sys = calloc(1, sizeof(intf_sys_t));
1963 p_sys->i_box_type = BOX_PLAYLIST;
1964 p_sys->b_box_plidx_follow = true;
1965 // p_sys->p_sub = msg_Subscribe(p_intf);
1966 p_sys->b_color = var_CreateGetBool(p_intf, "color");
1968 p_sys->category_view = true; //FIXME: switching back & forth is broken
1970 p_sys->psz_current_dir = var_CreateGetString(p_intf, "browse-dir");
1971 if (!p_sys->psz_current_dir || !*p_sys->psz_current_dir)
1973 free(p_sys->psz_current_dir);
1974 p_sys->psz_current_dir = config_GetUserDir(VLC_HOME_DIR);
1977 p_sys->w = initscr(); /* Initialize the curses library */
1980 start_color_and_pairs(p_intf);
1982 keypad(p_sys->w, TRUE); /* Don't do NL -> CR/NL */
1983 nonl(); /* Take input chars one at a time */
1984 cbreak(); /* Don't echo */
1985 noecho(); /* Invisible cursor */
1986 curs_set(0); /* Non blocking wgetch() */
1987 wtimeout(p_sys->w, 0);
1990 /* Stop printing errors to the console */
1991 freopen("/dev/null", "wb", stderr);
1995 p_intf->pf_run = Run;
1999 /*****************************************************************************
2000 * Close: destroy interface window
2001 *****************************************************************************/
2002 static void Close(vlc_object_t *p_this)
2004 intf_sys_t *p_sys = ((intf_thread_t*)p_this)->p_sys;
2006 PlaylistDestroy(p_sys);
2009 free(p_sys->psz_current_dir);
2010 free(p_sys->psz_old_search);
2013 vlc_object_release(p_sys->p_input);
2015 /* Close the ncurses interface */
2018 // msg_Unsubscribe(p_intf, p_sys->p_sub);