gui: qt: use float for rate
[vlc.git] / modules / gui / ncurses.c
blob2826d2d3786f5b51d107388c2b0d1caa9946e999
1 /*****************************************************************************
2 * ncurses.c : NCurses interface for vlc
3 *****************************************************************************
4 * Copyright © 2001-2011 the VideoLAN team
6 * Authors: Sam Hocevar <sam@zoy.org>
7 * Laurent Aimar <fenrir@via.ecp.fr>
8 * Yoann Peronneau <yoann@videolan.org>
9 * Derk-Jan Hartman <hartman at videolan dot org>
10 * Rafaël Carré <funman@videolanorg>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 /* UTF8 locale is required */
29 /*****************************************************************************
30 * Preamble
31 *****************************************************************************/
32 #ifdef HAVE_CONFIG_H
33 # include "config.h"
34 #endif
36 #define _XOPEN_SOURCE_EXTENDED 1
38 #include <assert.h>
39 #include <wchar.h>
40 #include <sys/stat.h>
41 #include <math.h>
42 #include <errno.h>
44 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
48 #include <ncurses.h>
50 #include <vlc_interface.h>
51 #include <vlc_vout.h>
52 #include <vlc_charset.h>
53 #include <vlc_input.h>
54 #include <vlc_es.h>
55 #include <vlc_playlist_legacy.h>
56 #include <vlc_meta.h>
57 #include <vlc_fs.h>
58 #include <vlc_url.h>
60 /*****************************************************************************
61 * Local prototypes.
62 *****************************************************************************/
63 static int Open (vlc_object_t *);
64 static void Close (vlc_object_t *);
66 /*****************************************************************************
67 * Module descriptor
68 *****************************************************************************/
70 #define BROWSE_TEXT N_("Filebrowser starting point")
71 #define BROWSE_LONGTEXT N_(\
72 "This option allows you to specify the directory the ncurses filebrowser " \
73 "will show you initially.")
75 vlc_module_begin ()
76 set_shortname("Ncurses")
77 set_description(N_("Ncurses interface"))
78 set_capability("interface", 10)
79 set_category(CAT_INTERFACE)
80 set_subcategory(SUBCAT_INTERFACE_MAIN)
81 set_callbacks(Open, Close)
82 add_shortcut("curses")
83 add_directory("browse-dir", NULL, BROWSE_TEXT, BROWSE_LONGTEXT)
84 vlc_module_end ()
86 #include "eject.c"
88 /*****************************************************************************
89 * intf_sys_t: description and status of ncurses interface
90 *****************************************************************************/
91 enum
93 BOX_NONE,
94 BOX_HELP,
95 BOX_INFO,
96 BOX_LOG,
97 BOX_PLAYLIST,
98 BOX_SEARCH,
99 BOX_OPEN,
100 BOX_BROWSE,
101 BOX_META,
102 BOX_OBJECTS,
103 BOX_STATS
106 static const char box_title[][19] = {
107 [BOX_NONE] = "",
108 [BOX_HELP] = " Help ",
109 [BOX_INFO] = " Information ",
110 [BOX_LOG] = " Messages ",
111 [BOX_PLAYLIST] = " Playlist ",
112 [BOX_SEARCH] = " Playlist ",
113 [BOX_OPEN] = " Playlist ",
114 [BOX_BROWSE] = " Browse ",
115 [BOX_META] = " Meta-information ",
116 [BOX_OBJECTS] = " Objects ",
117 [BOX_STATS] = " Stats ",
120 enum
122 C_DEFAULT = 0,
123 C_TITLE,
124 C_PLAYLIST_1,
125 C_PLAYLIST_2,
126 C_PLAYLIST_3,
127 C_BOX,
128 C_STATUS,
129 C_INFO,
130 C_ERROR,
131 C_WARNING,
132 C_DEBUG,
133 C_CATEGORY,
134 C_FOLDER,
135 /* XXX: new elements here ! */
137 C_MAX
140 /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
141 static const struct { short f; short b; } color_pairs[] =
143 /* element */ /* foreground*/ /* background*/
144 [C_TITLE] = { COLOR_YELLOW, COLOR_BLACK },
146 /* jamaican playlist, for rastafari sisters & brothers! */
147 [C_PLAYLIST_1] = { COLOR_GREEN, COLOR_BLACK },
148 [C_PLAYLIST_2] = { COLOR_YELLOW, COLOR_BLACK },
149 [C_PLAYLIST_3] = { COLOR_RED, COLOR_BLACK },
151 /* used in DrawBox() */
152 [C_BOX] = { COLOR_CYAN, COLOR_BLACK },
153 /* Source: State, Position, Volume, Chapters, etc...*/
154 [C_STATUS] = { COLOR_BLUE, COLOR_BLACK },
156 /* VLC messages, keep the order from highest priority to lowest */
157 [C_INFO] = { COLOR_BLACK, COLOR_WHITE },
158 [C_ERROR] = { COLOR_RED, COLOR_BLACK },
159 [C_WARNING] = { COLOR_YELLOW, COLOR_BLACK },
160 [C_DEBUG] = { COLOR_WHITE, COLOR_BLACK },
162 /* Category title: help, info, metadata */
163 [C_CATEGORY] = { COLOR_MAGENTA, COLOR_BLACK },
164 /* Folder (BOX_BROWSE) */
165 [C_FOLDER] = { COLOR_RED, COLOR_BLACK },
168 struct dir_entry_t
170 bool file;
171 char *path;
174 struct pl_item_t
176 input_item_t *item;
177 char *display;
180 struct intf_sys_t
182 vlc_thread_t thread;
184 bool color;
186 /* rgb values for the color yellow */
187 short yellow_r;
188 short yellow_g;
189 short yellow_b;
191 int box_type;
192 int box_y; // start of box content
193 int box_height;
194 int box_lines_total; // number of lines in the box
195 int box_start; // first line of box displayed
196 int box_idx; // selected line
198 struct
200 int type;
201 vlc_log_t *item;
202 char *msg;
203 } msgs[50]; // ring buffer
204 int i_msgs;
205 int verbosity;
206 vlc_mutex_t msg_lock;
208 /* Search Box context */
209 char search_chain[20];
211 /* Open Box Context */
212 char open_chain[50];
214 /* File Browser context */
215 char *current_dir;
216 int n_dir_entries;
217 struct dir_entry_t **dir_entries;
218 bool show_hidden_files;
220 /* Playlist context */
221 struct pl_item_t **plist;
222 int plist_entries;
223 bool need_update;
224 bool plidx_follow;
225 input_item_t *node; /* current node */
228 /*****************************************************************************
229 * Directories
230 *****************************************************************************/
232 static void DirsDestroy(intf_sys_t *sys)
234 while (sys->n_dir_entries) {
235 struct dir_entry_t *dir_entry = sys->dir_entries[--sys->n_dir_entries];
236 free(dir_entry->path);
237 free(dir_entry);
239 free(sys->dir_entries);
240 sys->dir_entries = NULL;
243 static int comdir_entries(const void *a, const void *b)
245 struct dir_entry_t *dir_entry1 = *(struct dir_entry_t**)a;
246 struct dir_entry_t *dir_entry2 = *(struct dir_entry_t**)b;
248 if (dir_entry1->file == dir_entry2->file)
249 return strcasecmp(dir_entry1->path, dir_entry2->path);
251 return dir_entry1->file ? 1 : -1;
254 static bool IsFile(const char *current_dir, const char *entry)
256 bool ret = true;
257 #ifdef S_ISDIR
258 char *uri;
259 if (asprintf(&uri, "%s" DIR_SEP "%s", current_dir, entry) != -1) {
260 struct stat st;
261 ret = vlc_stat(uri, &st) || !S_ISDIR(st.st_mode);
262 free(uri);
264 #endif
265 return ret;
268 static void ReadDir(intf_thread_t *intf)
270 intf_sys_t *sys = intf->p_sys;
272 if (!sys->current_dir || !*sys->current_dir) {
273 msg_Dbg(intf, "no current dir set");
274 return;
277 DIR *current_dir = vlc_opendir(sys->current_dir);
278 if (!current_dir) {
279 msg_Warn(intf, "cannot open directory `%s' (%s)", sys->current_dir,
280 vlc_strerror_c(errno));
281 return;
284 DirsDestroy(sys);
286 const char *entry;
287 while ((entry = vlc_readdir(current_dir))) {
288 if (!sys->show_hidden_files && *entry == '.' && strcmp(entry, ".."))
289 continue;
291 struct dir_entry_t *dir_entry = malloc(sizeof *dir_entry);
292 if (unlikely(dir_entry == NULL))
293 continue;
295 dir_entry->file = IsFile(sys->current_dir, entry);
296 dir_entry->path = strdup(entry);
297 if (unlikely(dir_entry->path == NULL))
299 free(dir_entry);
300 continue;
302 TAB_APPEND(sys->n_dir_entries, sys->dir_entries, dir_entry);
303 continue;
306 closedir(current_dir);
308 if (sys->n_dir_entries > 0)
309 qsort(sys->dir_entries, sys->n_dir_entries,
310 sizeof(struct dir_entry_t*), &comdir_entries);
313 /*****************************************************************************
314 * Adjust index position after a change (list navigation or item switching)
315 *****************************************************************************/
316 static void CheckIdx(intf_sys_t *sys)
318 int lines = sys->box_lines_total;
319 int height = LINES - sys->box_y - 2;
320 if (height > lines - 1)
321 height = lines - 1;
323 /* make sure the new index is within the box */
324 if (sys->box_idx <= 0) {
325 sys->box_idx = 0;
326 sys->box_start = 0;
327 } else if (sys->box_idx >= lines - 1 && lines > 0) {
328 sys->box_idx = lines - 1;
329 sys->box_start = sys->box_idx - height;
332 /* Fix box start (1st line of the box displayed) */
333 if (sys->box_idx < sys->box_start ||
334 sys->box_idx > height + sys->box_start + 1) {
335 sys->box_start = sys->box_idx - height/2;
336 if (sys->box_start < 0)
337 sys->box_start = 0;
338 } else if (sys->box_idx == sys->box_start - 1) {
339 sys->box_start--;
340 } else if (sys->box_idx == height + sys->box_start + 1) {
341 sys->box_start++;
345 /*****************************************************************************
346 * Playlist
347 *****************************************************************************/
348 static void PlaylistDestroy(intf_sys_t *sys)
350 while (sys->plist_entries) {
351 struct pl_item_t *p_pl_item = sys->plist[--sys->plist_entries];
353 input_item_Release(p_pl_item->item);
354 free(p_pl_item->display);
355 free(p_pl_item);
357 free(sys->plist);
358 sys->plist = NULL;
361 static bool PlaylistAddChild(intf_sys_t *sys, playlist_item_t *p_child,
362 const char *c, const char d)
364 int ret;
365 char *name = input_item_GetTitleFbName(p_child->p_input);
366 struct pl_item_t *p_pl_item = malloc(sizeof *p_pl_item);
368 if (!name || !p_pl_item)
369 goto error;
371 if (c && *c)
372 ret = asprintf(&p_pl_item->display, "%s%c-%s", c, d, name);
373 else
374 ret = asprintf(&p_pl_item->display, " %s", name);
375 if (ret == -1)
376 goto error;
378 free(name);
379 p_pl_item->item = input_item_Hold(p_child->p_input);
381 TAB_APPEND(sys->plist_entries, sys->plist, p_pl_item);
383 return true;
385 error:
386 free(name);
387 free(p_pl_item);
388 return false;
391 static void PlaylistAddNode(intf_sys_t *sys, playlist_item_t *node,
392 const char *c)
394 for (int k = 0; k < node->i_children; k++) {
395 bool last = k == node->i_children - 1;
396 playlist_item_t *p_child = node->pp_children[k];
397 if (!PlaylistAddChild(sys, p_child, c, last ? '`' : '|'))
398 return;
400 if (p_child->i_children <= 0)
401 continue;
403 if (*c) {
404 char *tmp;
405 if (asprintf(&tmp, "%s%c ", c, last ? ' ' : '|') == -1)
406 return;
407 PlaylistAddNode(sys, p_child, tmp);
408 free(tmp);
409 } else {
410 PlaylistAddNode(sys, p_child, " ");
415 static void PlaylistRebuild(intf_thread_t *intf)
417 intf_sys_t *sys = intf->p_sys;
418 playlist_t *p_playlist = pl_Get(intf);
420 PlaylistDestroy(sys);
421 PlaylistAddNode(sys, &p_playlist->root, "");
424 static int ItemChanged(vlc_object_t *p_this, const char *variable,
425 vlc_value_t oval, vlc_value_t nval, void *param)
427 playlist_t *playlist = (playlist_t *)p_this;
428 intf_sys_t *sys = param;
430 VLC_UNUSED(p_this); VLC_UNUSED(variable);
431 VLC_UNUSED(oval); VLC_UNUSED(nval);
433 playlist_Lock(playlist);
434 sys->need_update = true;
435 playlist_Unlock(playlist);
437 return VLC_SUCCESS;
440 static int PlaylistChanged(vlc_object_t *p_this, const char *variable,
441 vlc_value_t oval, vlc_value_t nval, void *param)
443 playlist_t *playlist = (playlist_t *)p_this;
444 intf_sys_t *sys = param;
445 playlist_item_t *node = playlist_CurrentPlayingItem(playlist);
447 VLC_UNUSED(variable);
448 VLC_UNUSED(oval); VLC_UNUSED(nval);
450 sys->need_update = true;
452 if (sys->node != NULL)
453 input_item_Release(sys->node);
454 sys->node = (node != NULL) ? input_item_Hold(node->p_input) : NULL;
456 return VLC_SUCCESS;
459 /* Playlist suxx */
460 static int SubSearchPlaylist(intf_sys_t *sys, char *searchstring,
461 int i_start, int i_stop)
463 for (int i = i_start + 1; i < i_stop; i++)
464 if (strcasestr(sys->plist[i]->display, searchstring))
465 return i;
467 return -1;
470 static void SearchPlaylist(intf_sys_t *sys)
472 char *str = sys->search_chain;
473 int i_first = sys->box_idx;
474 if (i_first < 0)
475 i_first = 0;
477 if (!str || !*str)
478 return;
480 int i_item = SubSearchPlaylist(sys, str, i_first + 1, sys->plist_entries);
481 if (i_item < 0)
482 i_item = SubSearchPlaylist(sys, str, 0, i_first);
484 if (i_item > 0) {
485 sys->box_idx = i_item;
486 CheckIdx(sys);
490 static inline bool IsIndex(intf_sys_t *sys, playlist_t *p_playlist, int i)
492 PL_ASSERT_LOCKED;
494 input_item_t *input = sys->plist[i]->item;
495 playlist_item_t *item = playlist_ItemGetByInput(p_playlist, input);
496 if (unlikely(item == NULL))
497 return false;
499 if (item->i_children == 0 && input == sys->node)
500 return true;
502 playlist_item_t *p_played_item = playlist_CurrentPlayingItem(p_playlist);
503 if (p_played_item != NULL)
504 return input == p_played_item->p_input;
506 return false;
509 static void FindIndex(intf_sys_t *sys, playlist_t *p_playlist)
511 int plidx = sys->box_idx;
512 int max = sys->plist_entries;
514 PL_LOCK;
516 if (!IsIndex(sys, p_playlist, plidx))
517 for (int i = 0; i < max; i++)
518 if (IsIndex(sys, p_playlist, i)) {
519 sys->box_idx = i;
520 CheckIdx(sys);
521 break;
524 PL_UNLOCK;
526 sys->plidx_follow = true;
529 /****************************************************************************
530 * Drawing
531 ****************************************************************************/
533 static void start_color_and_pairs(intf_thread_t *intf)
535 intf_sys_t *sys = intf->p_sys;
537 if (!has_colors()) {
538 sys->color = false;
539 msg_Warn(intf, "Terminal doesn't support colors");
540 return;
543 start_color();
544 for (int i = C_DEFAULT + 1; i < C_MAX; i++)
545 init_pair(i, color_pairs[i].f, color_pairs[i].b);
547 /* untested, in all my terminals, !can_change_color() --funman */
548 if (can_change_color()) {
549 color_content(COLOR_YELLOW, &sys->yellow_r, &sys->yellow_g, &sys->yellow_b);
550 init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
554 static void DrawBox(int y, int h, bool color, const char *title)
556 int w = COLS;
557 if (w <= 3 || h <= 0)
558 return;
560 if (color) color_set(C_BOX, NULL);
562 if (!title) title = "";
563 int len = strlen(title);
565 if (len > w - 2)
566 len = w - 2;
568 mvaddch(y, 0, ACS_ULCORNER);
569 mvhline(y, 1, ACS_HLINE, (w-len-2)/2);
570 mvprintw(y, 1+(w-len-2)/2, "%s", title);
571 mvhline(y, (w-len)/2+len, ACS_HLINE, w - 1 - ((w-len)/2+len));
572 mvaddch(y, w-1,ACS_URCORNER);
574 for (int i = 0; i < h; i++) {
575 mvaddch(++y, 0, ACS_VLINE);
576 mvaddch(y, w-1, ACS_VLINE);
579 mvaddch(++y, 0, ACS_LLCORNER);
580 mvhline(y, 1, ACS_HLINE, w - 2);
581 mvaddch(y, w-1, ACS_LRCORNER);
582 if (color) color_set(C_DEFAULT, NULL);
585 static void DrawEmptyLine(int y, int x, int w)
587 if (w <= 0) return;
589 mvhline(y, x, ' ', w);
592 static void DrawLine(int y, int x, int w)
594 if (w <= 0) return;
596 attrset(A_REVERSE);
597 mvhline(y, x, ' ', w);
598 attroff(A_REVERSE);
601 static void mvnprintw(int y, int x, int w, const char *p_fmt, ...)
603 va_list vl_args;
604 char *p_buf;
605 int len;
607 if (w <= 0)
608 return;
610 va_start(vl_args, p_fmt);
611 int i_ret = vasprintf(&p_buf, p_fmt, vl_args);
612 va_end(vl_args);
614 if (i_ret == -1)
615 return;
617 len = strlen(p_buf);
619 wchar_t wide[len + 1];
621 EnsureUTF8(p_buf);
622 size_t i_char_len = mbstowcs(wide, p_buf, len);
624 size_t i_width; /* number of columns */
626 if (i_char_len == (size_t)-1) /* an invalid character was encountered */ {
627 free(p_buf);
628 return;
631 i_width = wcswidth(wide, i_char_len);
632 if (i_width == (size_t)-1) {
633 /* a non printable character was encountered */
634 i_width = 0;
635 for (unsigned i = 0 ; i < i_char_len ; i++) {
636 int i_cwidth = wcwidth(wide[i]);
637 if (i_cwidth != -1)
638 i_width += i_cwidth;
642 if (i_width <= (size_t)w) {
643 mvprintw(y, x, "%s", p_buf);
644 mvhline(y, x + i_width, ' ', w - i_width);
645 free(p_buf);
646 return;
649 int i_total_width = 0;
650 int i = 0;
651 while (i_total_width < w) {
652 i_total_width += wcwidth(wide[i]);
653 if (w > 7 && i_total_width >= w/2) {
654 wide[i ] = '.';
655 wide[i+1] = '.';
656 i_total_width -= wcwidth(wide[i]) - 2;
657 if (i > 0) {
658 /* we require this check only if at least one character
659 * 4 or more columns wide exists (which i doubt) */
660 wide[i-1] = '.';
661 i_total_width -= wcwidth(wide[i-1]) - 1;
664 /* find the widest string */
665 int j, i_2nd_width = 0;
666 for (j = i_char_len - 1; i_2nd_width < w - i_total_width; j--)
667 i_2nd_width += wcwidth(wide[j]);
669 /* we already have i_total_width columns filled, and we can't
670 * have more than w columns */
671 if (i_2nd_width > w - i_total_width)
672 j++;
674 wmemmove(&wide[i+2], &wide[j+1], i_char_len - j - 1);
675 wide[i + 2 + i_char_len - j - 1] = '\0';
676 break;
678 i++;
680 if (w <= 7) /* we don't add the '...' else we lose too much chars */
681 wide[i] = '\0';
683 size_t i_wlen = wcslen(wide) * 6 + 1; /* worst case */
684 char ellipsized[i_wlen];
685 wcstombs(ellipsized, wide, i_wlen);
686 mvprintw(y, x, "%s", ellipsized);
688 free(p_buf);
691 static void MainBoxWrite(intf_sys_t *sys, int l, const char *p_fmt, ...)
693 va_list vl_args;
694 char *p_buf;
695 bool b_selected = l == sys->box_idx;
697 if (l < sys->box_start || l - sys->box_start >= sys->box_height)
698 return;
700 va_start(vl_args, p_fmt);
701 int i_ret = vasprintf(&p_buf, p_fmt, vl_args);
702 va_end(vl_args);
703 if (i_ret == -1)
704 return;
706 if (b_selected) attron(A_REVERSE);
707 mvnprintw(sys->box_y + l - sys->box_start, 1, COLS - 2, "%s", p_buf);
708 if (b_selected) attroff(A_REVERSE);
710 free(p_buf);
713 static int SubDrawObject(intf_sys_t *sys, int l, vlc_object_t *p_obj, int i_level, const char *prefix)
715 char *name = vlc_object_get_name(p_obj);
716 MainBoxWrite(sys, l++, "%*s%s%s \"%s\" (%p)", 2 * i_level++, "", prefix,
717 p_obj->obj.object_type, name ? name : "", (void *)p_obj);
718 free(name);
720 size_t count = 0, size;
721 vlc_object_t **tab = NULL;
723 do {
724 size = count;
725 tab = xrealloc(tab, size * sizeof (*tab));
726 count = vlc_list_children(p_obj, tab, size);
727 } while (size < count);
729 for (size_t i = 0; i < count ; i++) {
730 l = SubDrawObject(sys, l, tab[i], i_level,
731 (i == count - 1) ? "`-" : "|-" );
732 vlc_object_release(tab[i]);
734 free(tab);
735 return l;
738 static int DrawObjects(intf_thread_t *intf, input_thread_t *input)
740 (void) input;
741 return SubDrawObject(intf->p_sys, 0, VLC_OBJECT(intf->obj.libvlc), 0, "");
744 static int DrawMeta(intf_thread_t *intf, input_thread_t *p_input)
746 intf_sys_t *sys = intf->p_sys;
747 input_item_t *item;
748 int l = 0;
750 if (!p_input)
751 return 0;
753 item = input_GetItem(p_input);
754 vlc_mutex_lock(&item->lock);
755 for (int i=0; i<VLC_META_TYPE_COUNT; i++) {
756 const char *meta = vlc_meta_Get(item->p_meta, i);
757 if (!meta || !*meta)
758 continue;
760 if (sys->color) color_set(C_CATEGORY, NULL);
761 MainBoxWrite(sys, l++, " [%s]", vlc_meta_TypeToLocalizedString(i));
762 if (sys->color) color_set(C_DEFAULT, NULL);
763 MainBoxWrite(sys, l++, " %s", meta);
765 vlc_mutex_unlock(&item->lock);
767 return l;
770 static int DrawInfo(intf_thread_t *intf, input_thread_t *p_input)
772 intf_sys_t *sys = intf->p_sys;
773 input_item_t *item;
774 int l = 0;
776 if (!p_input)
777 return 0;
779 item = input_GetItem(p_input);
780 vlc_mutex_lock(&item->lock);
781 for (int i = 0; i < item->i_categories; i++) {
782 info_category_t *p_category = item->pp_categories[i];
783 info_t *p_info;
785 if (sys->color) color_set(C_CATEGORY, NULL);
786 MainBoxWrite(sys, l++, _(" [%s]"), p_category->psz_name);
787 if (sys->color) color_set(C_DEFAULT, NULL);
788 info_foreach(p_info, &p_category->infos)
789 MainBoxWrite(sys, l++, _(" %s: %s"),
790 p_info->psz_name, p_info->psz_value);
792 vlc_mutex_unlock(&item->lock);
794 return l;
797 static int DrawStats(intf_thread_t *intf, input_thread_t *p_input)
799 intf_sys_t *sys = intf->p_sys;
800 input_item_t *item;
801 input_stats_t *p_stats;
802 int l = 0, i_audio = 0, i_video = 0;
804 if (!p_input)
805 return 0;
807 item = input_GetItem(p_input);
808 assert(item);
810 vlc_mutex_lock(&item->lock);
811 p_stats = item->p_stats;
813 for (int i = 0; i < item->i_es ; i++) {
814 i_audio += (item->es[i]->i_cat == AUDIO_ES);
815 i_video += (item->es[i]->i_cat == VIDEO_ES);
818 /* Input */
819 if (sys->color) color_set(C_CATEGORY, NULL);
820 MainBoxWrite(sys, l++, _("+-[Incoming]"));
821 if (sys->color) color_set(C_DEFAULT, NULL);
822 MainBoxWrite(sys, l++, _("| input bytes read : %8.0f KiB"),
823 (float)(p_stats->i_read_bytes)/1024);
824 MainBoxWrite(sys, l++, _("| input bitrate : %6.0f kb/s"),
825 p_stats->f_input_bitrate*8000);
826 MainBoxWrite(sys, l++, _("| demux bytes read : %8.0f KiB"),
827 (float)(p_stats->i_demux_read_bytes)/1024);
828 MainBoxWrite(sys, l++, _("| demux bitrate : %6.0f kb/s"),
829 p_stats->f_demux_bitrate*8000);
831 /* Video */
832 if (i_video) {
833 if (sys->color) color_set(C_CATEGORY, NULL);
834 MainBoxWrite(sys, l++, _("+-[Video Decoding]"));
835 if (sys->color) color_set(C_DEFAULT, NULL);
836 MainBoxWrite(sys, l++, _("| video decoded : %5"PRIi64),
837 p_stats->i_decoded_video);
838 MainBoxWrite(sys, l++, _("| frames displayed : %5"PRIi64),
839 p_stats->i_displayed_pictures);
840 MainBoxWrite(sys, l++, _("| frames lost : %5"PRIi64),
841 p_stats->i_lost_pictures);
843 /* Audio*/
844 if (i_audio) {
845 if (sys->color) color_set(C_CATEGORY, NULL);
846 MainBoxWrite(sys, l++, _("+-[Audio Decoding]"));
847 if (sys->color) color_set(C_DEFAULT, NULL);
848 MainBoxWrite(sys, l++, _("| audio decoded : %5"PRIi64),
849 p_stats->i_decoded_audio);
850 MainBoxWrite(sys, l++, _("| buffers played : %5"PRIi64),
851 p_stats->i_played_abuffers);
852 MainBoxWrite(sys, l++, _("| buffers lost : %5"PRIi64),
853 p_stats->i_lost_abuffers);
855 if (sys->color) color_set(C_DEFAULT, NULL);
857 vlc_mutex_unlock(&item->lock);
859 return l;
862 static int DrawHelp(intf_thread_t *intf, input_thread_t *input)
864 intf_sys_t *sys = intf->p_sys;
865 int l = 0;
867 #define H(a) MainBoxWrite(sys, l++, a)
869 if (sys->color) color_set(C_CATEGORY, NULL);
870 H(_("[Display]"));
871 if (sys->color) color_set(C_DEFAULT, NULL);
872 H(_(" h,H Show/Hide help box"));
873 H(_(" i Show/Hide info box"));
874 H(_(" M Show/Hide metadata box"));
875 H(_(" L Show/Hide messages box"));
876 H(_(" P Show/Hide playlist box"));
877 H(_(" B Show/Hide filebrowser"));
878 H(_(" x Show/Hide objects box"));
879 H(_(" S Show/Hide statistics box"));
880 H(_(" Esc Close Add/Search entry"));
881 H(_(" Ctrl-l Refresh the screen"));
882 H("");
884 if (sys->color) color_set(C_CATEGORY, NULL);
885 H(_("[Global]"));
886 if (sys->color) color_set(C_DEFAULT, NULL);
887 H(_(" q, Q, Esc Quit"));
888 H(_(" s Stop"));
889 H(_(" <space> Pause/Play"));
890 H(_(" f Toggle Fullscreen"));
891 H(_(" c Cycle through audio tracks"));
892 H(_(" v Cycle through subtitles tracks"));
893 H(_(" b Cycle through video tracks"));
894 H(_(" n, p Next/Previous playlist item"));
895 H(_(" [, ] Next/Previous title"));
896 H(_(" <, > Next/Previous chapter"));
897 /* xgettext: You can use ← and → characters */
898 H(_(" <left>,<right> Seek -/+ 1%%"));
899 H(_(" a, z Volume Up/Down"));
900 H(_(" m Mute"));
901 /* xgettext: You can use ↑ and ↓ characters */
902 H(_(" <up>,<down> Navigate through the box line by line"));
903 /* xgettext: You can use ⇞ and ⇟ characters */
904 H(_(" <pageup>,<pagedown> Navigate through the box page by page"));
905 /* xgettext: You can use ↖ and ↘ characters */
906 H(_(" <start>,<end> Navigate to start/end of box"));
907 H("");
909 if (sys->color) color_set(C_CATEGORY, NULL);
910 H(_("[Playlist]"));
911 if (sys->color) color_set(C_DEFAULT, NULL);
912 H(_(" r Toggle Random playing"));
913 H(_(" l Toggle Loop Playlist"));
914 H(_(" R Toggle Repeat item"));
915 H(_(" o Order Playlist by title"));
916 H(_(" O Reverse order Playlist by title"));
917 H(_(" g Go to the current playing item"));
918 H(_(" / Look for an item"));
919 H(_(" ; Look for the next item"));
920 H(_(" A Add an entry"));
921 /* xgettext: You can use ⌫ character to translate <backspace> */
922 H(_(" D, <backspace>, <del> Delete an entry"));
923 H(_(" e Eject (if stopped)"));
924 H("");
926 if (sys->color) color_set(C_CATEGORY, NULL);
927 H(_("[Filebrowser]"));
928 if (sys->color) color_set(C_DEFAULT, NULL);
929 H(_(" <enter> Add the selected file to the playlist"));
930 H(_(" <space> Add the selected directory to the playlist"));
931 H(_(" . Show/Hide hidden files"));
932 H("");
934 if (sys->color) color_set(C_CATEGORY, NULL);
935 H(_("[Player]"));
936 if (sys->color) color_set(C_DEFAULT, NULL);
937 /* xgettext: You can use ↑ and ↓ characters */
938 H(_(" <up>,<down> Seek +/-5%%"));
940 #undef H
941 (void) input;
942 return l;
945 static int DrawBrowse(intf_thread_t *intf, input_thread_t *input)
947 intf_sys_t *sys = intf->p_sys;
949 for (int i = 0; i < sys->n_dir_entries; i++) {
950 struct dir_entry_t *dir_entry = sys->dir_entries[i];
951 char type = dir_entry->file ? ' ' : '+';
953 if (sys->color)
954 color_set(dir_entry->file ? C_DEFAULT : C_FOLDER, NULL);
955 MainBoxWrite(sys, i, " %c %s", type, dir_entry->path);
958 (void) input;
959 return sys->n_dir_entries;
962 static int DrawPlaylist(intf_thread_t *intf, input_thread_t *input)
964 intf_sys_t *sys = intf->p_sys;
965 playlist_t *p_playlist = pl_Get(intf);
967 PL_LOCK;
968 if (sys->need_update) {
969 PlaylistRebuild(intf);
970 sys->need_update = false;
972 PL_UNLOCK;
974 if (sys->plidx_follow)
975 FindIndex(sys, p_playlist);
977 for (int i = 0; i < sys->plist_entries; i++) {
978 char c;
979 playlist_item_t *current;
980 input_item_t *item = sys->plist[i]->item;
982 PL_LOCK;
983 current = playlist_CurrentPlayingItem(p_playlist);
985 if ((sys->node != NULL && item == sys->node) ||
986 (sys->node == NULL && current != NULL && item == current->p_input))
987 c = '*';
988 else if (current != NULL && current->p_input == item)
989 c = '>';
990 else
991 c = ' ';
992 PL_UNLOCK;
994 if (sys->color) color_set(i%3 + C_PLAYLIST_1, NULL);
995 MainBoxWrite(sys, i, "%c%s", c, sys->plist[i]->display);
996 if (sys->color) color_set(C_DEFAULT, NULL);
999 (void) input;
1000 return sys->plist_entries;
1003 static int DrawMessages(intf_thread_t *intf, input_thread_t *input)
1005 intf_sys_t *sys = intf->p_sys;
1006 int l = 0;
1008 vlc_mutex_lock(&sys->msg_lock);
1009 int i = sys->i_msgs;
1010 for(;;) {
1011 vlc_log_t *msg = sys->msgs[i].item;
1012 if (msg) {
1013 if (sys->color)
1014 color_set(sys->msgs[i].type + C_INFO, NULL);
1015 MainBoxWrite(sys, l++, "[%s] %s", msg->psz_module, sys->msgs[i].msg);
1018 if (++i == sizeof sys->msgs / sizeof *sys->msgs)
1019 i = 0;
1021 if (i == sys->i_msgs) /* did we loop around the ring buffer ? */
1022 break;
1025 vlc_mutex_unlock(&sys->msg_lock);
1026 if (sys->color)
1027 color_set(C_DEFAULT, NULL);
1029 (void) input;
1030 return l;
1033 static int DrawStatus(intf_thread_t *intf, input_thread_t *p_input)
1035 intf_sys_t *sys = intf->p_sys;
1036 playlist_t *p_playlist = pl_Get(intf);
1037 const char *name = _("VLC media player");
1038 const size_t name_len = strlen(name) + sizeof(PACKAGE_VERSION);
1039 int y = 0;
1040 const char *repeat, *loop, *random;
1043 /* Title */
1044 int padding = COLS - name_len; /* center title */
1045 if (padding < 0)
1046 padding = 0;
1048 attrset(A_REVERSE);
1049 if (sys->color) color_set(C_TITLE, NULL);
1050 DrawEmptyLine(y, 0, COLS);
1051 mvnprintw(y++, padding / 2, COLS, "%s %s", name, PACKAGE_VERSION);
1052 if (sys->color) color_set(C_STATUS, NULL);
1053 attroff(A_REVERSE);
1055 y++; /* leave a blank line */
1057 repeat = var_GetBool(p_playlist, "repeat") ? _("[Repeat]") : "";
1058 random = var_GetBool(p_playlist, "random") ? _("[Random]") : "";
1059 loop = var_GetBool(p_playlist, "loop") ? _("[Loop]") : "";
1061 if (p_input) {
1062 vlc_value_t val;
1063 char *path, *uri;
1065 uri = input_item_GetURI(input_GetItem(p_input));
1066 path = vlc_uri2path(uri);
1068 mvnprintw(y++, 0, COLS, _(" Source : %s"), path?path:uri);
1069 free(uri);
1070 free(path);
1072 var_Get(p_input, "state", &val);
1073 switch(val.i_int)
1075 static const char *input_state[] = {
1076 [PLAYING_S] = " State : Playing %s%s%s",
1077 [OPENING_S] = " State : Opening/Connecting %s%s%s",
1078 [PAUSE_S] = " State : Paused %s%s%s",
1080 char buf1[MSTRTIME_MAX_SIZE];
1081 char buf2[MSTRTIME_MAX_SIZE];
1082 float volume;
1084 case INIT_S:
1085 case END_S:
1086 y += 2;
1087 break;
1089 case PLAYING_S:
1090 case OPENING_S:
1091 case PAUSE_S:
1092 mvnprintw(y++, 0, COLS, _(input_state[val.i_int]),
1093 repeat, random, loop);
1094 /* fall-through */
1096 default:
1097 secstotimestr(buf1, SEC_FROM_VLC_TICK(var_GetInteger(p_input, "time")));
1098 secstotimestr(buf2, SEC_FROM_VLC_TICK(var_GetInteger(p_input, "length")));
1100 mvnprintw(y++, 0, COLS, _(" Position : %s/%s"), buf1, buf2);
1102 volume = playlist_VolumeGet(p_playlist);
1103 int mute = playlist_MuteGet(p_playlist);
1104 mvnprintw(y++, 0, COLS,
1105 mute ? _(" Volume : Mute") :
1106 volume >= 0.f ? _(" Volume : %3ld%%") : _(" Volume : ----"),
1107 lroundf(volume * 100.f));
1109 if (!var_Get(p_input, "title", &val)) {
1110 int i_title_count = var_CountChoices(p_input, "title");
1111 if (i_title_count > 0)
1112 mvnprintw(y++, 0, COLS, _(" Title : %"PRId64"/%d"),
1113 val.i_int, i_title_count);
1116 if (!var_Get(p_input, "chapter", &val)) {
1117 int i_chapter_count = var_CountChoices(p_input, "chapter");
1118 if (i_chapter_count > 0) mvnprintw(y++, 0, COLS, _(" Chapter : %"PRId64"/%d"),
1119 val.i_int, i_chapter_count);
1122 } else {
1123 mvnprintw(y++, 0, COLS, _(" Source: <no current item>"));
1124 mvnprintw(y++, 0, COLS, " %s%s%s", repeat, random, loop);
1125 mvnprintw(y++, 0, COLS, _(" [ h for help ]"));
1126 DrawEmptyLine(y++, 0, COLS);
1129 if (sys->color) color_set(C_DEFAULT, NULL);
1130 DrawBox(y++, 1, sys->color, ""); /* position slider */
1131 DrawEmptyLine(y, 1, COLS-2);
1132 if (p_input)
1133 DrawLine(y, 1, (int)((COLS-2) * var_GetFloat(p_input, "position")));
1135 y += 2; /* skip slider and box */
1137 return y;
1140 static void FillTextBox(intf_sys_t *sys)
1142 int width = COLS - 2;
1144 DrawEmptyLine(7, 1, width);
1145 if (sys->box_type == BOX_OPEN)
1146 mvnprintw(7, 1, width, _("Open: %s"), sys->open_chain);
1147 else
1148 mvnprintw(7, 1, width, _("Find: %s"), sys->search_chain);
1151 static void FillBox(intf_thread_t *intf, input_thread_t *input)
1153 intf_sys_t *sys = intf->p_sys;
1154 static int (* const draw[]) (intf_thread_t *, input_thread_t *) = {
1155 [BOX_HELP] = DrawHelp,
1156 [BOX_INFO] = DrawInfo,
1157 [BOX_META] = DrawMeta,
1158 [BOX_OBJECTS] = DrawObjects,
1159 [BOX_STATS] = DrawStats,
1160 [BOX_BROWSE] = DrawBrowse,
1161 [BOX_PLAYLIST] = DrawPlaylist,
1162 [BOX_SEARCH] = DrawPlaylist,
1163 [BOX_OPEN] = DrawPlaylist,
1164 [BOX_LOG] = DrawMessages,
1167 sys->box_lines_total = draw[sys->box_type](intf, input);
1169 if (sys->box_type == BOX_SEARCH || sys->box_type == BOX_OPEN)
1170 FillTextBox(sys);
1173 static void Redraw(intf_thread_t *intf, input_thread_t *input)
1175 intf_sys_t *sys = intf->p_sys;
1176 int box = sys->box_type;
1177 int y = DrawStatus(intf, input);
1179 sys->box_height = LINES - y - 2;
1180 DrawBox(y++, sys->box_height, sys->color, _(box_title[box]));
1182 sys->box_y = y;
1184 if (box != BOX_NONE) {
1185 FillBox(intf, input);
1187 if (sys->box_lines_total == 0)
1188 sys->box_start = 0;
1189 else if (sys->box_start > sys->box_lines_total - 1)
1190 sys->box_start = sys->box_lines_total - 1;
1191 y += __MIN(sys->box_lines_total - sys->box_start,
1192 sys->box_height);
1195 while (y < LINES - 1)
1196 DrawEmptyLine(y++, 1, COLS - 2);
1198 refresh();
1201 static void ChangePosition(input_thread_t *p_input, float increment)
1203 float pos;
1205 if (!p_input || var_GetInteger(p_input, "state") != PLAYING_S)
1206 return;
1208 pos = var_GetFloat(p_input, "position") + increment;
1210 if (pos > 0.99) pos = 0.99;
1211 if (pos < 0.0) pos = 0.0;
1213 var_SetFloat(p_input, "position", pos);
1216 static inline void RemoveLastUTF8Entity(char *psz, int len)
1218 while (len && ((psz[--len] & 0xc0) == 0x80)) /* UTF8 continuation byte */
1220 psz[len] = '\0';
1223 static char *GetDiscDevice(const char *name)
1225 static const struct { const char *s; size_t n; const char *v; } devs[] =
1227 { "cdda://", 7, "cd-audio", },
1228 { "dvd://", 6, "dvd", },
1229 { "vcd://", 6, "vcd", },
1231 char *device;
1233 for (unsigned i = 0; i < sizeof devs / sizeof *devs; i++) {
1234 size_t n = devs[i].n;
1235 if (!strncmp(name, devs[i].s, n)) {
1236 if (name[n] == '@' || name[n] == '\0')
1237 return config_GetPsz(devs[i].v);
1238 /* Omit the beginning MRL-selector characters */
1239 return strdup(name + n);
1243 device = strdup(name);
1245 if (device) /* Remove what we have after @ */
1246 device[strcspn(device, "@")] = '\0';
1248 return device;
1251 static void Eject(intf_thread_t *intf, input_thread_t *p_input)
1253 char *device, *name;
1254 playlist_t * p_playlist = pl_Get(intf);
1256 /* If there's a stream playing, we aren't allowed to eject ! */
1257 if (p_input)
1258 return;
1260 PL_LOCK;
1262 if (!playlist_CurrentPlayingItem(p_playlist)) {
1263 PL_UNLOCK;
1264 return;
1267 name = playlist_CurrentPlayingItem(p_playlist)->p_input->psz_name;
1268 device = name ? GetDiscDevice(name) : NULL;
1270 PL_UNLOCK;
1272 if (device) {
1273 intf_Eject(intf, device);
1274 free(device);
1278 static void PlayPause(intf_thread_t *intf, input_thread_t *p_input)
1280 if (p_input) {
1281 int64_t state = var_GetInteger( p_input, "state" );
1282 state = (state != PLAYING_S) ? PLAYING_S : PAUSE_S;
1283 var_SetInteger( p_input, "state", state );
1284 } else
1285 playlist_Play(pl_Get(intf));
1288 static void AddItem(intf_thread_t *intf, const char *path)
1290 char *uri = vlc_path2uri(path, NULL);
1291 if (uri == NULL)
1292 return;
1294 input_item_t *item = input_item_New(uri, NULL);
1295 free(uri);
1296 if (unlikely(item == NULL))
1297 return;
1299 playlist_t *playlist = pl_Get(intf);
1300 playlist_item_t *node;
1302 playlist_Lock(playlist);
1303 node = playlist_CurrentPlayingItem(playlist);
1305 while (node != NULL) {
1306 if (node == playlist->p_playing)
1307 break;
1308 node = node->p_parent;
1311 if (node == NULL)
1312 node = playlist->p_playing;
1314 playlist_NodeAddInput(playlist, item, node, PLAYLIST_END);
1315 playlist_Unlock(playlist);
1317 input_item_Release(item);
1320 static inline void BoxSwitch(intf_sys_t *sys, int box)
1322 sys->box_type = (sys->box_type == box) ? BOX_NONE : box;
1323 sys->box_start = 0;
1324 sys->box_idx = 0;
1327 static bool HandlePlaylistKey(intf_thread_t *intf, int key)
1329 intf_sys_t *sys = intf->p_sys;
1330 playlist_t *p_playlist = pl_Get(intf);
1332 switch(key)
1334 /* Playlist Settings */
1335 case 'r': var_ToggleBool(p_playlist, "random"); return true;
1336 case 'l': var_ToggleBool(p_playlist, "loop"); return true;
1337 case 'R': var_ToggleBool(p_playlist, "repeat"); return true;
1339 /* Playlist sort */
1340 case 'o':
1341 case 'O':
1342 playlist_Lock(p_playlist);
1343 playlist_RecursiveNodeSort(p_playlist, &p_playlist->root,
1344 SORT_TITLE_NODES_FIRST,
1345 (key == 'o')? ORDER_NORMAL : ORDER_REVERSE);
1346 sys->need_update = true;
1347 playlist_Unlock(p_playlist);
1348 return true;
1350 case ';':
1351 SearchPlaylist(sys);
1352 return true;
1354 case 'g':
1355 FindIndex(sys, p_playlist);
1356 return true;
1358 /* Deletion */
1359 case 'D':
1360 case KEY_BACKSPACE:
1361 case 0x7f:
1362 case KEY_DC:
1364 input_item_t *input = sys->plist[sys->box_idx]->item;
1365 playlist_item_t *item;
1367 PL_LOCK;
1368 item = playlist_ItemGetByInput(p_playlist, input);
1369 playlist_NodeDelete(p_playlist, item);
1371 if (sys->box_idx >= sys->box_lines_total - 1)
1372 sys->box_idx = sys->box_lines_total - 2;
1373 sys->need_update = true;
1374 PL_UNLOCK;
1375 return true;
1378 case KEY_ENTER:
1379 case '\r':
1380 case '\n':
1382 struct pl_item_t *p_pl_item = sys->plist[sys->box_idx];
1383 if (p_pl_item == NULL)
1384 return false;
1386 playlist_item_t *item;
1388 playlist_Lock(p_playlist);
1389 item = playlist_ItemGetByInput(p_playlist, p_pl_item->item);
1391 if (item->i_children) {
1392 playlist_item_t *parent = item;
1394 if (item->i_children == -1) {
1395 while (parent->p_parent != NULL)
1396 parent = parent->p_parent;
1397 } else {
1398 if (sys->node != NULL)
1399 input_item_Release(sys->node);
1400 sys->node = parent->p_input ? input_item_Hold(parent->p_input)
1401 : NULL;
1402 item = NULL;
1405 playlist_ViewPlay(p_playlist, parent, item);
1406 } else { /* We only want to set the current node */
1407 playlist_Control(p_playlist, PLAYLIST_STOP, true);
1408 if (sys->node != NULL)
1409 input_item_Release(sys->node);
1410 sys->node = p_pl_item->item ? input_item_Hold(p_pl_item->item)
1411 : NULL;
1413 playlist_Unlock(p_playlist);
1415 sys->plidx_follow = true;
1416 return true;
1420 return false;
1423 static bool HandleBrowseKey(intf_thread_t *intf, int key)
1425 intf_sys_t *sys = intf->p_sys;
1426 struct dir_entry_t *dir_entry;
1428 switch(key)
1430 case '.':
1431 sys->show_hidden_files = !sys->show_hidden_files;
1432 ReadDir(intf);
1433 return true;
1435 case KEY_ENTER:
1436 case '\r':
1437 case '\n':
1438 case ' ':
1439 dir_entry = sys->dir_entries[sys->box_idx];
1440 char *path;
1441 if (asprintf(&path, "%s" DIR_SEP "%s", sys->current_dir,
1442 dir_entry->path) == -1)
1443 return true;
1445 if (!dir_entry->file && key != ' ') {
1446 free(sys->current_dir);
1447 sys->current_dir = path;
1448 ReadDir(intf);
1450 sys->box_start = 0;
1451 sys->box_idx = 0;
1452 return true;
1455 AddItem(intf, path);
1456 free(path);
1457 BoxSwitch(sys, BOX_PLAYLIST);
1458 return true;
1461 return false;
1464 static void OpenSelection(intf_thread_t *intf)
1466 intf_sys_t *sys = intf->p_sys;
1468 AddItem(intf, sys->open_chain);
1469 sys->plidx_follow = true;
1472 static void HandleEditBoxKey(intf_thread_t *intf, int key, int box)
1474 intf_sys_t *sys = intf->p_sys;
1475 bool search = box == BOX_SEARCH;
1476 char *str = search ? sys->search_chain: sys->open_chain;
1477 size_t len = strlen(str);
1479 assert(box == BOX_SEARCH || box == BOX_OPEN);
1481 switch(key)
1483 case 0x0c: /* ^l */
1484 case KEY_CLEAR: clear(); return;
1486 case KEY_ENTER:
1487 case '\r':
1488 case '\n':
1489 if (search)
1490 SearchPlaylist(sys);
1491 else
1492 OpenSelection(intf);
1494 sys->box_type = BOX_PLAYLIST;
1495 return;
1497 case 0x1b: /* ESC */
1498 /* Alt+key combinations return 2 keys in the terminal keyboard:
1499 * ESC, and the 2nd key.
1500 * If some other key is available immediately (where immediately
1501 * means after getch() 1 second delay), that means that the
1502 * ESC key was not pressed.
1504 * man 3X curs_getch says:
1506 * Use of the escape key by a programmer for a single
1507 * character function is discouraged, as it will cause a delay
1508 * of up to one second while the keypad code looks for a
1509 * following function-key sequence.
1512 if (getch() == ERR)
1513 sys->box_type = BOX_PLAYLIST;
1514 return;
1516 case KEY_BACKSPACE:
1517 case 0x7f:
1518 RemoveLastUTF8Entity(str, len);
1519 break;
1521 default:
1522 if (len + 1 < (search ? sizeof sys->search_chain
1523 : sizeof sys->open_chain)) {
1524 str[len + 0] = key;
1525 str[len + 1] = '\0';
1529 if (search)
1530 SearchPlaylist(sys);
1533 static void InputNavigate(input_thread_t* p_input, const char *var)
1535 if (p_input)
1536 var_TriggerCallback(p_input, var);
1539 static void CycleESTrack(input_thread_t *input, const char *var)
1541 if (!input)
1542 return;
1544 vlc_value_t *list;
1545 size_t count;
1547 if (var_Change(input, var, VLC_VAR_GETCHOICES,
1548 &count, &list, (char ***)NULL) < 0)
1549 return;
1551 int64_t current = var_GetInteger(input, var);
1553 size_t i;
1554 for (i = 0; i < count; i++)
1555 if (list[i].i_int == current)
1556 break;
1558 if (++i >= count)
1559 i = 0;
1560 var_SetInteger(input, var, list[i].i_int);
1561 free(list);
1564 static void HandleCommonKey(intf_thread_t *intf, input_thread_t *input,
1565 int key)
1567 intf_sys_t *sys = intf->p_sys;
1568 playlist_t *p_playlist = pl_Get(intf);
1569 switch(key)
1571 case 0x1b: /* ESC */
1572 if (getch() != ERR)
1573 return;
1575 case 'q':
1576 case 'Q':
1577 case KEY_EXIT:
1578 libvlc_Quit(intf->obj.libvlc);
1579 return;
1581 case 'h':
1582 case 'H': BoxSwitch(sys, BOX_HELP); return;
1583 case 'i': BoxSwitch(sys, BOX_INFO); return;
1584 case 'M': BoxSwitch(sys, BOX_META); return;
1585 case 'L': BoxSwitch(sys, BOX_LOG); return;
1586 case 'P': BoxSwitch(sys, BOX_PLAYLIST); return;
1587 case 'B': BoxSwitch(sys, BOX_BROWSE); return;
1588 case 'x': BoxSwitch(sys, BOX_OBJECTS); return;
1589 case 'S': BoxSwitch(sys, BOX_STATS); return;
1591 case '/': /* Search */
1592 sys->plidx_follow = false;
1593 BoxSwitch(sys, BOX_SEARCH);
1594 return;
1596 case 'A': /* Open */
1597 sys->open_chain[0] = '\0';
1598 BoxSwitch(sys, BOX_OPEN);
1599 return;
1601 /* Navigation */
1602 case KEY_RIGHT: ChangePosition(input, +0.01); return;
1603 case KEY_LEFT: ChangePosition(input, -0.01); return;
1605 /* Common control */
1606 case 'f':
1607 if (input) {
1608 vout_thread_t *p_vout = input_GetVout(input);
1609 if (p_vout) {
1610 bool fs = var_ToggleBool(p_playlist, "fullscreen");
1611 var_SetBool(p_vout, "fullscreen", fs);
1612 vlc_object_release(p_vout);
1615 return;
1617 case ' ': PlayPause(intf, input); return;
1618 case 's': playlist_Stop(p_playlist); return;
1619 case 'e': Eject(intf, input); return;
1621 case '[': InputNavigate(input, "prev-title"); return;
1622 case ']': InputNavigate(input, "next-title"); return;
1623 case '<': InputNavigate(input, "prev-chapter"); return;
1624 case '>': InputNavigate(input, "next-chapter"); return;
1626 case 'p': playlist_Prev(p_playlist); break;
1627 case 'n': playlist_Next(p_playlist); break;
1628 case 'a': playlist_VolumeUp(p_playlist, 1, NULL); break;
1629 case 'z': playlist_VolumeDown(p_playlist, 1, NULL); break;
1630 case 'm': playlist_MuteToggle(p_playlist); break;
1632 case 'c': CycleESTrack(input, "audio-es"); break;
1633 case 'v': CycleESTrack(input, "spu-es"); break;
1634 case 'b': CycleESTrack(input, "video-es"); break;
1636 case 0x0c: /* ^l */
1637 case KEY_CLEAR:
1638 break;
1640 default:
1641 return;
1644 clear();
1645 return;
1648 static bool HandleListKey(intf_thread_t *intf, int key)
1650 intf_sys_t *sys = intf->p_sys;
1651 playlist_t *p_playlist = pl_Get(intf);
1653 switch(key)
1655 #ifdef __FreeBSD__
1656 /* workaround for FreeBSD + xterm:
1657 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1658 case KEY_SELECT:
1659 #endif
1660 case KEY_END: sys->box_idx = sys->box_lines_total - 1; break;
1661 case KEY_HOME: sys->box_idx = 0; break;
1662 case KEY_UP: sys->box_idx--; break;
1663 case KEY_DOWN: sys->box_idx++; break;
1664 case KEY_PPAGE:sys->box_idx -= sys->box_height; break;
1665 case KEY_NPAGE:sys->box_idx += sys->box_height; break;
1666 default:
1667 return false;
1670 CheckIdx(sys);
1672 if (sys->box_type == BOX_PLAYLIST) {
1673 PL_LOCK;
1674 sys->plidx_follow = IsIndex(sys, p_playlist, sys->box_idx);
1675 PL_UNLOCK;
1678 return true;
1681 static void HandleKey(intf_thread_t *intf, input_thread_t *input)
1683 intf_sys_t *sys = intf->p_sys;
1684 int key = getch();
1685 int box = sys->box_type;
1687 if (key == -1)
1688 return;
1690 if (box == BOX_SEARCH || box == BOX_OPEN) {
1691 HandleEditBoxKey(intf, key, sys->box_type);
1692 return;
1695 if (box == BOX_NONE)
1696 switch(key)
1698 #ifdef __FreeBSD__
1699 case KEY_SELECT:
1700 #endif
1701 case KEY_END: ChangePosition(input, +.99); return;
1702 case KEY_HOME: ChangePosition(input, -1.0); return;
1703 case KEY_UP: ChangePosition(input, +0.05); return;
1704 case KEY_DOWN: ChangePosition(input, -0.05); return;
1705 default: HandleCommonKey(intf, input, key); return;
1708 if (box == BOX_BROWSE && HandleBrowseKey(intf, key))
1709 return;
1711 if (box == BOX_PLAYLIST && HandlePlaylistKey(intf, key))
1712 return;
1714 if (HandleListKey(intf, key))
1715 return;
1717 HandleCommonKey(intf, input, key);
1723 static vlc_log_t *msg_Copy (const vlc_log_t *msg)
1725 vlc_log_t *copy = (vlc_log_t *)xmalloc (sizeof (*copy));
1726 copy->i_object_id = msg->i_object_id;
1727 copy->psz_object_type = msg->psz_object_type;
1728 copy->psz_module = strdup (msg->psz_module);
1729 copy->psz_header = msg->psz_header ? strdup (msg->psz_header) : NULL;
1730 return copy;
1733 static void msg_Free (vlc_log_t *msg)
1735 free ((char *)msg->psz_module);
1736 free ((char *)msg->psz_header);
1737 free (msg);
1740 static void MsgCallback(void *data, int type, const vlc_log_t *msg,
1741 const char *format, va_list ap)
1743 intf_sys_t *sys = data;
1744 char *text;
1746 if (sys->verbosity < 0
1747 || sys->verbosity < (type - VLC_MSG_ERR)
1748 || vasprintf(&text, format, ap) == -1)
1749 return;
1751 vlc_mutex_lock(&sys->msg_lock);
1753 sys->msgs[sys->i_msgs].type = type;
1754 if (sys->msgs[sys->i_msgs].item != NULL)
1755 msg_Free(sys->msgs[sys->i_msgs].item);
1756 sys->msgs[sys->i_msgs].item = msg_Copy(msg);
1757 free(sys->msgs[sys->i_msgs].msg);
1758 sys->msgs[sys->i_msgs].msg = text;
1760 if (++sys->i_msgs == (sizeof sys->msgs / sizeof *sys->msgs))
1761 sys->i_msgs = 0;
1763 vlc_mutex_unlock(&sys->msg_lock);
1766 static const struct vlc_logger_operations log_ops = { MsgCallback, NULL };
1768 /*****************************************************************************
1769 * Run: ncurses thread
1770 *****************************************************************************/
1771 static void *Run(void *data)
1773 intf_thread_t *intf = data;
1774 playlist_t *p_playlist = pl_Get(intf);
1776 for (;;) {
1777 vlc_testcancel();
1779 int canc = vlc_savecancel();
1780 input_thread_t *input = playlist_CurrentInput(p_playlist);
1782 Redraw(intf, input);
1783 HandleKey(intf, input);
1784 if (input)
1785 vlc_object_release(input);
1786 vlc_restorecancel(canc);
1788 vlc_assert_unreachable();
1791 /*****************************************************************************
1792 * Open: initialize and create window
1793 *****************************************************************************/
1794 static int Open(vlc_object_t *p_this)
1796 intf_thread_t *intf = (intf_thread_t *)p_this;
1797 intf_sys_t *sys = intf->p_sys = calloc(1, sizeof(intf_sys_t));
1798 playlist_t *p_playlist = pl_Get(intf);
1800 if (!sys)
1801 return VLC_ENOMEM;
1803 vlc_mutex_init(&sys->msg_lock);
1805 sys->verbosity = var_InheritInteger(intf, "verbose");
1806 vlc_LogSet(intf->obj.libvlc, &log_ops, sys);
1808 sys->box_type = BOX_PLAYLIST;
1809 sys->plidx_follow = true;
1810 sys->color = var_CreateGetBool(intf, "color");
1812 sys->current_dir = var_CreateGetNonEmptyString(intf, "browse-dir");
1813 if (!sys->current_dir)
1814 sys->current_dir = config_GetUserDir(VLC_HOME_DIR);
1816 initscr(); /* Initialize the curses library */
1818 if (sys->color)
1819 start_color_and_pairs(intf);
1821 keypad(stdscr, TRUE);
1822 nonl(); /* Don't do NL -> CR/NL */
1823 cbreak(); /* Take input chars one at a time */
1824 noecho(); /* Don't echo */
1825 curs_set(0); /* Invisible cursor */
1826 timeout(1000); /* blocking getch() */
1827 clear();
1829 /* Stop printing errors to the console */
1830 if (!freopen("/dev/null", "wb", stderr))
1831 msg_Err(intf, "Couldn't close stderr (%s)", vlc_strerror_c(errno));
1833 ReadDir(intf);
1834 PL_LOCK;
1835 PlaylistRebuild(intf),
1836 PL_UNLOCK;
1838 var_AddCallback(p_playlist, "item-change", ItemChanged, sys);
1839 var_AddCallback(p_playlist, "playlist-item-append", PlaylistChanged, sys);
1841 if (vlc_clone(&sys->thread, Run, intf, VLC_THREAD_PRIORITY_LOW))
1842 abort(); /* TODO */
1844 return VLC_SUCCESS;
1847 /*****************************************************************************
1848 * Close: destroy interface window
1849 *****************************************************************************/
1850 static void Close(vlc_object_t *p_this)
1852 intf_thread_t *intf = (intf_thread_t *)p_this;
1853 intf_sys_t *sys = intf->p_sys;
1854 playlist_t *playlist = pl_Get(intf);
1856 vlc_cancel(sys->thread);
1857 vlc_join(sys->thread, NULL);
1859 var_DelCallback(playlist, "playlist-item-append", PlaylistChanged, sys);
1860 var_DelCallback(playlist, "item-change", ItemChanged, sys);
1862 PlaylistDestroy(sys);
1863 DirsDestroy(sys);
1865 free(sys->current_dir);
1867 if (can_change_color())
1868 /* Restore yellow to its original color */
1869 init_color(COLOR_YELLOW, sys->yellow_r, sys->yellow_g, sys->yellow_b);
1871 endwin(); /* Close the ncurses interface */
1873 vlc_LogSet(p_this->obj.libvlc, NULL, NULL);
1874 vlc_mutex_destroy(&sys->msg_lock);
1875 for(unsigned i = 0; i < sizeof sys->msgs / sizeof *sys->msgs; i++) {
1876 if (sys->msgs[i].item)
1877 msg_Free(sys->msgs[i].item);
1878 free(sys->msgs[i].msg);
1880 free(sys);