ncurses.c 53.4 KB
Newer Older
1
/*****************************************************************************
2
 * ncurses.c : NCurses interface for vlc
3
 *****************************************************************************
4
 * Copyright © 2001-2011 the VideoLAN team
5
 * $Id$
6
 *
7
 * Authors: Sam Hocevar <sam@zoy.org>
8
 *          Laurent Aimar <fenrir@via.ecp.fr>
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
9 10
 *          Yoann Peronneau <yoann@videolan.org>
 *          Derk-Jan Hartman <hartman at videolan dot org>
11
 *          Rafaël Carré <funman@videolanorg>
12
 *
13 14 15 16
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
17
 *
18 19 20 21 22 23 24
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
25
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26 27
 *****************************************************************************/

28
/* UTF8 locale is required */
Rémi Duraffort's avatar
Rémi Duraffort committed
29

30 31 32
/*****************************************************************************
 * Preamble
 *****************************************************************************/
33 34 35 36
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

Rafaël Carré's avatar
Rafaël Carré committed
37
#define _XOPEN_SOURCE_EXTENDED 1
38 39

#include <assert.h>
Rafaël Carré's avatar
Rafaël Carré committed
40
#include <wchar.h>
41
#include <sys/stat.h>
42
#include <math.h>
43 44 45

#include <vlc_common.h>
#include <vlc_plugin.h>
46

47 48
#include <ncurses.h>

Clément Stenac's avatar
Clément Stenac committed
49
#include <vlc_interface.h>
50
#include <vlc_vout.h>
Clément Stenac's avatar
Clément Stenac committed
51
#include <vlc_charset.h>
52
#include <vlc_input.h>
Rafaël Carré's avatar
Rafaël Carré committed
53
#include <vlc_es.h>
54
#include <vlc_playlist.h>
55
#include <vlc_meta.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
56
#include <vlc_fs.h>
Pierre Ynard's avatar
Pierre Ynard committed
57
#include <vlc_url.h>
58 59 60 61

/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
Rafaël Carré's avatar
Rafaël Carré committed
62 63
static int  Open           (vlc_object_t *);
static void Close          (vlc_object_t *);
64 65 66 67

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
68 69

#define BROWSE_TEXT N_("Filebrowser starting point")
Rafaël Carré's avatar
Rafaël Carré committed
70
#define BROWSE_LONGTEXT N_(\
Felix Paul Kühne's avatar
Felix Paul Kühne committed
71
    "This option allows you to specify the directory the ncurses filebrowser " \
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
72 73
    "will show you initially.")

74
vlc_module_begin ()
Rafaël Carré's avatar
Rafaël Carré committed
75 76 77 78 79 80 81 82
    set_shortname("Ncurses")
    set_description(N_("Ncurses interface"))
    set_capability("interface", 10)
    set_category(CAT_INTERFACE)
    set_subcategory(SUBCAT_INTERFACE_MAIN)
    set_callbacks(Open, Close)
    add_shortcut("curses")
    add_directory("browse-dir", NULL, BROWSE_TEXT, BROWSE_LONGTEXT, false)
83
vlc_module_end ()
84

85 86
#include "eject.c"

87 88 89
/*****************************************************************************
 * intf_sys_t: description and status of ncurses interface
 *****************************************************************************/
90 91 92 93 94
enum
{
    BOX_NONE,
    BOX_HELP,
    BOX_INFO,
Rafaël Carré's avatar
Rafaël Carré committed
95
    BOX_LOG,
96 97
    BOX_PLAYLIST,
    BOX_SEARCH,
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
98
    BOX_OPEN,
99
    BOX_BROWSE,
100
    BOX_META,
101 102
    BOX_OBJECTS,
    BOX_STATS
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
103
};
104

Rafaël Carré's avatar
Rafaël Carré committed
105
static const char box_title[][19] = {
106 107 108
    [BOX_NONE]      = "",
    [BOX_HELP]      = " Help ",
    [BOX_INFO]      = " Information ",
Rafaël Carré's avatar
Rafaël Carré committed
109
    [BOX_LOG]       = " Messages ",
110 111 112 113 114 115 116 117 118
    [BOX_PLAYLIST]  = " Playlist ",
    [BOX_SEARCH]    = " Playlist ",
    [BOX_OPEN]      = " Playlist ",
    [BOX_BROWSE]    = " Browse ",
    [BOX_META]      = " Meta-information ",
    [BOX_OBJECTS]   = " Objects ",
    [BOX_STATS]     = " Stats ",
};

119
enum
Rémi Duraffort's avatar
Rémi Duraffort committed
120 121 122 123 124 125 126 127 128 129 130 131 132
{
    C_DEFAULT = 0,
    C_TITLE,
    C_PLAYLIST_1,
    C_PLAYLIST_2,
    C_PLAYLIST_3,
    C_BOX,
    C_STATUS,
    C_INFO,
    C_ERROR,
    C_WARNING,
    C_DEBUG,
    C_CATEGORY,
133
    C_FOLDER,
134
    /* XXX: new elements here ! */
135 136 137 138 139 140 141 142 143 144

    C_MAX
};

/* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */
static const struct { short f; short b; } color_pairs[] =
{
    /* element */       /* foreground*/ /* background*/
    [C_TITLE]       = { COLOR_YELLOW,   COLOR_BLACK },

Rafaël Carré's avatar
Rafaël Carré committed
145
    /* jamaican playlist, for rastafari sisters & brothers! */
146 147 148 149 150 151 152 153 154 155 156 157 158 159
    [C_PLAYLIST_1]  = { COLOR_GREEN,    COLOR_BLACK },
    [C_PLAYLIST_2]  = { COLOR_YELLOW,   COLOR_BLACK },
    [C_PLAYLIST_3]  = { COLOR_RED,      COLOR_BLACK },

    /* used in DrawBox() */
    [C_BOX]         = { COLOR_CYAN,     COLOR_BLACK },
    /* Source: State, Position, Volume, Chapters, etc...*/
    [C_STATUS]      = { COLOR_BLUE,     COLOR_BLACK },

    /* VLC messages, keep the order from highest priority to lowest */
    [C_INFO]        = { COLOR_BLACK,    COLOR_WHITE },
    [C_ERROR]       = { COLOR_RED,      COLOR_BLACK },
    [C_WARNING]     = { COLOR_YELLOW,   COLOR_BLACK },
    [C_DEBUG]       = { COLOR_WHITE,    COLOR_BLACK },
Rafaël Carré's avatar
Rafaël Carré committed
160

161 162 163 164
    /* Category title: help, info, metadata */
    [C_CATEGORY]    = { COLOR_MAGENTA,  COLOR_BLACK },
    /* Folder (BOX_BROWSE) */
    [C_FOLDER]      = { COLOR_RED,      COLOR_BLACK },
Rémi Duraffort's avatar
Rémi Duraffort committed
165
};
166

Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
167 168
struct dir_entry_t
{
Rafaël Carré's avatar
Rafaël Carré committed
169 170
    bool        file;
    char        *path;
171
};
172

173 174
struct pl_item_t
{
Rafaël Carré's avatar
Rafaël Carré committed
175 176
    playlist_item_t *item;
    char            *display;
177
};
178

179 180
struct intf_sys_t
{
181
    vlc_thread_t    thread;
182 183
    input_thread_t *p_input;

Rafaël Carré's avatar
Rafaël Carré committed
184 185
    bool            color;
    bool            exit;
Rémi Duraffort's avatar
Rémi Duraffort committed
186

Rafaël Carré's avatar
Rafaël Carré committed
187 188 189 190 191 192
    int             box_type;
    int             box_y;            // start of box content
    int             box_height;
    int             box_lines_total;  // number of lines in the box
    int             box_start;        // first line of box displayed
    int             box_idx;          // selected line
193

194
    msg_subscription_t sub;         // message bank subscription
195 196 197 198 199 200
    struct
    {
        int              type;
        msg_item_t      *item;
        char            *msg;
    } msgs[50];      // ring buffer
Rafaël Carré's avatar
Rafaël Carré committed
201
    int                 i_msgs;
Rafaël Carré's avatar
Rafaël Carré committed
202
    int                 verbosity;
Rafaël Carré's avatar
Rafaël Carré committed
203
    vlc_mutex_t         msg_lock;
204

205
    /* Search Box context */
Rafaël Carré's avatar
Rafaël Carré committed
206
    char            search_chain[20];
207

208
    /* Open Box Context */
Rafaël Carré's avatar
Rafaël Carré committed
209
    char            open_chain[50];
210

211
    /* File Browser context */
Rafaël Carré's avatar
Rafaël Carré committed
212 213 214 215
    char            *current_dir;
    int             n_dir_entries;
    struct dir_entry_t  **dir_entries;
    bool            show_hidden_files;
216

217
    /* Playlist context */
Rafaël Carré's avatar
Rafaël Carré committed
218 219 220
    struct pl_item_t    **plist;
    int             plist_entries;
    bool            need_update;
221
    vlc_mutex_t     pl_lock;
Rafaël Carré's avatar
Rafaël Carré committed
222 223
    bool            plidx_follow;
    playlist_item_t *node;        /* current node */
224

225 226 227
};

/*****************************************************************************
228
 * Directories
229
 *****************************************************************************/
230

Rafaël Carré's avatar
Rafaël Carré committed
231
static void DirsDestroy(intf_sys_t *sys)
232
{
Rafaël Carré's avatar
Rafaël Carré committed
233 234 235 236
    while (sys->n_dir_entries) {
        struct dir_entry_t *dir_entry = sys->dir_entries[--sys->n_dir_entries];
        free(dir_entry->path);
        free(dir_entry);
237
    }
Rafaël Carré's avatar
Rafaël Carré committed
238 239
    free(sys->dir_entries);
    sys->dir_entries = NULL;
240 241
}

Rafaël Carré's avatar
Rafaël Carré committed
242
static int comdir_entries(const void *a, const void *b)
243
{
Rafaël Carré's avatar
Rafaël Carré committed
244 245
    struct dir_entry_t *dir_entry1 = *(struct dir_entry_t**)a;
    struct dir_entry_t *dir_entry2 = *(struct dir_entry_t**)b;
Rafaël Carré's avatar
Rafaël Carré committed
246

Rafaël Carré's avatar
Rafaël Carré committed
247 248
    if (dir_entry1->file == dir_entry2->file)
        return strcasecmp(dir_entry1->path, dir_entry2->path);
249

Rafaël Carré's avatar
Rafaël Carré committed
250
    return dir_entry1->file ? 1 : -1;
251
}
252

Rafaël Carré's avatar
Rafaël Carré committed
253 254 255 256 257
static bool IsFile(const char *current_dir, const char *entry)
{
    bool ret = true;
#ifdef S_ISDIR
    char *uri;
Rafaël Carré's avatar
Rafaël Carré committed
258 259
    if (asprintf(&uri, "%s" DIR_SEP "%s", current_dir, entry) != -1) {
        struct stat st;
Rafaël Carré's avatar
Rafaël Carré committed
260 261 262 263 264 265 266
        ret = vlc_stat(uri, &st) || !S_ISDIR(st.st_mode);
        free(uri);
    }
#endif
    return ret;
}

Rafaël Carré's avatar
Rafaël Carré committed
267
static void ReadDir(intf_thread_t *intf)
268
{
Rafaël Carré's avatar
Rafaël Carré committed
269
    intf_sys_t *sys = intf->p_sys;
Rémi Duraffort's avatar
Rémi Duraffort committed
270

Rafaël Carré's avatar
Rafaël Carré committed
271 272
    if (!sys->current_dir || !*sys->current_dir) {
        msg_Dbg(intf, "no current dir set");
Rafaël Carré's avatar
Rafaël Carré committed
273 274
        return;
    }
275

Rafaël Carré's avatar
Rafaël Carré committed
276 277 278
    DIR *current_dir = vlc_opendir(sys->current_dir);
    if (!current_dir) {
        msg_Warn(intf, "cannot open directory `%s' (%m)", sys->current_dir);
Rafaël Carré's avatar
Rafaël Carré committed
279 280 281
        return;
    }

Rafaël Carré's avatar
Rafaël Carré committed
282
    DirsDestroy(sys);
Rafaël Carré's avatar
Rafaël Carré committed
283

Rafaël Carré's avatar
Rafaël Carré committed
284 285 286 287
    char *entry;
    while ((entry = vlc_readdir(current_dir))) {
        if (!sys->show_hidden_files && *entry == '.' && strcmp(entry, ".."))
            goto next;
Rémi Duraffort's avatar
Rémi Duraffort committed
288

Rafaël Carré's avatar
Rafaël Carré committed
289 290
        struct dir_entry_t *dir_entry = malloc(sizeof *dir_entry);
        if (!dir_entry)
Rafaël Carré's avatar
Rafaël Carré committed
291
            goto next;
292

Rafaël Carré's avatar
Rafaël Carré committed
293 294 295 296
        dir_entry->file = IsFile(sys->current_dir, entry);
        dir_entry->path = entry;
        INSERT_ELEM(sys->dir_entries, sys->n_dir_entries,
             sys->n_dir_entries, dir_entry);
297
        continue;
298

Rafaël Carré's avatar
Rafaël Carré committed
299
next:
Rafaël Carré's avatar
Rafaël Carré committed
300
        free(entry);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
301
    }
Rafaël Carré's avatar
Rafaël Carré committed
302

Rafaël Carré's avatar
Rafaël Carré committed
303 304
    qsort(sys->dir_entries, sys->n_dir_entries,
           sizeof(struct dir_entry_t*), &comdir_entries);
Rafaël Carré's avatar
Rafaël Carré committed
305

Rafaël Carré's avatar
Rafaël Carré committed
306
    closedir(current_dir);
307 308 309
}

/*****************************************************************************
310
 * Adjust index position after a change (list navigation or item switching)
311
 *****************************************************************************/
Rafaël Carré's avatar
Rafaël Carré committed
312
static void CheckIdx(intf_sys_t *sys)
313
{
Rafaël Carré's avatar
Rafaël Carré committed
314 315
    int lines = sys->box_lines_total;
    int height = LINES - sys->box_y - 2;
316 317
    if (height > lines - 1)
        height = lines - 1;
318 319

    /* make sure the new index is within the box */
Rafaël Carré's avatar
Rafaël Carré committed
320 321 322 323 324 325
    if (sys->box_idx <= 0) {
        sys->box_idx = 0;
        sys->box_start = 0;
    } else if (sys->box_idx >= lines - 1 && lines > 0) {
        sys->box_idx = lines - 1;
        sys->box_start = sys->box_idx - height;
326
    }
327

328
    /* Fix box start (1st line of the box displayed) */
Rafaël Carré's avatar
Rafaël Carré committed
329 330 331 332 333 334 335 336 337
    if (sys->box_idx < sys->box_start ||
        sys->box_idx > height + sys->box_start + 1) {
        sys->box_start = sys->box_idx - height/2;
        if (sys->box_start < 0)
            sys->box_start = 0;
    } else if (sys->box_idx == sys->box_start - 1) {
        sys->box_start--;
    } else if (sys->box_idx == height + sys->box_start + 1) {
        sys->box_start++;
338 339 340 341 342 343
    }
}

/*****************************************************************************
 * Playlist
 *****************************************************************************/
Rafaël Carré's avatar
Rafaël Carré committed
344
static void PlaylistDestroy(intf_sys_t *sys)
345
{
Rafaël Carré's avatar
Rafaël Carré committed
346 347 348
    while (sys->plist_entries) {
        struct pl_item_t *p_pl_item = sys->plist[--sys->plist_entries];
        free(p_pl_item->display);
Rafaël Carré's avatar
Rafaël Carré committed
349
        free(p_pl_item);
350
    }
Rafaël Carré's avatar
Rafaël Carré committed
351 352
    free(sys->plist);
    sys->plist = NULL;
353 354
}

Rafaël Carré's avatar
Rafaël Carré committed
355
static bool PlaylistAddChild(intf_sys_t *sys, playlist_item_t *p_child,
356 357 358
                             const char *c, const char d)
{
    int ret;
Rafaël Carré's avatar
Rafaël Carré committed
359
    char *name = input_item_GetTitleFbName(p_child->p_input);
360
    struct pl_item_t *p_pl_item = malloc(sizeof *p_pl_item);
361

Rafaël Carré's avatar
Rafaël Carré committed
362
    if (!name || !p_pl_item)
363
        goto error;
Rafaël Carré's avatar
Rafaël Carré committed
364

Rafaël Carré's avatar
Rafaël Carré committed
365
    p_pl_item->item = p_child;
366 367

    if (c && *c)
Rafaël Carré's avatar
Rafaël Carré committed
368
        ret = asprintf(&p_pl_item->display, "%s%c-%s", c, d, name);
369
    else
Rafaël Carré's avatar
Rafaël Carré committed
370
        ret = asprintf(&p_pl_item->display, " %s", name);
371

Rafaël Carré's avatar
Rafaël Carré committed
372 373
    free(name);
    name = NULL;
374 375 376 377

    if (ret == -1)
        goto error;

Rafaël Carré's avatar
Rafaël Carré committed
378 379
    INSERT_ELEM(sys->plist, sys->plist_entries,
                 sys->plist_entries, p_pl_item);
380 381 382 383

    return true;

error:
Rafaël Carré's avatar
Rafaël Carré committed
384
    free(name);
385 386 387 388
    free(p_pl_item);
    return false;
}

Rafaël Carré's avatar
Rafaël Carré committed
389
static void PlaylistAddNode(intf_sys_t *sys, playlist_item_t *node,
390 391
                            const char *c)
{
Rafaël Carré's avatar
Rafaël Carré committed
392 393 394 395
    for (int k = 0; k < node->i_children; k++) {
        bool last = k == node->i_children - 1;
        playlist_item_t *p_child = node->pp_children[k];
        if (!PlaylistAddChild(sys, p_child, c, last ? '`' : '|'))
396
            return;
397

398 399 400
        if (p_child->i_children <= 0)
            continue;

Rafaël Carré's avatar
Rafaël Carré committed
401 402 403
        if (*c) {
            char *tmp;
            if (asprintf(&tmp, "%s%c ", c, last ? ' ' : '|') == -1)
404
                return;
Rafaël Carré's avatar
Rafaël Carré committed
405 406 407 408
            PlaylistAddNode(sys, p_child, tmp);
            free(tmp);
        } else {
            PlaylistAddNode(sys, p_child, " ");
409
        }
410 411
    }
}
412

Rafaël Carré's avatar
Rafaël Carré committed
413
static void PlaylistRebuild(intf_thread_t *intf)
414
{
Rafaël Carré's avatar
Rafaël Carré committed
415 416
    intf_sys_t *sys = intf->p_sys;
    playlist_t *p_playlist = pl_Get(intf);
417

Rafaël Carré's avatar
Rafaël Carré committed
418 419
    PlaylistDestroy(sys);
    PlaylistAddNode(sys, p_playlist->p_root_onelevel, "");
420 421
}

Rafaël Carré's avatar
Rafaël Carré committed
422
static int ItemChanged(vlc_object_t *p_this, const char *variable,
Rafaël Carré's avatar
Rafaël Carré committed
423
                            vlc_value_t oval, vlc_value_t nval, void *param)
424
{
Rafaël Carré's avatar
Rafaël Carré committed
425
    VLC_UNUSED(p_this); VLC_UNUSED(variable);
426
    VLC_UNUSED(oval); VLC_UNUSED(nval);
427

Rafaël Carré's avatar
Rafaël Carré committed
428
    intf_sys_t *sys = ((intf_thread_t *)param)->p_sys;
429

Rafaël Carré's avatar
Rafaël Carré committed
430 431 432
    vlc_mutex_lock(&sys->pl_lock);
    sys->need_update = true;
    vlc_mutex_unlock(&sys->pl_lock);
433 434 435 436

    return VLC_SUCCESS;
}

Rafaël Carré's avatar
Rafaël Carré committed
437
static int PlaylistChanged(vlc_object_t *p_this, const char *variable,
438 439
                            vlc_value_t oval, vlc_value_t nval, void *param)
{
Rafaël Carré's avatar
Rafaël Carré committed
440
    VLC_UNUSED(p_this); VLC_UNUSED(variable);
441
    VLC_UNUSED(oval); VLC_UNUSED(nval);
Rafaël Carré's avatar
Rafaël Carré committed
442 443 444
    intf_thread_t *intf   = (intf_thread_t *)param;
    intf_sys_t *sys       = intf->p_sys;
    playlist_item_t *node = playlist_CurrentPlayingItem(pl_Get(intf));
445

Rafaël Carré's avatar
Rafaël Carré committed
446 447 448 449
    vlc_mutex_lock(&sys->pl_lock);
    sys->need_update = true;
    sys->node = node ? node->p_parent : NULL;
    vlc_mutex_unlock(&sys->pl_lock);
450

451 452 453 454
    return VLC_SUCCESS;
}

/* Playlist suxx */
Rafaël Carré's avatar
Rafaël Carré committed
455
static int SubSearchPlaylist(intf_sys_t *sys, char *searchstring,
Rafaël Carré's avatar
Rafaël Carré committed
456
                              int i_start, int i_stop)
457
{
Rafaël Carré's avatar
Rafaël Carré committed
458 459
    for (int i = i_start + 1; i < i_stop; i++)
        if (strcasestr(sys->plist[i]->display, searchstring))
460
            return i;
461

462
    return -1;
463 464
}

465
static void SearchPlaylist(intf_sys_t *sys)
466
{
467 468
    char *str = sys->search_chain;
    int i_first = sys->box_idx;
469 470
    if (i_first < 0)
        i_first = 0;
471

472
    if (!str || !*str)
473 474
        return;

Rafaël Carré's avatar
Rafaël Carré committed
475
    int i_item = SubSearchPlaylist(sys, str, i_first + 1, sys->plist_entries);
Rafaël Carré's avatar
Rafaël Carré committed
476
    if (i_item < 0)
Rafaël Carré's avatar
Rafaël Carré committed
477
        i_item = SubSearchPlaylist(sys, str, 0, i_first);
478

Rafaël Carré's avatar
Rafaël Carré committed
479 480 481
    if (i_item > 0) {
        sys->box_idx = i_item;
        CheckIdx(sys);
482
    }
483
}
484

Rafaël Carré's avatar
Rafaël Carré committed
485
static inline bool IsIndex(intf_sys_t *sys, playlist_t *p_playlist, int i)
486
{
Rafaël Carré's avatar
Rafaël Carré committed
487
    playlist_item_t *item = sys->plist[i]->item;
488 489 490

    PL_ASSERT_LOCKED;

Rafaël Carré's avatar
Rafaël Carré committed
491 492 493
    vlc_mutex_lock(&sys->pl_lock);
    if (item->i_children == 0 && item == sys->node) {
        vlc_mutex_unlock(&sys->pl_lock);
494
        return true;
495
    }
Rafaël Carré's avatar
Rafaël Carré committed
496
    vlc_mutex_unlock(&sys->pl_lock);
497

Rafaël Carré's avatar
Rafaël Carré committed
498 499 500
    playlist_item_t *p_played_item = playlist_CurrentPlayingItem(p_playlist);
    if (p_played_item && item->p_input && p_played_item->p_input)
        return item->p_input->i_id == p_played_item->p_input->i_id;
501 502

    return false;
503 504
}

Rafaël Carré's avatar
Rafaël Carré committed
505
static void FindIndex(intf_sys_t *sys, playlist_t *p_playlist)
506
{
Rafaël Carré's avatar
Rafaël Carré committed
507 508
    int plidx = sys->box_idx;
    int max = sys->plist_entries;
509

510 511
    PL_LOCK;

Rafaël Carré's avatar
Rafaël Carré committed
512 513 514 515 516
    if (!IsIndex(sys, p_playlist, plidx))
        for (int i = 0; i < max; i++)
            if (IsIndex(sys, p_playlist, i)) {
                sys->box_idx = i;
                CheckIdx(sys);
517 518
                break;
            }
519

520 521
    PL_UNLOCK;

Rafaël Carré's avatar
Rafaël Carré committed
522
    sys->plidx_follow = true;
523 524
}

525 526 527 528
/****************************************************************************
 * Drawing
 ****************************************************************************/

Rafaël Carré's avatar
Rafaël Carré committed
529
static void start_color_and_pairs(intf_thread_t *intf)
Rémi Duraffort's avatar
Rémi Duraffort committed
530
{
Rafaël Carré's avatar
Rafaël Carré committed
531 532 533
    if (!has_colors()) {
        intf->p_sys->color = false;
        msg_Warn(intf, "Terminal doesn't support colors");
Rémi Duraffort's avatar
Rémi Duraffort committed
534 535 536 537
        return;
    }

    start_color();
Rafaël Carré's avatar
Rafaël Carré committed
538
    for (int i = C_DEFAULT + 1; i < C_MAX; i++)
539
        init_pair(i, color_pairs[i].f, color_pairs[i].b);
Rémi Duraffort's avatar
Rémi Duraffort committed
540 541

    /* untested, in all my terminals, !can_change_color() --funman */
Rafaël Carré's avatar
Rafaël Carré committed
542 543
    if (can_change_color())
        init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
Rémi Duraffort's avatar
Rémi Duraffort committed
544 545
}

Rafaël Carré's avatar
Rafaël Carré committed
546
static void DrawBox(int y, int h, bool color, const char *title)
547
{
548 549
    int w = COLS;
    if (w <= 3 || h <= 0)
Rafaël Carré's avatar
Rafaël Carré committed
550
        return;
551

Rafaël Carré's avatar
Rafaël Carré committed
552
    if (color) color_set(C_BOX, NULL);
553

Rafaël Carré's avatar
Rafaël Carré committed
554
    if (!title) title = "";
Rafaël Carré's avatar
Rafaël Carré committed
555
    int len = strlen(title);
556

Rafaël Carré's avatar
Rafaël Carré committed
557 558
    if (len > w - 2)
        len = w - 2;
559

560
    mvaddch(y, 0,    ACS_ULCORNER);
Rafaël Carré's avatar
Rafaël Carré committed
561 562 563
    mvhline(y, 1,  ACS_HLINE, (w-len-2)/2);
    mvprintw(y, 1+(w-len-2)/2, "%s", title);
    mvhline(y, (w-len)/2+len,  ACS_HLINE, w - 1 - ((w-len)/2+len));
564
    mvaddch(y, w-1,ACS_URCORNER);
565

Rafaël Carré's avatar
Rafaël Carré committed
566
    for (int i = 0; i < h; i++) {
567 568
        mvaddch(++y, 0,   ACS_VLINE);
        mvaddch(y, w-1, ACS_VLINE);
569
    }
Rafaël Carré's avatar
Rafaël Carré committed
570

571 572 573
    mvaddch(++y, 0,   ACS_LLCORNER);
    mvhline(y,   1,   ACS_HLINE, w - 2);
    mvaddch(y,   w-1, ACS_LRCORNER);
Rafaël Carré's avatar
Rafaël Carré committed
574
    if (color) color_set(C_DEFAULT, NULL);
575 576
}

577
static void DrawEmptyLine(int y, int x, int w)
578
{
Rafaël Carré's avatar
Rafaël Carré committed
579 580
    if (w <= 0) return;

581
    mvhline(y, x, ' ', w);
582 583
}

584
static void DrawLine(int y, int x, int w)
585
{
Rafaël Carré's avatar
Rafaël Carré committed
586 587 588
    if (w <= 0) return;

    attrset(A_REVERSE);
589
    mvhline(y, x, ' ', w);
Rafaël Carré's avatar
Rafaël Carré committed
590
    attroff(A_REVERSE);
591
}
592

Rafaël Carré's avatar
Rafaël Carré committed
593
static void mvnprintw(int y, int x, int w, const char *p_fmt, ...)
594 595
{
    va_list  vl_args;
Rafaël Carré's avatar
Rafaël Carré committed
596
    char    *p_buf;
Rafaël Carré's avatar
Rafaël Carré committed
597
    int      len;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
598

Rafaël Carré's avatar
Rafaël Carré committed
599
    if (w <= 0)
600
        return;
601

Rafaël Carré's avatar
Rafaël Carré committed
602 603
    va_start(vl_args, p_fmt);
    if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
604
        return;
Rafaël Carré's avatar
Rafaël Carré committed
605
    va_end(vl_args);
606

Rafaël Carré's avatar
Rafaël Carré committed
607
    len = strlen(p_buf);
608

Rafaël Carré's avatar
Rafaël Carré committed
609
    wchar_t wide[len + 1];
610

Rafaël Carré's avatar
Rafaël Carré committed
611
    EnsureUTF8(p_buf);
Rafaël Carré's avatar
Rafaël Carré committed
612
    size_t i_char_len = mbstowcs(wide, p_buf, len);
613

614 615
    size_t i_width; /* number of columns */

Rafaël Carré's avatar
Rafaël Carré committed
616
    if (i_char_len == (size_t)-1) /* an invalid character was encountered */ {
Rafaël Carré's avatar
Rafaël Carré committed
617
        free(p_buf);
618 619
        return;
    }
Rafaël Carré's avatar
Rafaël Carré committed
620

Rafaël Carré's avatar
Rafaël Carré committed
621 622
    i_width = wcswidth(wide, i_char_len);
    if (i_width == (size_t)-1) {
Rafaël Carré's avatar
Rafaël Carré committed
623 624
        /* a non printable character was encountered */
        i_width = 0;
Rafaël Carré's avatar
Rafaël Carré committed
625 626
        for (unsigned i = 0 ; i < i_char_len ; i++) {
            int i_cwidth = wcwidth(wide[i]);
Rafaël Carré's avatar
Rafaël Carré committed
627 628
            if (i_cwidth != -1)
                i_width += i_cwidth;
629 630
        }
    }
Rafaël Carré's avatar
Rafaël Carré committed
631

Rafaël Carré's avatar
Rafaël Carré committed
632
    if (i_width <= (size_t)w) {
Rafaël Carré's avatar
Rafaël Carré committed
633 634 635 636 637 638 639 640
        mvprintw(y, x, "%s", p_buf);
        mvhline(y, x + i_width, ' ', w - i_width);
        free(p_buf);
        return;
    }

    int i_total_width = 0;
    int i = 0;
Rafaël Carré's avatar
Rafaël Carré committed
641 642 643 644 645 646 647
    while (i_total_width < w) {
        i_total_width += wcwidth(wide[i]);
        if (w > 7 && i_total_width >= w/2) {
            wide[i  ] = '.';
            wide[i+1] = '.';
            i_total_width -= wcwidth(wide[i]) - 2;
            if (i > 0) {
Rafaël Carré's avatar
Rafaël Carré committed
648 649
                /* we require this check only if at least one character
                 * 4 or more columns wide exists (which i doubt) */
Rafaël Carré's avatar
Rafaël Carré committed
650 651
                wide[i-1] = '.';
                i_total_width -= wcwidth(wide[i-1]) - 1;
Rafaël Carré's avatar
Rafaël Carré committed
652
            }
653

Rafaël Carré's avatar
Rafaël Carré committed
654 655
            /* find the widest string */
            int j, i_2nd_width = 0;
Rafaël Carré's avatar
Rafaël Carré committed
656 657
            for (j = i_char_len - 1; i_2nd_width < w - i_total_width; j--)
                i_2nd_width += wcwidth(wide[j]);
658

Rafaël Carré's avatar
Rafaël Carré committed
659 660 661 662
            /* we already have i_total_width columns filled, and we can't
             * have more than w columns */
            if (i_2nd_width > w - i_total_width)
                j++;
663

Rafaël Carré's avatar
Rafaël Carré committed
664 665
            wmemmove(&wide[i+2], &wide[j+1], i_char_len - j - 1);
            wide[i + 2 + i_char_len - j - 1] = '\0';
Rafaël Carré's avatar
Rafaël Carré committed
666
            break;
667
        }
Rafaël Carré's avatar
Rafaël Carré committed
668
        i++;
669
    }
Rafaël Carré's avatar
Rafaël Carré committed
670
    if (w <= 7) /* we don't add the '...' else we lose too much chars */
Rafaël Carré's avatar
Rafaël Carré committed
671
        wide[i] = '\0';
Rafaël Carré's avatar
Rafaël Carré committed
672

Rafaël Carré's avatar
Rafaël Carré committed
673 674 675 676
    size_t i_wlen = wcslen(wide) * 6 + 1; /* worst case */
    char ellipsized[i_wlen];
    wcstombs(ellipsized, wide, i_wlen);
    mvprintw(y, x, "%s", ellipsized);
677

Rafaël Carré's avatar
Rafaël Carré committed
678
    free(p_buf);
679
}
680

Rafaël Carré's avatar
Rafaël Carré committed
681
static void MainBoxWrite(intf_sys_t *sys, int l, const char *p_fmt, ...)
682
{
683
    va_list     vl_args;
Rafaël Carré's avatar
Rafaël Carré committed
684
    char        *p_buf;
Rafaël Carré's avatar
Rafaël Carré committed
685
    bool        b_selected = l == sys->box_idx;
686

Rafaël Carré's avatar
Rafaël Carré committed
687
    if (l < sys->box_start || l - sys->box_start >= sys->box_height)
688 689
        return;

Rafaël Carré's avatar
Rafaël Carré committed
690 691
    va_start(vl_args, p_fmt);
    if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
692
        return;
Rafaël Carré's avatar
Rafaël Carré committed
693
    va_end(vl_args);
694

695
    if (b_selected) attron(A_REVERSE);
Rafaël Carré's avatar
Rafaël Carré committed
696
    mvnprintw(sys->box_y + l - sys->box_start, 1, COLS - 2, "%s", p_buf);
697 698
    if (b_selected) attroff(A_REVERSE);

Rafaël Carré's avatar
Rafaël Carré committed
699
    free(p_buf);
700 701
}

Rafaël Carré's avatar
Rafaël Carré committed
702
static int SubDrawObject(intf_sys_t *sys, int l, vlc_object_t *p_obj, int i_level, const char *prefix)
703
{
Rafaël Carré's avatar
Rafaël Carré committed
704 705 706 707 708 709 710 711
    char *name = vlc_object_get_name(p_obj);
    MainBoxWrite(sys, l++, "%*s%s%s \"%s\" (%p)", 2 * i_level++, "", prefix,
                  p_obj->psz_object_type, name ? name : "", p_obj);
    free(name);

    vlc_list_t *list = vlc_list_children(p_obj);
    for (int i = 0; i < list->i_count ; i++) {
        l = SubDrawObject(sys, l, list->p_values[i].p_object, i_level,
712
            (i == list->i_count - 1) ? "`-" : "|-" );
713
    }
Rafaël Carré's avatar
Rafaël Carré committed
714
    vlc_list_release(list);
715
    return l;
716 717
}

Rafaël Carré's avatar
Rafaël Carré committed
718
static int DrawObjects(intf_thread_t *intf)
719
{
Rafaël Carré's avatar
Rafaël Carré committed
720
    return SubDrawObject(intf->p_sys, 0, VLC_OBJECT(intf->p_libvlc), 0, "");
721 722
}

Rafaël Carré's avatar
Rafaël Carré committed
723
static int DrawMeta(intf_thread_t *intf)
724
{
Rafaël Carré's avatar
Rafaël Carré committed
725 726 727
    intf_sys_t *sys = intf->p_sys;
    input_thread_t *p_input = sys->p_input;
    input_item_t *item;
728
    int l = 0;
729

730 731
    if (!p_input)
        return 0;
732

Rafaël Carré's avatar
Rafaël Carré committed
733 734 735 736 737
    item = input_GetItem(p_input);
    vlc_mutex_lock(&item->lock);
    for (int i=0; i<VLC_META_TYPE_COUNT; i++) {
        const char *meta = vlc_meta_Get(item->p_meta, i);
        if (!meta || !*meta)
738
            continue;
739

Rafaël Carré's avatar
Rafaël Carré committed
740 741 742 743
        if (sys->color) color_set(C_CATEGORY, NULL);
        MainBoxWrite(sys, l++, "  [%s]", vlc_meta_TypeToLocalizedString(i));
        if (sys->color) color_set(C_DEFAULT, NULL);
        MainBoxWrite(sys, l++, "      %s", meta);
744
    }
Rafaël Carré's avatar
Rafaël Carré committed
745
    vlc_mutex_unlock(&item->lock);
746

747 748
    return l;
}
749

Rafaël Carré's avatar
Rafaël Carré committed
750
static int DrawInfo(intf_thread_t *intf)
751
{
Rafaël Carré's avatar
Rafaël Carré committed
752 753 754
    intf_sys_t *sys = intf->p_sys;
    input_thread_t *p_input = sys->p_input;
    input_item_t *item;
755
    int l = 0;
756

757 758 759
    if (!p_input)
        return 0;

Rafaël Carré's avatar
Rafaël Carré committed
760 761 762 763 764 765 766 767
    item = input_GetItem(p_input);
    vlc_mutex_lock(&item->lock);
    for (int i = 0; i < item->i_categories; i++) {
        info_category_t *p_category = item->pp_categories[i];
        if (sys->color) color_set(C_CATEGORY, NULL);
        MainBoxWrite(sys, l++, _("  [%s]"), p_category->psz_name);
        if (sys->color) color_set(C_DEFAULT, NULL);
        for (int j = 0; j < p_category->i_infos; j++) {
768
            info_t *p_info = p_category->pp_infos[j];
Rafaël Carré's avatar
Rafaël Carré committed
769
            MainBoxWrite(sys, l++, _("      %s: %s"),
770 771 772
                         p_info->psz_name, p_info->psz_value);
        }
    }
Rafaël Carré's avatar
Rafaël Carré committed
773
    vlc_mutex_unlock(&item->lock);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
774

775 776
    return l;
}
777

Rafaël Carré's avatar
Rafaël Carré committed
778
static int DrawStats(intf_thread_t *intf)
779
{
Rafaël Carré's avatar
Rafaël Carré committed
780 781 782
    intf_sys_t *sys = intf->p_sys;
    input_thread_t *p_input = sys->p_input;
    input_item_t *item;
783 784
    input_stats_t *p_stats;
    int l = 0, i_audio = 0, i_video = 0;
785

786 787
    if (!p_input)
        return 0;
788

Rafaël Carré's avatar
Rafaël Carré committed
789 790
    item = input_GetItem(p_input);
    assert(item);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
791

Rafaël Carré's avatar
Rafaël Carré committed
792 793
    vlc_mutex_lock(&item->lock);
    p_stats = item->p_stats;
794
    vlc_mutex_lock(&p_stats->lock);
795

Rafaël Carré's avatar
Rafaël Carré committed
796 797 798
    for (int i = 0; i < item->i_es ; i++) {
        i_audio += (item->es[i]->i_cat == AUDIO_ES);
        i_video += (item->es[i]->i_cat == VIDEO_ES);
799 800
    }

801
    /* Input */
Rafaël Carré's avatar
Rafaël Carré committed
802
    if (sys->color) color_set(C_CATEGORY, NULL);
803
    MainBoxWrite(sys, l++, _("+-[Incoming]"));
Rafaël Carré's avatar
Rafaël Carré committed
804
    if (sys->color) color_set(C_DEFAULT, NULL);
805
    MainBoxWrite(sys, l++, _("| input bytes read : %8.0f KiB"),
806
            (float)(p_stats->i_read_bytes)/1024);
807
    MainBoxWrite(sys, l++, _("| input bitrate    :   %6.0f kb/s"),
808
            p_stats->f_input_bitrate*8000);
809
    MainBoxWrite(sys, l++, _("| demux bytes read : %8.0f KiB"),
810
            (float)(p_stats->i_demux_read_bytes)/1024);
811
    MainBoxWrite(sys, l++, _("| demux bitrate    :   %6.0f kb/s"),
812 813 814
            p_stats->f_demux_bitrate*8000);

    /* Video */
Rafaël Carré's avatar
Rafaël Carré committed
815 816
    if (i_video) {
        if (sys->color) color_set(C_CATEGORY, NULL);
817
        MainBoxWrite(sys, l++, _("+-[Video Decoding]"));
Rafaël Carré's avatar
Rafaël Carré committed
818
        if (sys->color) color_set(C_DEFAULT, NULL);
819
        MainBoxWrite(sys, l++, _("| video decoded    :    %5"PRIi64),
820
                p_stats->i_decoded_video);
821
        MainBoxWrite(sys, l++, _("| frames displayed :    %5"PRIi64),
822
                p_stats->i_displayed_pictures);
823
        MainBoxWrite(sys, l++, _("| frames lost      :    %5"PRIi64),
824 825 826
                p_stats->i_lost_pictures);
    }
    /* Audio*/
Rafaël Carré's avatar
Rafaël Carré committed
827 828
    if (i_audio) {
        if (sys->color) color_set(C_CATEGORY, NULL);
829
        MainBoxWrite(sys, l++, _("+-[Audio Decoding]"));
Rafaël Carré's avatar
Rafaël Carré committed
830
        if (sys->color) color_set(C_DEFAULT, NULL);
831
        MainBoxWrite(sys, l++, _("| audio decoded    :    %5"PRIi64),
832
                p_stats->i_decoded_audio);
833
        MainBoxWrite(sys, l++, _("| buffers played   :    %5"PRIi64),
834
                p_stats->i_played_abuffers);
835
        MainBoxWrite(sys, l++, _("| buffers lost     :    %5"PRIi64),
836
                p_stats->i_lost_abuffers);
837
    }
838
    /* Sout */
Rafaël Carré's avatar
Rafaël Carré committed
839
    if (sys->color) color_set(C_CATEGORY, NULL);
840
    MainBoxWrite(sys, l++, _("+-[Streaming]"));
Rafaël Carré's avatar
Rafaël Carré committed
841
    if (sys->color) color_set(C_DEFAULT, NULL);
842 843
    MainBoxWrite(sys, l++, _("| packets sent     :    %5"PRIi64), p_stats->i_sent_packets);
    MainBoxWrite(sys, l++, _("| bytes sent       : %8.0f KiB"),
844
            (float)(p_stats->i_sent_bytes)/1025);
845
    MainBoxWrite(sys, l++, _("| sending bitrate  :   %6.0f kb/s"),
846
            p_stats->f_send_bitrate*8000);
Rafaël Carré's avatar
Rafaël Carré committed
847
    if (sys->color) color_set(C_DEFAULT, NULL);
848 849

    vlc_mutex_unlock(&p_stats->lock);
Rafaël Carré's avatar
Rafaël Carré committed
850
    vlc_mutex_unlock(&item->lock);
851 852 853

    return l;
}
854

Rafaël Carré's avatar
Rafaël Carré committed
855
static int DrawHelp(intf_thread_t *intf)
856
{
Rafaël Carré's avatar
Rafaël Carré committed
857
    intf_sys_t *sys = intf->p_sys;
858
    int l = 0;
859

Rafaël Carré's avatar
Rafaël Carré committed
860
#define H(a) MainBoxWrite(sys, l++, a)
861

Rafaël Carré's avatar
Rafaël Carré committed
862
    if (sys->color) color_set(C_CATEGORY, NULL);
863
    H(_("[Display]"));