1 /*****************************************************************************
2 * ncurses.c : NCurses interface for vlc
3 *****************************************************************************
4 * Copyright © 2001-2010 the VideoLAN team
7 * Authors: Sam Hocevar <sam@zoy.org>
8 * Laurent Aimar <fenrir@via.ecp.fr>
9 * Yoann Peronneau <yoann@videolan.org>
10 * Derk-Jan Hartman <hartman at videolan dot org>
11 * Rafaël Carré <funman@videolanorg>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 *****************************************************************************/
28 /* UTF8 locale is required */
30 /*****************************************************************************
32 *****************************************************************************/
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
40 #define _XOPEN_SOURCE_EXTENDED 1
45 #include <vlc_interface.h>
48 #include <vlc_charset.h>
49 #include <vlc_input.h>
51 #include <vlc_playlist.h>
58 #ifdef HAVE_SYS_STAT_H
59 # include <sys/stat.h>
62 /*****************************************************************************
64 *****************************************************************************/
65 static int Open (vlc_object_t
*);
66 static void Close (vlc_object_t
*);
68 /*****************************************************************************
70 *****************************************************************************/
72 #define BROWSE_TEXT N_("Filebrowser starting point")
73 #define BROWSE_LONGTEXT N_(\
74 "This option allows you to specify the directory the ncurses filebrowser " \
75 "will show you initially.")
78 set_shortname("Ncurses")
79 set_description(N_("Ncurses interface"))
80 set_capability("interface", 10)
81 set_category(CAT_INTERFACE
)
82 set_subcategory(SUBCAT_INTERFACE_MAIN
)
83 set_callbacks(Open
, Close
)
84 add_shortcut("curses")
85 add_directory("browse-dir", NULL
, BROWSE_TEXT
, BROWSE_LONGTEXT
, false)
88 /*****************************************************************************
89 * intf_sys_t: description and status of ncurses interface
90 *****************************************************************************/
106 static const char *box_title
[] = {
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
},
176 playlist_item_t
*p_item
;
182 input_thread_t
*p_input
;
188 int i_box_y
; // start of box content
190 int i_box_lines_total
; // number of lines in the box
191 int i_box_start
; // first line of box displayed
192 int i_box_idx
; // selected line
194 msg_subscription_t
*p_sub
; // message bank subscription
195 msg_item_t
*msgs
[50]; // ring buffer
197 vlc_mutex_t msg_lock
;
199 /* Search Box context */
200 char psz_search_chain
[20];
201 char *psz_old_search
;
204 /* Open Box Context */
205 char psz_open_chain
[50];
207 /* File Browser context */
208 char *psz_current_dir
;
210 struct dir_entry_t
**pp_dir_entries
;
211 bool b_show_hidden_files
;
213 /* Playlist context */
214 struct pl_item_t
**pp_plist
;
218 playlist_item_t
*p_node
; /* current node */
227 /*****************************************************************************
229 *****************************************************************************/
231 static void DirsDestroy(intf_sys_t
*p_sys
)
233 while (p_sys
->i_dir_entries
)
235 struct dir_entry_t
*p_dir_entry
;
236 p_dir_entry
= p_sys
->pp_dir_entries
[--p_sys
->i_dir_entries
];
237 free(p_dir_entry
->psz_path
);
240 free(p_sys
->pp_dir_entries
);
241 p_sys
->pp_dir_entries
= NULL
;
244 static int comp_dir_entries(const void *pp_dir_entry1
, const void *pp_dir_entry2
)
246 struct dir_entry_t
*p_dir_entry1
= *(struct dir_entry_t
**)pp_dir_entry1
;
247 struct dir_entry_t
*p_dir_entry2
= *(struct dir_entry_t
**)pp_dir_entry2
;
249 if (p_dir_entry1
->b_file
== p_dir_entry2
->b_file
)
250 return strcasecmp(p_dir_entry1
->psz_path
, p_dir_entry2
->psz_path
);
252 return p_dir_entry1
->b_file
? 1 : -1;
255 static bool IsFile(const char *current_dir
, const char *entry
)
262 if (asprintf(&uri
, "%s" DIR_SEP
"%s", current_dir
, entry
) != -1)
264 ret
= vlc_stat(uri
, &st
) || !S_ISDIR(st
.st_mode
);
271 static void ReadDir(intf_thread_t
*p_intf
)
273 intf_sys_t
*p_sys
= p_intf
->p_sys
;
276 if (!p_sys
->psz_current_dir
|| !*p_sys
->psz_current_dir
)
278 msg_Dbg(p_intf
, "no current dir set");
285 p_current_dir
= vlc_opendir(p_sys
->psz_current_dir
);
289 /* something went bad, get out of here ! */
290 msg_Warn(p_intf
, "cannot open directory `%s' (%m)",
291 p_sys
->psz_current_dir
);
295 /* Clean the old shit */
298 /* while we still have entries in the directory */
299 while ((psz_entry
= vlc_readdir(p_current_dir
)))
301 struct dir_entry_t
*p_dir_entry
;
303 if (!p_sys
->b_show_hidden_files
)
304 if (*psz_entry
== '.' && strcmp(psz_entry
, ".."))
307 if (!(p_dir_entry
= malloc(sizeof *p_dir_entry
)))
310 p_dir_entry
->b_file
= IsFile(p_sys
->psz_current_dir
, psz_entry
);
311 p_dir_entry
->psz_path
= psz_entry
;
312 INSERT_ELEM(p_sys
->pp_dir_entries
, p_sys
->i_dir_entries
,
313 p_sys
->i_dir_entries
, p_dir_entry
);
321 qsort(p_sys
->pp_dir_entries
, p_sys
->i_dir_entries
,
322 sizeof(struct dir_entry_t
*), &comp_dir_entries
);
324 closedir(p_current_dir
);
327 /*****************************************************************************
328 * Adjust index position after a change (list navigation or item switching)
329 *****************************************************************************/
330 static void CheckIdx(intf_sys_t
*p_sys
)
332 int lines
= p_sys
->i_box_lines_total
;
333 int height
= LINES
- p_sys
->i_box_y
- 2;
334 if (height
> lines
- 1)
337 /* make sure the new index is within the box */
338 if (p_sys
->i_box_idx
<= 0)
340 p_sys
->i_box_idx
= 0;
341 p_sys
->i_box_start
= 0;
343 else if (p_sys
->i_box_idx
>= lines
- 1 && lines
> 0)
345 p_sys
->i_box_idx
= lines
- 1;
346 p_sys
->i_box_start
= p_sys
->i_box_idx
- height
;
349 /* Fix box start (1st line of the box displayed) */
350 if (p_sys
->i_box_idx
< p_sys
->i_box_start
||
351 p_sys
->i_box_idx
> height
+ p_sys
->i_box_start
+ 1)
353 p_sys
->i_box_start
= p_sys
->i_box_idx
- height
/2;
354 if (p_sys
->i_box_start
< 0)
355 p_sys
->i_box_start
= 0;
357 else if (p_sys
->i_box_idx
== p_sys
->i_box_start
- 1)
358 p_sys
->i_box_start
--;
359 else if (p_sys
->i_box_idx
== height
+ p_sys
->i_box_start
+ 1)
360 p_sys
->i_box_start
++;
363 /*****************************************************************************
365 *****************************************************************************/
366 static void PlaylistDestroy(intf_sys_t
*p_sys
)
368 while (p_sys
->i_plist_entries
)
370 struct pl_item_t
*p_pl_item
= p_sys
->pp_plist
[--p_sys
->i_plist_entries
];
371 free(p_pl_item
->psz_display
);
374 free(p_sys
->pp_plist
);
375 p_sys
->pp_plist
= NULL
;
378 static bool PlaylistAddChild(intf_sys_t
*p_sys
, playlist_item_t
*p_child
,
379 const char *c
, const char d
)
382 char *psz_name
= input_item_GetTitleFbName(p_child
->p_input
);
383 struct pl_item_t
*p_pl_item
= malloc(sizeof *p_pl_item
);
385 if (!psz_name
|| !p_pl_item
)
388 p_pl_item
->p_item
= p_child
;
391 ret
= asprintf(&p_pl_item
->psz_display
, "%s%c-%s", c
, d
, psz_name
);
393 ret
= asprintf(&p_pl_item
->psz_display
, " %s", psz_name
);
401 INSERT_ELEM(p_sys
->pp_plist
, p_sys
->i_plist_entries
,
402 p_sys
->i_plist_entries
, p_pl_item
);
412 static void PlaylistAddNode(intf_sys_t
*p_sys
, playlist_item_t
*p_node
,
415 for(int k
= 0; k
< p_node
->i_children
; k
++)
417 playlist_item_t
*p_child
= p_node
->pp_children
[k
];
418 char d
= k
== p_node
->i_children
- 1 ? '`' : '|';
419 if(!PlaylistAddChild(p_sys
, p_child
, c
, d
))
422 if (p_child
->i_children
<= 0)
428 if (asprintf(&psz_tmp
, "%s%c ", c
,
429 k
== p_node
->i_children
- 1 ? ' ' : '|') == -1)
431 PlaylistAddNode(p_sys
, p_child
, psz_tmp
);
435 PlaylistAddNode(p_sys
, p_child
, " ");
439 static void PlaylistRebuild(intf_thread_t
*p_intf
)
441 intf_sys_t
*p_sys
= p_intf
->p_sys
;
442 playlist_t
*p_playlist
= pl_Get(p_intf
);
445 PlaylistDestroy(p_sys
);
446 PlaylistAddNode(p_sys
, p_playlist
->p_root_onelevel
, "");
450 static int PlaylistChanged(vlc_object_t
*p_this
, const char *psz_variable
,
451 vlc_value_t oval
, vlc_value_t nval
, void *param
)
453 VLC_UNUSED(p_this
); VLC_UNUSED(psz_variable
);
454 VLC_UNUSED(oval
); VLC_UNUSED(nval
);
456 intf_thread_t
*p_intf
= (intf_thread_t
*)param
;
457 intf_sys_t
*p_sys
= p_intf
->p_sys
;
458 playlist_item_t
*p_node
= playlist_CurrentPlayingItem(pl_Get(p_intf
));
460 p_sys
->b_need_update
= true;
461 p_sys
->p_node
= p_node
? p_node
->p_parent
: NULL
;
467 static int SubSearchPlaylist(intf_sys_t
*p_sys
, char *psz_searchstring
,
468 int i_start
, int i_stop
)
470 for(int i
= i_start
+ 1; i
< i_stop
; i
++)
471 if (strcasestr(p_sys
->pp_plist
[i
]->psz_display
, psz_searchstring
))
477 static void SearchPlaylist(intf_sys_t
*p_sys
, char *psz_searchstring
)
479 int i_item
, i_first
= p_sys
->i_before_search
;
484 if (!psz_searchstring
|| !*psz_searchstring
)
486 p_sys
->i_box_idx
= p_sys
->i_before_search
;
490 i_item
= SubSearchPlaylist(p_sys
, psz_searchstring
, i_first
+ 1,
491 p_sys
->i_plist_entries
);
493 i_item
= SubSearchPlaylist(p_sys
, psz_searchstring
, 0, i_first
);
497 p_sys
->i_box_idx
= i_item
;
502 static inline bool IsIndex(intf_sys_t
*p_sys
, playlist_t
*p_playlist
, int i
)
504 playlist_item_t
*p_item
= p_sys
->pp_plist
[i
]->p_item
;
505 playlist_item_t
*p_played_item
;
509 if (p_item
->i_children
== 0 && p_item
== p_sys
->p_node
)
512 p_played_item
= playlist_CurrentPlayingItem(p_playlist
);
513 if (p_played_item
&& p_item
->p_input
&& p_played_item
->p_input
)
514 return p_item
->p_input
->i_id
== p_played_item
->p_input
->i_id
;
519 static void FindIndex(intf_sys_t
*p_sys
, playlist_t
*p_playlist
)
521 int plidx
= p_sys
->i_box_idx
;
522 int max
= p_sys
->i_plist_entries
;
526 if (!IsIndex(p_sys
, p_playlist
, plidx
))
527 for(int i
= 0; i
< max
; i
++)
528 if (IsIndex(p_sys
, p_playlist
, i
))
530 p_sys
->i_box_idx
= i
;
537 p_sys
->b_plidx_follow
= true;
540 /****************************************************************************
542 ****************************************************************************/
544 static void start_color_and_pairs(intf_thread_t
*p_intf
)
548 p_intf
->p_sys
->b_color
= false;
549 msg_Warn(p_intf
, "Terminal doesn't support colors");
554 for(int i
= C_DEFAULT
+ 1; i
< C_MAX
; i
++)
555 init_pair(i
, color_pairs
[i
].f
, color_pairs
[i
].b
);
557 /* untested, in all my terminals, !can_change_color() --funman */
558 if (can_change_color())
559 init_color(COLOR_YELLOW
, 960, 500, 0); /* YELLOW -> ORANGE */
562 static void DrawBox(int y
, int h
, bool b_color
, const char *title
)
567 if (w
<= 3 || h
<= 0)
570 if (b_color
) color_set(C_BOX
, NULL
);
572 if (!title
) title
= "";
573 i_len
= strlen(title
);
578 mvaddch(y
, 0, ACS_ULCORNER
);
579 mvhline(y
, 1, ACS_HLINE
, (w
-i_len
-2)/2);
580 mvprintw(y
, 1+(w
-i_len
-2)/2, "%s", title
);
581 mvhline(y
, (w
-i_len
)/2+i_len
, ACS_HLINE
, w
- 1 - ((w
-i_len
)/2+i_len
));
582 mvaddch(y
, w
-1,ACS_URCORNER
);
584 for(int i
= 0; i
< h
; i
++)
586 mvaddch(++y
, 0, ACS_VLINE
);
587 mvaddch(y
, w
-1, ACS_VLINE
);
590 mvaddch(++y
, 0, ACS_LLCORNER
);
591 mvhline(y
, 1, ACS_HLINE
, w
- 2);
592 mvaddch(y
, w
-1, ACS_LRCORNER
);
593 if (b_color
) color_set(C_DEFAULT
, NULL
);
596 static void DrawEmptyLine(int y
, int x
, int w
)
600 mvhline(y
, x
, ' ', w
);
603 static void DrawLine(int y
, int x
, int w
)
608 mvhline(y
, x
, ' ', w
);
612 static void mvnprintw(int y
, int x
, int w
, const char *p_fmt
, ...)
621 va_start(vl_args
, p_fmt
);
622 if (vasprintf(&p_buf
, p_fmt
, vl_args
) == -1)
626 i_len
= strlen(p_buf
);
628 wchar_t psz_wide
[i_len
+ 1];
631 size_t i_char_len
= mbstowcs(psz_wide
, p_buf
, i_len
);
633 size_t i_width
; /* number of columns */
635 if (i_char_len
== (size_t)-1) /* an invalid character was encountered */
641 i_width
= wcswidth(psz_wide
, i_char_len
);
642 if (i_width
== (size_t)-1)
644 /* a non printable character was encountered */
646 for(unsigned i
= 0 ; i
< i_char_len
; i
++)
648 int i_cwidth
= wcwidth(psz_wide
[i
]);
654 if (i_width
<= (size_t)w
)
656 mvprintw(y
, x
, "%s", p_buf
);
657 mvhline(y
, x
+ i_width
, ' ', w
- i_width
);
662 int i_total_width
= 0;
664 while (i_total_width
< w
)
666 i_total_width
+= wcwidth(psz_wide
[i
]);
667 if (w
> 7 && i_total_width
>= w
/2)
671 i_total_width
-= wcwidth(psz_wide
[i
]) - 2;
674 /* we require this check only if at least one character
675 * 4 or more columns wide exists (which i doubt) */
677 i_total_width
-= wcwidth(psz_wide
[i
-1]) - 1;
680 /* find the widest string */
681 int j
, i_2nd_width
= 0;
682 for(j
= i_char_len
- 1; i_2nd_width
< w
- i_total_width
; j
--)
683 i_2nd_width
+= wcwidth(psz_wide
[j
]);
685 /* we already have i_total_width columns filled, and we can't
686 * have more than w columns */
687 if (i_2nd_width
> w
- i_total_width
)
690 wmemmove(&psz_wide
[i
+2], &psz_wide
[j
+1], i_char_len
- j
- 1);
691 psz_wide
[i
+ 2 + i_char_len
- j
- 1] = '\0';
696 if (w
<= 7) /* we don't add the '...' else we lose too much chars */
699 size_t i_wlen
= wcslen(psz_wide
) * 6 + 1; /* worst case */
700 char psz_ellipsized
[i_wlen
];
701 wcstombs(psz_ellipsized
, psz_wide
, i_wlen
);
702 mvprintw(y
, x
, "%s", psz_ellipsized
);
707 static void MainBoxWrite(intf_sys_t
*p_sys
, int l
, const char *p_fmt
, ...)
711 bool b_selected
= l
== p_sys
->i_box_idx
;
713 if (l
< p_sys
->i_box_start
|| l
- p_sys
->i_box_start
>= p_sys
->i_box_height
)
716 va_start(vl_args
, p_fmt
);
717 if (vasprintf(&p_buf
, p_fmt
, vl_args
) == -1)
721 if (b_selected
) attron(A_REVERSE
);
722 mvnprintw(p_sys
->i_box_y
+ l
- p_sys
->i_box_start
, 1, COLS
- 2, "%s", p_buf
);
723 if (b_selected
) attroff(A_REVERSE
);
728 static int SubDrawObject(intf_sys_t
*p_sys
, int l
, vlc_object_t
*p_obj
, int i_level
, const char *prefix
)
730 int x
= 2 * i_level
++;
731 char *psz_name
= vlc_object_get_name(p_obj
);
734 memset(space
, ' ', x
);
739 MainBoxWrite(p_sys
, l
++, "%s%s%s \"%s\" (%p)", space
, prefix
,
740 p_obj
->psz_object_type
, psz_name
, p_obj
);
744 MainBoxWrite(p_sys
, l
++, "%s%s%s (%p)", space
, prefix
,
745 p_obj
->psz_object_type
, p_obj
);
747 list
= vlc_list_children(p_obj
);
748 for(int i
= 0; i
< list
->i_count
; i
++)
750 l
= SubDrawObject(p_sys
, l
, list
->p_values
[i
].p_object
, i_level
,
751 (i
== list
->i_count
- 1) ? "`-" : "|-" );
753 vlc_list_release(list
);
757 static int DrawObjects(intf_thread_t
*p_intf
)
759 return SubDrawObject(p_intf
->p_sys
, 0, VLC_OBJECT(p_intf
->p_libvlc
), 0, "");
762 static int DrawMeta(intf_thread_t
*p_intf
)
764 intf_sys_t
*p_sys
= p_intf
->p_sys
;
765 input_thread_t
*p_input
= p_sys
->p_input
;
766 input_item_t
*p_item
;
772 p_item
= input_GetItem(p_input
);
773 vlc_mutex_lock(&p_item
->lock
);
774 for(int i
=0; i
<VLC_META_TYPE_COUNT
; i
++)
776 const char *psz_meta
= vlc_meta_Get(p_item
->p_meta
, i
);
777 if (!psz_meta
|| !*psz_meta
)
780 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
781 MainBoxWrite(p_sys
, l
++, " [%s]", vlc_meta_TypeToLocalizedString(i
));
782 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
783 MainBoxWrite(p_sys
, l
++, " %s", psz_meta
);
785 vlc_mutex_unlock(&p_item
->lock
);
790 static int DrawInfo(intf_thread_t
*p_intf
)
792 intf_sys_t
*p_sys
= p_intf
->p_sys
;
793 input_thread_t
*p_input
= p_sys
->p_input
;
794 input_item_t
*p_item
;
800 p_item
= input_GetItem(p_input
);
801 vlc_mutex_lock(&p_item
->lock
);
802 for(int i
= 0; i
< p_item
->i_categories
; i
++)
804 info_category_t
*p_category
= p_item
->pp_categories
[i
];
805 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
806 MainBoxWrite(p_sys
, l
++, _(" [%s]"), p_category
->psz_name
);
807 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
808 for(int j
= 0; j
< p_category
->i_infos
; j
++)
810 info_t
*p_info
= p_category
->pp_infos
[j
];
811 MainBoxWrite(p_sys
, l
++, _(" %s: %s"),
812 p_info
->psz_name
, p_info
->psz_value
);
815 vlc_mutex_unlock(&p_item
->lock
);
820 static int DrawStats(intf_thread_t
*p_intf
)
822 intf_sys_t
*p_sys
= p_intf
->p_sys
;
823 input_thread_t
*p_input
= p_sys
->p_input
;
824 input_item_t
*p_item
;
825 input_stats_t
*p_stats
;
826 int l
= 0, i_audio
= 0, i_video
= 0;
831 p_item
= input_GetItem(p_input
);
834 vlc_mutex_lock(&p_item
->lock
);
835 p_stats
= p_item
->p_stats
;
836 vlc_mutex_lock(&p_stats
->lock
);
838 for(int i
= 0; i
< p_item
->i_es
; i
++)
840 i_audio
+= (p_item
->es
[i
]->i_cat
== AUDIO_ES
);
841 i_video
+= (p_item
->es
[i
]->i_cat
== VIDEO_ES
);
845 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
846 MainBoxWrite(p_sys
, l
++, _(" [Incoming]"));
847 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
848 MainBoxWrite(p_sys
, l
++, _(" input bytes read : %8.0f KiB"),
849 (float)(p_stats
->i_read_bytes
)/1024);
850 MainBoxWrite(p_sys
, l
++, _(" input bitrate : %6.0f kb/s"),
851 p_stats
->f_input_bitrate
*8000);
852 MainBoxWrite(p_sys
, l
++, _(" demux bytes read : %8.0f KiB"),
853 (float)(p_stats
->i_demux_read_bytes
)/1024);
854 MainBoxWrite(p_sys
, l
++, _(" demux bitrate : %6.0f kb/s"),
855 p_stats
->f_demux_bitrate
*8000);
860 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
861 MainBoxWrite(p_sys
, l
++, _(" [Video Decoding]"));
862 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
863 MainBoxWrite(p_sys
, l
++, _(" video decoded : %"PRId64
),
864 p_stats
->i_decoded_video
);
865 MainBoxWrite(p_sys
, l
++, _(" frames displayed : %"PRId64
),
866 p_stats
->i_displayed_pictures
);
867 MainBoxWrite(p_sys
, l
++, _(" frames lost : %"PRId64
),
868 p_stats
->i_lost_pictures
);
873 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
874 MainBoxWrite(p_sys
, l
++, _(" [Audio Decoding]"));
875 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
876 MainBoxWrite(p_sys
, l
++, _(" audio decoded : %"PRId64
),
877 p_stats
->i_decoded_audio
);
878 MainBoxWrite(p_sys
, l
++, _(" buffers played : %"PRId64
),
879 p_stats
->i_played_abuffers
);
880 MainBoxWrite(p_sys
, l
++, _(" buffers lost : %"PRId64
),
881 p_stats
->i_lost_abuffers
);
884 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
885 MainBoxWrite(p_sys
, l
++, _(" [Streaming]"));
886 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
887 MainBoxWrite(p_sys
, l
++, _(" packets sent : %5i"), p_stats
->i_sent_packets
);
888 MainBoxWrite(p_sys
, l
++, _(" bytes sent : %8.0f KiB"),
889 (float)(p_stats
->i_sent_bytes
)/1025);
890 MainBoxWrite(p_sys
, l
++, _(" sending bitrate : %6.0f kb/s"),
891 p_stats
->f_send_bitrate
*8000);
892 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
894 vlc_mutex_unlock(&p_stats
->lock
);
895 vlc_mutex_unlock(&p_item
->lock
);
900 static int DrawHelp(intf_thread_t
*p_intf
)
902 intf_sys_t
*p_sys
= p_intf
->p_sys
;
905 #define H(a) MainBoxWrite(p_sys, l++, a)
907 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
909 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
910 H(_(" h,H Show/Hide help box"));
911 H(_(" i Show/Hide info box"));
912 H(_(" m Show/Hide metadata box"));
913 H(_(" L Show/Hide messages box"));
914 H(_(" P Show/Hide playlist box"));
915 H(_(" B Show/Hide filebrowser"));
916 H(_(" x Show/Hide objects box"));
917 H(_(" S Show/Hide statistics box"));
918 H(_(" Esc Close Add/Search entry"));
919 H(_(" Ctrl-l Refresh the screen"));
922 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
924 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
925 H(_(" q, Q, Esc Quit"));
927 H(_(" <space> Pause/Play"));
928 H(_(" f Toggle Fullscreen"));
929 H(_(" n, p Next/Previous playlist item"));
930 H(_(" [, ] Next/Previous title"));
931 H(_(" <, > Next/Previous chapter"));
932 /* xgettext: You can use ← and → characters */
933 H(_(" <left>,<right> Seek -/+ 1%%"));
934 H(_(" a, z Volume Up/Down"));
935 /* xgettext: You can use ↑ and ↓ characters */
936 H(_(" <up>,<down> Navigate through the box line by line"));
937 /* xgettext: You can use ⇞ and ⇟ characters */
938 H(_(" <pageup>,<pagedown> Navigate through the box page by page"));
939 /* xgettext: You can use ↖ and ↘ characters */
940 H(_(" <start>,<end> Navigate to start/end of box"));
943 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
945 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
946 H(_(" r Toggle Random playing"));
947 H(_(" l Toggle Loop Playlist"));
948 H(_(" R Toggle Repeat item"));
949 H(_(" o Order Playlist by title"));
950 H(_(" O Reverse order Playlist by title"));
951 H(_(" g Go to the current playing item"));
952 H(_(" / Look for an item"));
953 H(_(" A Add an entry"));
954 /* xgettext: You can use ⌫ character to translate <backspace> */
955 H(_(" D, <backspace>, <del> Delete an entry"));
956 H(_(" e Eject (if stopped)"));
959 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
960 H(_("[Filebrowser]"));
961 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
962 H(_(" <enter> Add the selected file to the playlist"));
963 H(_(" <space> Add the selected directory to the playlist"));
964 H(_(" . Show/Hide hidden files"));
967 if (p_sys
->b_color
) color_set(C_CATEGORY
, NULL
);
969 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
970 /* xgettext: You can use ↑ and ↓ characters */
971 H(_(" <up>,<down> Seek +/-5%%"));
977 static int DrawBrowse(intf_thread_t
*p_intf
)
979 intf_sys_t
*p_sys
= p_intf
->p_sys
;
981 for(int i
= 0; i
< p_sys
->i_dir_entries
; i
++)
983 struct dir_entry_t
*p_dir_entry
= p_sys
->pp_dir_entries
[i
];
984 char type
= p_dir_entry
->b_file
? ' ' : '+';
987 color_set(p_dir_entry
->b_file
? C_DEFAULT
: C_FOLDER
, NULL
);
988 MainBoxWrite(p_sys
, i
, " %c %s", type
, p_dir_entry
->psz_path
);
991 return p_sys
->i_dir_entries
;
994 static int DrawPlaylist(intf_thread_t
*p_intf
)
996 intf_sys_t
*p_sys
= p_intf
->p_sys
;
997 playlist_t
*p_playlist
= pl_Get(p_intf
);
999 if (p_sys
->b_need_update
)
1001 PlaylistRebuild(p_intf
);
1002 p_sys
->b_need_update
= false;
1005 if (p_sys
->b_plidx_follow
)
1006 FindIndex(p_sys
, p_playlist
);
1008 for(int i
= 0; i
< p_sys
->i_plist_entries
; i
++)
1011 playlist_item_t
*p_current_item
;
1012 playlist_item_t
*p_item
= p_sys
->pp_plist
[i
]->p_item
;
1013 playlist_item_t
*p_node
= p_sys
->p_node
;
1017 p_current_item
= playlist_CurrentPlayingItem(p_playlist
);
1018 if ((p_node
&& p_item
->p_input
== p_node
->p_input
) ||
1019 (!p_node
&& p_current_item
&& p_item
->p_input
== p_current_item
->p_input
))
1021 else if (p_item
== p_node
|| p_current_item
== p_item
)
1027 if (p_sys
->b_color
) color_set(i
%3 + C_PLAYLIST_1
, NULL
);
1028 MainBoxWrite(p_sys
, i
, "%c%s", c
, p_sys
->pp_plist
[i
]->psz_display
);
1029 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
1032 return p_sys
->i_plist_entries
;
1035 static int DrawMessages(intf_thread_t
*p_intf
)
1037 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1041 vlc_mutex_lock(&p_sys
->msg_lock
);
1045 msg_item_t
*msg
= p_sys
->msgs
[i
];
1049 color_set(msg
->i_type
+ C_INFO
, NULL
);
1050 MainBoxWrite(p_sys
, l
++, "[%s] %s", msg
->psz_module
, msg
->psz_msg
);
1053 if (++i
== sizeof p_sys
->msgs
/ sizeof *p_sys
->msgs
)
1056 if (i
== p_sys
->i_msgs
) /* did we loop around the ring buffer ? */
1060 vlc_mutex_unlock(&p_sys
->msg_lock
);
1062 color_set(C_DEFAULT
, NULL
);
1066 static int DrawStatus(intf_thread_t
*p_intf
)
1068 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1069 input_thread_t
*p_input
= p_sys
->p_input
;
1070 playlist_t
*p_playlist
= pl_Get(p_intf
);
1071 static const char name
[] = "VLC media player "PACKAGE_VERSION
;
1072 const size_t name_len
= sizeof name
- 1; /* without \0 termination */
1074 const char *repeat
, *loop
, *random
;
1078 int padding
= COLS
- name_len
; /* center title */
1083 if (p_sys
->b_color
) color_set(C_TITLE
, NULL
);
1084 DrawEmptyLine(y
, 0, COLS
);
1085 mvnprintw(y
++, padding
/ 2, COLS
, "%s", name
);
1086 if (p_sys
->b_color
) color_set(C_STATUS
, NULL
);
1089 y
++; /* leave a blank line */
1091 repeat
= var_GetBool(p_playlist
, "repeat") ? _("[Repeat] ") : "";
1092 random
= var_GetBool(p_playlist
, "random") ? _("[Random] ") : "";
1093 loop
= var_GetBool(p_playlist
, "loop") ? _("[Loop]") : "";
1095 if (p_input
&& !p_input
->b_dead
)
1098 char *psz_path
, *psz_uri
;
1100 psz_uri
= input_item_GetURI(input_GetItem(p_input
));
1101 psz_path
= make_path(psz_uri
);
1103 mvnprintw(y
++, 0, COLS
, _(" Source : %s"), psz_path
?psz_path
:psz_uri
);
1107 var_Get(p_input
, "state", &val
);
1110 static const char *input_state
[] = {
1111 [PLAYING_S
] = " State : Playing %s%s%s",
1112 [OPENING_S
] = " State : Opening/Connecting %s%s%s",
1113 [PAUSE_S
] = " State : Paused %s%s%s",
1115 char buf1
[MSTRTIME_MAX_SIZE
];
1116 char buf2
[MSTRTIME_MAX_SIZE
];
1117 audio_volume_t i_volume
;
1127 mvnprintw(y
++, 0, COLS
, _(input_state
[val
.i_int
]),
1128 repeat
, random
, loop
);
1131 var_Get(p_input
, "time", &val
);
1132 secstotimestr(buf1
, val
.i_time
/ CLOCK_FREQ
);
1133 var_Get(p_input
, "length", &val
);
1134 secstotimestr(buf2
, val
.i_time
/ CLOCK_FREQ
);
1136 mvnprintw(y
++, 0, COLS
, _(" Position : %s/%s"), buf1
, buf2
);
1138 i_volume
= aout_VolumeGet(p_playlist
);
1139 mvnprintw(y
++, 0, COLS
, _(" Volume : %i%%"),
1140 i_volume
*200/AOUT_VOLUME_MAX
);
1142 if (!var_Get(p_input
, "title", &val
))
1144 int i_title_count
= var_CountChoices(p_input
, "title");
1145 if (i_title_count
> 0)
1146 mvnprintw(y
++, 0, COLS
, _(" Title : %"PRId64
"/%d"),
1147 val
.i_int
, i_title_count
);
1150 if (!var_Get(p_input
, "chapter", &val
))
1152 int i_chapter_count
= var_CountChoices(p_input
, "chapter");
1153 if (i_chapter_count
> 0)
1154 mvnprintw(y
++, 0, COLS
, _(" Chapter : %"PRId64
"/%d"),
1155 val
.i_int
, i_chapter_count
);
1161 mvnprintw(y
++, 0, COLS
, _(" Source: <no current item> "));
1162 mvnprintw(y
++, 0, COLS
, " %s%s%s", repeat
, random
, loop
);
1163 mvnprintw(y
++, 0, COLS
, _(" [ h for help ]"));
1164 DrawEmptyLine(y
++, 0, COLS
);
1167 if (p_sys
->b_color
) color_set(C_DEFAULT
, NULL
);
1168 DrawBox(y
++, 1, p_sys
->b_color
, ""); /* position slider */
1169 DrawEmptyLine(y
, 1, COLS
-2);
1171 DrawLine(y
, 1, (int)((COLS
-2) * var_GetFloat(p_input
, "position")));
1173 y
+= 2; /* skip slider and box */
1178 static void FillTextBox(intf_sys_t
*p_sys
)
1180 int width
= COLS
- 2;
1181 const char *title
= p_sys
->i_box_type
== BOX_OPEN
? "Open: %s" : "Find: %s";
1182 char *chain
= p_sys
->i_box_type
== BOX_OPEN
? p_sys
->psz_open_chain
:
1183 p_sys
->psz_old_search
? p_sys
->psz_old_search
:
1184 p_sys
->psz_search_chain
;
1186 DrawEmptyLine(7, 1, width
);
1187 mvnprintw(7, 1, width
, _(title
), chain
);
1190 static void FillBox(intf_thread_t
*p_intf
)
1192 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1193 static int (* const draw
[]) (intf_thread_t
*) = {
1194 [BOX_HELP
] = DrawHelp
,
1195 [BOX_INFO
] = DrawInfo
,
1196 [BOX_META
] = DrawMeta
,
1197 [BOX_OBJECTS
] = DrawObjects
,
1198 [BOX_STATS
] = DrawStats
,
1199 [BOX_BROWSE
] = DrawBrowse
,
1200 [BOX_PLAYLIST
] = DrawPlaylist
,
1201 [BOX_SEARCH
] = DrawPlaylist
,
1202 [BOX_OPEN
] = DrawPlaylist
,
1203 [BOX_LOG
] = DrawMessages
,
1206 p_sys
->i_box_lines_total
= draw
[p_sys
->i_box_type
](p_intf
);
1208 if (p_sys
->i_box_type
== BOX_SEARCH
|| p_sys
->i_box_type
== BOX_OPEN
)
1212 static void Redraw(intf_thread_t
*p_intf
)
1214 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1215 int box
= p_sys
->i_box_type
;
1216 int y
= DrawStatus(p_intf
);
1218 p_sys
->i_box_height
= LINES
- y
- 2;
1219 DrawBox(y
++, p_sys
->i_box_height
, p_sys
->b_color
, _(box_title
[box
]));
1223 if (box
!= BOX_NONE
)
1227 if (p_sys
->i_box_lines_total
== 0)
1228 p_sys
->i_box_start
= 0;
1229 else if (p_sys
->i_box_start
> p_sys
->i_box_lines_total
- 1)
1230 p_sys
->i_box_start
= p_sys
->i_box_lines_total
- 1;
1231 y
+= __MIN(p_sys
->i_box_lines_total
- p_sys
->i_box_start
,
1232 p_sys
->i_box_height
);
1235 while (y
< LINES
- 1)
1236 DrawEmptyLine(y
++, 1, COLS
- 2);
1241 static void ChangePosition(intf_thread_t
*p_intf
, float increment
)
1243 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1244 input_thread_t
*p_input
= p_sys
->p_input
;
1247 if (!p_input
|| var_GetInteger(p_input
, "state") != PLAYING_S
)
1250 pos
= var_GetFloat(p_input
, "position") + increment
;
1252 if (pos
> 0.99) pos
= 0.99;
1253 if (pos
< 0.0) pos
= 0.0;
1255 var_SetFloat(p_input
, "position", pos
);
1258 static inline void RemoveLastUTF8Entity(char *psz
, int len
)
1260 while (len
&& ((psz
[--len
] & 0xc0) == 0x80)) /* UTF8 continuation byte */
1265 static char *GetDiscDevice(intf_thread_t
*p_intf
, const char *name
)
1267 static const struct { const char *s
; size_t n
; const char *v
; } devs
[] =
1269 { "cdda://", 7, "cd-audio", },
1270 { "dvd://", 6, "dvd", },
1271 { "vcd://", 6, "vcd", },
1275 for (unsigned i
= 0; i
< sizeof devs
/ sizeof *devs
; i
++)
1277 size_t n
= devs
[i
].n
;
1278 if (!strncmp(name
, devs
[i
].s
, n
))
1283 return config_GetPsz(p_intf
, devs
[i
].v
);
1285 /* Omit the beginning MRL-selector characters */
1286 return strdup(name
+ n
);
1290 device
= strdup(name
);
1292 if (device
) /* Remove what we have after @ */
1293 device
[strcspn(device
, "@")] = '\0';
1298 static void Eject(intf_thread_t
*p_intf
)
1300 char *psz_device
, *psz_name
;
1301 playlist_t
* p_playlist
= pl_Get(p_intf
);
1303 /* If there's a stream playing, we aren't allowed to eject ! */
1304 if (p_intf
->p_sys
->p_input
)
1309 if (!playlist_CurrentPlayingItem(p_playlist
))
1315 psz_name
= playlist_CurrentPlayingItem(p_playlist
)->p_input
->psz_name
;
1316 psz_device
= psz_name
? GetDiscDevice(p_intf
, psz_name
) : NULL
;
1322 intf_Eject(p_intf
, psz_device
);
1327 static void PlayPause(intf_thread_t
*p_intf
)
1329 input_thread_t
*p_input
= p_intf
->p_sys
->p_input
;
1333 int64_t state
= var_GetInteger( p_input
, "state" );
1334 state
= (state
!= PLAYING_S
) ? PLAYING_S
: PAUSE_S
;
1335 var_SetInteger( p_input
, "state", state
);
1338 playlist_Play(pl_Get(p_intf
));
1341 static inline void BoxSwitch(intf_sys_t
*p_sys
, int box
)
1343 p_sys
->i_box_type
= (p_sys
->i_box_type
== box
) ? BOX_NONE
: box
;
1344 p_sys
->i_box_start
= 0;
1345 p_sys
->i_box_idx
= 0;
1348 static bool HandlePlaylistKey(intf_thread_t
*p_intf
, int key
)
1350 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1351 playlist_t
*p_playlist
= pl_Get(p_intf
);
1352 struct pl_item_t
*p_pl_item
;
1356 /* Playlist Settings */
1357 case 'r': var_ToggleBool(p_playlist
, "random"); return true;
1358 case 'l': var_ToggleBool(p_playlist
, "loop"); return true;
1359 case 'R': var_ToggleBool(p_playlist
, "repeat"); return true;
1364 playlist_RecursiveNodeSort(p_playlist
, p_playlist
->p_root_onelevel
,
1365 SORT_TITLE_NODES_FIRST
,
1366 (key
== 'o')? ORDER_NORMAL
: ORDER_REVERSE
);
1367 p_sys
->b_need_update
= true;
1371 FindIndex(p_sys
, p_playlist
);
1380 playlist_item_t
*p_item
;
1383 p_item
= p_sys
->pp_plist
[p_sys
->i_box_idx
]->p_item
;
1384 if (p_item
->i_children
== -1)
1385 playlist_DeleteFromInput(p_playlist
, p_item
->p_input
, pl_Locked
);
1387 playlist_NodeDelete(p_playlist
, p_item
, true , false);
1389 p_sys
->b_need_update
= true;
1396 if (!(p_pl_item
= p_sys
->pp_plist
[p_sys
->i_box_idx
]))
1399 if (p_pl_item
->p_item
->i_children
)
1401 playlist_item_t
*p_item
, *p_parent
= p_pl_item
->p_item
;
1402 if (p_parent
->i_children
== -1)
1406 while (p_parent
->p_parent
)
1407 p_parent
= p_parent
->p_parent
;
1411 p_sys
->p_node
= p_parent
;
1415 playlist_Control(p_playlist
, PLAYLIST_VIEWPLAY
, pl_Unlocked
,
1419 { /* We only want to set the current node */
1420 playlist_Stop(p_playlist
);
1421 p_sys
->p_node
= p_pl_item
->p_item
;
1424 p_sys
->b_plidx_follow
= true;
1431 static bool HandleBrowseKey(intf_thread_t
*p_intf
, int key
)
1433 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1434 struct dir_entry_t
*dir_entry
;
1439 p_sys
->b_show_hidden_files
= !p_sys
->b_show_hidden_files
;
1447 dir_entry
= p_sys
->pp_dir_entries
[p_sys
->i_box_idx
];
1449 if (asprintf(&psz_path
, "%s" DIR_SEP
"%s", p_sys
->psz_current_dir
,
1450 dir_entry
->psz_path
) == -1)
1453 if (!dir_entry
->b_file
&& key
!= ' ')
1455 free(p_sys
->psz_current_dir
);
1456 p_sys
->psz_current_dir
= psz_path
;
1459 p_sys
->i_box_start
= 0;
1460 p_sys
->i_box_idx
= 0;
1464 char *psz_uri
= make_URI(psz_path
, dir_entry
->b_file
? "file"
1467 if (psz_uri
== NULL
)
1470 playlist_t
*p_playlist
= pl_Get(p_intf
);
1471 playlist_item_t
*p_parent
= p_sys
->p_node
;
1474 playlist_item_t
*p_item
;
1476 p_item
= playlist_CurrentPlayingItem(p_playlist
);
1477 p_parent
= p_item
? p_item
->p_parent
: NULL
;
1480 p_parent
= p_playlist
->p_local_onelevel
;
1483 while (p_parent
->p_parent
&& p_parent
->p_parent
->p_parent
)
1484 p_parent
= p_parent
->p_parent
;
1486 input_item_t
*p_input
= p_playlist
->p_local_onelevel
->p_input
;
1487 playlist_Add(p_playlist
, psz_uri
, NULL
, PLAYLIST_APPEND
,
1488 PLAYLIST_END
, p_parent
->p_input
== p_input
, false);
1490 BoxSwitch(p_sys
, BOX_PLAYLIST
);
1498 static void HandleEditBoxKey(intf_thread_t
*p_intf
, int key
, int box
)
1500 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1501 bool search
= box
== BOX_SEARCH
;
1502 char *str
= search
? p_sys
->psz_search_chain
: p_sys
->psz_open_chain
;
1503 size_t len
= strlen(str
);
1505 assert(box
== BOX_SEARCH
|| box
== BOX_OPEN
);
1510 case KEY_CLEAR
: clear(); return;
1518 p_sys
->psz_old_search
= strdup(p_sys
->psz_search_chain
);
1519 else if (p_sys
->psz_old_search
)
1520 SearchPlaylist(p_sys
, p_sys
->psz_old_search
);
1524 char *psz_uri
= make_URI(p_sys
->psz_open_chain
, NULL
);
1525 if (psz_uri
== NULL
)
1527 p_sys
->i_box_type
= BOX_PLAYLIST
;
1531 playlist_t
*p_playlist
= pl_Get(p_intf
);
1532 playlist_item_t
*p_parent
= p_sys
->p_node
, *p_current
;
1537 p_current
= playlist_CurrentPlayingItem(p_playlist
);
1538 p_parent
= p_current
? p_current
->p_parent
: NULL
;
1540 p_parent
= p_playlist
->p_local_onelevel
;
1543 while (p_parent
->p_parent
&& p_parent
->p_parent
->p_parent
)
1544 p_parent
= p_parent
->p_parent
;
1547 playlist_Add(p_playlist
, psz_uri
, NULL
,
1548 PLAYLIST_APPEND
|PLAYLIST_GO
, PLAYLIST_END
,
1549 p_parent
->p_input
== p_playlist
->p_local_onelevel
->p_input
,
1553 p_sys
->b_plidx_follow
= true;
1555 p_sys
->i_box_type
= BOX_PLAYLIST
;
1558 case 0x1b: /* ESC */
1559 /* Alt+key combinations return 2 keys in the terminal keyboard:
1560 * ESC, and the 2nd key.
1561 * If some other key is available immediately (where immediately
1562 * means after getch() 1 second delay), that means that the
1563 * ESC key was not pressed.
1565 * man 3X curs_getch says:
1567 * Use of the escape key by a programmer for a single
1568 * character function is discouraged, as it will cause a delay
1569 * of up to one second while the keypad code looks for a
1570 * following function-key sequence.
1576 if (search
) p_sys
->i_box_idx
= p_sys
->i_before_search
;
1577 p_sys
->i_box_type
= BOX_PLAYLIST
;
1582 RemoveLastUTF8Entity(str
, len
);
1586 if (len
+ 1 < (search
? sizeof p_sys
->psz_search_chain
1587 : sizeof p_sys
->psz_open_chain
))
1589 str
[len
+ 0] = (char) key
;
1590 str
[len
+ 1] = '\0';
1596 free(p_sys
->psz_old_search
);
1597 p_sys
->psz_old_search
= NULL
;
1598 SearchPlaylist(p_sys
, str
);
1602 static void InputNavigate(input_thread_t
* p_input
, const char *var
)
1605 var_TriggerCallback(p_input
, var
);
1608 static void HandleCommonKey(intf_thread_t
*p_intf
, int key
)
1610 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1611 playlist_t
*p_playlist
= pl_Get(p_intf
);
1614 case 0x1b: /* ESC */
1621 libvlc_Quit(p_intf
->p_libvlc
);
1622 p_sys
->b_exit
= true; // terminate the main loop
1626 case 'H': BoxSwitch(p_sys
, BOX_HELP
); return;
1627 case 'i': BoxSwitch(p_sys
, BOX_INFO
); return;
1628 case 'm': BoxSwitch(p_sys
, BOX_META
); return;
1629 case 'L': BoxSwitch(p_sys
, BOX_LOG
); return;
1630 case 'P': BoxSwitch(p_sys
, BOX_PLAYLIST
); return;
1631 case 'B': BoxSwitch(p_sys
, BOX_BROWSE
); return;
1632 case 'x': BoxSwitch(p_sys
, BOX_OBJECTS
); return;
1633 case 'S': BoxSwitch(p_sys
, BOX_STATS
); return;
1635 case '/': /* Search */
1636 p_sys
->psz_search_chain
[0] = '\0';
1637 p_sys
->b_plidx_follow
= false;
1638 if (p_sys
->i_box_type
== BOX_PLAYLIST
)
1640 p_sys
->i_before_search
= p_sys
->i_box_idx
;
1641 p_sys
->i_box_type
= BOX_SEARCH
;
1645 p_sys
->i_before_search
= 0;
1646 BoxSwitch(p_sys
, BOX_SEARCH
);
1650 case 'A': /* Open */
1651 p_sys
->psz_open_chain
[0] = '\0';
1652 if (p_sys
->i_box_type
== BOX_PLAYLIST
)
1653 p_sys
->i_box_type
= BOX_OPEN
;
1655 BoxSwitch(p_sys
, BOX_OPEN
);
1659 case KEY_RIGHT
: ChangePosition(p_intf
, +0.01); return;
1660 case KEY_LEFT
: ChangePosition(p_intf
, -0.01); return;
1662 /* Common control */
1666 vout_thread_t
*p_vout
= input_GetVout(p_sys
->p_input
);
1669 bool fs
= var_ToggleBool(p_playlist
, "fullscreen");
1670 var_SetBool(p_vout
, "fullscreen", fs
);
1671 vlc_object_release(p_vout
);
1676 case ' ': PlayPause(p_intf
); return;
1677 case 's': playlist_Stop(p_playlist
); return;
1678 case 'e': Eject(p_intf
); return;
1680 case '[': InputNavigate(p_sys
->p_input
, "prev-title"); return;
1681 case ']': InputNavigate(p_sys
->p_input
, "next-title"); return;
1682 case '<': InputNavigate(p_sys
->p_input
, "prev-chapter"); return;
1683 case '>': InputNavigate(p_sys
->p_input
, "next-chapter"); return;
1685 case 'p': playlist_Prev(p_playlist
); break;
1686 case 'n': playlist_Next(p_playlist
); break;
1687 case 'a': aout_VolumeUp(p_playlist
, 1, NULL
); break;
1688 case 'z': aout_VolumeDown(p_playlist
, 1, NULL
); break;
1702 static bool HandleListKey(intf_thread_t
*p_intf
, int key
)
1704 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1705 playlist_t
*p_playlist
= pl_Get(p_intf
);
1710 /* workaround for FreeBSD + xterm:
1711 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
1714 case KEY_END
: p_sys
->i_box_idx
= p_sys
->i_box_lines_total
- 1; break;
1715 case KEY_HOME
: p_sys
->i_box_idx
= 0; break;
1716 case KEY_UP
: p_sys
->i_box_idx
--; break;
1717 case KEY_DOWN
: p_sys
->i_box_idx
++; break;
1718 case KEY_PPAGE
:p_sys
->i_box_idx
-= p_sys
->i_box_height
; break;
1719 case KEY_NPAGE
:p_sys
->i_box_idx
+= p_sys
->i_box_height
; break;
1726 if (p_sys
->i_box_type
== BOX_PLAYLIST
)
1729 p_sys
->b_plidx_follow
= IsIndex(p_sys
, p_playlist
, p_sys
->i_box_idx
);
1736 static void HandleKey(intf_thread_t
*p_intf
)
1738 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1740 int box
= p_sys
->i_box_type
;
1745 if (box
== BOX_SEARCH
|| box
== BOX_OPEN
)
1747 HandleEditBoxKey(p_intf
, key
, p_sys
->i_box_type
);
1751 if (box
== BOX_NONE
)
1757 case KEY_END
: ChangePosition(p_intf
, +.99); return;
1758 case KEY_HOME
: ChangePosition(p_intf
, -1.0); return;
1759 case KEY_UP
: ChangePosition(p_intf
, +0.05); return;
1760 case KEY_DOWN
: ChangePosition(p_intf
, -0.05); return;
1761 default: HandleCommonKey(p_intf
, key
); return;
1764 if (box
== BOX_BROWSE
&& HandleBrowseKey(p_intf
, key
))
1767 if (box
== BOX_PLAYLIST
&& HandlePlaylistKey(p_intf
, key
))
1770 if (HandleListKey(p_intf
, key
))
1773 HandleCommonKey(p_intf
, key
);
1780 static void MsgCallback(msg_cb_data_t
*data
, const msg_item_t
*msg
)
1782 intf_sys_t
*p_sys
= data
->p_sys
;
1783 int canc
= vlc_savecancel();
1785 vlc_mutex_lock(&p_sys
->msg_lock
);
1787 if (p_sys
->msgs
[p_sys
->i_msgs
])
1788 msg_Free(p_sys
->msgs
[p_sys
->i_msgs
]);
1789 p_sys
->msgs
[p_sys
->i_msgs
++] = msg_Copy(msg
);
1791 if (p_sys
->i_msgs
== (sizeof p_sys
->msgs
/ sizeof *p_sys
->msgs
))
1794 vlc_mutex_unlock(&p_sys
->msg_lock
);
1796 vlc_restorecancel(canc
);
1799 static inline void UpdateInput(intf_sys_t
*p_sys
, playlist_t
*p_playlist
)
1801 if (!p_sys
->p_input
)
1803 p_sys
->p_input
= playlist_CurrentInput(p_playlist
);
1805 else if (p_sys
->p_input
->b_dead
)
1807 vlc_object_release(p_sys
->p_input
);
1808 p_sys
->p_input
= NULL
;
1812 /*****************************************************************************
1813 * Run: ncurses thread
1814 *****************************************************************************/
1815 static void Run(intf_thread_t
*p_intf
)
1817 intf_sys_t
*p_sys
= p_intf
->p_sys
;
1818 playlist_t
*p_playlist
= pl_Get(p_intf
);
1820 int canc
= vlc_savecancel();
1822 var_AddCallback(p_playlist
, "intf-change", PlaylistChanged
, p_intf
);
1823 var_AddCallback(p_playlist
, "playlist-item-append", PlaylistChanged
, p_intf
);
1825 while (vlc_object_alive(p_intf
) && !p_sys
->b_exit
)
1827 UpdateInput(p_sys
, p_playlist
);
1832 var_DelCallback(p_playlist
, "intf-change", PlaylistChanged
, p_intf
);
1833 var_DelCallback(p_playlist
, "playlist-item-append", PlaylistChanged
, p_intf
);
1834 vlc_restorecancel(canc
);
1837 /*****************************************************************************
1838 * Open: initialize and create window
1839 *****************************************************************************/
1840 static int Open(vlc_object_t
*p_this
)
1842 intf_thread_t
*p_intf
= (intf_thread_t
*)p_this
;
1843 intf_sys_t
*p_sys
= p_intf
->p_sys
= calloc(1, sizeof(intf_sys_t
));
1844 struct msg_cb_data_t
*msg_cb_data
;
1849 msg_cb_data
= malloc(sizeof *msg_cb_data
);
1856 msg_cb_data
->p_sys
= p_sys
;
1857 vlc_mutex_init(&p_sys
->msg_lock
);
1859 memset(p_sys
->msgs
, 0, sizeof p_sys
->msgs
);
1860 p_sys
->p_sub
= msg_Subscribe(p_intf
->p_libvlc
, MsgCallback
, msg_cb_data
);
1861 msg_SubscriptionSetVerbosity(p_sys
->p_sub
,
1862 var_GetInteger(p_intf
->p_libvlc
, "verbose"));
1864 p_sys
->i_box_type
= BOX_PLAYLIST
;
1865 p_sys
->b_plidx_follow
= true;
1866 p_sys
->b_color
= var_CreateGetBool(p_intf
, "color");
1868 p_sys
->psz_current_dir
= var_CreateGetString(p_intf
, "browse-dir");
1869 if (!p_sys
->psz_current_dir
|| !*p_sys
->psz_current_dir
)
1871 free(p_sys
->psz_current_dir
);
1872 p_sys
->psz_current_dir
= config_GetUserDir(VLC_HOME_DIR
);
1875 initscr(); /* Initialize the curses library */
1878 start_color_and_pairs(p_intf
);
1880 keypad(stdscr
, TRUE
); /* Don't do NL -> CR/NL */
1881 nonl(); /* Take input chars one at a time */
1882 cbreak(); /* Don't echo */
1883 noecho(); /* Invisible cursor */
1884 curs_set(0); /* Non blocking getch() */
1888 /* Stop printing errors to the console */
1889 if(!freopen("/dev/null", "wb", stderr
))
1890 msg_Err(p_intf
, "Couldn't close stderr (%m)");
1893 PlaylistRebuild(p_intf
),
1895 p_intf
->pf_run
= Run
;
1899 /*****************************************************************************
1900 * Close: destroy interface window
1901 *****************************************************************************/
1902 static void Close(vlc_object_t
*p_this
)
1904 intf_sys_t
*p_sys
= ((intf_thread_t
*)p_this
)->p_sys
;
1906 PlaylistDestroy(p_sys
);
1909 free(p_sys
->psz_current_dir
);
1910 free(p_sys
->psz_old_search
);
1913 vlc_object_release(p_sys
->p_input
);
1915 endwin(); /* Close the ncurses interface */
1917 msg_Unsubscribe(p_sys
->p_sub
);
1918 vlc_mutex_destroy(&p_sys
->msg_lock
);
1919 for(unsigned i
= 0; i
< sizeof p_sys
->msgs
/ sizeof *p_sys
->msgs
; i
++)
1921 msg_Free(p_sys
->msgs
[i
]);