ncurses.c 85.2 KB
Newer Older
1
/*****************************************************************************
2
 * ncurses.c : NCurses interface for vlc
3
 *****************************************************************************
ivoire's avatar
ivoire committed
4
 * Copyright © 2001-2007 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
 *****************************************************************************/

ivoire's avatar
ivoire committed
28
29
30
31
32
33
/*
 * Note that when we use wide characters (and link with libncursesw),
 * we assume that an UTF8 locale is used (or compatible, such as ASCII).
 * Other characters encodings are not supported.
 */

34
35
36
/*****************************************************************************
 * Preamble
 *****************************************************************************/
37
38
39
40
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
41
#include <vlc/vlc.h>
42
#include <vlc_plugin.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43

44
45
#ifdef HAVE_NCURSESW
#   define _XOPEN_SOURCE_EXTENDED 1
46
#   include <wchar.h>
47
#endif
48

49
50
#include <ncurses.h>

zorglub's avatar
zorglub committed
51
#include <vlc_interface.h>
52
53
#include <vlc_vout.h>
#include <vlc_aout.h>
zorglub's avatar
zorglub committed
54
#include <vlc_charset.h>
55
#include <vlc_input.h>
Rafaël Carré's avatar
Rafaël Carré committed
56
#include <vlc_es.h>
57
#include <vlc_playlist.h>
58
#include <vlc_meta.h>
59

60
61
#include <assert.h>

hartman's avatar
hartman committed
62
63
64
65
66
67
68
69
#ifdef HAVE_SYS_STAT_H
#   include <sys/stat.h>
#endif
#if (!defined( WIN32 ) || defined(__MINGW32__))
/* Mingw has its own version of dirent */
#   include <dirent.h>
#endif

70
71
#ifdef HAVE_CDDAX
#define CDDA_MRL "cddax://"
72
#else
73
74
75
76
77
78
#define CDDA_MRL "cdda://"
#endif

#ifdef HAVE_VCDX
#define VCD_MRL "vcdx://"
#else
79
#define VCD_MRL "vcd://"
80
81
#endif

82
83
84
#define SEARCH_CHAIN_SIZE 20
#define OPEN_CHAIN_SIZE 50

85
86
87
88
/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
static int  Open           ( vlc_object_t * );
89
static void Close          ( vlc_object_t * );
90

91
static void Run            ( intf_thread_t * );
92
static void PlayPause      ( intf_thread_t * );
93
94
95
static void Eject          ( intf_thread_t * );

static int  HandleKey      ( intf_thread_t *, int );
96
static void Redraw         ( intf_thread_t *, time_t * );
97
98

static playlist_item_t *PlaylistGetRoot( intf_thread_t * );
99
static void PlaylistRebuild( intf_thread_t * );
Laurent Aimar's avatar
Laurent Aimar committed
100
static void PlaylistAddNode( intf_thread_t *, playlist_item_t *, int, const char *);
101
102
103
static void PlaylistDestroy( intf_thread_t * );
static int  PlaylistChanged( vlc_object_t *, const char *, vlc_value_t,
                             vlc_value_t, void * );
104
static inline bool PlaylistIsPlaying( intf_thread_t *,
105
                                            playlist_item_t * );
106
static void FindIndex      ( intf_thread_t * );
107
static void SearchPlaylist ( intf_thread_t *, char * );
108
static int  SubSearchPlaylist( intf_thread_t *, char *, int, int );
109

110
static void ManageSlider   ( intf_thread_t * );
hartman's avatar
hartman committed
111
static void ReadDir        ( intf_thread_t * );
112

ivoire's avatar
ivoire committed
113
114
static void start_color_and_pairs ( intf_thread_t * );

115
116
117
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
hartman's avatar
hartman committed
118
119
120

#define BROWSE_TEXT N_("Filebrowser starting point")
#define BROWSE_LONGTEXT N_( \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
121
    "This option allows you to specify the directory the ncurses filebrowser " \
hartman's avatar
hartman committed
122
123
    "will show you initially.")

124
vlc_module_begin();
125
    set_shortname( "Ncurses" );
126
    set_description( N_("Ncurses interface") );
127
    set_capability( "interface", 10 );
zorglub's avatar
zorglub committed
128
    set_category( CAT_INTERFACE );
zorglub's avatar
zorglub committed
129
    set_subcategory( SUBCAT_INTERFACE_MAIN );
130
131
    set_callbacks( Open, Close );
    add_shortcut( "curses" );
132
    add_directory( "browse-dir", NULL, NULL, BROWSE_TEXT, BROWSE_LONGTEXT, false );
133
134
135
136
137
vlc_module_end();

/*****************************************************************************
 * intf_sys_t: description and status of ncurses interface
 *****************************************************************************/
138
139
140
141
142
143
enum
{
    BOX_NONE,
    BOX_HELP,
    BOX_INFO,
    BOX_LOG,
144
145
    BOX_PLAYLIST,
    BOX_SEARCH,
hartman's avatar
hartman committed
146
    BOX_OPEN,
147
    BOX_BROWSE,
148
    BOX_META,
149
150
    BOX_OBJECTS,
    BOX_STATS
hartman's avatar
hartman committed
151
};
152
enum
ivoire's avatar
ivoire committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
{
    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,
    C_FOLDER
};
enum
169
170
171
172
{
    VIEW_CATEGORY,
    VIEW_ONELEVEL
};
hartman's avatar
hartman committed
173
174
struct dir_entry_t
{
175
    bool  b_file;
hartman's avatar
hartman committed
176
    char        *psz_path;
177
};
178
179
180
181
182
struct pl_item_t
{
    playlist_item_t *p_item;
    char            *psz_display;
};
183
184
struct intf_sys_t
{
185
    input_thread_t *p_input;
186
    playlist_t     *p_playlist;
187

188
189
    bool      b_color;
    bool      b_color_started;
ivoire's avatar
ivoire committed
190

191
192
193
194
195
196
197
198
199
200
201
    float           f_slider;
    float           f_slider_old;

    WINDOW          *w;

    int             i_box_type;
    int             i_box_y;
    int             i_box_lines;
    int             i_box_lines_total;
    int             i_box_start;

202
203
    int             i_box_plidx;    /* Playlist index */
    int             b_box_plidx_follow;
204
    int             i_box_bidx;     /* browser index */
205

206
207
    playlist_item_t *p_node;        /* current node */

208
    int             b_box_cleared;
209

210
    msg_subscription_t* p_sub;                  /* message bank subscription */
211
212
213
214
215
216

    char            *psz_search_chain;          /* for playlist searching    */
    char            *psz_old_search;            /* for searching next        */
    int             i_before_search;

    char            *psz_open_chain;
ivoire's avatar
ivoire committed
217
#ifndef HAVE_NCURSESW
218
    char             psz_partial_keys[7];
ivoire's avatar
ivoire committed
219
#endif
220

hartman's avatar
hartman committed
221
222
223
    char            *psz_current_dir;
    int             i_dir_entries;
    struct dir_entry_t  **pp_dir_entries;
224
    bool      b_show_hidden_files;
225
226
227
228

    int             i_current_view;             /* playlist view             */
    struct pl_item_t    **pp_plist;
    int             i_plist_entries;
229
    bool      b_need_update;              /* for playlist view         */
230
231

    int             i_verbose;                  /* stores verbosity level    */
232
233
};

234
static void DrawBox( WINDOW *win, int y, int x, int h, int w, const char *title, bool b_color );
235
236
237
static void DrawLine( WINDOW *win, int y, int x, int w );
static void DrawEmptyLine( WINDOW *win, int y, int x, int w );

238
239
240
241
/*****************************************************************************
 * Open: initialize and create window
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
242
{
243
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
244
    intf_sys_t    *p_sys;
245
    vlc_value_t    val;
246
247

    /* Allocate instance and initialize some members */
248
    p_sys = p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
249
    p_sys->p_node = NULL;
250
251
252
253
254
255
256
    p_sys->p_input = NULL;
    p_sys->f_slider = 0.0;
    p_sys->f_slider_old = 0.0;
    p_sys->i_box_type = BOX_PLAYLIST;
    p_sys->i_box_lines = 0;
    p_sys->i_box_start= 0;
    p_sys->i_box_lines_total = 0;
257
258
    p_sys->b_box_plidx_follow = true;
    p_sys->b_box_cleared = false;
259
    p_sys->i_box_plidx = 0;
260
    p_sys->i_box_bidx = 0;
261
    p_sys->p_sub = msg_Subscribe( p_intf );
Rafaël Carré's avatar
Rafaël Carré committed
262
    p_sys->b_color = var_CreateGetBool( p_intf, "color" );
263
    p_sys->b_color_started = false;
ivoire's avatar
ivoire committed
264
265

#ifndef HAVE_NCURSESW
266
    memset( p_sys->psz_partial_keys, 0, sizeof( p_sys->psz_partial_keys ) );
ivoire's avatar
ivoire committed
267
#endif
268
269

    /* Initialize the curses library */
270
    p_sys->w = initscr();
ivoire's avatar
ivoire committed
271
272
273
274

    if( p_sys->b_color )
        start_color_and_pairs( p_intf );

275
    keypad( p_sys->w, TRUE );
276
277
278
279
280
281
    /* Don't do NL -> CR/NL */
    nonl();
    /* Take input chars one at a time */
    cbreak();
    /* Don't echo */
    noecho();
ivoire's avatar
ivoire committed
282
283
284
285
    /* Invisible cursor */
    curs_set( 0 );
    /* Non blocking wgetch() */
    wtimeout( p_sys->w, 0 );
286
287
288

    clear();

289
290
291
    /* exported function */
    p_intf->pf_run = Run;

292
293
294
    /* Remember verbosity level */
    var_Get( p_intf->p_libvlc, "verbose", &val );
    p_sys->i_verbose = val.i_int;
295
296
    /* Set quiet mode */
    val.i_int = -1;
297
    var_Set( p_intf->p_libvlc, "verbose", val );
298

299
    /* Set defaul playlist view */
zorglub's avatar
zorglub committed
300
    p_sys->i_current_view = VIEW_CATEGORY;
301
302
    p_sys->pp_plist = NULL;
    p_sys->i_plist_entries = 0;
303
    p_sys->b_need_update = false;
304

305
306
307
308
309
310
311
    /* Initialize search chain */
    p_sys->psz_search_chain = (char *)malloc( SEARCH_CHAIN_SIZE + 1 );
    p_sys->psz_old_search = NULL;
    p_sys->i_before_search = 0;

    /* Initialize open chain */
    p_sys->psz_open_chain = (char *)malloc( OPEN_CHAIN_SIZE + 1 );
312

hartman's avatar
hartman committed
313
314
315
    /* Initialize browser options */
    var_Create( p_intf, "browse-dir", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
    var_Get( p_intf, "browse-dir", &val);
316

hartman's avatar
hartman committed
317
318
    if( val.psz_string && *val.psz_string )
    {
319
        p_sys->psz_current_dir = strdup( val.psz_string );
hartman's avatar
hartman committed
320
321
322
    }
    else
    {
323
        p_sys->psz_current_dir = strdup( config_GetHomeDir() );
hartman's avatar
hartman committed
324
    }
325

326
327
    free( val.psz_string );

hartman's avatar
hartman committed
328
    p_sys->i_dir_entries = 0;
329
    p_sys->pp_dir_entries = NULL;
330
    p_sys->b_show_hidden_files = false;
hartman's avatar
hartman committed
331
    ReadDir( p_intf );
332

333
    return VLC_SUCCESS;
334
335
336
337
338
339
}

/*****************************************************************************
 * Close: destroy interface window
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
340
{
341
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
342
    intf_sys_t    *p_sys = p_intf->p_sys;
343

344
345
    PlaylistDestroy( p_intf );

346
    while( p_sys->i_dir_entries )
hartman's avatar
hartman committed
347
    {
348
        struct dir_entry_t *p_dir_entry = p_sys->pp_dir_entries[0];
349
        free( p_dir_entry->psz_path );
350
        REMOVE_ELEM( p_sys->pp_dir_entries, p_sys->i_dir_entries, 0 );
351
        free( p_dir_entry );
hartman's avatar
hartman committed
352
353
    }
    p_sys->pp_dir_entries = NULL;
354

355
356
357
358
    free( p_sys->psz_current_dir );
    free( p_sys->psz_search_chain );
    free( p_sys->psz_old_search );
    free( p_sys->psz_open_chain );
359

360
361
362
363
    if( p_sys->p_input )
    {
        vlc_object_release( p_sys->p_input );
    }
364
    pl_Release( p_intf );
365
366
367
368

    /* Close the ncurses interface */
    endwin();

369
370
    msg_Unsubscribe( p_intf, p_sys->p_sub );

371
372
373
374
375
    /* Restores initial verbose setting */
    vlc_value_t val;
    val.i_int = p_sys->i_verbose;
    var_Set( p_intf->p_libvlc, "verbose", val );

376
    /* Destroy structure */
377
    free( p_sys );
378
379
380
381
382
383
384
}

/*****************************************************************************
 * Run: ncurses thread
 *****************************************************************************/
static void Run( intf_thread_t *p_intf )
{
385
    intf_sys_t    *p_sys = p_intf->p_sys;
386
    playlist_t    *p_playlist = pl_Yield( p_intf );
387
    p_sys->p_playlist = p_playlist;
388
389

    int i_key;
390
391
392
393
394
395
    time_t t_last_refresh;

    /*
     * force drawing the interface for the first time
     */
    t_last_refresh = ( time( 0 ) - 1);
396
397
398
399
    /*
     * force building of the playlist array
     */
    PlaylistRebuild( p_intf );
400
401
    var_AddCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
    var_AddCallback( p_playlist, "item-append", PlaylistChanged, p_intf );
402

403
    while( !intf_ShouldDie( p_intf ) )
404
405
406
    {
        msleep( INTF_IDLE_SLEEP );

407
        /* Update the input */
408
409
        PL_LOCK;
        if( p_sys->p_input == NULL )
410
        {
411
412
            p_sys->p_input = p_playlist->p_input;
            if( p_sys->p_input )
413
            {
414
                if( !p_sys->p_input->b_dead )
415
                {
416
                    vlc_object_yield( p_sys->p_input );
417
                }
418
            }
419
        }
420
421
422
423
424
        else if( p_sys->p_input->b_dead )
        {
            vlc_object_release( p_sys->p_input );
            p_sys->p_input = NULL;
            p_sys->f_slider = p_sys->f_slider_old = 0.0;
425
            p_sys->b_box_cleared = false;
426
427
        }
        PL_UNLOCK;
428

429
        if( p_sys->b_box_plidx_follow && p_playlist->status.p_item )
430
        {
431
            FindIndex( p_intf );
432
        }
433

ivoire's avatar
ivoire committed
434
        while( ( i_key = wgetch( p_sys->w ) ) != -1 )
435
436
437
438
        {
            /*
             * HandleKey returns 1 if the screen needs to be redrawn
             */
439
            if( HandleKey( p_intf, i_key ) )
440
441
442
443
            {
                Redraw( p_intf, &t_last_refresh );
            }
        }
444
445
446
447
448
        /* Hack */
        if( p_sys->f_slider > 0.0001 && !p_sys->b_box_cleared )
        {
            clear();
            Redraw( p_intf, &t_last_refresh );
449
            p_sys->b_box_cleared = true;
450
        }
451
452
453
454

        /*
         * redraw the screen every second
         */
455
        if( (time(0) - t_last_refresh) >= 1 )
456
        {
457
            ManageSlider( p_intf );
458
459
460
            Redraw( p_intf, &t_last_refresh );
        }
    }
461
462
    var_DelCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
    var_DelCallback( p_playlist, "item-append", PlaylistChanged, p_intf );
463
464
465
}

/* following functions are local */
ivoire's avatar
ivoire committed
466
467
468
469
470
471
static void start_color_and_pairs( intf_thread_t *p_intf )
{
    assert( p_intf->p_sys->b_color && !p_intf->p_sys->b_color_started );

    if( !has_colors() )
    {
472
        p_intf->p_sys->b_color = false;
ivoire's avatar
ivoire committed
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
        msg_Warn( p_intf, "Terminal doesn't support colors" );
        return;
    }

    start_color();

    /* Available colors: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE */

    /* untested, in all my terminals, !can_change_color() --funman */
    if( can_change_color() )
        init_color( COLOR_YELLOW, 960, 500, 0 ); /* YELLOW -> ORANGE */

    /* title */
    init_pair( C_TITLE, COLOR_YELLOW, COLOR_BLACK );

    /* jamaican playlist */
    init_pair( C_PLAYLIST_1, COLOR_GREEN, COLOR_BLACK );
    init_pair( C_PLAYLIST_2, COLOR_YELLOW, COLOR_BLACK );
    init_pair( C_PLAYLIST_3, COLOR_RED, COLOR_BLACK );

    /* used in DrawBox() */
    init_pair( C_BOX, COLOR_CYAN, COLOR_BLACK );
    /* Source, State, Position, Volume, Chapters, etc...*/
    init_pair( C_STATUS, COLOR_BLUE, COLOR_BLACK );

    /* VLC messages, keep the order from highest priority to lowest */

    /* infos */
    init_pair( C_INFO, COLOR_BLACK, COLOR_WHITE );
    /* errors */
    init_pair( C_ERROR, COLOR_RED, COLOR_BLACK );
    /* warnings */
    init_pair( C_WARNING, COLOR_YELLOW, COLOR_BLACK );
/* debug */
    init_pair( C_DEBUG, COLOR_WHITE, COLOR_BLACK );

    /* Category title (help, info, metadata) */
    init_pair( C_CATEGORY, COLOR_MAGENTA, COLOR_BLACK );

    /* Folder (BOX_BROWSE) */
    init_pair( C_FOLDER, COLOR_RED, COLOR_BLACK );

515
    p_intf->p_sys->b_color_started = true;
ivoire's avatar
ivoire committed
516
517
518
}

#ifndef HAVE_NCURSESW
519
static char *KeyToUTF8( int i_key, char *psz_part )
520
{
521
    char *psz_utf8;
522
523
524
525
526
527
528
529
530
531
532
533
534
535
    int len = strlen( psz_part );
    if( len == 6 )
    {
        /* overflow error - should not happen */
        memset( psz_part, 0, 6 );
        return NULL;
    }

    psz_part[len] = (char)i_key;

    psz_utf8 = FromLocaleDup( psz_part );

    /* Ugly check for incomplete bytes sequences
     * (in case of non-UTF8 multibyte local encoding) */
536
    char *psz;
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
    for( psz = psz_utf8; *psz; psz++ )
        if( ( *psz == '?' ) && ( *psz_utf8 != '?' ) )
        {
            /* incomplete bytes sequence detected
             * (VLC core inserted dummy question marks) */
            free( psz_utf8 );
            return NULL;
        }

    /* Check for incomplete UTF8 bytes sequence */
    if( EnsureUTF8( psz_utf8 ) == NULL )
    {
        free( psz_utf8 );
        return NULL;
    }

    memset( psz_part, 0, 6 );
    return psz_utf8;
555
}
ivoire's avatar
ivoire committed
556
#endif
557
558
559
560
561
562
563
564
565
566

static inline int RemoveLastUTF8Entity( char *psz, int len )
{
    while( len && ( (psz[--len] & 0xc0) == 0x80 ) );
                       /* UTF8 continuation byte */

    psz[len] = '\0';
    return len;
}

567
568
static int HandleKey( intf_thread_t *p_intf, int i_key )
{
569
570
    intf_sys_t *p_sys = p_intf->p_sys;
    vlc_value_t val;
571

Felix Paul Kühne's avatar
Felix Paul Kühne committed
572
    #define ReturnTrue \
573
    do { \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
574
    vlc_object_release( p_playlist ); \
575
576
    return 1; \
    } while(0)
577

Felix Paul Kühne's avatar
Felix Paul Kühne committed
578
    #define ReturnFalse \
579
    do { \
Felix Paul Kühne's avatar
Felix Paul Kühne committed
580
    vlc_object_release( p_playlist ); \
581
582
    return 0; \
    } while(0)
Felix Paul Kühne's avatar
Felix Paul Kühne committed
583
584

    playlist_t *p_playlist = pl_Yield( p_intf );
585

586
    if( p_sys->i_box_type == BOX_PLAYLIST )
587
    {
588
589
        int b_ret = true;
        bool b_box_plidx_follow = false;
590

591
592
        switch( i_key )
        {
hartman's avatar
hartman committed
593
594
            vlc_value_t val;
            /* Playlist Settings */
595
            case 'r':
596
                var_Get( p_playlist, "random", &val );
hartman's avatar
hartman committed
597
                val.b_bool = !val.b_bool;
598
                var_Set( p_playlist, "random", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
599
                ReturnTrue;
hartman's avatar
hartman committed
600
            case 'l':
601
                var_Get( p_playlist, "loop", &val );
hartman's avatar
hartman committed
602
                val.b_bool = !val.b_bool;
603
                var_Set( p_playlist, "loop", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
604
                ReturnTrue;
hartman's avatar
hartman committed
605
            case 'R':
606
                var_Get( p_playlist, "repeat", &val );
hartman's avatar
hartman committed
607
                val.b_bool = !val.b_bool;
608
                var_Set( p_playlist, "repeat", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
609
                ReturnTrue;
hartman's avatar
hartman committed
610
611

            /* Playlist sort */
612
            case 'o':
613
                playlist_RecursiveNodeSort( p_playlist,
614
615
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_NORMAL );
616
                p_sys->b_need_update = true;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
617
                ReturnTrue;
618
            case 'O':
619
                playlist_RecursiveNodeSort( p_playlist,
620
621
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_REVERSE );
622
                p_sys->b_need_update = true;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
623
                ReturnTrue;
624

625
626
627
628
629
            /* Playlist view */
            case 'v':
                switch( p_sys->i_current_view )
                {
                    case VIEW_CATEGORY:
630
                        p_sys->i_current_view = VIEW_ONELEVEL;
631
632
                        break;
                    default:
zorglub's avatar
zorglub committed
633
                        p_sys->i_current_view = VIEW_CATEGORY;
634
                }
635
                //p_sys->b_need_update = true;
636
                PlaylistRebuild( p_intf );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
637
                ReturnTrue;
638

639
            /* Playlist navigation */
640
641
642
            case 'g':
                FindIndex( p_intf );
                break;
643
            case KEY_HOME:
644
645
                p_sys->i_box_plidx = 0;
                break;
Rafaël Carré's avatar
Rafaël Carré committed
646
647
648
649
650
#ifdef __FreeBSD__
/* workaround for FreeBSD + xterm:
 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
            case KEY_SELECT:
#endif
651
            case KEY_END:
652
                p_sys->i_box_plidx = p_playlist->items.i_size - 1;
653
                break;
654
            case KEY_UP:
655
656
                p_sys->i_box_plidx--;
                break;
657
            case KEY_DOWN:
658
659
                p_sys->i_box_plidx++;
                break;
660
            case KEY_PPAGE:
661
662
                p_sys->i_box_plidx -= p_sys->i_box_lines;
                break;
663
            case KEY_NPAGE:
664
665
                p_sys->i_box_plidx += p_sys->i_box_lines;
                break;
hartman's avatar
hartman committed
666
            case 'D':
667
            case KEY_BACKSPACE:
ivoire's avatar
ivoire committed
668
            case 0x7f:
669
670
            case KEY_DC:
            {
671
                playlist_item_t *p_item;
672

673
                PL_LOCK;
674
675
676
                p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
                if( p_item->i_children == -1 )
                {
677
                    playlist_DeleteFromInput( p_playlist,
678
                                              p_item->p_input->i_id, true );
679
680
                }
                else
681
                {
682
                    playlist_NodeDelete( p_playlist, p_item,
683
                                         true , false );
684
                }
685
                PL_UNLOCK;
686
                PlaylistRebuild( p_intf );
687
                break;
688
689
            }

690
            case KEY_ENTER:
ivoire's avatar
ivoire committed
691
692
            case '\r':
            case '\n':
693
                if( !p_sys->pp_plist[p_sys->i_box_plidx] )
694
                {
695
                    b_ret = false;
696
697
                    break;
                }
698
699
                if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == -1 )
700
                {
701
702
                    playlist_item_t *p_item, *p_parent;
                    p_item = p_parent =
703
                            p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
704

705
                    if( !p_parent )
706
                        p_parent = p_playlist->p_root_onelevel;
707
708
                    while( p_parent->p_parent )
                        p_parent = p_parent->p_parent;
709
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
710
                                      true, p_parent, p_item );
711
                }
Rafaël Carré's avatar
Rafaël Carré committed
712
713
714
                else if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == 0 )
                {   /* We only want to set the current node */
715
716
                    playlist_Stop( p_playlist );
                    p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
Rafaël Carré's avatar
Rafaël Carré committed
717
                }
718
                else
Rafaël Carré's avatar
Rafaël Carré committed
719
                {
720
                    p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
721
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, true,
722
                        p_sys->pp_plist[p_sys->i_box_plidx]->p_item, NULL );
723
                }
724
                b_box_plidx_follow = true;
725
                break;
726
            default:
727
                b_ret = false;
728
729
                break;
        }
730
731
732

        if( b_ret )
        {
733
734
            int i_max = p_sys->i_plist_entries;
            if( p_sys->i_box_plidx >= i_max ) p_sys->i_box_plidx = i_max - 1;
735
            if( p_sys->i_box_plidx < 0 ) p_sys->i_box_plidx = 0;
736
737
            if( PlaylistIsPlaying( p_intf,
                    p_sys->pp_plist[p_sys->i_box_plidx]->p_item ) )
738
                b_box_plidx_follow = true;
739
            p_sys->b_box_plidx_follow = b_box_plidx_follow;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
740
            ReturnTrue;
741
        }
742
    }
hartman's avatar
hartman committed
743
744
    if( p_sys->i_box_type == BOX_BROWSE )
    {
745
        bool b_ret = true;
hartman's avatar
hartman committed
746
747
748
749
        /* Browser navigation */
        switch( i_key )
        {
            case KEY_HOME:
750
                p_sys->i_box_bidx = 0;
hartman's avatar
hartman committed
751
                break;
Rafaël Carré's avatar
Rafaël Carré committed
752
753
754
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
hartman's avatar
hartman committed
755
            case KEY_END:
756
                p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
hartman's avatar
hartman committed
757
758
                break;
            case KEY_UP:
759
                p_sys->i_box_bidx--;
hartman's avatar
hartman committed
760
761
                break;
            case KEY_DOWN:
762
                p_sys->i_box_bidx++;
hartman's avatar
hartman committed
763
764
                break;
            case KEY_PPAGE:
765
                p_sys->i_box_bidx -= p_sys->i_box_lines;
hartman's avatar
hartman committed
766
767
                break;
            case KEY_NPAGE:
768
                p_sys->i_box_bidx += p_sys->i_box_lines;
hartman's avatar
hartman committed
769
                break;
770
771
            case '.': /* Toggle show hidden files */
                p_sys->b_show_hidden_files = ( p_sys->b_show_hidden_files ==
772
                    true ? false : true );
773
774
                ReadDir( p_intf );
                break;
hartman's avatar
hartman committed
775
776

            case KEY_ENTER:
ivoire's avatar
ivoire committed
777
778
            case '\r':
            case '\n':
779
780
            case ' ':
                if( p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ' )
hartman's avatar
hartman committed
781
                {
782
783
                    int i_size_entry = strlen( "directory://" ) +
                                       strlen( p_sys->psz_current_dir ) +
784
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
785
786
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

787
                    sprintf( psz_uri, "directory://%s/%s", p_sys->psz_current_dir, p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path );
788

789
790
791
                    playlist_item_t *p_parent = p_sys->p_node;
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
792
                    if( !p_parent )
793
                        p_parent = p_playlist->p_local_onelevel;
794

Rafaël Carré's avatar
Rafaël Carré committed
795
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
796
797
                        p_parent = p_parent->p_parent;

798
799
                    playlist_Add( p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
                                  PLAYLIST_END,
800
                                  p_parent->p_input ==
801
                                    p_playlist->p_local_onelevel->p_input
802
                                  , false );
803

hartman's avatar
hartman committed
804
805
806
807
808
809
                    p_sys->i_box_type = BOX_PLAYLIST;
                    free( psz_uri );
                }
                else
                {
                    int i_size_entry = strlen( p_sys->psz_current_dir ) +
810
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
811
812
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

813
                    sprintf( psz_uri, "%s/%s", p_sys->psz_current_dir, p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path );
814

hartman's avatar
hartman committed
815
816
817
818
819
820
                    p_sys->psz_current_dir = strdup( psz_uri );
                    ReadDir( p_intf );
                    free( psz_uri );
                }
                break;
            default:
821
                b_ret = false;
hartman's avatar
hartman committed
822
                break;
823
824
825
        }
        if( b_ret )
        {
826
827
            if( p_sys->i_box_bidx >= p_sys->i_dir_entries ) p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
            if( p_sys->i_box_bidx < 0 ) p_sys->i_box_bidx = 0;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
828
            ReturnTrue;
hartman's avatar
hartman committed
829
830
        }
    }
831
    else if( p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
832
833
             p_sys->i_box_type == BOX_META || p_sys->i_box_type == BOX_STATS ||
             p_sys->i_box_type == BOX_OBJECTS )
834
835
836
837
838
    {
        switch( i_key )
        {
            case KEY_HOME:
                p_sys->i_box_start = 0;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
839
                ReturnTrue;
Rafaël Carré's avatar
Rafaël Carré committed
840
841
842
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
843
844
            case KEY_END:
                p_sys->i_box_start = p_sys->i_box_lines_total - 1;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
845
                ReturnTrue;
846
847
            case KEY_UP:
                if( p_sys->i_box_start > 0 ) p_sys->i_box_start--;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
848
                ReturnTrue;
849
850
851
852
853
            case KEY_DOWN:
                if( p_sys->i_box_start < p_sys->i_box_lines_total - 1 )
                {
                    p_sys->i_box_start++;
                }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
854
                ReturnTrue;
855
856
857
            case KEY_PPAGE:
                p_sys->i_box_start -= p_sys->i_box_lines;
                if( p_sys->i_box_start < 0 ) p_sys->i_box_start = 0;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
858
                ReturnTrue;
859
860
861
862
863
864
            case KEY_NPAGE:
                p_sys->i_box_start += p_sys->i_box_lines;
                if( p_sys->i_box_start >= p_sys->i_box_lines_total )
                {
                    p_sys->i_box_start = p_sys->i_box_lines_total - 1;
                }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
865
                ReturnTrue;
866
867
868
869
870
871
872
873
874
875
            default:
                break;
        }
    }
    else if( p_sys->i_box_type == BOX_NONE )
    {
        switch( i_key )
        {
            case KEY_HOME:
                p_sys->f_slider = 0;
876
                ManageSlider( p_intf );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
877
                ReturnTrue;
Rafaël Carré's avatar
Rafaël Carré committed
878
879
880
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
881
882
            case KEY_END:
                p_sys->f_slider = 99.9;
883
                ManageSlider( p_intf );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
884
                ReturnTrue;
885
            case KEY_UP:
886
                p_sys->f_slider += 5.0;
887
                if( p_sys->f_slider >= 99.0 ) p_sys->f_slider = 99.0;
888
                ManageSlider( p_intf );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
889
                ReturnTrue;
890
            case KEY_DOWN:
891
                p_sys->f_slider -= 5.0;
892
                if( p_sys->f_slider < 0.0 ) p_sys->f_slider = 0.0;
893
                ManageSlider( p_intf );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
894
                ReturnTrue;
895
896
897
898
899

            default:
                break;
        }
    }
900
901
    else if( p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain )
    {
ivoire's avatar
ivoire committed
902
        int i_chain_len = strlen( p_sys->psz_search_chain );
903
904
        switch( i_key )
        {
ivoire's avatar
ivoire committed
905
            case KEY_CLEAR: