ncurses.c 85.3 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
42
#include <vlc/vlc.h>

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

48
49
#include <ncurses.h>

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

hartman's avatar
hartman committed
59
60
61
62
63
64
65
66
#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

67
68
#ifdef HAVE_CDDAX
#define CDDA_MRL "cddax://"
69
#else
70
71
72
73
74
75
#define CDDA_MRL "cdda://"
#endif

#ifdef HAVE_VCDX
#define VCD_MRL "vcdx://"
#else
76
#define VCD_MRL "vcd://"
77
78
#endif

79
80
81
#define SEARCH_CHAIN_SIZE 20
#define OPEN_CHAIN_SIZE 50

82
83
84
85
/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
static int  Open           ( vlc_object_t * );
86
static void Close          ( vlc_object_t * );
87

88
static void Run            ( intf_thread_t * );
89
static void PlayPause      ( intf_thread_t * );
90
91
92
static void Eject          ( intf_thread_t * );

static int  HandleKey      ( intf_thread_t *, int );
93
static void Redraw         ( intf_thread_t *, time_t * );
94
95

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

107
static void ManageSlider   ( intf_thread_t * );
hartman's avatar
hartman committed
108
static void ReadDir        ( intf_thread_t * );
109

ivoire's avatar
ivoire committed
110
111
static void start_color_and_pairs ( intf_thread_t * );

112
113
114
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
hartman's avatar
hartman committed
115
116
117

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

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

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

185
186
    bool      b_color;
    bool      b_color_started;
ivoire's avatar
ivoire committed
187

188
189
190
191
192
193
194
195
196
197
198
    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;

199
200
    int             i_box_plidx;    /* Playlist index */
    int             b_box_plidx_follow;
201
    int             i_box_bidx;     /* browser index */
202

203
204
    playlist_item_t *p_node;        /* current node */

205
    int             b_box_cleared;
206

207
    msg_subscription_t* p_sub;                  /* message bank subscription */
208
209
210
211
212
213

    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
214
#ifndef HAVE_NCURSESW
215
    char             psz_partial_keys[7];
ivoire's avatar
ivoire committed
216
#endif
217

hartman's avatar
hartman committed
218
219
220
    char            *psz_current_dir;
    int             i_dir_entries;
    struct dir_entry_t  **pp_dir_entries;
221
    bool      b_show_hidden_files;
222
223
224
225

    int             i_current_view;             /* playlist view             */
    struct pl_item_t    **pp_plist;
    int             i_plist_entries;
226
    bool      b_need_update;              /* for playlist view         */
227
228

    int             i_verbose;                  /* stores verbosity level    */
229
230
};

231
static void DrawBox( WINDOW *win, int y, int x, int h, int w, const char *title, bool b_color );
232
233
234
static void DrawLine( WINDOW *win, int y, int x, int w );
static void DrawEmptyLine( WINDOW *win, int y, int x, int w );

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

    /* Allocate instance and initialize some members */
245
    p_sys = p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
246
    p_sys->p_node = NULL;
247
248
249
250
251
252
253
    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;
254
255
    p_sys->b_box_plidx_follow = true;
    p_sys->b_box_cleared = false;
256
    p_sys->i_box_plidx = 0;
257
    p_sys->i_box_bidx = 0;
258
    p_sys->p_sub = msg_Subscribe( p_intf, MSG_QUEUE_NORMAL );
Rafaël Carré's avatar
Rafaël Carré committed
259
    p_sys->b_color = var_CreateGetBool( p_intf, "color" );
260
    p_sys->b_color_started = false;
ivoire's avatar
ivoire committed
261
262

#ifndef HAVE_NCURSESW
263
    memset( p_sys->psz_partial_keys, 0, sizeof( p_sys->psz_partial_keys ) );
ivoire's avatar
ivoire committed
264
#endif
265
266

    /* Initialize the curses library */
267
    p_sys->w = initscr();
ivoire's avatar
ivoire committed
268
269
270
271

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

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

    clear();

286
287
288
    /* exported function */
    p_intf->pf_run = Run;

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

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

302
303
304
305
306
307
308
    /* 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 );
309

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

hartman's avatar
hartman committed
314
315
    if( val.psz_string && *val.psz_string )
    {
316
        p_sys->psz_current_dir = strdup( val.psz_string );
hartman's avatar
hartman committed
317
318
319
320
        free( val.psz_string );
    }
    else
    {
321
        p_sys->psz_current_dir = strdup( p_intf->p_libvlc->psz_homedir );
hartman's avatar
hartman committed
322
    }
323

hartman's avatar
hartman committed
324
    p_sys->i_dir_entries = 0;
325
    p_sys->pp_dir_entries = NULL;
326
    p_sys->b_show_hidden_files = false;
hartman's avatar
hartman committed
327
    ReadDir( p_intf );
328

329
    return VLC_SUCCESS;
330
331
332
333
334
335
}

/*****************************************************************************
 * Close: destroy interface window
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
336
{
337
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
338
    intf_sys_t    *p_sys = p_intf->p_sys;
hartman's avatar
hartman committed
339
    int i;
340

341
342
    PlaylistDestroy( p_intf );

hartman's avatar
hartman committed
343
344
345
    for( i = 0; i < p_sys->i_dir_entries; i++ )
    {
        struct dir_entry_t *p_dir_entry = p_sys->pp_dir_entries[i];
346
        free( p_dir_entry->psz_path );
hartman's avatar
hartman committed
347
        REMOVE_ELEM( p_sys->pp_dir_entries, p_sys->i_dir_entries, i );
348
        free( p_dir_entry );
hartman's avatar
hartman committed
349
350
    }
    p_sys->pp_dir_entries = NULL;
351

352
353
354
355
    free( p_sys->psz_current_dir );
    free( p_sys->psz_search_chain );
    free( p_sys->psz_old_search );
    free( p_sys->psz_open_chain );
356

357
358
359
360
    if( p_sys->p_input )
    {
        vlc_object_release( p_sys->p_input );
    }
361
    pl_Release( p_intf );
362
363
364
365

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

366
367
    msg_Unsubscribe( p_intf, p_sys->p_sub );

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

373
    /* Destroy structure */
374
    free( p_sys );
375
376
377
378
379
380
381
}

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

    int i_key;
387
388
389
390
391
392
    time_t t_last_refresh;

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

400
    while( !intf_ShouldDie( p_intf ) )
401
402
403
    {
        msleep( INTF_IDLE_SLEEP );

404
        /* Update the input */
405
406
        PL_LOCK;
        if( p_sys->p_input == NULL )
407
        {
408
409
            p_sys->p_input = p_playlist->p_input;
            if( p_sys->p_input )
410
            {
411
                if( !p_sys->p_input->b_dead )
412
                {
413
                    vlc_object_yield( p_sys->p_input );
414
                }
415
            }
416
        }
417
418
419
420
421
        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;
422
            p_sys->b_box_cleared = false;
423
424
        }
        PL_UNLOCK;
425

426
        if( p_sys->b_box_plidx_follow && p_playlist->status.p_item )
427
        {
428
            FindIndex( p_intf );
429
        }
ivoire's avatar
ivoire committed
430
431
    
        while( ( i_key = wgetch( p_sys->w ) ) != -1 )
432
433
434
435
        {
            /*
             * HandleKey returns 1 if the screen needs to be redrawn
             */
436
            if( HandleKey( p_intf, i_key ) )
437
438
439
440
            {
                Redraw( p_intf, &t_last_refresh );
            }
        }
441
442
443
444
445
        /* Hack */
        if( p_sys->f_slider > 0.0001 && !p_sys->b_box_cleared )
        {
            clear();
            Redraw( p_intf, &t_last_refresh );
446
            p_sys->b_box_cleared = true;
447
        }
448
449
450
451

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

/* following functions are local */
ivoire's avatar
ivoire committed
463
464
465
466
467
468
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() )
    {
469
        p_intf->p_sys->b_color = false;
ivoire's avatar
ivoire committed
470
471
472
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
        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 );

512
    p_intf->p_sys->b_color_started = true;
ivoire's avatar
ivoire committed
513
514
515
}

#ifndef HAVE_NCURSESW
516
static char *KeyToUTF8( int i_key, char *psz_part )
517
{
518
    char *psz_utf8;
519
520
521
522
523
524
525
526
527
528
529
530
531
532
    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) */
533
    char *psz;
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
    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;
552
}
ivoire's avatar
ivoire committed
553
#endif
554
555
556
557
558
559
560
561
562
563

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

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

564
565
static int HandleKey( intf_thread_t *p_intf, int i_key )
{
566
567
    intf_sys_t *p_sys = p_intf->p_sys;
    vlc_value_t val;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
568
569
570
571
572
573
574
575
576
577
    
    #define ReturnTrue \
    vlc_object_release( p_playlist ); \
    return 1
    
    #define ReturnFalse \
    vlc_object_release( p_playlist ); \
    return 0

    playlist_t *p_playlist = pl_Yield( p_intf );
578

579
    if( p_sys->i_box_type == BOX_PLAYLIST )
580
    {
581
582
        int b_ret = true;
        bool b_box_plidx_follow = false;
583

584
585
        switch( i_key )
        {
hartman's avatar
hartman committed
586
587
            vlc_value_t val;
            /* Playlist Settings */
588
            case 'r':
589
                var_Get( p_playlist, "random", &val );
hartman's avatar
hartman committed
590
                val.b_bool = !val.b_bool;
591
                var_Set( p_playlist, "random", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
592
                ReturnTrue;
hartman's avatar
hartman committed
593
            case 'l':
594
                var_Get( p_playlist, "loop", &val );
hartman's avatar
hartman committed
595
                val.b_bool = !val.b_bool;
596
                var_Set( p_playlist, "loop", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
597
                ReturnTrue;
hartman's avatar
hartman committed
598
            case 'R':
599
                var_Get( p_playlist, "repeat", &val );
hartman's avatar
hartman committed
600
                val.b_bool = !val.b_bool;
601
                var_Set( p_playlist, "repeat", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
602
                ReturnTrue;
hartman's avatar
hartman committed
603
604

            /* Playlist sort */
605
            case 'o':
606
                playlist_RecursiveNodeSort( p_playlist,
607
608
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_NORMAL );
609
                p_sys->b_need_update = true;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
610
                ReturnTrue;
611
            case 'O':
612
                playlist_RecursiveNodeSort( p_playlist,
613
614
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_REVERSE );
615
                p_sys->b_need_update = true;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
616
                ReturnTrue;
617

618
619
620
621
622
            /* Playlist view */
            case 'v':
                switch( p_sys->i_current_view )
                {
                    case VIEW_CATEGORY:
623
                        p_sys->i_current_view = VIEW_ONELEVEL;
624
625
                        break;
                    default:
zorglub's avatar
zorglub committed
626
                        p_sys->i_current_view = VIEW_CATEGORY;
627
                }
628
                //p_sys->b_need_update = true;
629
                PlaylistRebuild( p_intf );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
630
                ReturnTrue;
631

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

666
                PL_LOCK;
667
668
669
                p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
                if( p_item->i_children == -1 )
                {
670
                    playlist_DeleteFromInput( p_playlist,
671
                                              p_item->p_input->i_id, true );
672
673
                }
                else
674
                {
675
                    playlist_NodeDelete( p_playlist, p_item,
676
                                         true , false );
677
                }
678
                PL_UNLOCK;
679
                PlaylistRebuild( p_intf );
680
                break;
681
682
            }

683
            case KEY_ENTER:
ivoire's avatar
ivoire committed
684
685
            case '\r':
            case '\n':
686
                if( !p_sys->pp_plist[p_sys->i_box_plidx] )
687
                {
688
                    b_ret = false;
689
690
                    break;
                }
691
692
                if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == -1 )
693
                {
694
695
                    playlist_item_t *p_item, *p_parent;
                    p_item = p_parent =
696
                            p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
697

698
                    if( !p_parent )
699
                        p_parent = p_playlist->p_root_onelevel;
700
701
                    while( p_parent->p_parent )
                        p_parent = p_parent->p_parent;
702
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
703
                                      true, p_parent, p_item );
704
                }
Rafaël Carré's avatar
Rafaël Carré committed
705
706
707
                else if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == 0 )
                {   /* We only want to set the current node */
708
709
                    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
710
                }
711
                else
Rafaël Carré's avatar
Rafaël Carré committed
712
                {
713
                    p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
714
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, true,
715
                        p_sys->pp_plist[p_sys->i_box_plidx]->p_item, NULL );
716
                }
717
                b_box_plidx_follow = true;
718
                break;
719
            default:
720
                b_ret = false;
721
722
                break;
        }
723
724
725

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

            case KEY_ENTER:
ivoire's avatar
ivoire committed
770
771
            case '\r':
            case '\n':
772
773
            case ' ':
                if( p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ' )
hartman's avatar
hartman committed
774
                {
775
776
                    int i_size_entry = strlen( "directory://" ) +
                                       strlen( p_sys->psz_current_dir ) +
777
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
778
779
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

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

782
783
784
                    playlist_item_t *p_parent = p_sys->p_node;
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
785
                    if( !p_parent )
786
                        p_parent = p_playlist->p_local_onelevel;
787

Rafaël Carré's avatar
Rafaël Carré committed
788
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
789
790
                        p_parent = p_parent->p_parent;

791
792
                    playlist_Add( p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
                                  PLAYLIST_END,
Rafaël Carré's avatar
Rafaël Carré committed
793
                                  p_parent->p_input == 
794
                                    p_playlist->p_local_onelevel->p_input
795
                                  , false );
796

hartman's avatar
hartman committed
797
798
799
800
801
802
                    p_sys->i_box_type = BOX_PLAYLIST;
                    free( psz_uri );
                }
                else
                {
                    int i_size_entry = strlen( p_sys->psz_current_dir ) +
803
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
804
805
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

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

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

            default:
                break;
        }
    }
893
894
    else if( p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain )
    {
ivoire's avatar
ivoire committed
895
        int i_chain_len = strlen( p_sys->psz_search_chain );
896
897
        switch( i_key )
        {
ivoire's avatar
ivoire committed
898
            case KEY_CLEAR:
899
900
            case 0x0c:      /* ^l */
                clear();
Felix Paul Kühne's avatar
Felix Paul Kühne committed
901
                ReturnTrue;
902
            case KEY_ENTER:
ivoire's avatar
ivoire committed
903
904
            case '\r':
            case '\n':
905
906
907
908
909
910
911
912
913