ncurses.c 57.2 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>
hartman's avatar
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
dionoea's avatar
dionoea committed
25
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
26
27
 *****************************************************************************/

28
/* UTF8 locale is required */
ivoire's avatar
ivoire committed
29

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

37
#include <vlc_common.h>
38
#include <vlc_plugin.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
39

Rafaël Carré's avatar
Rafaël Carré committed
40
41
#define _XOPEN_SOURCE_EXTENDED 1
#include <wchar.h>
42

43
44
#include <ncurses.h>

zorglub's avatar
zorglub committed
45
#include <vlc_interface.h>
46
#include <vlc_vout.h>
47
#include <vlc_aout_intf.h>
zorglub's avatar
zorglub committed
48
#include <vlc_charset.h>
49
#include <vlc_input.h>
Rafaël Carré's avatar
Rafaël Carré committed
50
#include <vlc_es.h>
51
#include <vlc_playlist.h>
52
#include <vlc_meta.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
53
#include <vlc_fs.h>
Pierre Ynard's avatar
Pierre Ynard committed
54
#include <vlc_url.h>
55

56
57
#include <assert.h>

hartman's avatar
hartman committed
58
59
60
61
#ifdef HAVE_SYS_STAT_H
#   include <sys/stat.h>
#endif

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

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
hartman's avatar
hartman committed
71
72

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

77
vlc_module_begin ()
Rafaël Carré's avatar
Rafaël Carré committed
78
79
80
81
82
83
84
85
    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)
86
vlc_module_end ()
87
88
89
90

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

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

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

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

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

hartman's avatar
hartman committed
168
169
struct dir_entry_t
{
170
    bool        b_file;
hartman's avatar
hartman committed
171
    char        *psz_path;
172
};
173

174
175
176
177
178
struct pl_item_t
{
    playlist_item_t *p_item;
    char            *psz_display;
};
179

180
181
struct intf_sys_t
{
182
183
    input_thread_t *p_input;

184
    bool            b_color;
Rafaël Carré's avatar
Rafaël Carré committed
185
    bool            b_exit;
ivoire's avatar
ivoire committed
186

187
    int             i_box_type;
188
189
190
191
192
    int             i_box_y;            // start of box content
    int             i_box_height;
    int             i_box_lines_total;  // number of lines in the box
    int             i_box_start;        // first line of box displayed
    int             i_box_idx;          // selected line
193

Rafaël Carré's avatar
Rafaël Carré committed
194
195
196
    msg_subscription_t  *p_sub;         // message bank subscription
    msg_item_t          *msgs[50];      // ring buffer
    int                 i_msgs;
197
    int                 i_verbosity;
Rafaël Carré's avatar
Rafaël Carré committed
198
    vlc_mutex_t         msg_lock;
199

200
    /* Search Box context */
Rafaël Carré's avatar
Rafaël Carré committed
201
202
    char            psz_search_chain[20];
    char            *psz_old_search;
203
204
    int             i_before_search;

205
    /* Open Box Context */
Rafaël Carré's avatar
Rafaël Carré committed
206
    char            psz_open_chain[50];
207

208
    /* File Browser context */
hartman's avatar
hartman committed
209
210
211
    char            *psz_current_dir;
    int             i_dir_entries;
    struct dir_entry_t  **pp_dir_entries;
212
    bool            b_show_hidden_files;
213

214
    /* Playlist context */
215
216
    struct pl_item_t    **pp_plist;
    int             i_plist_entries;
217
    bool            b_need_update;
218
    vlc_mutex_t     pl_lock;
219
220
221
    bool            b_plidx_follow;
    playlist_item_t *p_node;        /* current node */

222
223
224
};

/*****************************************************************************
225
 * Directories
226
 *****************************************************************************/
227

228
229
230
231
232
233
234
235
236
237
238
239
240
241
static void DirsDestroy(intf_sys_t *p_sys)
{
    while (p_sys->i_dir_entries)
    {
        struct dir_entry_t *p_dir_entry;
        p_dir_entry = p_sys->pp_dir_entries[--p_sys->i_dir_entries];
        free(p_dir_entry->psz_path);
        free(p_dir_entry);
    }
    free(p_sys->pp_dir_entries);
    p_sys->pp_dir_entries = NULL;
}

static int comp_dir_entries(const void *pp_dir_entry1, const void *pp_dir_entry2)
242
{
243
244
    struct dir_entry_t *p_dir_entry1 = *(struct dir_entry_t**)pp_dir_entry1;
    struct dir_entry_t *p_dir_entry2 = *(struct dir_entry_t**)pp_dir_entry2;
Rafaël Carré's avatar
Rafaël Carré committed
245
246
247

    if (p_dir_entry1->b_file == p_dir_entry2->b_file)
        return strcasecmp(p_dir_entry1->psz_path, p_dir_entry2->psz_path);
248
249

    return p_dir_entry1->b_file ? 1 : -1;
250
}
251

Rafaël Carré's avatar
Rafaël Carré committed
252
253
254
255
256
257
258
static bool IsFile(const char *current_dir, const char *entry)
{
    bool ret = true;
#ifdef S_ISDIR
    char *uri;
    struct stat st;

Pierre Ynard's avatar
Pierre Ynard committed
259
    if (asprintf(&uri, "%s" DIR_SEP "%s", current_dir, entry) != -1)
Rafaël Carré's avatar
Rafaël Carré committed
260
261
262
263
264
265
266
267
    {
        ret = vlc_stat(uri, &st) || !S_ISDIR(st.st_mode);
        free(uri);
    }
#endif
    return ret;
}

Rafaël Carré's avatar
Rafaël Carré committed
268
static void ReadDir(intf_thread_t *p_intf)
269
270
271
{
    intf_sys_t *p_sys = p_intf->p_sys;
    DIR *p_current_dir;
ivoire's avatar
ivoire committed
272

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

Rafaël Carré's avatar
Rafaël Carré committed
279
    char *psz_entry;
ivoire's avatar
ivoire committed
280

Rafaël Carré's avatar
Rafaël Carré committed
281
282
283
284
285
286
287
288
289
290
291
292
    /* Open the dir */
    p_current_dir = vlc_opendir(p_sys->psz_current_dir);

    if (!p_current_dir)
    {
        /* something went bad, get out of here ! */
        msg_Warn(p_intf, "cannot open directory `%s' (%m)",
                  p_sys->psz_current_dir);
        return;
    }

    /* Clean the old shit */
293
    DirsDestroy(p_sys);
Rafaël Carré's avatar
Rafaël Carré committed
294
295
296
297
298
299

    /* while we still have entries in the directory */
    while ((psz_entry = vlc_readdir(p_current_dir)))
    {
        struct dir_entry_t *p_dir_entry;

Rafaël Carré's avatar
Rafaël Carré committed
300
301
302
        if (!p_sys->b_show_hidden_files)
            if (*psz_entry == '.' && strcmp(psz_entry, ".."))
                goto next;
ivoire's avatar
ivoire committed
303

Rafaël Carré's avatar
Rafaël Carré committed
304
305
        if (!(p_dir_entry = malloc(sizeof *p_dir_entry)))
            goto next;
306

Rafaël Carré's avatar
Rafaël Carré committed
307
        p_dir_entry->b_file = IsFile(p_sys->psz_current_dir, psz_entry);
308
        p_dir_entry->psz_path = psz_entry;
Rafaël Carré's avatar
Rafaël Carré committed
309
310
        INSERT_ELEM(p_sys->pp_dir_entries, p_sys->i_dir_entries,
             p_sys->i_dir_entries, p_dir_entry);
311
        continue;
312

Rafaël Carré's avatar
Rafaël Carré committed
313
next:
Rafaël Carré's avatar
Rafaël Carré committed
314
        free(psz_entry);
hartman's avatar
hartman committed
315
    }
Rafaël Carré's avatar
Rafaël Carré committed
316
317
318
319
320
321

    /* Sort */
    qsort(p_sys->pp_dir_entries, p_sys->i_dir_entries,
           sizeof(struct dir_entry_t*), &comp_dir_entries);

    closedir(p_current_dir);
322
323
324
}

/*****************************************************************************
325
 * Adjust index position after a change (list navigation or item switching)
326
 *****************************************************************************/
327
328
static void CheckIdx(intf_sys_t *p_sys)
{
Rafaël Carré's avatar
Rafaël Carré committed
329
    int lines = p_sys->i_box_lines_total;
330
    int height = LINES - p_sys->i_box_y - 2;
331
332
    if (height > lines - 1)
        height = lines - 1;
333
334
335
336
337
338
339

    /* make sure the new index is within the box */
    if (p_sys->i_box_idx <= 0)
    {
        p_sys->i_box_idx = 0;
        p_sys->i_box_start = 0;
    }
340
    else if (p_sys->i_box_idx >= lines - 1 && lines > 0)
341
    {
342
        p_sys->i_box_idx = lines - 1;
343
344
        p_sys->i_box_start = p_sys->i_box_idx - height;
    }
345

346
    /* Fix box start (1st line of the box displayed) */
Rafaël Carré's avatar
Rafaël Carré committed
347
    if (p_sys->i_box_idx < p_sys->i_box_start ||
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
        p_sys->i_box_idx > height + p_sys->i_box_start + 1)
    {
        p_sys->i_box_start = p_sys->i_box_idx - height/2;
        if (p_sys->i_box_start < 0)
            p_sys->i_box_start = 0;
    }
    else if (p_sys->i_box_idx == p_sys->i_box_start - 1)
        p_sys->i_box_start--;
    else if (p_sys->i_box_idx == height + p_sys->i_box_start + 1)
        p_sys->i_box_start++;
}

/*****************************************************************************
 * Playlist
 *****************************************************************************/
363
static void PlaylistDestroy(intf_sys_t *p_sys)
364
{
Rafaël Carré's avatar
Rafaël Carré committed
365
    while (p_sys->i_plist_entries)
366
    {
367
        struct pl_item_t *p_pl_item = p_sys->pp_plist[--p_sys->i_plist_entries];
Rafaël Carré's avatar
Rafaël Carré committed
368
369
        free(p_pl_item->psz_display);
        free(p_pl_item);
370
    }
371
    free(p_sys->pp_plist);
372
    p_sys->pp_plist = NULL;
373
374
}

375
376
377
378
379
380
static bool PlaylistAddChild(intf_sys_t *p_sys, playlist_item_t *p_child,
                             const char *c, const char d)
{
    int ret;
    char *psz_name = input_item_GetTitleFbName(p_child->p_input);
    struct pl_item_t *p_pl_item = malloc(sizeof *p_pl_item);
381

382
383
    if (!psz_name || !p_pl_item)
        goto error;
Rafaël Carré's avatar
Rafaël Carré committed
384

385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
    p_pl_item->p_item = p_child;

    if (c && *c)
        ret = asprintf(&p_pl_item->psz_display, "%s%c-%s", c, d, psz_name);
    else
        ret = asprintf(&p_pl_item->psz_display, " %s", psz_name);

    free(psz_name);
    psz_name = NULL;

    if (ret == -1)
        goto error;

    INSERT_ELEM(p_sys->pp_plist, p_sys->i_plist_entries,
                 p_sys->i_plist_entries, p_pl_item);

    return true;

error:
    free(psz_name);
    free(p_pl_item);
    return false;
}

static void PlaylistAddNode(intf_sys_t *p_sys, playlist_item_t *p_node,
                            const char *c)
{
    for(int k = 0; k < p_node->i_children; k++)
    {
        playlist_item_t *p_child = p_node->pp_children[k];
        char d = k == p_node->i_children - 1 ? '`' : '|';
        if(!PlaylistAddChild(p_sys, p_child, c, d))
417
            return;
418

419
420
421
422
        if (p_child->i_children <= 0)
            continue;

        if (*c)
423
        {
424
            char *psz_tmp;
Rafaël Carré's avatar
Rafaël Carré committed
425
426
            if (asprintf(&psz_tmp, "%s%c ", c,
                     k == p_node->i_children - 1 ? ' ' : '|') == -1)
427
                return;
428
            PlaylistAddNode(p_sys, p_child, psz_tmp);
Rafaël Carré's avatar
Rafaël Carré committed
429
            free(psz_tmp);
430
        }
431
432
        else
            PlaylistAddNode(p_sys, p_child, " ");
433
434
    }
}
435

Rafaël Carré's avatar
Rafaël Carré committed
436
static void PlaylistRebuild(intf_thread_t *p_intf)
437
438
{
    intf_sys_t *p_sys = p_intf->p_sys;
Rafaël Carré's avatar
Rafaël Carré committed
439
    playlist_t *p_playlist = pl_Get(p_intf);
440
441

    PL_LOCK;
442
    PlaylistDestroy(p_sys);
443
    PlaylistAddNode(p_sys, p_playlist->p_root_onelevel, "");
444
445
446
    PL_UNLOCK;
}

447
static int ItemChanged(vlc_object_t *p_this, const char *psz_variable,
Rafaël Carré's avatar
Rafaël Carré committed
448
                            vlc_value_t oval, vlc_value_t nval, void *param)
449
450
451
{
    VLC_UNUSED(p_this); VLC_UNUSED(psz_variable);
    VLC_UNUSED(oval); VLC_UNUSED(nval);
452

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    intf_thread_t *p_intf   = (intf_thread_t *)param;
    intf_sys_t *p_sys       = p_intf->p_sys;

    vlc_mutex_lock(&p_sys->pl_lock);
    p_sys->b_need_update = true;
    vlc_mutex_unlock(&p_sys->pl_lock);

    return VLC_SUCCESS;
}

static int PlaylistChanged(vlc_object_t *p_this, const char *psz_variable,
                            vlc_value_t oval, vlc_value_t nval, void *param)
{
    VLC_UNUSED(p_this); VLC_UNUSED(psz_variable);
    VLC_UNUSED(oval); VLC_UNUSED(nval);
468
469
470
471
    intf_thread_t *p_intf   = (intf_thread_t *)param;
    intf_sys_t *p_sys       = p_intf->p_sys;
    playlist_item_t *p_node = playlist_CurrentPlayingItem(pl_Get(p_intf));

472
    vlc_mutex_lock(&p_sys->pl_lock);
473
474
    p_sys->b_need_update = true;
    p_sys->p_node = p_node ? p_node->p_parent : NULL;
475
    vlc_mutex_unlock(&p_sys->pl_lock);
476

477
478
479
480
    return VLC_SUCCESS;
}

/* Playlist suxx */
481
static int SubSearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring,
Rafaël Carré's avatar
Rafaël Carré committed
482
                              int i_start, int i_stop)
483
{
484
    for(int i = i_start + 1; i < i_stop; i++)
Rafaël Carré's avatar
Rafaël Carré committed
485
        if (strcasestr(p_sys->pp_plist[i]->psz_display, psz_searchstring))
486
            return i;
487

488
    return -1;
489
490
}

491
static void SearchPlaylist(intf_sys_t *p_sys, char *psz_searchstring)
492
{
493
    int i_item, i_first = p_sys->i_before_search;
494

495
496
    if (i_first < 0)
        i_first = 0;
497

498
    if (!psz_searchstring || !*psz_searchstring)
499
    {
500
        p_sys->i_box_idx = p_sys->i_before_search;
501
502
503
        return;
    }

504
505
    i_item = SubSearchPlaylist(p_sys, psz_searchstring, i_first + 1,
                               p_sys->i_plist_entries);
Rafaël Carré's avatar
Rafaël Carré committed
506
    if (i_item < 0)
507
        i_item = SubSearchPlaylist(p_sys, psz_searchstring, 0, i_first);
508

509
    if (i_item > 0)
510
511
512
513
    {
        p_sys->i_box_idx = i_item;
        CheckIdx(p_sys);
    }
514
}
515

516
517
518
static inline bool IsIndex(intf_sys_t *p_sys, playlist_t *p_playlist, int i)
{
    playlist_item_t *p_item = p_sys->pp_plist[i]->p_item;
519
520
521
522
    playlist_item_t *p_played_item;

    PL_ASSERT_LOCKED;

523
524
525
    vlc_mutex_lock(&p_sys->pl_lock);
    if (p_item->i_children == 0 && p_item == p_sys->p_node) {
        vlc_mutex_unlock(&p_sys->pl_lock);
526
        return true;
527
528
    }
    vlc_mutex_unlock(&p_sys->pl_lock);
529
530
531
532
533
534

    p_played_item = playlist_CurrentPlayingItem(p_playlist);
    if (p_played_item && p_item->p_input && p_played_item->p_input)
        return p_item->p_input->i_id == p_played_item->p_input->i_id;

    return false;
535
536
}

537
static void FindIndex(intf_sys_t *p_sys, playlist_t *p_playlist)
538
{
539
540
    int plidx = p_sys->i_box_idx;
    int max = p_sys->i_plist_entries;
541

542
543
544
545
    PL_LOCK;

    if (!IsIndex(p_sys, p_playlist, plidx))
        for(int i = 0; i < max; i++)
546
547
            if (IsIndex(p_sys, p_playlist, i))
            {
548
549
                p_sys->i_box_idx = i;
                CheckIdx(p_sys);
550
551
                break;
            }
552

553
554
555
    PL_UNLOCK;

    p_sys->b_plidx_follow = true;
556
557
}

558
559
560
561
/****************************************************************************
 * Drawing
 ****************************************************************************/

Rafaël Carré's avatar
Rafaël Carré committed
562
static void start_color_and_pairs(intf_thread_t *p_intf)
ivoire's avatar
ivoire committed
563
{
Rafaël Carré's avatar
Rafaël Carré committed
564
    if (!has_colors())
ivoire's avatar
ivoire committed
565
    {
566
        p_intf->p_sys->b_color = false;
Rafaël Carré's avatar
Rafaël Carré committed
567
        msg_Warn(p_intf, "Terminal doesn't support colors");
ivoire's avatar
ivoire committed
568
569
570
571
        return;
    }

    start_color();
572
573
    for(int i = C_DEFAULT + 1; i < C_MAX; i++)
        init_pair(i, color_pairs[i].f, color_pairs[i].b);
ivoire's avatar
ivoire committed
574
575

    /* untested, in all my terminals, !can_change_color() --funman */
Rafaël Carré's avatar
Rafaël Carré committed
576
577
    if (can_change_color())
        init_color(COLOR_YELLOW, 960, 500, 0); /* YELLOW -> ORANGE */
ivoire's avatar
ivoire committed
578
579
}

580
static void DrawBox(int y, int h, bool b_color, const char *title)
581
{
582
    int i_len;
583
    int w = COLS;
584

585
    if (w <= 3 || h <= 0)
Rafaël Carré's avatar
Rafaël Carré committed
586
        return;
587

588
589
    if (b_color) color_set(C_BOX, NULL);

Rafaël Carré's avatar
Rafaël Carré committed
590
591
    if (!title) title = "";
    i_len = strlen(title);
592

Rafaël Carré's avatar
Rafaël Carré committed
593
594
    if (i_len > w - 2)
        i_len = w - 2;
595

596
597
598
599
600
    mvaddch(y, 0,    ACS_ULCORNER);
    mvhline(y, 1,  ACS_HLINE, (w-i_len-2)/2);
    mvprintw(y, 1+(w-i_len-2)/2, "%s", title);
    mvhline(y, (w-i_len)/2+i_len,  ACS_HLINE, w - 1 - ((w-i_len)/2+i_len));
    mvaddch(y, w-1,ACS_URCORNER);
601

602
    for(int i = 0; i < h; i++)
Rafaël Carré's avatar
Rafaël Carré committed
603
    {
604
605
        mvaddch(++y, 0,   ACS_VLINE);
        mvaddch(y, w-1, ACS_VLINE);
606
    }
Rafaël Carré's avatar
Rafaël Carré committed
607

608
609
610
611
    mvaddch(++y, 0,   ACS_LLCORNER);
    mvhline(y,   1,   ACS_HLINE, w - 2);
    mvaddch(y,   w-1, ACS_LRCORNER);
    if (b_color) color_set(C_DEFAULT, NULL);
612
613
}

614
static void DrawEmptyLine(int y, int x, int w)
615
{
Rafaël Carré's avatar
Rafaël Carré committed
616
617
    if (w <= 0) return;

618
    mvhline(y, x, ' ', w);
619
620
}

621
static void DrawLine(int y, int x, int w)
622
{
Rafaël Carré's avatar
Rafaël Carré committed
623
624
625
    if (w <= 0) return;

    attrset(A_REVERSE);
626
    mvhline(y, x, ' ', w);
Rafaël Carré's avatar
Rafaël Carré committed
627
    attroff(A_REVERSE);
628
}
629

Rafaël Carré's avatar
Rafaël Carré committed
630
static void mvnprintw(int y, int x, int w, const char *p_fmt, ...)
631
632
{
    va_list  vl_args;
Rafaël Carré's avatar
Rafaël Carré committed
633
    char    *p_buf;
634
    int      i_len;
hartman's avatar
hartman committed
635

Rafaël Carré's avatar
Rafaël Carré committed
636
    if (w <= 0)
637
        return;
638

Rafaël Carré's avatar
Rafaël Carré committed
639
640
    va_start(vl_args, p_fmt);
    if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
641
        return;
Rafaël Carré's avatar
Rafaël Carré committed
642
    va_end(vl_args);
643

Rafaël Carré's avatar
Rafaël Carré committed
644
    i_len = strlen(p_buf);
645

646
    wchar_t psz_wide[i_len + 1];
647

Rafaël Carré's avatar
Rafaël Carré committed
648
649
    EnsureUTF8(p_buf);
    size_t i_char_len = mbstowcs(psz_wide, p_buf, i_len);
650

651
652
    size_t i_width; /* number of columns */

Rafaël Carré's avatar
Rafaël Carré committed
653
    if (i_char_len == (size_t)-1) /* an invalid character was encountered */
654
    {
Rafaël Carré's avatar
Rafaël Carré committed
655
        free(p_buf);
656
657
        return;
    }
Rafaël Carré's avatar
Rafaël Carré committed
658
659
660

    i_width = wcswidth(psz_wide, i_char_len);
    if (i_width == (size_t)-1)
661
    {
Rafaël Carré's avatar
Rafaël Carré committed
662
663
664
        /* a non printable character was encountered */
        i_width = 0;
        for(unsigned i = 0 ; i < i_char_len ; i++)
665
        {
Rafaël Carré's avatar
Rafaël Carré committed
666
667
668
            int i_cwidth = wcwidth(psz_wide[i]);
            if (i_cwidth != -1)
                i_width += i_cwidth;
669
670
        }
    }
Rafaël Carré's avatar
Rafaël Carré committed
671
672
673
674
675
676
677
678
679
680
681
682

    if (i_width <= (size_t)w)
    {
        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;
    while (i_total_width < w)
683
    {
Rafaël Carré's avatar
Rafaël Carré committed
684
685
        i_total_width += wcwidth(psz_wide[i]);
        if (w > 7 && i_total_width >= w/2)
686
        {
Rafaël Carré's avatar
Rafaël Carré committed
687
688
689
690
            psz_wide[i  ] = '.';
            psz_wide[i+1] = '.';
            i_total_width -= wcwidth(psz_wide[i]) - 2;
            if (i > 0)
691
            {
Rafaël Carré's avatar
Rafaël Carré committed
692
693
694
695
696
                /* we require this check only if at least one character
                 * 4 or more columns wide exists (which i doubt) */
                psz_wide[i-1] = '.';
                i_total_width -= wcwidth(psz_wide[i-1]) - 1;
            }
697

Rafaël Carré's avatar
Rafaël Carré committed
698
699
700
701
            /* find the widest string */
            int j, i_2nd_width = 0;
            for(j = i_char_len - 1; i_2nd_width < w - i_total_width; j--)
                i_2nd_width += wcwidth(psz_wide[j]);
702

Rafaël Carré's avatar
Rafaël Carré committed
703
704
705
706
            /* 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++;
707

Rafaël Carré's avatar
Rafaël Carré committed
708
709
710
            wmemmove(&psz_wide[i+2], &psz_wide[j+1], i_char_len - j - 1);
            psz_wide[i + 2 + i_char_len - j - 1] = '\0';
            break;
711
        }
Rafaël Carré's avatar
Rafaël Carré committed
712
        i++;
713
    }
Rafaël Carré's avatar
Rafaël Carré committed
714
715
716
717
718
719
720
    if (w <= 7) /* we don't add the '...' else we lose too much chars */
        psz_wide[i] = '\0';

    size_t i_wlen = wcslen(psz_wide) * 6 + 1; /* worst case */
    char psz_ellipsized[i_wlen];
    wcstombs(psz_ellipsized, psz_wide, i_wlen);
    mvprintw(y, x, "%s", psz_ellipsized);
721

Rafaël Carré's avatar
Rafaël Carré committed
722
    free(p_buf);
723
}
724

725
static void MainBoxWrite(intf_sys_t *p_sys, int l, const char *p_fmt, ...)
726
{
727
    va_list     vl_args;
Rafaël Carré's avatar
Rafaël Carré committed
728
    char        *p_buf;
729
    bool        b_selected = l == p_sys->i_box_idx;
730

731
    if (l < p_sys->i_box_start || l - p_sys->i_box_start >= p_sys->i_box_height)
732
733
        return;

Rafaël Carré's avatar
Rafaël Carré committed
734
735
    va_start(vl_args, p_fmt);
    if (vasprintf(&p_buf, p_fmt, vl_args) == -1)
736
        return;
Rafaël Carré's avatar
Rafaël Carré committed
737
    va_end(vl_args);
738

739
740
741
742
    if (b_selected) attron(A_REVERSE);
    mvnprintw(p_sys->i_box_y + l - p_sys->i_box_start, 1, COLS - 2, "%s", p_buf);
    if (b_selected) attroff(A_REVERSE);

Rafaël Carré's avatar
Rafaël Carré committed
743
    free(p_buf);
744
745
}

746
static int SubDrawObject(intf_sys_t *p_sys, int l, vlc_object_t *p_obj, int i_level, const char *prefix)
747
{
748
    int x = 2 * i_level++;
Rafaël Carré's avatar
Rafaël Carré committed
749
    char *psz_name = vlc_object_get_name(p_obj);
750
751
752
753
754
    vlc_list_t *list;
    char space[x+3];
    memset(space, ' ', x);
    space[x] = '\0';

Rafaël Carré's avatar
Rafaël Carré committed
755
    if (psz_name)
756
    {
757
758
        MainBoxWrite(p_sys, l++, "%s%s%s \"%s\" (%p)", space, prefix,
                      p_obj->psz_object_type, psz_name, p_obj);
Rafaël Carré's avatar
Rafaël Carré committed
759
        free(psz_name);
760
    }
761
    else
762
763
        MainBoxWrite(p_sys, l++, "%s%s%s (%p)", space, prefix,
                      p_obj->psz_object_type, p_obj);
764

765
    list = vlc_list_children(p_obj);
Rafaël Carré's avatar
Rafaël Carré committed
766
    for(int i = 0; i < list->i_count ; i++)
767
    {
768
769
        l = SubDrawObject(p_sys, l, list->p_values[i].p_object, i_level,
            (i == list->i_count - 1) ? "`-" : "|-" );
770
    }
Rafaël Carré's avatar
Rafaël Carré committed
771
    vlc_list_release(list);
772
    return l;
773
774
}

Rafaël Carré's avatar
Rafaël Carré committed
775
static int DrawObjects(intf_thread_t *p_intf)
776
{
777
778
779
780
781
782
    return SubDrawObject(p_intf->p_sys, 0, VLC_OBJECT(p_intf->p_libvlc), 0, "");
}

static int DrawMeta(intf_thread_t *p_intf)
{
    intf_sys_t *p_sys = p_intf->p_sys;
783
    input_thread_t *p_input = p_sys->p_input;
784
785
    input_item_t *p_item;
    int l = 0;
786

787
788
    if (!p_input)
        return 0;
789

790
791
792
793
794
795
796
    p_item = input_GetItem(p_input);
    vlc_mutex_lock(&p_item->lock);
    for(int i=0; i<VLC_META_TYPE_COUNT; i++)
    {
        const char *psz_meta = vlc_meta_Get(p_item->p_meta, i);
        if (!psz_meta || !*psz_meta)
            continue;
797

798
        if (p_sys->b_color) color_set(C_CATEGORY, NULL);
799
        MainBoxWrite(p_sys, l++, "  [%s]", vlc_meta_TypeToLocalizedString(i));
800
801
802
803
        if (p_sys->b_color) color_set(C_DEFAULT, NULL);
        MainBoxWrite(p_sys, l++, "      %s", psz_meta);
    }
    vlc_mutex_unlock(&p_item->lock);
804

805
806
    return l;
}
807

808
809
810
811
812
813
static int DrawInfo(intf_thread_t *p_intf)
{
    intf_sys_t *p_sys = p_intf->p_sys;
    input_thread_t *p_input = p_sys->p_input;
    input_item_t *p_item;
    int l = 0;
814

815
816
817
818
819
820
821
822
823
824
825
826
    if (!p_input)
        return 0;

    p_item = input_GetItem(p_input);
    vlc_mutex_lock(&p_item->lock);
    for(int i = 0; i < p_item->i_categories; i++)
    {
        info_category_t *p_category = p_item->pp_categories[i];
        if (p_sys->b_color) color_set(C_CATEGORY, NULL);
        MainBoxWrite(p_sys, l++, _("  [%s]"), p_category->psz_name);
        if (p_sys->b_color) color_set(C_DEFAULT, NULL);
        for(int j = 0; j < p_category->i_infos; j++)
827
        {
828
829
830
831
832
833
            info_t *p_info = p_category->pp_infos[j];
            MainBoxWrite(p_sys, l++, _("      %s: %s"),
                         p_info->psz_name, p_info->psz_value);
        }
    }
    vlc_mutex_unlock(&p_item->lock);
hartman's avatar
hartman committed
834

835
836
    return l;
}
837

838
839
840
841
842
843
844
static int DrawStats(intf_thread_t *p_intf)
{
    intf_sys_t *p_sys = p_intf->p_sys;
    input_thread_t *p_input = p_sys->p_input;
    input_item_t *p_item;
    input_stats_t *p_stats;
    int l = 0, i_audio = 0, i_video = 0;
845

846
847
    if (!p_input)
        return 0;
848

849
850
    p_item = input_GetItem(p_input);
    assert(p_item);
hartman's avatar
hartman committed
851

852
853
854
    vlc_mutex_lock(&p_item->lock);
    p_stats = p_item->p_stats;
    vlc_mutex_lock(&p_stats->lock);
855

856
    for(int i = 0; i < p_item->i_es ; i++)
857
    {
858
859
        i_audio += (p_item->es[i]->i_cat == AUDIO_ES);
        i_video += (p_item->es[i]->i_cat == VIDEO_ES);
860
861
    }

862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
    /* Input */
    if (p_sys->b_color) color_set(C_CATEGORY, NULL);
    MainBoxWrite(p_sys, l++, _("  [Incoming]"));
    if (p_sys->b_color) color_set(C_DEFAULT, NULL);
    MainBoxWrite(p_sys, l++, _("      input bytes read : %8.0f KiB"),
            (float)(p_stats->i_read_bytes)/1024);
    MainBoxWrite(p_sys, l++, _("      input bitrate    :   %6.0f kb/s"),
            p_stats->f_input_bitrate*8000);
    MainBoxWrite(p_sys, l++, _("      demux bytes read : %8.0f KiB"),
            (float)(p_stats->i_demux_read_bytes)/1024);
    MainBoxWrite(p_sys, l++, _("      demux bitrate    :   %6.0f kb/s"),
            p_stats->f_demux_bitrate*8000);

    /* Video */
    if (i_video)
    {
        if (p_sys->b_color) color_set(C_CATEGORY, NULL);
        MainBoxWrite(p_sys, l++, _("  [Video Decoding]"));
        if (p_sys->b_color) color_set(C_DEFAULT, NULL);
        MainBoxWrite(p_sys, l++, _("      video decoded    :    %"PRId64),
                p_stats->i_decoded_video);
        MainBoxWrite(p_sys, l++, _("      frames displayed :    %"PRId64),
                p_stats->i_displayed_pictures);
        MainBoxWrite(p_sys, l++, _("      frames lost      :    %"PRId64),
                p_stats->i_lost_pictures);
    }
    /* Audio*/
    if (i_audio)
890
    {
891
892
893
894
895
896
897
898
899
        if (p_sys->b_color) color_set(C_CATEGORY, NULL);
        MainBoxWrite(p_sys, l++, _("  [Audio Decoding]"));
        if (p_sys->b_color) color_set(C_DEFAULT, NULL);
        MainBoxWrite(p_sys, l++, _("      audio decoded    :    %"PRId64),
                p_stats->i_decoded_audio);
        MainBoxWrite(p_sys, l++, _("      buffers played   :    %"PRId64),
                p_stats->i_played_abuffers);
        MainBoxWrite(p_sys, l++, _("      buffers lost     :    %"PRId64),
                p_stats->i_lost_abuffers);
900
    }
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
    /* Sout */
    if (p_sys->b_color) color_set(C_CATEGORY, NULL);
    MainBoxWrite(p_sys, l++, _("  [Streaming]"));
    if (p_sys->b_color) color_set(C_DEFAULT, NULL);
    MainBoxWrite(p_sys, l++, _("      packets sent     :    %5i"), p_stats->i_sent_packets);
    MainBoxWrite(p_sys, l++, _("      bytes sent       : %8.0f KiB"),
            (float)(p_stats->i_sent_bytes)/1025);
    MainBoxWrite(p_sys, l++, _("      sending bitrate  :   %6.0f kb/s"),
            p_stats->f_send_bitrate*8000);
    if (p_sys->b_color) color_set(C_DEFAULT, NULL);

    vlc_mutex_unlock(&p_stats->lock);
    vlc_mutex_unlock(&p_item->lock);

    return l;
}
917

918
919
920
921
static int DrawHelp(intf_thread_t *p_intf)
{
    intf_sys_t *p_sys = p_intf->p_sys;
    int l = 0;
922

923
#define H(a) MainBoxWrite(p_sys, l++, a)
924
925

    if (p_sys->b_color) color_set(C_CATEGORY, NULL);
926
    H(_("[Display]"));
927
    if (p_sys->b_color) color_set(C_DEFAULT, NULL);
928
929
930
931
932
933
934
935
936
937
938
    H(_(" h,H                    Show/Hide help box"));
    H(_(" i                      Show/Hide info box"));
    H(_(" m                      Show/Hide metadata box"));
    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("");
939
940

    if (p_sys->b_color) color_set(C_CATEGORY, NULL);
941
    H(_("[Global]"));
942
    if (p_sys->b_color) color_set(C_DEFAULT, NULL);
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
    H(_(" q, Q, Esc              Quit"));
    H(_(" s                      Stop"));
    H(_(" <space>                Pause/Play"));
    H(_(" f                      Toggle Fullscreen"));
    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"));
    /* 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("");
960
961

    if (p_sys->b_color) color_set(C_CATEGORY, NULL);
962
    H(_("[Playlist]"));
963
    if (p_sys->b_color) color_set(C_DEFAULT, NULL);
964
965
966
967
968
969
970
971
972
973
974
975
    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"));
    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("");
976
977

    if (p_sys->b_color) color_set(C_CATEGORY, NULL);