1 /*****************************************************************************
2 * ncurses.c : NCurses interface for vlc
3 *****************************************************************************
4 * Copyright © 2001-2011 the VideoLAN team
7 * Authors: Sam Hocevar <sam@zoy.org>
8 * Laurent Aimar <fenrir@via.ecp.fr>
9 * Yoann Peronneau <yoann@videolan.org>
10 * Derk-Jan Hartman <hartman at videolan dot org>
11 * Rafaël Carré <funman@videolanorg>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 /* UTF8 locale is required */
30 /*****************************************************************************
32 *****************************************************************************/
37 #define _XOPEN_SOURCE_EXTENDED 1
45 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
46 #include <vlc_common.h>
47 #include <vlc_plugin.h>
51 #include <vlc_interface.h>
53 #include <vlc_charset.h>
54 #include <vlc_input.h>
56 #include <vlc_playlist.h>
61 /*****************************************************************************
63 *****************************************************************************/
64 static int Open (vlc_object_t
*);
65 static void Close (vlc_object_t
*);
67 /*****************************************************************************
69 *****************************************************************************/
71 #define BROWSE_TEXT N_("Filebrowser starting point")
72 #define BROWSE_LONGTEXT N_(\
73 "This option allows you to specify the directory the ncurses filebrowser " \
74 "will show you initially.")
77 set_shortname("Ncurses")
78 set_description(N_("Ncurses interface"))
79 set_capability("interface", 10)
80 set_category(CAT_INTERFACE
)
81 set_subcategory(SUBCAT_INTERFACE_MAIN
)
82 set_callbacks(Open
, Close
)
83 add_shortcut("curses")
84 add_directory("browse-dir", NULL
, BROWSE_TEXT
, BROWSE_LONGTEXT
, false)
89 /*****************************************************************************
90 * intf_sys_t: description and status of ncurses interface
91 *****************************************************************************/
107 static const char box_title
[][19] = {
109 [BOX_HELP
] = " Help ",
110 [BOX_INFO
] = " Information ",
111 [BOX_LOG
] = " Messages ",
112 [BOX_PLAYLIST
] = " Playlist ",
113 [BOX_SEARCH
] = " Playlist ",
114 [BOX_OPEN
] = " Playlist ",
115 [BOX_BROWSE
] = " Browse ",
116 [BOX_META
] = " Meta-information ",
117 [BOX_OBJECTS
] = " Objects ",
118 [BOX_STATS
] = " Stats ",
136 /* XXX: new elements here ! */
141 /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
142 static const struct { short f
; short b
; } color_pairs
[] =
144 /* element */ /* foreground*/ /* background*/
145 [C_TITLE
] = { COLOR_YELLOW
, COLOR_BLACK
},
147 /* jamaican playlist, for rastafari sisters & brothers! */
148 [C_PLAYLIST_1
] = { COLOR_GREEN
, COLOR_BLACK
},
149 [C_PLAYLIST_2
] = { COLOR_YELLOW
, COLOR_BLACK
},
150 [C_PLAYLIST_3
] = { COLOR_RED
, COLOR_BLACK
},
152 /* used in DrawBox() */
153 [C_BOX
] = { COLOR_CYAN
, COLOR_BLACK
},
154 /* Source: State, Position, Volume, Chapters, etc...*/
155 [C_STATUS
] = { COLOR_BLUE
, COLOR_BLACK
},
157 /* VLC messages, keep the order from highest priority to lowest */
158 [C_INFO
] = { COLOR_BLACK
, COLOR_WHITE
},
159 [C_ERROR
] = { COLOR_RED
, COLOR_BLACK
},
160 [C_WARNING
] = { COLOR_YELLOW
, COLOR_BLACK
},
161 [C_DEBUG
] = { COLOR_WHITE
, COLOR_BLACK
},
163 /* Category title: help, info, metadata */
164 [C_CATEGORY
] = { COLOR_MAGENTA
, COLOR_BLACK
},
165 /* Folder (BOX_BROWSE) */
166 [C_FOLDER
] = { COLOR_RED
, COLOR_BLACK
},
187 /* rgb values for the color yellow */
193 int box_y
; // start of box content
195 int box_lines_total
; // number of lines in the box
196 int box_start
; // first line of box displayed
197 int box_idx
; // selected line
204 } msgs
[50]; // ring buffer
207 vlc_mutex_t msg_lock
;
209 /* Search Box context */
210 char search_chain
[20];
212 /* Open Box Context */
215 /* File Browser context */
218 struct dir_entry_t
**dir_entries
;
219 bool show_hidden_files
;
221 /* Playlist context */
222 struct pl_item_t
**plist
;
226 input_item_t
*node
; /* current node */
229 /*****************************************************************************
231 *****************************************************************************/
233 static void DirsDestroy(intf_sys_t
*sys
)
235 while (sys
->n_dir_entries
) {
236 struct dir_entry_t
*dir_entry
= sys
->dir_entries
[--sys
->n_dir_entries
];
237 free(dir_entry
->path
);
240 free(sys
->dir_entries
);
241 sys
->dir_entries
= NULL
;
244 static int comdir_entries(const void *a
, const void *b
)
246 struct dir_entry_t
*dir_entry1
= *(struct dir_entry_t
**)a
;
247 struct dir_entry_t
*dir_entry2
= *(struct dir_entry_t
**)b
;
249 if (dir_entry1
->file
== dir_entry2
->file
)
250 return strcasecmp(dir_entry1
->path
, dir_entry2
->path
);
252 return dir_entry1
->file
? 1 : -1;
255 static bool IsFile(const char *current_dir
, const char *entry
)
260 if (asprintf(&uri
, "%s" DIR_SEP
"%s", current_dir
, entry
) != -1) {
262 ret
= vlc_stat(uri
, &st
) || !S_ISDIR(st
.st_mode
);
269 static void ReadDir(intf_thread_t
*intf
)
271 intf_sys_t
*sys
= intf
->p_sys
;
273 if (!sys
->current_dir
|| !*sys
->current_dir
) {
274 msg_Dbg(intf
, "no current dir set");
278 DIR *current_dir
= vlc_opendir(sys
->current_dir
);
280 msg_Warn(intf
, "cannot open directory `%s' (%s)", sys
->current_dir
,
281 vlc_strerror_c(errno
));
288 while ((entry
= vlc_readdir(current_dir
))) {
289 if (!sys
->show_hidden_files
&& *entry
== '.' && strcmp(entry
, ".."))
292 struct dir_entry_t
*dir_entry
= malloc(sizeof *dir_entry
);
293 if (unlikely(dir_entry
== NULL
))
296 dir_entry
->file
= IsFile(sys
->current_dir
, entry
);
297 dir_entry
->path
= xstrdup(entry
);
298 TAB_APPEND(sys
->n_dir_entries
, sys
->dir_entries
, dir_entry
);
302 closedir(current_dir
);
304 if (sys
->n_dir_entries
> 0)
305 qsort(sys
->dir_entries
, sys
->n_dir_entries
,
306 sizeof(struct dir_entry_t
*), &comdir_entries
);
309 /*****************************************************************************
310 * Adjust index position after a change (list navigation or item switching)
311 *****************************************************************************/
312 static void CheckIdx(intf_sys_t
*sys
)
314 int lines
= sys
->box_lines_total
;
315 int height
= LINES
- sys
->box_y
- 2;
316 if (height
> lines
- 1)
319 /* make sure the new index is within the box */
320 if (sys
->box_idx
<= 0) {
323 } else if (sys
->box_idx
>= lines
- 1 && lines
> 0) {
324 sys
->box_idx
= lines
- 1;
325 sys
->box_start
= sys
->box_idx
- height
;
328 /* Fix box start (1st line of the box displayed) */
329 if (sys
->box_idx
< sys
->box_start
||
330 sys
->box_idx
> height
+ sys
->box_start
+ 1) {
331 sys
->box_start
= sys
->box_idx
- height
/2;
332 if (sys
->box_start
< 0)
334 } else if (sys
->box_idx
== sys
->box_start
- 1) {
336 } else if (sys
->box_idx
== height
+ sys
->box_start
+ 1) {
341 /*****************************************************************************
343 *****************************************************************************/
344 static void PlaylistDestroy(intf_sys_t
*sys
)
346 while (sys
->plist_entries
) {
347 struct pl_item_t
*p_pl_item
= sys
->plist
[--sys
->plist_entries
];
349 input_item_Release(p_pl_item
->item
);
350 free(p_pl_item
->display
);
357 static bool PlaylistAddChild(intf_sys_t
*sys
, playlist_item_t
*p_child
,
358 const char *c
, const char d
)
361 char *name
= input_item_GetTitleFbName(p_child
->p_input
);
362 struct pl_item_t
*p_pl_item
= malloc(sizeof *p_pl_item
);
364 if (!name
|| !p_pl_item
)
368 ret
= asprintf(&p_pl_item
->display
, "%s%c-%s", c
, d
, name
);
370 ret
= asprintf(&p_pl_item
->display
, " %s", name
);
375 p_pl_item
->item
= input_item_Hold(p_child
->p_input
);
377 TAB_APPEND(sys
->plist_entries
, sys
->plist
, p_pl_item
);
387 static void PlaylistAddNode(intf_sys_t
*sys
, playlist_item_t
*node
,
390 for (int k
= 0; k
< node
->i_children
; k
++) {
391 bool last
= k
== node
->i_children
- 1;
392 playlist_item_t
*p_child
= node
->pp_children
[k
];
393 if (!PlaylistAddChild(sys
, p_child
, c
, last
? '`' : '|'))
396 if (p_child
->i_children
<= 0)
401 if (asprintf(&tmp
, "%s%c ", c
, last
? ' ' : '|') == -1)
403 PlaylistAddNode(sys
, p_child
, tmp
);
406 PlaylistAddNode(sys
, p_child
, " ");
411 static void PlaylistRebuild(intf_thread_t
*intf
)
413 intf_sys_t
*sys
= intf
->p_sys
;
414 playlist_t
*p_playlist
= pl_Get(intf
);
416 PlaylistDestroy(sys
);
417 PlaylistAddNode(sys
, &p_playlist
->root
, "");
420 static int ItemChanged(vlc_object_t
*p_this
, const char *variable
,
421 vlc_value_t oval
, vlc_value_t nval
, void *param
)
423 playlist_t
*playlist
= (playlist_t
*)p_this
;
424 intf_sys_t
*sys
= param
;
426 VLC_UNUSED(p_this
); VLC_UNUSED(variable
);
427 VLC_UNUSED(oval
); VLC_UNUSED(nval
);
429 playlist_Lock(playlist
);
430 sys
->need_update
= true;
431 playlist_Unlock(playlist
);
436 static int PlaylistChanged(vlc_object_t
*p_this
, const char *variable
,
437 vlc_value_t oval
, vlc_value_t nval
, void *param
)
439 playlist_t
*playlist
= (playlist_t
*)p_this
;
440 intf_sys_t
*sys
= param
;
441 playlist_item_t
*node
= playlist_CurrentPlayingItem(playlist
);
443 VLC_UNUSED(variable
);
444 VLC_UNUSED(oval
); VLC_UNUSED(nval
);
446 sys
->need_update
= true;
448 if (sys
->node
!= NULL
)
449 input_item_Release(sys
->node
);
450 sys
->node
= (node
!= NULL
) ? input_item_Hold(node
->p_input
) : NULL
;
456 static int SubSearchPlaylist(intf_sys_t
*sys
, char *searchstring
,
457 int i_start
, int i_stop
)
459 for (int i
= i_start
+ 1; i
< i_stop
; i
++)
460 if (strcasestr(sys
->plist
[i
]->display
, searchstring
))
466 static void SearchPlaylist(intf_sys_t
*sys
)
468 char *str
= sys
->search_chain
;
469 int i_first
= sys
->box_idx
;
476 int i_item
= SubSearchPlaylist(sys
, str
, i_first
+ 1, sys
->plist_entries
);
478 i_item
= SubSearchPlaylist(sys
, str
, 0, i_first
);
481 sys
->box_idx
= i_item
;
486 static inline bool IsIndex(intf_sys_t
*sys
, playlist_t
*p_playlist
, int i
)
490 input_item_t
*input
= sys
->plist
[i
]->item
;
491 playlist_item_t
*item
= playlist_ItemGetByInput(p_playlist
, input
);
492 if (unlikely(item
== NULL
))
495 if (item
->i_children
== 0 && input
== sys
->node
)
498 playlist_item_t
*p_played_item
= playlist_CurrentPlayingItem(p_playlist
);
499 if (p_played_item
!= NULL
)
500 return input
== p_played_item
->p_input
;
505 static void FindIndex(intf_sys_t
*sys
, playlist_t
*p_playlist
)
507 int plidx
= sys
->box_idx
;
508 int max
= sys
->plist_entries
;
512 if (!IsIndex(sys
, p_playlist
, plidx
))
513 for (int i
= 0; i
< max
; i
++)
514 if (IsIndex(sys
, p_playlist
, i
)) {
522 sys
->plidx_follow
= true;
525 /****************************************************************************
527 ****************************************************************************/
529 static void start_color_and_pairs(intf_thread_t
*intf
)
531 intf_sys_t
*sys
= intf
->p_sys
;
535 msg_Warn(intf
, "Terminal doesn't support colors");
540 for (int i
= C_DEFAULT
+ 1; i
< C_MAX
; i
++)
541 init_pair(i
, color_pairs
[i
].f
, color_pairs
[i
].b
);
543 /* untested, in all my terminals, !can_change_color() --funman */
544 if (can_change_color()) {
545 color_content(COLOR_YELLOW
, &sys
->yellow_r
, &sys
->yellow_g
, &sys
->yellow_b
);
546 init_color(COLOR_YELLOW
, 960, 500, 0); /* YELLOW -> ORANGE */
550 static void DrawBox(int y
, int h
, bool color
, const char *title
)
553 if (w
<= 3 || h
<= 0)
556 if (color
) color_set(C_BOX
, NULL
);
558 if (!title
) title
= "";
559 int len
= strlen(title
);
564 mvaddch(y
, 0, ACS_ULCORNER
);
565 mvhline(y
, 1, ACS_HLINE
, (w
-len
-2)/2);
566 mvprintw(y
, 1+(w
-len
-2)/2, "%s", title
);
567 mvhline(y
, (w
-len
)/2+len
, ACS_HLINE
, w
- 1 - ((w
-len
)/2+len
));
568 mvaddch(y
, w
-1,ACS_URCORNER
);
570 for (int i
= 0; i
< h
; i
++) {
571 mvaddch(++y
, 0, ACS_VLINE
);
572 mvaddch(y
, w
-1, ACS_VLINE
);
575 mvaddch(++y
, 0, ACS_LLCORNER
);
576 mvhline(y
, 1, ACS_HLINE
, w
- 2);
577 mvaddch(y
, w
-1, ACS_LRCORNER
);
578 if (color
) color_set(C_DEFAULT
, NULL
);
581 static void DrawEmptyLine(int y
, int x
, int w
)
585 mvhline(y
, x
, ' ', w
);
588 static void DrawLine(int y
, int x
, int w
)
593 mvhline(y
, x
, ' ', w
);
597 static void mvnprintw(int y
, int x
, int w
, const char *p_fmt
, ...)
606 va_start(vl_args
, p_fmt
);
607 int i_ret
= vasprintf(&p_buf
, p_fmt
, vl_args
);
615 wchar_t wide
[len
+ 1];
618 size_t i_char_len
= mbstowcs(wide
, p_buf
, len
);
620 size_t i_width
; /* number of columns */
622 if (i_char_len
== (size_t)-1) /* an invalid character was encountered */ {
627 i_width
= wcswidth(wide
, i_char_len
);
628 if (i_width
== (size_t)-1) {
629 /* a non printable character was encountered */
631 for (unsigned i
= 0 ; i
< i_char_len
; i
++) {
632 int i_cwidth
= wcwidth(wide
[i
]);
638 if (i_width
<= (size_t)w
) {
639 mvprintw(y
, x
, "%s", p_buf
);
640 mvhline(y
, x
+ i_width
, ' ', w
- i_width
);
645 int i_total_width
= 0;
647 while (i_total_width
< w
) {
648 i_total_width
+= wcwidth(wide
[i
]);
649 if (w
> 7 && i_total_width
>= w
/2) {
652 i_total_width
-= wcwidth(wide
[i
]) - 2;
654 /* we require this check only if at least one character
655 * 4 or more columns wide exists (which i doubt) */
657 i_total_width
-= wcwidth(wide
[i
-1]) - 1;
660 /* find the widest string */
661 int j
, i_2nd_width
= 0;
662 for (j
= i_char_len
- 1; i_2nd_width
< w
- i_total_width
; j
--)
663 i_2nd_width
+= wcwidth(wide
[j
]);
665 /* we already have i_total_width columns filled, and we can't
666 * have more than w columns */
667 if (i_2nd_width
> w
- i_total_width
)
670 wmemmove(&wide
[i
+2], &wide
[j
+1], i_char_len
- j
- 1);
671 wide
[i
+ 2 + i_char_len
- j
- 1] = '\0';
676 if (w
<= 7) /* we don't add the '...' else we lose too much chars */
679 size_t i_wlen
= wcslen(wide
) * 6 + 1; /* worst case */
680 char ellipsized
[i_wlen
];
681 wcstombs(ellipsized
, wide
, i_wlen
);
682 mvprintw(y
, x
, "%s", ellipsized
);
687 static void MainBoxWrite(intf_sys_t
*sys
, int l
, const char *p_fmt
, ...)
691 bool b_selected
= l
== sys
->box_idx
;
693 if (l
< sys
->box_start
|| l
- sys
->box_start
>= sys
->box_height
)
696 va_start(vl_args
, p_fmt
);
697 int i_ret
= vasprintf(&p_buf
, p_fmt
, vl_args
);
702 if (b_selected
) attron(A_REVERSE
);
703 mvnprintw(sys
->box_y
+ l
- sys
->box_start
, 1, COLS
- 2, "%s", p_buf
);
704 if (b_selected
) attroff(A_REVERSE
);
709 static int SubDrawObject(intf_sys_t
*sys
, int l
, vlc_object_t
*p_obj
, int i_level
, const char *prefix
)
711 char *name
= vlc_object_get_name(p_obj
);
712 MainBoxWrite(sys
, l
++, "%*s%s%s \"%s\" (%p)", 2 * i_level
++, "", prefix
,
713 p_obj
->obj
.object_type
, name
? name
: "", (void *)p_obj
);
716 vlc_list_t
*list
= vlc_list_children(p_obj
);
717 for (int i
= 0; i
< list
->i_count
; i
++) {
718 l
= SubDrawObject(sys
, l
, list
->p_values
[i
].p_address
, i_level
,
719 (i
== list
->i_count
- 1) ? "`-" : "|-" );
721 vlc_list_release(list
);
725 static int DrawObjects(intf_thread_t
*intf
, input_thread_t
*input
)
728 return SubDrawObject(intf
->p_sys
, 0, VLC_OBJECT(intf
->obj
.libvlc
), 0, "");
731 static int DrawMeta(intf_thread_t
*intf
, input_thread_t
*p_input
)
733 intf_sys_t
*sys
= intf
->p_sys
;
740 item
= input_GetItem(p_input
);
741 vlc_mutex_lock(&item
->lock
);
742 for (int i
=0; i
<VLC_META_TYPE_COUNT
; i
++) {
743 const char *meta
= vlc_meta_Get(item
->p_meta
, i
);
747 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
748 MainBoxWrite(sys
, l
++, " [%s]", vlc_meta_TypeToLocalizedString(i
));
749 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
750 MainBoxWrite(sys
, l
++, " %s", meta
);
752 vlc_mutex_unlock(&item
->lock
);
757 static int DrawInfo(intf_thread_t
*intf
, input_thread_t
*p_input
)
759 intf_sys_t
*sys
= intf
->p_sys
;
766 item
= input_GetItem(p_input
);
767 vlc_mutex_lock(&item
->lock
);
768 for (int i
= 0; i
< item
->i_categories
; i
++) {
769 info_category_t
*p_category
= item
->pp_categories
[i
];
770 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
771 MainBoxWrite(sys
, l
++, _(" [%s]"), p_category
->psz_name
);
772 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
773 for (int j
= 0; j
< p_category
->i_infos
; j
++) {
774 info_t
*p_info
= p_category
->pp_infos
[j
];
775 MainBoxWrite(sys
, l
++, _(" %s: %s"),
776 p_info
->psz_name
, p_info
->psz_value
);
779 vlc_mutex_unlock(&item
->lock
);
784 static int DrawStats(intf_thread_t
*intf
, input_thread_t
*p_input
)
786 intf_sys_t
*sys
= intf
->p_sys
;
788 input_stats_t
*p_stats
;
789 int l
= 0, i_audio
= 0, i_video
= 0;
794 item
= input_GetItem(p_input
);
797 vlc_mutex_lock(&item
->lock
);
798 p_stats
= item
->p_stats
;
799 vlc_mutex_lock(&p_stats
->lock
);
801 for (int i
= 0; i
< item
->i_es
; i
++) {
802 i_audio
+= (item
->es
[i
]->i_cat
== AUDIO_ES
);
803 i_video
+= (item
->es
[i
]->i_cat
== VIDEO_ES
);
807 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
808 MainBoxWrite(sys
, l
++, _("+-[Incoming]"));
809 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
810 MainBoxWrite(sys
, l
++, _("| input bytes read : %8.0f KiB"),
811 (float)(p_stats
->i_read_bytes
)/1024);
812 MainBoxWrite(sys
, l
++, _("| input bitrate : %6.0f kb/s"),
813 p_stats
->f_input_bitrate
*8000);
814 MainBoxWrite(sys
, l
++, _("| demux bytes read : %8.0f KiB"),
815 (float)(p_stats
->i_demux_read_bytes
)/1024);
816 MainBoxWrite(sys
, l
++, _("| demux bitrate : %6.0f kb/s"),
817 p_stats
->f_demux_bitrate
*8000);
821 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
822 MainBoxWrite(sys
, l
++, _("+-[Video Decoding]"));
823 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
824 MainBoxWrite(sys
, l
++, _("| video decoded : %5"PRIi64
),
825 p_stats
->i_decoded_video
);
826 MainBoxWrite(sys
, l
++, _("| frames displayed : %5"PRIi64
),
827 p_stats
->i_displayed_pictures
);
828 MainBoxWrite(sys
, l
++, _("| frames lost : %5"PRIi64
),
829 p_stats
->i_lost_pictures
);
833 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
834 MainBoxWrite(sys
, l
++, _("+-[Audio Decoding]"));
835 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
836 MainBoxWrite(sys
, l
++, _("| audio decoded : %5"PRIi64
),
837 p_stats
->i_decoded_audio
);
838 MainBoxWrite(sys
, l
++, _("| buffers played : %5"PRIi64
),
839 p_stats
->i_played_abuffers
);
840 MainBoxWrite(sys
, l
++, _("| buffers lost : %5"PRIi64
),
841 p_stats
->i_lost_abuffers
);
844 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
845 MainBoxWrite(sys
, l
++, _("+-[Streaming]"));
846 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
847 MainBoxWrite(sys
, l
++, _("| packets sent : %5"PRIi64
), p_stats
->i_sent_packets
);
848 MainBoxWrite(sys
, l
++, _("| bytes sent : %8.0f KiB"),
849 (float)(p_stats
->i_sent_bytes
)/1025);
850 MainBoxWrite(sys
, l
++, _("| sending bitrate : %6.0f kb/s"),
851 p_stats
->f_send_bitrate
*8000);
852 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
854 vlc_mutex_unlock(&p_stats
->lock
);
855 vlc_mutex_unlock(&item
->lock
);
860 static int DrawHelp(intf_thread_t
*intf
, input_thread_t
*input
)
862 intf_sys_t
*sys
= intf
->p_sys
;
865 #define H(a) MainBoxWrite(sys, l++, a)
867 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
869 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
870 H(_(" h,H Show/Hide help box"));
871 H(_(" i Show/Hide info box"));
872 H(_(" M Show/Hide metadata box"));
873 H(_(" L Show/Hide messages box"));
874 H(_(" P Show/Hide playlist box"));
875 H(_(" B Show/Hide filebrowser"));
876 H(_(" x Show/Hide objects box"));
877 H(_(" S Show/Hide statistics box"));
878 H(_(" Esc Close Add/Search entry"));
879 H(_(" Ctrl-l Refresh the screen"));
882 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
884 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
885 H(_(" q, Q, Esc Quit"));
887 H(_(" <space> Pause/Play"));
888 H(_(" f Toggle Fullscreen"));
889 H(_(" c Cycle through audio tracks"));
890 H(_(" v Cycle through subtitles tracks"));
891 H(_(" b Cycle through video tracks"));
892 H(_(" n, p Next/Previous playlist item"));
893 H(_(" [, ] Next/Previous title"));
894 H(_(" <, > Next/Previous chapter"));
895 /* xgettext: You can use ← and → characters */
896 H(_(" <left>,<right> Seek -/+ 1%%"));
897 H(_(" a, z Volume Up/Down"));
899 /* xgettext: You can use ↑ and ↓ characters */
900 H(_(" <up>,<down> Navigate through the box line by line"));
901 /* xgettext: You can use ⇞ and ⇟ characters */
902 H(_(" <pageup>,<pagedown> Navigate through the box page by page"));
903 /* xgettext: You can use ↖ and ↘ characters */
904 H(_(" <start>,<end> Navigate to start/end of box"));
907 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
909 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
910 H(_(" r Toggle Random playing"));
911 H(_(" l Toggle Loop Playlist"));
912 H(_(" R Toggle Repeat item"));
913 H(_(" o Order Playlist by title"));
914 H(_(" O Reverse order Playlist by title"));
915 H(_(" g Go to the current playing item"));
916 H(_(" / Look for an item"));
917 H(_(" ; Look for the next item"));
918 H(_(" A Add an entry"));
919 /* xgettext: You can use ⌫ character to translate <backspace> */
920 H(_(" D, <backspace>, <del> Delete an entry"));
921 H(_(" e Eject (if stopped)"));
924 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
925 H(_("[Filebrowser]"));
926 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
927 H(_(" <enter> Add the selected file to the playlist"));
928 H(_(" <space> Add the selected directory to the playlist"));
929 H(_(" . Show/Hide hidden files"));
932 if (sys
->color
) color_set(C_CATEGORY
, NULL
);
934 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
935 /* xgettext: You can use ↑ and ↓ characters */
936 H(_(" <up>,<down> Seek +/-5%%"));
943 static int DrawBrowse(intf_thread_t
*intf
, input_thread_t
*input
)
945 intf_sys_t
*sys
= intf
->p_sys
;
947 for (int i
= 0; i
< sys
->n_dir_entries
; i
++) {
948 struct dir_entry_t
*dir_entry
= sys
->dir_entries
[i
];
949 char type
= dir_entry
->file
? ' ' : '+';
952 color_set(dir_entry
->file
? C_DEFAULT
: C_FOLDER
, NULL
);
953 MainBoxWrite(sys
, i
, " %c %s", type
, dir_entry
->path
);
957 return sys
->n_dir_entries
;
960 static int DrawPlaylist(intf_thread_t
*intf
, input_thread_t
*input
)
962 intf_sys_t
*sys
= intf
->p_sys
;
963 playlist_t
*p_playlist
= pl_Get(intf
);
966 if (sys
->need_update
) {
967 PlaylistRebuild(intf
);
968 sys
->need_update
= false;
972 if (sys
->plidx_follow
)
973 FindIndex(sys
, p_playlist
);
975 for (int i
= 0; i
< sys
->plist_entries
; i
++) {
977 playlist_item_t
*current
;
978 input_item_t
*item
= sys
->plist
[i
]->item
;
981 current
= playlist_CurrentPlayingItem(p_playlist
);
983 if ((sys
->node
!= NULL
&& item
== sys
->node
) ||
984 (sys
->node
== NULL
&& current
!= NULL
&& item
== current
->p_input
))
986 else if (current
!= NULL
&& current
->p_input
== item
)
992 if (sys
->color
) color_set(i
%3 + C_PLAYLIST_1
, NULL
);
993 MainBoxWrite(sys
, i
, "%c%s", c
, sys
->plist
[i
]->display
);
994 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
998 return sys
->plist_entries
;
1001 static int DrawMessages(intf_thread_t
*intf
, input_thread_t
*input
)
1003 intf_sys_t
*sys
= intf
->p_sys
;
1006 vlc_mutex_lock(&sys
->msg_lock
);
1007 int i
= sys
->i_msgs
;
1009 vlc_log_t
*msg
= sys
->msgs
[i
].item
;
1012 color_set(sys
->msgs
[i
].type
+ C_INFO
, NULL
);
1013 MainBoxWrite(sys
, l
++, "[%s] %s", msg
->psz_module
, sys
->msgs
[i
].msg
);
1016 if (++i
== sizeof sys
->msgs
/ sizeof *sys
->msgs
)
1019 if (i
== sys
->i_msgs
) /* did we loop around the ring buffer ? */
1023 vlc_mutex_unlock(&sys
->msg_lock
);
1025 color_set(C_DEFAULT
, NULL
);
1031 static int DrawStatus(intf_thread_t
*intf
, input_thread_t
*p_input
)
1033 intf_sys_t
*sys
= intf
->p_sys
;
1034 playlist_t
*p_playlist
= pl_Get(intf
);
1035 char *name
= _("VLC media player");
1036 const size_t name_len
= strlen(name
) + sizeof(PACKAGE_VERSION
);
1038 const char *repeat
, *loop
, *random
;
1042 int padding
= COLS
- name_len
; /* center title */
1047 if (sys
->color
) color_set(C_TITLE
, NULL
);
1048 DrawEmptyLine(y
, 0, COLS
);
1049 mvnprintw(y
++, padding
/ 2, COLS
, "%s %s", name
, PACKAGE_VERSION
);
1050 if (sys
->color
) color_set(C_STATUS
, NULL
);
1053 y
++; /* leave a blank line */
1055 repeat
= var_GetBool(p_playlist
, "repeat") ? _("[Repeat] ") : "";
1056 random
= var_GetBool(p_playlist
, "random") ? _("[Random] ") : "";
1057 loop
= var_GetBool(p_playlist
, "loop") ? _("[Loop]") : "";
1063 uri
= input_item_GetURI(input_GetItem(p_input
));
1064 path
= vlc_uri2path(uri
);
1066 mvnprintw(y
++, 0, COLS
, _(" Source : %s"), path
?path
:uri
);
1070 var_Get(p_input
, "state", &val
);
1073 static const char *input_state
[] = {
1074 [PLAYING_S
] = " State : Playing %s%s%s",
1075 [OPENING_S
] = " State : Opening/Connecting %s%s%s",
1076 [PAUSE_S
] = " State : Paused %s%s%s",
1078 char buf1
[MSTRTIME_MAX_SIZE
];
1079 char buf2
[MSTRTIME_MAX_SIZE
];
1090 mvnprintw(y
++, 0, COLS
, _(input_state
[val
.i_int
]),
1091 repeat
, random
, loop
);
1094 val
.i_int
= var_GetInteger(p_input
, "time");
1095 secstotimestr(buf1
, val
.i_int
/ CLOCK_FREQ
);
1096 val
.i_int
= var_GetInteger(p_input
, "length");
1097 secstotimestr(buf2
, val
.i_int
/ CLOCK_FREQ
);
1099 mvnprintw(y
++, 0, COLS
, _(" Position : %s/%s"), buf1
, buf2
);
1101 volume
= playlist_VolumeGet(p_playlist
);
1102 int mute
= playlist_MuteGet(p_playlist
);
1103 mvnprintw(y
++, 0, COLS
,
1104 mute
? _(" Volume : Mute") :
1105 volume
>= 0.f
? _(" Volume : %3ld%%") : _(" Volume : ----"),
1106 lroundf(volume
* 100.f
));
1108 if (!var_Get(p_input
, "title", &val
)) {
1109 int i_title_count
= var_CountChoices(p_input
, "title");
1110 if (i_title_count
> 0)
1111 mvnprintw(y
++, 0, COLS
, _(" Title : %"PRId64
"/%d"),
1112 val
.i_int
, i_title_count
);
1115 if (!var_Get(p_input
, "chapter", &val
)) {
1116 int i_chapter_count
= var_CountChoices(p_input
, "chapter");
1117 if (i_chapter_count
> 0) mvnprintw(y
++, 0, COLS
, _(" Chapter : %"PRId64
"/%d"),
1118 val
.i_int
, i_chapter_count
);
1122 mvnprintw(y
++, 0, COLS
, _(" Source: <no current item>"));
1123 mvnprintw(y
++, 0, COLS
, " %s%s%s", repeat
, random
, loop
);
1124 mvnprintw(y
++, 0, COLS
, _(" [ h for help ]"));
1125 DrawEmptyLine(y
++, 0, COLS
);
1128 if (sys
->color
) color_set(C_DEFAULT
, NULL
);
1129 DrawBox(y
++, 1, sys
->color
, ""); /* position slider */
1130 DrawEmptyLine(y
, 1, COLS
-2);
1132 DrawLine(y
, 1, (int)((COLS
-2) * var_GetFloat(p_input
, "position")));
1134 y
+= 2; /* skip slider and box */
1139 static void FillTextBox(intf_sys_t
*sys
)
1141 int width
= COLS
- 2;
1143 DrawEmptyLine(7, 1, width
);
1144 if (sys
->box_type
== BOX_OPEN
)
1145 mvnprintw(7, 1, width
, _("Open: %s"), sys
->open_chain
);
1147 mvnprintw(7, 1, width
, _("Find: %s"), sys
->search_chain
);
1150 static void FillBox(intf_thread_t
*intf
, input_thread_t
*input
)
1152 intf_sys_t
*sys
= intf
->p_sys
;
1153 static int (* const draw
[]) (intf_thread_t
*, input_thread_t
*) = {
1154 [BOX_HELP
] = DrawHelp
,
1155 [BOX_INFO
] = DrawInfo
,
1156 [BOX_META
] = DrawMeta
,
1157 [BOX_OBJECTS
] = DrawObjects
,
1158 [BOX_STATS
] = DrawStats
,
1159 [BOX_BROWSE
] = DrawBrowse
,
1160 [BOX_PLAYLIST
] = DrawPlaylist
,
1161 [BOX_SEARCH
] = DrawPlaylist
,
1162 [BOX_OPEN
] = DrawPlaylist
,
1163 [BOX_LOG
] = DrawMessages
,
1166 sys
->box_lines_total
= draw
[sys
->box_type
](intf
, input
);
1168 if (sys
->box_type
== BOX_SEARCH
|| sys
->box_type
== BOX_OPEN
)
1172 static void Redraw(intf_thread_t
*intf
, input_thread_t
*input
)
1174 intf_sys_t
*sys
= intf
->p_sys
;
1175 int box
= sys
->box_type
;
1176 int y
= DrawStatus(intf
, input
);
1178 sys
->box_height
= LINES
- y
- 2;
1179 DrawBox(y
++, sys
->box_height
, sys
->color
, _(box_title
[box
]));
1183 if (box
!= BOX_NONE
) {
1184 FillBox(intf
, input
);
1186 if (sys
->box_lines_total
== 0)
1188 else if (sys
->box_start
> sys
->box_lines_total
- 1)
1189 sys
->box_start
= sys
->box_lines_total
- 1;
1190 y
+= __MIN(sys
->box_lines_total
- sys
->box_start
,
1194 while (y
< LINES
- 1)
1195 DrawEmptyLine(y
++, 1, COLS
- 2);
1200 static void ChangePosition(input_thread_t
*p_input
, float increment
)
1204 if (!p_input
|| var_GetInteger(p_input
, "state") != PLAYING_S
)
1207 pos
= var_GetFloat(p_input
, "position") + increment
;
1209 if (pos
> 0.99) pos
= 0.99;
1210 if (pos
< 0.0) pos
= 0.0;
1212 var_SetFloat(p_input
, "position", pos
);
1215 static inline void RemoveLastUTF8Entity(char *psz
, int len
)
1217 while (len
&& ((psz
[--len
] & 0xc0) == 0x80)) /* UTF8 continuation byte */
1222 static char *GetDiscDevice(intf_thread_t
*intf
, const char *name
)
1224 static const struct { const char *s
; size_t n
; const char *v
; } devs
[] =
1226 { "cdda://", 7, "cd-audio", },
1227 { "dvd://", 6, "dvd", },
1228 { "vcd://", 6, "vcd", },
1232 for (unsigned i
= 0; i
< sizeof devs
/ sizeof *devs
; i
++) {
1233 size_t n
= devs
[i
].n
;
1234 if (!strncmp(name
, devs
[i
].s
, n
)) {
1235 if (name
[n
] == '@' || name
[n
] == '\0')
1236 return config_GetPsz(intf
, devs
[i
].v
);
1237 /* Omit the beginning MRL-selector characters */
1238 return strdup(name
+ n
);
1242 device
= strdup(name
);
1244 if (device
) /* Remove what we have after @ */
1245 device
[strcspn(device
, "@")] = '\0';
1250 static void Eject(intf_thread_t
*intf
, input_thread_t
*p_input
)
1252 char *device
, *name
;
1253 playlist_t
* p_playlist
= pl_Get(intf
);
1255 /* If there's a stream playing, we aren't allowed to eject ! */
1261 if (!playlist_CurrentPlayingItem(p_playlist
)) {
1266 name
= playlist_CurrentPlayingItem(p_playlist
)->p_input
->psz_name
;
1267 device
= name
? GetDiscDevice(intf
, name
) : NULL
;
1272 intf_Eject(intf
, device
);
1277 static void PlayPause(intf_thread_t
*intf
, input_thread_t
*p_input
)
1280 int64_t state
= var_GetInteger( p_input
, "state" );
1281 state
= (state
!= PLAYING_S
) ? PLAYING_S
: PAUSE_S
;
1282 var_SetInteger( p_input
, "state", state
);
1284 playlist_Play(pl_Get(intf
));
1287 static void AddItem(intf_thread_t
*intf
, const char *path
)
1289 char *uri
= vlc_path2uri(path
, NULL
);
1293 input_item_t
*item
= input_item_New(uri
, NULL
);
1295 if (unlikely(item
== NULL
))
1298 playlist_t
*playlist
= pl_Get(intf
);
1299 playlist_item_t
*node
;
1301 playlist_Lock(playlist
);
1302 node
= playlist_CurrentPlayingItem(playlist
);
1304 while (node
!= NULL
) {
1305 if (node
== playlist
->p_playing
|| node
== playlist
->p_media_library
)
1307 node
= node
->p_parent
;
1311 node
= playlist
->p_playing
;
1313 playlist_NodeAddInput(playlist
, item
, node
, PLAYLIST_END
);
1314 playlist_Unlock(playlist
);
1316 input_item_Release(item
);
1319 static inline void BoxSwitch(intf_sys_t
*sys
, int box
)
1321 sys
->box_type
= (sys
->box_type
== box
) ? BOX_NONE
: box
;
1326 static bool HandlePlaylistKey(intf_thread_t
*intf
, int key
)
1328 intf_sys_t
*sys
= intf
->p_sys
;
1329 playlist_t
*p_playlist
= pl_Get(intf
);
1333 /* Playlist Settings */
1334 case 'r': var_ToggleBool(p_playlist
, "random"); return true;
1335 case 'l': var_ToggleBool(p_playlist
, "loop"); return true;
1336 case 'R': var_ToggleBool(p_playlist
, "repeat"); return true;
1341 playlist_Lock(p_playlist
);
1342 playlist_RecursiveNodeSort(p_playlist
, &p_playlist
->root
,
1343 SORT_TITLE_NODES_FIRST
,
1344 (key
== 'o')? ORDER_NORMAL
: ORDER_REVERSE
);
1345 sys
->need_update
= true;
1346 playlist_Unlock(p_playlist
);
1350 SearchPlaylist(sys
);
1354 FindIndex(sys
, p_playlist
);
1363 input_item_t
*input
= sys
->plist
[sys
->box_idx
]->item
;
1364 playlist_item_t
*item
;
1367 item
= playlist_ItemGetByInput(p_playlist
, input
);
1368 playlist_NodeDelete(p_playlist
, item
);
1370 if (sys
->box_idx
>= sys
->box_lines_total
- 1)
1371 sys
->box_idx
= sys
->box_lines_total
- 2;
1372 sys
->need_update
= true;
1381 struct pl_item_t
*p_pl_item
= sys
->plist
[sys
->box_idx
];
1382 if (p_pl_item
== NULL
)
1385 playlist_item_t
*item
;
1387 playlist_Lock(p_playlist
);
1388 item
= playlist_ItemGetByInput(p_playlist
, p_pl_item
->item
);
1390 if (item
->i_children
) {
1391 playlist_item_t
*parent
= item
;
1393 if (item
->i_children
== -1) {
1394 while (parent
->p_parent
!= NULL
)
1395 parent
= parent
->p_parent
;
1397 if (sys
->node
!= NULL
)
1398 input_item_Release(sys
->node
);
1399 sys
->node
= parent
->p_input
? input_item_Hold(parent
->p_input
)
1404 playlist_ViewPlay(p_playlist
, parent
, item
);
1405 } else { /* We only want to set the current node */
1406 playlist_Control(p_playlist
, PLAYLIST_STOP
, true);
1407 if (sys
->node
!= NULL
)
1408 input_item_Release(sys
->node
);
1409 sys
->node
= p_pl_item
->item
? input_item_Hold(p_pl_item
->item
)
1412 playlist_Unlock(p_playlist
);
1414 sys
->plidx_follow
= true;
1422 static bool HandleBrowseKey(intf_thread_t
*intf
, int key
)
1424 intf_sys_t
*sys
= intf
->p_sys
;
1425 struct dir_entry_t
*dir_entry
;
1430 sys
->show_hidden_files
= !sys
->show_hidden_files
;
1438 dir_entry
= sys
->dir_entries
[sys
->box_idx
];
1440 if (asprintf(&path
, "%s" DIR_SEP
"%s", sys
->current_dir
,
1441 dir_entry
->path
) == -1)
1444 if (!dir_entry
->file
&& key
!= ' ') {
1445 free(sys
->current_dir
);
1446 sys
->current_dir
= path
;
1454 AddItem(intf
, path
);
1456 BoxSwitch(sys
, BOX_PLAYLIST
);
1463 static void OpenSelection(intf_thread_t
*intf
)
1465 intf_sys_t
*sys
= intf
->p_sys
;
1467 AddItem(intf
, sys
->open_chain
);
1468 sys
->plidx_follow
= true;
1471 static void HandleEditBoxKey(intf_thread_t
*intf
, int key
, int box
)
1473 intf_sys_t
*sys
= intf
->p_sys
;
1474 bool search
= box
== BOX_SEARCH
;
1475 char *str
= search
? sys
->search_chain
: sys
->open_chain
;
1476 size_t len
= strlen(str
);
1478 assert(box
== BOX_SEARCH
|| box
== BOX_OPEN
);
1483 case KEY_CLEAR
: clear(); return;
1489 SearchPlaylist(sys
);
1491 OpenSelection(intf
);
1493 sys
->box_type
= BOX_PLAYLIST
;
1496 case 0x1b: /* ESC */
1497 /* Alt+key combinations return 2 keys in the terminal keyboard:
1498 * ESC, and the 2nd key.
1499 * If some other key is available immediately (where immediately
1500 * means after getch() 1 second delay), that means that the
1501 * ESC key was not pressed.
1503 * man 3X curs_getch says:
1505 * Use of the escape key by a programmer for a single
1506 * character function is discouraged, as it will cause a delay
1507 * of up to one second while the keypad code looks for a
1508 * following function-key sequence.
1512 sys
->box_type
= BOX_PLAYLIST
;
1517 RemoveLastUTF8Entity(str
, len
);
1521 if (len
+ 1 < (search
? sizeof sys
->search_chain
1522 : sizeof sys
->open_chain
)) {
1524 str
[len
+ 1] = '\0';
1529 SearchPlaylist(sys
);
1532 static void InputNavigate(input_thread_t
* p_input
, const char *var
)
1535 var_TriggerCallback(p_input
, var
);
1538 static void CycleESTrack(input_thread_t
*input
, const char *var
)
1544 if (var_Change(input
, var
, VLC_VAR_GETCHOICES
, &val
, NULL
) < 0)
1547 vlc_list_t
*list
= val
.p_list
;
1548 int64_t current
= var_GetInteger(input
, var
);
1551 for (i
= 0; i
< list
->i_count
; i
++)
1552 if (list
->p_values
[i
].i_int
== current
)
1555 if (++i
>= list
->i_count
)
1557 var_SetInteger(input
, var
, list
->p_values
[i
].i_int
);
1560 static void HandleCommonKey(intf_thread_t
*intf
, input_thread_t
*input
,
1563 intf_sys_t
*sys
= intf
->p_sys
;
1564 playlist_t
*p_playlist
= pl_Get(intf
);
1567 case 0x1b: /* ESC */
1574 libvlc_Quit(intf
->obj
.libvlc
);
1578 case 'H': BoxSwitch(sys
, BOX_HELP
); return;
1579 case 'i': BoxSwitch(sys
, BOX_INFO
); return;
1580 case 'M': BoxSwitch(sys
, BOX_META
); return;
1581 case 'L': BoxSwitch(sys
, BOX_LOG
); return;
1582 case 'P': BoxSwitch(sys
, BOX_PLAYLIST
); return;
1583 case 'B': BoxSwitch(sys
, BOX_BROWSE
); return;
1584 case 'x': BoxSwitch(sys
, BOX_OBJECTS
); return;
1585 case 'S': BoxSwitch(sys
, BOX_STATS
); return;
1587 case '/': /* Search */
1588 sys
->plidx_follow
= false;
1589 BoxSwitch(sys
, BOX_SEARCH
);
1592 case 'A': /* Open */
1593 sys
->open_chain
[0] = '\0';
1594 BoxSwitch(sys
, BOX_OPEN
);
1598 case KEY_RIGHT
: ChangePosition(input
, +0.01); return;
1599 case KEY_LEFT
: ChangePosition(input
, -0.01); return;
1601 /* Common control */
1604 vout_thread_t
*p_vout
= input_GetVout(input
);
1606 bool fs
= var_ToggleBool(p_playlist
, "fullscreen");
1607 var_SetBool(p_vout
, "fullscreen", fs
);
1608 vlc_object_release(p_vout
);
1613 case ' ': PlayPause(intf
, input
); return;
1614 case 's': playlist_Stop(p_playlist
); return;
1615 case 'e': Eject(intf
, input
); return;
1617 case '[': InputNavigate(input
, "prev-title"); return;
1618 case ']': InputNavigate(input
, "next-title"); return;
1619 case '<': InputNavigate(input
, "prev-chapter"); return;
1620 case '>': InputNavigate(input
, "next-chapter"); return;
1622 case 'p': playlist_Prev(p_playlist
); break;
1623 case 'n': playlist_Next(p_playlist
); break;
1624 case 'a': playlist_VolumeUp(p_playlist
, 1, NULL
); break;
1625 case 'z': playlist_VolumeDown(p_playlist
, 1, NULL
); break;
1626 case 'm': playlist_MuteToggle(p_playlist
); break;
1628 case 'c': CycleESTrack(input
, "audio-es"); break;
1629 case 'v': CycleESTrack(input
, "spu-es"); break;
1630 case 'b': CycleESTrack(input
, "video-es"); break;
1644 static bool HandleListKey(intf_thread_t
*intf
, int key
)
1646 intf_sys_t
*sys
= intf
->p_sys
;
1647 playlist_t
*p_playlist
= pl_Get(intf
);
1652 /* workaround for FreeBSD + xterm:
1653 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1656 case KEY_END
: sys
->box_idx
= sys
->box_lines_total
- 1; break;
1657 case KEY_HOME
: sys
->box_idx
= 0; break;
1658 case KEY_UP
: sys
->box_idx
--; break;
1659 case KEY_DOWN
: sys
->box_idx
++; break;
1660 case KEY_PPAGE
:sys
->box_idx
-= sys
->box_height
; break;
1661 case KEY_NPAGE
:sys
->box_idx
+= sys
->box_height
; break;
1668 if (sys
->box_type
== BOX_PLAYLIST
) {
1670 sys
->plidx_follow
= IsIndex(sys
, p_playlist
, sys
->box_idx
);
1677 static void HandleKey(intf_thread_t
*intf
, input_thread_t
*input
)
1679 intf_sys_t
*sys
= intf
->p_sys
;
1681 int box
= sys
->box_type
;
1686 if (box
== BOX_SEARCH
|| box
== BOX_OPEN
) {
1687 HandleEditBoxKey(intf
, key
, sys
->box_type
);
1691 if (box
== BOX_NONE
)
1697 case KEY_END
: ChangePosition(input
, +.99); return;
1698 case KEY_HOME
: ChangePosition(input
, -1.0); return;
1699 case KEY_UP
: ChangePosition(input
, +0.05); return;
1700 case KEY_DOWN
: ChangePosition(input
, -0.05); return;
1701 default: HandleCommonKey(intf
, input
, key
); return;
1704 if (box
== BOX_BROWSE
&& HandleBrowseKey(intf
, key
))
1707 if (box
== BOX_PLAYLIST
&& HandlePlaylistKey(intf
, key
))
1710 if (HandleListKey(intf
, key
))
1713 HandleCommonKey(intf
, input
, key
);
1719 static vlc_log_t
*msg_Copy (const vlc_log_t
*msg
)
1721 vlc_log_t
*copy
= (vlc_log_t
*)xmalloc (sizeof (*copy
));
1722 copy
->i_object_id
= msg
->i_object_id
;
1723 copy
->psz_object_type
= msg
->psz_object_type
;
1724 copy
->psz_module
= strdup (msg
->psz_module
);
1725 copy
->psz_header
= msg
->psz_header
? strdup (msg
->psz_header
) : NULL
;
1729 static void msg_Free (vlc_log_t
*msg
)
1731 free ((char *)msg
->psz_module
);
1732 free ((char *)msg
->psz_header
);
1736 static void MsgCallback(void *data
, int type
, const vlc_log_t
*msg
,
1737 const char *format
, va_list ap
)
1739 intf_sys_t
*sys
= data
;
1742 if (sys
->verbosity
< 0
1743 || sys
->verbosity
< (type
- VLC_MSG_ERR
)
1744 || vasprintf(&text
, format
, ap
) == -1)
1747 vlc_mutex_lock(&sys
->msg_lock
);
1749 sys
->msgs
[sys
->i_msgs
].type
= type
;
1750 if (sys
->msgs
[sys
->i_msgs
].item
!= NULL
)
1751 msg_Free(sys
->msgs
[sys
->i_msgs
].item
);
1752 sys
->msgs
[sys
->i_msgs
].item
= msg_Copy(msg
);
1753 free(sys
->msgs
[sys
->i_msgs
].msg
);
1754 sys
->msgs
[sys
->i_msgs
].msg
= text
;
1756 if (++sys
->i_msgs
== (sizeof sys
->msgs
/ sizeof *sys
->msgs
))
1759 vlc_mutex_unlock(&sys
->msg_lock
);
1762 /*****************************************************************************
1763 * Run: ncurses thread
1764 *****************************************************************************/
1765 static void *Run(void *data
)
1767 intf_thread_t
*intf
= data
;
1768 playlist_t
*p_playlist
= pl_Get(intf
);
1773 int canc
= vlc_savecancel();
1774 input_thread_t
*input
= playlist_CurrentInput(p_playlist
);
1776 Redraw(intf
, input
);
1777 HandleKey(intf
, input
);
1779 vlc_object_release(input
);
1780 vlc_restorecancel(canc
);
1782 vlc_assert_unreachable();
1785 /*****************************************************************************
1786 * Open: initialize and create window
1787 *****************************************************************************/
1788 static int Open(vlc_object_t
*p_this
)
1790 intf_thread_t
*intf
= (intf_thread_t
*)p_this
;
1791 intf_sys_t
*sys
= intf
->p_sys
= calloc(1, sizeof(intf_sys_t
));
1792 playlist_t
*p_playlist
= pl_Get(intf
);
1797 vlc_mutex_init(&sys
->msg_lock
);
1799 sys
->verbosity
= var_InheritInteger(intf
, "verbose");
1800 vlc_LogSet(intf
->obj
.libvlc
, MsgCallback
, sys
);
1802 sys
->box_type
= BOX_PLAYLIST
;
1803 sys
->plidx_follow
= true;
1804 sys
->color
= var_CreateGetBool(intf
, "color");
1806 sys
->current_dir
= var_CreateGetNonEmptyString(intf
, "browse-dir");
1807 if (!sys
->current_dir
)
1808 sys
->current_dir
= config_GetUserDir(VLC_HOME_DIR
);
1810 initscr(); /* Initialize the curses library */
1813 start_color_and_pairs(intf
);
1815 keypad(stdscr
, TRUE
);
1816 nonl(); /* Don't do NL -> CR/NL */
1817 cbreak(); /* Take input chars one at a time */
1818 noecho(); /* Don't echo */
1819 curs_set(0); /* Invisible cursor */
1820 timeout(1000); /* blocking getch() */
1823 /* Stop printing errors to the console */
1824 if (!freopen("/dev/null", "wb", stderr
))
1825 msg_Err(intf
, "Couldn't close stderr (%s)", vlc_strerror_c(errno
));
1829 PlaylistRebuild(intf
),
1832 var_AddCallback(p_playlist
, "item-change", ItemChanged
, sys
);
1833 var_AddCallback(p_playlist
, "playlist-item-append", PlaylistChanged
, sys
);
1835 if (vlc_clone(&sys
->thread
, Run
, intf
, VLC_THREAD_PRIORITY_LOW
))
1841 /*****************************************************************************
1842 * Close: destroy interface window
1843 *****************************************************************************/
1844 static void Close(vlc_object_t
*p_this
)
1846 intf_thread_t
*intf
= (intf_thread_t
*)p_this
;
1847 intf_sys_t
*sys
= intf
->p_sys
;
1848 playlist_t
*playlist
= pl_Get(intf
);
1850 vlc_cancel(sys
->thread
);
1851 vlc_join(sys
->thread
, NULL
);
1853 var_DelCallback(playlist
, "playlist-item-append", PlaylistChanged
, sys
);
1854 var_DelCallback(playlist
, "item-change", ItemChanged
, sys
);
1856 PlaylistDestroy(sys
);
1859 free(sys
->current_dir
);
1861 if (can_change_color())
1862 /* Restore yellow to its original color */
1863 init_color(COLOR_YELLOW
, sys
->yellow_r
, sys
->yellow_g
, sys
->yellow_b
);
1865 endwin(); /* Close the ncurses interface */
1867 vlc_LogSet(p_this
->obj
.libvlc
, NULL
, NULL
);
1868 vlc_mutex_destroy(&sys
->msg_lock
);
1869 for(unsigned i
= 0; i
< sizeof sys
->msgs
/ sizeof *sys
->msgs
; i
++) {
1870 if (sys
->msgs
[i
].item
)
1871 msg_Free(sys
->msgs
[i
].item
);
1872 free(sys
->msgs
[i
].msg
);