ncurses.c 53.1 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

37
#define _XOPEN_SOURCE_EXTENDED 1
38 39

#include <assert.h>
40
#include <wchar.h>
41
#include <sys/stat.h>
42
#include <math.h>
43
#include <errno.h>
44

45
#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
46 47
#include <vlc_common.h>
#include <vlc_plugin.h>
48

49 50
#include <ncurses.h>

51
#include <vlc_interface.h>
52
#include <vlc_vout.h>
53
#include <vlc_charset.h>
54
#include <vlc_input.h>
55
#include <vlc_es.h>
56
#include <vlc_playlist.h>
57
#include <vlc_meta.h>
58
#include <vlc_fs.h>
Pierre Ynard's avatar
Pierre Ynard committed
59
#include <vlc_url.h>
60 61 62 63

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

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
70 71

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

76
vlc_module_begin ()
Rafaël Carré's avatar
Rafaël Carré committed
77 78 79 80 81 82 83
    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")
84
    add_directory("browse-dir", NULL, BROWSE_TEXT, BROWSE_LONGTEXT)
85
vlc_module_end ()
86

87 88
#include "eject.c"

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

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

121
enum
Rémi Duraffort's avatar
Rémi Duraffort committed
122 123 124 125 126 127 128 129 130 131 132 133 134
{
    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,
135
    C_FOLDER,
136
    /* XXX: new elements here ! */
137 138 139 140 141 142 143 144 145 146

    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
147
    /* jamaican playlist, for rastafari sisters & brothers! */
148 149 150 151 152 153 154 155 156 157 158 159 160 161
    [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
162

163 164 165 166
    /* 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
167
};
168

169 170
struct dir_entry_t
{
Rafaël Carré's avatar
Rafaël Carré committed
171 172
    bool        file;
    char        *path;
173
};
174

175 176
struct pl_item_t
{
177 178
    input_item_t *item;
    char         *display;
179
};
180

181 182
struct intf_sys_t
{
183
    vlc_thread_t    thread;
184

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

187 188 189 190 191
    /* rgb values for the color yellow */
    short           yellow_r;
    short           yellow_g;
    short           yellow_b;

Rafaël Carré's avatar
Rafaël Carré committed
192 193 194 195 196 197
    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
198

199 200 201
    struct
    {
        int              type;
202
        vlc_log_t       *item;
203 204
        char            *msg;
    } msgs[50];      // ring buffer
Rafaël Carré's avatar
Rafaël Carré committed
205
    int                 i_msgs;
Rafaël Carré's avatar
Rafaël Carré committed
206
    int                 verbosity;
Rafaël Carré's avatar
Rafaël Carré committed
207
    vlc_mutex_t         msg_lock;
208

209
    /* Search Box context */
Rafaël Carré's avatar
Rafaël Carré committed
210
    char            search_chain[20];
211

212
    /* Open Box Context */
Rafaël Carré's avatar
Rafaël Carré committed
213
    char            open_chain[50];
214

215
    /* File Browser context */
Rafaël Carré's avatar
Rafaël Carré committed
216 217 218 219
    char            *current_dir;
    int             n_dir_entries;
    struct dir_entry_t  **dir_entries;
    bool            show_hidden_files;
220

221
    /* Playlist context */
Rafaël Carré's avatar
Rafaël Carré committed
222 223 224 225
    struct pl_item_t    **plist;
    int             plist_entries;
    bool            need_update;
    bool            plidx_follow;
226
    input_item_t    *node;        /* current node */
227 228 229
};

/*****************************************************************************
230
 * Directories
231
 *****************************************************************************/
232

Rafaël Carré's avatar
Rafaël Carré committed
233
static void DirsDestroy(intf_sys_t *sys)
234
{
Rafaël Carré's avatar
Rafaël Carré committed
235 236 237 238
    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);
239
    }
Rafaël Carré's avatar
Rafaël Carré committed
240 241
    free(sys->dir_entries);
    sys->dir_entries = NULL;
242 243
}

Rafaël Carré's avatar
Rafaël Carré committed
244
static int comdir_entries(const void *a, const void *b)
245
{
Rafaël Carré's avatar
Rafaël Carré committed
246 247
    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
248

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

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

Rafaël Carré's avatar
Rafaël Carré committed
255 256 257 258 259
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
260 261
    if (asprintf(&uri, "%s" DIR_SEP "%s", current_dir, entry) != -1) {
        struct stat st;
Rafaël Carré's avatar
Rafaël Carré committed
262 263 264 265 266 267 268
        ret = vlc_stat(uri, &st) || !S_ISDIR(st.st_mode);
        free(uri);
    }
#endif
    return ret;
}

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

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

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

Rafaël Carré's avatar
Rafaël Carré committed
285
    DirsDestroy(sys);
Rafaël Carré's avatar
Rafaël Carré committed
286

287
    const char *entry;
Rafaël Carré's avatar
Rafaël Carré committed
288 289
    while ((entry = vlc_readdir(current_dir))) {
        if (!sys->show_hidden_files && *entry == '.' && strcmp(entry, ".."))
290
            continue;
Rémi Duraffort's avatar
Rémi Duraffort committed
291

Rafaël Carré's avatar
Rafaël Carré committed
292
        struct dir_entry_t *dir_entry = malloc(sizeof *dir_entry);
293 294
        if (unlikely(dir_entry == NULL))
            continue;
295

Rafaël Carré's avatar
Rafaël Carré committed
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);
299
        continue;
300
    }
Rafaël Carré's avatar
Rafaël Carré committed
301

Rafaël Carré's avatar
Rafaël Carré committed
302
    closedir(current_dir);
303 304 305 306

    if (sys->n_dir_entries > 0)
        qsort(sys->dir_entries, sys->n_dir_entries,
              sizeof(struct dir_entry_t*), &comdir_entries);
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
    while (sys->plist_entries) {
        struct pl_item_t *p_pl_item = sys->plist[--sys->plist_entries];
348 349

        input_item_Release(p_pl_item->item);
Rafaël Carré's avatar
Rafaël Carré committed
350
        free(p_pl_item->display);
Rafaël Carré's avatar
Rafaël Carré committed
351
        free(p_pl_item);
352
    }
Rafaël Carré's avatar
Rafaël Carré committed
353 354
    free(sys->plist);
    sys->plist = NULL;
355 356
}

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

Rafaël Carré's avatar
Rafaël Carré committed
364
    if (!name || !p_pl_item)
365
        goto error;
Rafaël Carré's avatar
Rafaël Carré committed
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 372 373
    if (ret == -1)
        goto error;

374 375 376
    free(name);
    p_pl_item->item = input_item_Hold(p_child->p_input);

377
    TAB_APPEND(sys->plist_entries, sys->plist, p_pl_item);
378 379 380 381

    return true;

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

Rafaël Carré's avatar
Rafaël Carré committed
387
static void PlaylistAddNode(intf_sys_t *sys, playlist_item_t *node,
388 389
                            const char *c)
{
Rafaël Carré's avatar
Rafaël Carré committed
390 391 392 393
    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 ? '`' : '|'))
394
            return;
395

396 397 398
        if (p_child->i_children <= 0)
            continue;

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

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

Rafaël Carré's avatar
Rafaël Carré committed
416
    PlaylistDestroy(sys);
417
    PlaylistAddNode(sys, &p_playlist->root, "");
418 419
}

Rafaël Carré's avatar
Rafaël Carré committed
420
static int ItemChanged(vlc_object_t *p_this, const char *variable,
421
                       vlc_value_t oval, vlc_value_t nval, void *param)
422
{
423
    playlist_t *playlist = (playlist_t *)p_this;
424 425
    intf_sys_t *sys = param;

Rafaël Carré's avatar
Rafaël Carré committed
426
    VLC_UNUSED(p_this); VLC_UNUSED(variable);
427
    VLC_UNUSED(oval); VLC_UNUSED(nval);
428

429
    playlist_Lock(playlist);
Rafaël Carré's avatar
Rafaël Carré committed
430
    sys->need_update = true;
431
    playlist_Unlock(playlist);
432 433 434 435

    return VLC_SUCCESS;
}

Rafaël Carré's avatar
Rafaël Carré committed
436
static int PlaylistChanged(vlc_object_t *p_this, const char *variable,
437 438
                            vlc_value_t oval, vlc_value_t nval, void *param)
{
439 440 441 442 443
    playlist_t *playlist = (playlist_t *)p_this;
    intf_sys_t *sys = param;
    playlist_item_t *node = playlist_CurrentPlayingItem(playlist);

    VLC_UNUSED(variable);
444
    VLC_UNUSED(oval); VLC_UNUSED(nval);
445

Rafaël Carré's avatar
Rafaël Carré committed
446
    sys->need_update = true;
447 448 449 450

    if (sys->node != NULL)
        input_item_Release(sys->node);
    sys->node = (node != NULL) ? input_item_Hold(node->p_input) : NULL;
451

452 453 454 455
    return VLC_SUCCESS;
}

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

463
    return -1;
464 465
}

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

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

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

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

Rafaël Carré's avatar
Rafaël Carré committed
486
static inline bool IsIndex(intf_sys_t *sys, playlist_t *p_playlist, int i)
487
{
488 489
    PL_ASSERT_LOCKED;

490 491 492 493
    input_item_t *input = sys->plist[i]->item;
    playlist_item_t *item = playlist_ItemGetByInput(p_playlist, input);
    if (unlikely(item == NULL))
        return false;
494

495
    if (item->i_children == 0 && input == sys->node)
496 497
        return true;

Rafaël Carré's avatar
Rafaël Carré committed
498
    playlist_item_t *p_played_item = playlist_CurrentPlayingItem(p_playlist);
499 500
    if (p_played_item != NULL)
        return input == p_played_item->p_input;
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
{
531 532
    intf_sys_t *sys = intf->p_sys;

Rafaël Carré's avatar
Rafaël Carré committed
533
    if (!has_colors()) {
534
        sys->color = false;
Rafaël Carré's avatar
Rafaël Carré committed
535
        msg_Warn(intf, "Terminal doesn't support colors");
Rémi Duraffort's avatar
Rémi Duraffort committed
536 537 538 539
        return;
    }

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

    /* untested, in all my terminals, !can_change_color() --funman */
544 545
    if (can_change_color()) {
        color_content(COLOR_YELLOW, &sys->yellow_r, &sys->yellow_g, &sys->yellow_b);
Rafaël Carré's avatar
Rafaël Carré committed
546
        init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
547
    }
Rémi Duraffort's avatar
Rémi Duraffort committed
548 549
}

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

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

Rafaël Carré's avatar
Rafaël Carré committed
558
    if (!title) title = "";
Rafaël Carré's avatar
Rafaël Carré committed
559
    int len = strlen(title);
560

Rafaël Carré's avatar
Rafaël Carré committed
561 562
    if (len > w - 2)
        len = w - 2;
563

564
    mvaddch(y, 0,    ACS_ULCORNER);
Rafaël Carré's avatar
Rafaël Carré committed
565 566 567
    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));
568
    mvaddch(y, w-1,ACS_URCORNER);
569

Rafaël Carré's avatar
Rafaël Carré committed
570
    for (int i = 0; i < h; i++) {
571 572
        mvaddch(++y, 0,   ACS_VLINE);
        mvaddch(y, w-1, ACS_VLINE);
573
    }
Rafaël Carré's avatar
Rafaël Carré committed
574

575 576 577
    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
578
    if (color) color_set(C_DEFAULT, NULL);
579 580
}

581
static void DrawEmptyLine(int y, int x, int w)
582
{
Rafaël Carré's avatar
Rafaël Carré committed
583 584
    if (w <= 0) return;

585
    mvhline(y, x, ' ', w);
586 587
}

588
static void DrawLine(int y, int x, int w)
589
{
Rafaël Carré's avatar
Rafaël Carré committed
590 591 592
    if (w <= 0) return;

    attrset(A_REVERSE);
593
    mvhline(y, x, ' ', w);
Rafaël Carré's avatar
Rafaël Carré committed
594
    attroff(A_REVERSE);
595
}
596

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

Rafaël Carré's avatar
Rafaël Carré committed
603
    if (w <= 0)
604
        return;
605

Rafaël Carré's avatar
Rafaël Carré committed
606
    va_start(vl_args, p_fmt);
607
    int i_ret = vasprintf(&p_buf, p_fmt, vl_args);
Rafaël Carré's avatar
Rafaël Carré committed
608
    va_end(vl_args);
609

610 611 612
    if (i_ret == -1)
        return;

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

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

Rafaël Carré's avatar
Rafaël Carré committed
617
    EnsureUTF8(p_buf);
Rafaël Carré's avatar
Rafaël Carré committed
618
    size_t i_char_len = mbstowcs(wide, p_buf, len);
619

620 621
    size_t i_width; /* number of columns */

Rafaël Carré's avatar
Rafaël Carré committed
622
    if (i_char_len == (size_t)-1) /* an invalid character was encountered */ {
Rafaël Carré's avatar
Rafaël Carré committed
623
        free(p_buf);
624 625
        return;
    }
Rafaël Carré's avatar
Rafaël Carré committed
626

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

Rafaël Carré's avatar
Rafaël Carré committed
638
    if (i_width <= (size_t)w) {
Rafaël Carré's avatar
Rafaël Carré committed
639 640 641 642 643 644 645 646
        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
647 648 649 650 651 652 653
    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
654 655
                /* 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
656 657
                wide[i-1] = '.';
                i_total_width -= wcwidth(wide[i-1]) - 1;
Rafaël Carré's avatar
Rafaël Carré committed
658
            }
659

Rafaël Carré's avatar
Rafaël Carré committed
660 661
            /* find the widest string */
            int j, i_2nd_width = 0;
Rafaël Carré's avatar
Rafaël Carré committed
662 663
            for (j = i_char_len - 1; i_2nd_width < w - i_total_width; j--)
                i_2nd_width += wcwidth(wide[j]);
664

Rafaël Carré's avatar
Rafaël Carré committed
665 666 667 668
            /* 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++;
669

Rafaël Carré's avatar
Rafaël Carré committed
670 671
            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
672
            break;
673
        }
Rafaël Carré's avatar
Rafaël Carré committed
674
        i++;
675
    }
Rafaël Carré's avatar
Rafaël Carré committed
676
    if (w <= 7) /* we don't add the '...' else we lose too much chars */
Rafaël Carré's avatar
Rafaël Carré committed
677
        wide[i] = '\0';
Rafaël Carré's avatar
Rafaël Carré committed
678

Rafaël Carré's avatar
Rafaël Carré committed
679 680 681 682
    size_t i_wlen = wcslen(wide) * 6 + 1; /* worst case */
    char ellipsized[i_wlen];
    wcstombs(ellipsized, wide, i_wlen);
    mvprintw(y, x, "%s", ellipsized);
683

Rafaël Carré's avatar
Rafaël Carré committed
684
    free(p_buf);
685
}
686

Rafaël Carré's avatar
Rafaël Carré committed
687
static void MainBoxWrite(intf_sys_t *sys, int l, const char *p_fmt, ...)
688
{
689
    va_list     vl_args;
Rafaël Carré's avatar
Rafaël Carré committed
690
    char        *p_buf;
Rafaël Carré's avatar
Rafaël Carré committed
691
    bool        b_selected = l == sys->box_idx;
692

Rafaël Carré's avatar
Rafaël Carré committed
693
    if (l < sys->box_start || l - sys->box_start >= sys->box_height)
694 695
        return;

Rafaël Carré's avatar
Rafaël Carré committed
696
    va_start(vl_args, p_fmt);
697
    int i_ret = vasprintf(&p_buf, p_fmt, vl_args);
Rafaël Carré's avatar
Rafaël Carré committed
698
    va_end(vl_args);
699 700
    if (i_ret == -1)
        return;
701

702
    if (b_selected) attron(A_REVERSE);
Rafaël Carré's avatar
Rafaël Carré committed
703
    mvnprintw(sys->box_y + l - sys->box_start, 1, COLS - 2, "%s", p_buf);
704 705
    if (b_selected) attroff(A_REVERSE);

Rafaël Carré's avatar
Rafaël Carré committed
706
    free(p_buf);
707 708
}

Rafaël Carré's avatar
Rafaël Carré committed
709
static int SubDrawObject(intf_sys_t *sys, int l, vlc_object_t *p_obj, int i_level, const char *prefix)
710
{
Rafaël Carré's avatar
Rafaël Carré committed
711 712
    char *name = vlc_object_get_name(p_obj);
    MainBoxWrite(sys, l++, "%*s%s%s \"%s\" (%p)", 2 * i_level++, "", prefix,
713
                  p_obj->obj.object_type, name ? name : "", (void *)p_obj);
Rafaël Carré's avatar
Rafaël Carré committed
714 715
    free(name);

716 717 718 719 720 721 722 723 724 725 726 727 728
    size_t count = 0, size;
    vlc_object_t **tab = NULL;

    do {
        size = count;
        tab = xrealloc(tab, size * sizeof (*tab));
        count = vlc_list_children(p_obj, tab, size);
    } while (size < count);

    for (size_t i = 0; i < count ; i++) {
        l = SubDrawObject(sys, l, tab[i], i_level,
            (i == count - 1) ? "`-" : "|-" );
        vlc_object_release(tab[i]);
729
    }
730
    free(tab);
731
    return l;
732 733
}

734
static int DrawObjects(intf_thread_t *intf, input_thread_t *input)
735
{
736
    (void) input;
737
    return SubDrawObject(intf->p_sys, 0, VLC_OBJECT(intf->obj.libvlc), 0, "");
738 739
}

740
static int DrawMeta(intf_thread_t *intf, input_thread_t *p_input)
741
{
Rafaël Carré's avatar
Rafaël Carré committed
742 743
    intf_sys_t *sys = intf->p_sys;
    input_item_t *item;
744
    int l = 0;
745

746 747
    if (!p_input)
        return 0;
748

Rafaël Carré's avatar
Rafaël Carré committed
749 750 751 752 753
    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)
754
            continue;
755

Rafaël Carré's avatar
Rafaël Carré committed
756 757 758 759
        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);
760
    }
Rafaël Carré's avatar
Rafaël Carré committed
761
    vlc_mutex_unlock(&item->lock);
762

763 764
    return l;
}
765

766
static int DrawInfo(intf_thread_t *intf, input_thread_t *p_input)
767
{
Rafaël Carré's avatar
Rafaël Carré committed
768 769
    intf_sys_t *sys = intf->p_sys;
    input_item_t *item;
770
    int l = 0;
771

772 773 774
    if (!p_input)
        return 0;

Rafaël Carré's avatar
Rafaël Carré committed
775 776 777 778 779 780 781 782
    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++) {
783
            info_t *p_info = p_category->pp_infos[j];
Rafaël Carré's avatar
Rafaël Carré committed
784
            MainBoxWrite(sys, l++, _("      %s: %s"),
785 786 787
                         p_info->psz_name, p_info->psz_value);
        }
    }
Rafaël Carré's avatar
Rafaël Carré committed
788
    vlc_mutex_unlock(&item->lock);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
789

790 791
    return l;
}
792

793
static int DrawStats(intf_thread_t *intf, input_thread_t *p_input)
794
{
Rafaël Carré's avatar
Rafaël Carré committed
795 796
    intf_sys_t *sys = intf->p_sys;
    input_item_t *item;
797 798
    input_stats_t *p_stats;
    int l = 0, i_audio = 0, i_video = 0;
799

800 801
    if (!p_input)
        return 0;
802

Rafaël Carré's avatar
Rafaël Carré committed
803 804
    item = input_GetItem(p_input);
    assert(item);
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
805

Rafaël Carré's avatar
Rafaël Carré committed
806 807
    vlc_mutex_lock(&item->lock);
    p_stats = item->p_stats;
808

Rafaël Carré's avatar
Rafaël Carré committed
809 810 811
    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);
812 813
    }

814
    /* Input */
Rafaël Carré's avatar
Rafaël Carré committed
815
    if (sys->color) color_set(C_CATEGORY, NULL);
816
    MainBoxWrite(sys, l++, _("+-[Incoming]"));
Rafaël Carré's avatar
Rafaël Carré committed
817
    if (sys->color) color_set(C_DEFAULT, NULL);
818
    MainBoxWrite(sys, l++, _("| input bytes read : %8.0f KiB"),
819
            (float)(p_stats->i_read_bytes)/1024);
820
    MainBoxWrite(sys, l++, _("| input bitrate    :   %6.0f kb/s"),
821
            p_stats->f_input_bitrate*8000);
822
    MainBoxWrite(sys, l++, _("| demux bytes read : %8.0f KiB"),
823
            (float)(p_stats->i_demux_read_bytes)/1024);
824
    MainBoxWrite(sys, l++, _("| demux bitrate    :   %6.0f kb/s"),
825 826 827
            p_stats->f_demux_bitrate*8000);

    /* Video */
Rafaël Carré's avatar
Rafaël Carré committed
828 829
    if (i_video) {
        if (sys->color) color_set(C_CATEGORY, NULL);
830
        MainBoxWrite(sys, l++, _("+-[Video Decoding]"));
Rafaël Carré's avatar
Rafaël Carré committed
831
        if (sys->color) color_set(C_DEFAULT, NULL);
832
        MainBoxWrite(sys, l++, _("| video decoded    :    %5"PRIi64),
833
                p_stats->i_decoded_video);
834
        MainBoxWrite(sys, l++, _("| frames displayed :    %5"PRIi64),
835
                p_stats->i_displayed_pictures);
836
        MainBoxWrite(sys, l++, _("| frames lost      :    %5"PRIi64),
837 838 839
                p_stats->i_lost_pictures);
    }
    /* Audio*/
Rafaël Carré's avatar
Rafaël Carré committed
840 841
    if (i_audio) {
        if (sys->color) color_set(C_CATEGORY, NULL);
842
        MainBoxWrite(sys, l++, _("+-[Audio Decoding]"));
Rafaël Carré's avatar
Rafaël Carré committed
843
        if (sys->color) color_set(C_DEFAULT, NULL);
844
        MainBoxWrite(sys, l++, _("| audio decoded    :    %5"PRIi64),
845
                p_stats->i_decoded_audio);
846
        MainBoxWrite(sys, l++, _("| buffers played   :    %5"PRIi64),
847
                p_stats->i_played_abuffers);
848
        MainBoxWrite(sys, l++, _("| buffers lost     :    %5"PRIi64),
849
                p_stats->i_lost_abuffers);
850
    }
Rafaël Carré's avatar
Rafaël Carré committed
851
    if (sys->color) color_set(C_DEFAULT, NULL);
852

Rafaël Carré's avatar
Rafaël Carré committed
853
    vlc_mutex_unlock(&item->lock);
854 855 856

    return l;
}
857

858
static int DrawHelp(intf_thread_t *intf, input_thread_t *input)
859
{
Rafaël Carré's avatar
Rafaël Carré committed
860
    intf_sys_t *sys = intf->p_sys;
861
    int l = 0;
862

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

Rafaël Carré's avatar
Rafaël Carré committed
865
    if (sys->color) color_set(C_CATEGORY, NULL);
866
    H(_("[Display]"));
Rafaël Carré's avatar
Rafaël Carré committed
867
    if (sys->color) color_set(C_DEFAULT, NULL);
868 869
    H(_(" h,H                    Show/Hide help box"));
    H(_(" i                      Show/Hide info box"));
870
    H(_(" M                      Show/Hide metadata box"));
871 872 873 874 875 876 877 878
    H(_(" L                      Show/Hide messages box"));
    H(_(" P                      Show/Hide playlist box"));
    H(_(" B                      Show/Hide filebrowser"));
    H(_(" x                      Show/Hide objects box"));
    H(_(" S                      Show/Hide statistics box"));
    H(_(" Esc                    Close Add/Search entry"));
    H(_(" Ctrl-l                 Refresh the screen"));
    H("");
879

Rafaël Carré's avatar
Rafaël Carré committed
880
    if (sys->color) color_set(C_CATEGORY, NULL);
881
    H(_("[Global]"));
Rafaël Carré's avatar
Rafaël Carré committed
882
    if (sys->color) color_set(C_DEFAULT, NULL);
883 884 885 886
    H(_(" q, Q, Esc              Quit"));
    H(_(" s                      Stop"));
    H(_(" <space>                Pause/Play"));
    H(_(" f                      Toggle Fullscreen"));
887 888 889
    H(_(" c                      Cycle through audio tracks"));
    H(_(" v                      Cycle through subtitles tracks"));
    H(_(" b                      Cycle through video tracks"));
890 891 892 893 894 895
    H(_(" n, p                   Next/Previous playlist item"));
    H(_(" [, ]                   Next/Previous title"));
    H(_(" <, >                   Next/Previous chapter"));
    /* xgettext: You can use ← and → characters */
    H(_(" <left>,<right>         Seek -/+ 1%%"));
    H(_(" a, z                   Volume Up/Down"));
896
    H(_(" m                      Mute"));
897 898 899 900 901 902 903
    /* xgettext: You can use ↑ and ↓ characters */
    H(_(" <up>,<down>            Navigate through the box line by line"));
    /* xgettext: You can use ⇞ and ⇟ characters */
    H(_(" <pageup>,<pagedown>    Navigate through the box page by page"));
    /* xgettext: You can use ↖ and ↘ characters */
    H(_(" <start>,<end>          Navigate to start/end of box"));
    H("");
904

Rafaël Carré's avatar
Rafaël Carré committed
905
    if (sys->color) color_set(C_CATEGORY, NULL);
906
    H(_("[Playlist]"));
Rafaël Carré's avatar
Rafaël Carré committed
907
    if (sys->color) color_set(C_DEFAULT, NULL);
908 909 910 911 912 913 914
    H(_(" r                      Toggle Random playing"));
    H(_(" l                      Toggle Loop Playlist"));
    H(_(" R                      Toggle Repeat item"));
    H(_(" o                      Order Playlist by title"));
    H(_(" O                      Reverse order Playlist by title"));
    H(_(" g                      Go to the current playing item"));
    H(_(" /                      Look for an item"));
915
    H(_(" ;                      Look for the next item"));
916 917 918 919 920
    H(_(" A                      Add an entry"));
    /* xgettext: You can use ⌫ character to translate <backspace> */
    H(_(" D, <backspace>, <del>  Delete an entry"));
    H(_(" e                      Eject (if stopped)"));
    H("");
921

Rafaël Carré's avatar
Rafaël Carré committed
922
    if (sys->color) color_set(C_CATEGORY, NULL);
923
    H(_("[Filebrowser]"));
Rafaël Carré's avatar
Rafaël Carré committed
924
    if (sys->color) color_set(C_DEFAULT, NULL);
925 926 927 928
    H(_(" <enter>                Add the selected file to the playlist"));
    H(_(" <space>                Add the selected directory to the playlist"));
    H(_(" .                      Show/Hide hidden files"));
    H("");
929

Rafaël Carré's avatar
Rafaël Carré committed
930
    if (sys->color) color_set(C_CATEGORY, NULL);
931
    H(_("[Player]"));
Rafaël Carré's avatar
Rafaël Carré committed
932
    if (sys->color) color_set(C_DEFAULT, NULL);
933 934
    /* xgettext: You can use ↑ and ↓ characters */
    H(_(" <up>,<down>            Seek +/-5%%"));
935

936
#undef H
937
    (void) input;
938 939
    return l;
}
940

941
static int DrawBrowse(intf_thread_t *intf, input_thread_t *input)
942
{
Rafaël Carré's avatar
Rafaël Carré committed
943
    intf_sys_t *sys = intf->p_sys;
944

Rafaël Carré's avatar
Rafaël Carré committed
945 946 947
    for (int i = 0; i < sys->n_dir_entries; i++) {
        struct dir_entry_t *dir_entry = sys->dir_entries[i];
        char type = dir_entry->file ? ' ' : '+';
Rafaël Carré's avatar
Rafaël Carré committed
948

Rafaël Carré's avatar
Rafaël Carré committed
949 950 951
        if (sys->color)
            color_set(dir_entry->file ? C_DEFAULT : C_FOLDER, NULL);
        MainBoxWrite(sys, i, " %c %s", type, dir_entry->path);
952
    }
Rafaël Carré's avatar
Rafaël Carré committed
953

954
    (void) input;
Rafaël Carré's avatar
Rafaël Carré committed
955
    return sys->n_dir_entries;
956
}
Rafaël Carré's avatar
Rafaël Carré committed
957

958
static int DrawPlaylist(intf_thread_t *intf, input_thread_t *input)
959
{
Rafaël Carré's avatar
Rafaël Carré committed
960 961
    intf_sys_t *sys = intf->p_sys;
    playlist_t *p_playlist = pl_Get(intf);
Rafaël Carré's avatar
Rafaël Carré committed
962

963
    PL_LOCK;
Rafaël Carré's avatar
Rafaël Carré committed
964 965 966
    if (sys->need_update) {
        PlaylistRebuild(intf);
        sys->need_update = false;
967
    }
968
    PL_UNLOCK;
Rafaël Carré's avatar
Rafaël Carré committed
969

Rafaël Carré's avatar
Rafaël Carré committed
970 971
    if (sys->plidx_follow)
        FindIndex(sys, p_playlist);
Rafaël Carré's avatar
Rafaël Carré committed
972

Rafaël Carré's avatar
Rafaël Carré committed
973
    for (int i = 0; i < sys->plist_entries; i++) {
974
        char c;
975
        playlist_item_t *current;
976
        input_item_t *item = sys->plist[i]->item;
977

978
        PL_LOCK;
979 980
        current = playlist_CurrentPlayingItem(p_playlist);

981 982
        if ((sys->node != NULL && item == sys->node) ||
            (sys->node == NULL && current != NULL && item == current->p_input))
983
            c = '*';
984
        else if (current != NULL && current->p_input == item)
985
            c = '>';
986 987
        else
            c = ' ';
988
        PL_UNLOCK;
989

Rafaël Carré's avatar
Rafaël Carré committed
990 991 992
        if (sys->color) color_set(i%3 + C_PLAYLIST_1, NULL);
        MainBoxWrite(sys, i, "%c%s", c, sys->plist[i]->display);
        if (sys->color) color_set(C_DEFAULT, NULL);
993
    }
994

995
    (void) input;
Rafaël Carré's avatar
Rafaël Carré committed
996
    return sys->plist_entries;
997
}