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 /*****************************************************************************
31 *****************************************************************************/
36 #define _XOPEN_SOURCE_EXTENDED 1
44 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
45 #include <vlc_common.h>
46 #include <vlc_plugin.h>
50 #include <vlc_interface.h>
52 #include <vlc_charset.h>
53 #include <vlc_input.h>
55 #include <vlc_playlist_legacy.h>
60 /*****************************************************************************
62 *****************************************************************************/
63 static int Open (vlc_object_t
*);
64 static void Close (vlc_object_t
*);
66 /*****************************************************************************
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.")
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
)
88 /*****************************************************************************
89 * intf_sys_t: description and status of ncurses interface
90 *****************************************************************************/
106 static const char box_title
[][19] = {
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 ",
135 /* XXX: new elements here ! */
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
},
186 /* rgb values for the color yellow */
192 int box_y
; // start of box content
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
203 } msgs
[50]; // ring buffer
206 vlc_mutex_t msg_lock
;
208 /* Search Box context */
209 char search_chain
[20];
211 /* Open Box Context */
214 /* File Browser context */
217 struct dir_entry_t
**dir_entries
;
218 bool show_hidden_files
;
220 /* Playlist context */
221 struct pl_item_t
**plist
;
225 input_item_t
*node
; /* current node */
228 /*****************************************************************************
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
);
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
)
259 if (asprintf(&uri
, "%s" DIR_SEP
"%s", current_dir
, entry
) != -1) {
261 ret
= vlc_stat(uri
, &st
) || !S_ISDIR(st
.st_mode
);
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");
277 DIR *current_dir
= vlc_opendir(sys
->current_dir
);
279 msg_Warn(intf
, "cannot open directory `%s' (%s)", sys
->current_dir
,
280 vlc_strerror_c(errno
));
287 while ((entry
= vlc_readdir(current_dir
))) {
288 if (!sys
->show_hidden_files
&& *entry
== '.' && strcmp(entry
, ".."))
291 struct dir_entry_t
*dir_entry
= malloc(sizeof *dir_entry
);
292 if (unlikely(dir_entry
== NULL
))
295 dir_entry
->file
= IsFile(sys
->current_dir
, entry
);
296 dir_entry
->path
= strdup(entry
);
297 if (unlikely(dir_entry
->path
== NULL
))
302 TAB_APPEND(sys
->n_dir_entries
, sys
->dir_entries
, dir_entry
);
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)
323 /* make sure the new index is within the box */
324 if (sys
->box_idx
<= 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)
338 } else if (sys
->box_idx
== sys
->box_start
- 1) {
340 } else if (sys
->box_idx
== height
+ sys
->box_start
+ 1) {
345 /*****************************************************************************
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
);
361 static bool PlaylistAddChild(intf_sys_t
*sys
, playlist_item_t
*p_child
,
362 const char *c
, const char d
)
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
)
372 ret
= asprintf(&p_pl_item
->display
, "%s%c-%s", c
, d
, name
);
374 ret
= asprintf(&p_pl_item
->display
, " %s", name
);
379 p_pl_item
->item
= input_item_Hold(p_child
->p_input
);
381 TAB_APPEND(sys
->plist_entries
, sys
->plist
, p_pl_item
);
391 static void PlaylistAddNode(intf_sys_t
*sys
, playlist_item_t
*node
,
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
? '`' : '|'))
400 if (p_child
->i_children
<= 0)
405 if (asprintf(&tmp
, "%s%c ", c
, last
? ' ' : '|') == -1)
407 PlaylistAddNode(sys
, p_child
, tmp
);
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
);
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
;
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
))
470 static void SearchPlaylist(intf_sys_t
*sys
)
472 char *str
= sys
->search_chain
;
473 int i_first
= sys
->box_idx
;
480 int i_item
= SubSearchPlaylist(sys
, str
, i_first
+ 1, sys
->plist_entries
);
482 i_item
= SubSearchPlaylist(sys
, str
, 0, i_first
);
485 sys
->box_idx
= i_item
;
490 static inline bool IsIndex(intf_sys_t
*sys
, playlist_t
*p_playlist
, int i
)
494 input_item_t
*input
= sys
->plist
[i
]->item
;
495 playlist_item_t
*item
= playlist_ItemGetByInput(p_playlist
, input
);
496 if (unlikely(item
== NULL
))
499 if (item
->i_children
== 0 && input
== sys
->node
)
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
;
509 static void FindIndex(intf_sys_t
*sys
, playlist_t
*p_playlist
)
511 int plidx
= sys
->box_idx
;
512 int max
= sys
->plist_entries
;
516 if (!IsIndex(sys
, p_playlist
, plidx
))
517 for (int i
= 0; i
< max
; i
++)
518 if (IsIndex(sys
, p_playlist
, i
)) {
526 sys
->plidx_follow
= true;
529 /****************************************************************************
531 ****************************************************************************/
533 static void start_color_and_pairs(intf_thread_t
*intf
)
535 intf_sys_t
*sys
= intf
->p_sys
;
539 msg_Warn(intf
, "Terminal doesn't support colors");
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
)
557 if (w
<= 3 || h
<= 0)
560 if (color
) color_set(C_BOX
, NULL
);
562 if (!title
) title
= "";
563 int len
= strlen(title
);
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
)
589 mvhline(y
, x
, ' ', w
);
592 static void DrawLine(int y
, int x
, int w
)
597 mvhline(y
, x
, ' ', w
);
601 static void mvnprintw(int y
, int x
, int w
, const char *p_fmt
, ...)
610 va_start(vl_args
, p_fmt
);
611 int i_ret
= vasprintf(&p_buf
, p_fmt
, vl_args
);
619 wchar_t wide
[len
+ 1];
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 */ {
631 i_width
= wcswidth(wide
, i_char_len
);
632 if (i_width
== (size_t)-1) {
633 /* a non printable character was encountered */
635 for (unsigned i
= 0 ; i
< i_char_len
; i
++) {
636 int i_cwidth
= wcwidth(wide
[i
]);
642 if (i_width
<= (size_t)w
) {
643 mvprintw(y
, x
, "%s", p_buf
);
644 mvhline(y
, x
+ i_width
, ' ', w
- i_width
);
649 int i_total_width
= 0;
651 while (i_total_width
< w
) {
652 i_total_width
+= wcwidth(wide
[i
]);
653 if (w
> 7 && i_total_width
>= w
/2) {
656 i_total_width
-= wcwidth(wide
[i
]) - 2;
658 /* we require this check only if at least one character
659 * 4 or more columns wide exists (which i doubt) */
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
)
674 wmemmove(&wide
[i
+2], &wide
[j
+1], i_char_len
- j
- 1);
675 wide
[i
+ 2 + i_char_len
- j
- 1] = '\0';
680 if (w
<= 7) /* we don't add the '...' else we lose too much chars */
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
);
691 static void MainBoxWrite(intf_sys_t
*sys
, int l
, const char *p_fmt
, ...)
695 bool b_selected
= l
== sys
->box_idx
;
697 if (l
< sys
->box_start
|| l
- sys
->box_start
>= sys
->box_height
)
700 va_start(vl_args
, p_fmt
);
701 int i_ret
= vasprintf(&p_buf
, p_fmt
, vl_args
);
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
);
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
);
720 size_t count
= 0, size
;
721 vlc_object_t
**tab
= NULL
;
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
]);
738 static int DrawObjects(intf_thread_t
*intf
, input_thread_t
*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
;
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
);
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
);
770 static int DrawInfo(intf_thread_t
*intf
, input_thread_t
*p_input
)
772 intf_sys_t
*sys
= intf
->p_sys
;
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
];
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
);
797 static int DrawStats(intf_thread_t
*intf
, input_thread_t
*p_input
)
799 intf_sys_t
*sys
= intf
->p_sys
;
801 input_stats_t
*p_stats
;
802 int l
= 0, i_audio
= 0, i_video
= 0;
807 item
= input_GetItem(p_input
);
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
);
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);
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
);
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
);
862 static int DrawHelp(intf_thread_t
*intf
, input_thread_t
*input
)
864 intf_sys_t
*sys
= intf
->p_sys
;
867 #define H(a) MainBoxWrite(sys, l++, a)
869 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
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"));
884 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
886 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
887 H(_(" q, Q, Esc Quit"));
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"));
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"));
909 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
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)"));
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"));
934 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
936 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
937 /* xgettext: You can use ↑ and ↓ characters */
938 H(_(" <up>,<down> Seek +/-5%%"));
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
? ' ' : '+';
954 color_set(dir_entry
->file
? C_DEFAULT
: C_FOLDER
, NULL
);
955 MainBoxWrite(sys
, i
, " %c %s", type
, dir_entry
->path
);
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
);
968 if (sys
->need_update
) {
969 PlaylistRebuild(intf
);
970 sys
->need_update
= false;
974 if (sys
->plidx_follow
)
975 FindIndex(sys
, p_playlist
);
977 for (int i
= 0; i
< sys
->plist_entries
; i
++) {
979 playlist_item_t
*current
;
980 input_item_t
*item
= sys
->plist
[i
]->item
;
983 current
= playlist_CurrentPlayingItem(p_playlist
);
985 if ((sys
->node
!= NULL
&& item
== sys
->node
) ||
986 (sys
->node
== NULL
&& current
!= NULL
&& item
== current
->p_input
))
988 else if (current
!= NULL
&& current
->p_input
== item
)
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
);
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
;
1008 vlc_mutex_lock(&sys
->msg_lock
);
1009 int i
= sys
->i_msgs
;
1011 vlc_log_t
*msg
= sys
->msgs
[i
].item
;
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
)
1021 if (i
== sys
->i_msgs
) /* did we loop around the ring buffer ? */
1025 vlc_mutex_unlock(&sys
->msg_lock
);
1027 color_set(C_DEFAULT
, NULL
);
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
);
1040 const char *repeat
, *loop
, *random
;
1044 int padding
= COLS
- name_len
; /* center title */
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
);
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]") : "";
1065 uri
= input_item_GetURI(input_GetItem(p_input
));
1066 path
= vlc_uri2path(uri
);
1068 mvnprintw(y
++, 0, COLS
, _(" Source : %s"), path
?path
:uri
);
1072 var_Get(p_input
, "state", &val
);
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
];
1092 mvnprintw(y
++, 0, COLS
, _(input_state
[val
.i_int
]),
1093 repeat
, random
, loop
);
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
);
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);
1133 DrawLine(y
, 1, (int)((COLS
-2) * var_GetFloat(p_input
, "position")));
1135 y
+= 2; /* skip slider and box */
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
);
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
)
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
]));
1184 if (box
!= BOX_NONE
) {
1185 FillBox(intf
, input
);
1187 if (sys
->box_lines_total
== 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
,
1195 while (y
< LINES
- 1)
1196 DrawEmptyLine(y
++, 1, COLS
- 2);
1201 static void ChangePosition(input_thread_t
*p_input
, float increment
)
1205 if (!p_input
|| var_GetInteger(p_input
, "state") != PLAYING_S
)
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 */
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", },
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';
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 ! */
1262 if (!playlist_CurrentPlayingItem(p_playlist
)) {
1267 name
= playlist_CurrentPlayingItem(p_playlist
)->p_input
->psz_name
;
1268 device
= name
? GetDiscDevice(name
) : NULL
;
1273 intf_Eject(intf
, device
);
1278 static void PlayPause(intf_thread_t
*intf
, input_thread_t
*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
);
1285 playlist_Play(pl_Get(intf
));
1288 static void AddItem(intf_thread_t
*intf
, const char *path
)
1290 char *uri
= vlc_path2uri(path
, NULL
);
1294 input_item_t
*item
= input_item_New(uri
, NULL
);
1296 if (unlikely(item
== NULL
))
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
)
1308 node
= node
->p_parent
;
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
;
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
);
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;
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
);
1351 SearchPlaylist(sys
);
1355 FindIndex(sys
, p_playlist
);
1364 input_item_t
*input
= sys
->plist
[sys
->box_idx
]->item
;
1365 playlist_item_t
*item
;
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;
1382 struct pl_item_t
*p_pl_item
= sys
->plist
[sys
->box_idx
];
1383 if (p_pl_item
== NULL
)
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
;
1398 if (sys
->node
!= NULL
)
1399 input_item_Release(sys
->node
);
1400 sys
->node
= parent
->p_input
? input_item_Hold(parent
->p_input
)
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
)
1413 playlist_Unlock(p_playlist
);
1415 sys
->plidx_follow
= true;
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
;
1431 sys
->show_hidden_files
= !sys
->show_hidden_files
;
1439 dir_entry
= sys
->dir_entries
[sys
->box_idx
];
1441 if (asprintf(&path
, "%s" DIR_SEP
"%s", sys
->current_dir
,
1442 dir_entry
->path
) == -1)
1445 if (!dir_entry
->file
&& key
!= ' ') {
1446 free(sys
->current_dir
);
1447 sys
->current_dir
= path
;
1455 AddItem(intf
, path
);
1457 BoxSwitch(sys
, BOX_PLAYLIST
);
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
);
1484 case KEY_CLEAR
: clear(); return;
1490 SearchPlaylist(sys
);
1492 OpenSelection(intf
);
1494 sys
->box_type
= BOX_PLAYLIST
;
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.
1513 sys
->box_type
= BOX_PLAYLIST
;
1518 RemoveLastUTF8Entity(str
, len
);
1522 if (len
+ 1 < (search
? sizeof sys
->search_chain
1523 : sizeof sys
->open_chain
)) {
1525 str
[len
+ 1] = '\0';
1530 SearchPlaylist(sys
);
1533 static void InputNavigate(input_thread_t
* p_input
, const char *var
)
1536 var_TriggerCallback(p_input
, var
);
1539 static void CycleESTrack(input_thread_t
*input
, const char *var
)
1547 if (var_Change(input
, var
, VLC_VAR_GETCHOICES
,
1548 &count
, &list
, (char ***)NULL
) < 0)
1551 int64_t current
= var_GetInteger(input
, var
);
1554 for (i
= 0; i
< count
; i
++)
1555 if (list
[i
].i_int
== current
)
1560 var_SetInteger(input
, var
, list
[i
].i_int
);
1564 static void HandleCommonKey(intf_thread_t
*intf
, input_thread_t
*input
,
1567 intf_sys_t
*sys
= intf
->p_sys
;
1568 playlist_t
*p_playlist
= pl_Get(intf
);
1571 case 0x1b: /* ESC */
1578 libvlc_Quit(intf
->obj
.libvlc
);
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
);
1596 case 'A': /* Open */
1597 sys
->open_chain
[0] = '\0';
1598 BoxSwitch(sys
, BOX_OPEN
);
1602 case KEY_RIGHT
: ChangePosition(input
, +0.01); return;
1603 case KEY_LEFT
: ChangePosition(input
, -0.01); return;
1605 /* Common control */
1608 vout_thread_t
*p_vout
= input_GetVout(input
);
1610 bool fs
= var_ToggleBool(p_playlist
, "fullscreen");
1611 var_SetBool(p_vout
, "fullscreen", fs
);
1612 vlc_object_release(p_vout
);
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;
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
);
1656 /* workaround for FreeBSD + xterm:
1657 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
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;
1672 if (sys
->box_type
== BOX_PLAYLIST
) {
1674 sys
->plidx_follow
= IsIndex(sys
, p_playlist
, sys
->box_idx
);
1681 static void HandleKey(intf_thread_t
*intf
, input_thread_t
*input
)
1683 intf_sys_t
*sys
= intf
->p_sys
;
1685 int box
= sys
->box_type
;
1690 if (box
== BOX_SEARCH
|| box
== BOX_OPEN
) {
1691 HandleEditBoxKey(intf
, key
, sys
->box_type
);
1695 if (box
== BOX_NONE
)
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
))
1711 if (box
== BOX_PLAYLIST
&& HandlePlaylistKey(intf
, key
))
1714 if (HandleListKey(intf
, key
))
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
;
1733 static void msg_Free (vlc_log_t
*msg
)
1735 free ((char *)msg
->psz_module
);
1736 free ((char *)msg
->psz_header
);
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
;
1746 if (sys
->verbosity
< 0
1747 || sys
->verbosity
< (type
- VLC_MSG_ERR
)
1748 || vasprintf(&text
, format
, ap
) == -1)
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
))
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
);
1779 int canc
= vlc_savecancel();
1780 input_thread_t
*input
= playlist_CurrentInput(p_playlist
);
1782 Redraw(intf
, input
);
1783 HandleKey(intf
, 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
);
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 */
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() */
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
));
1835 PlaylistRebuild(intf
),
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
))
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
);
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
);