ncurses.c 85.4 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
593
                vlc_object_release( p_playlist );
                ReturnTrue;
hartman's avatar
hartman committed
594
            case 'l':
595
                var_Get( p_playlist, "loop", &val );
hartman's avatar
hartman committed
596
                val.b_bool = !val.b_bool;
597
                var_Set( p_playlist, "loop", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
598
599
                vlc_object_release( p_playlist );
                ReturnTrue;
hartman's avatar
hartman committed
600
            case 'R':
601
                var_Get( p_playlist, "repeat", &val );
hartman's avatar
hartman committed
602
                val.b_bool = !val.b_bool;
603
                var_Set( p_playlist, "repeat", val );
Felix Paul Kühne's avatar
Felix Paul Kühne committed
604
605
                vlc_object_release( p_playlist );
                ReturnTrue;
hartman's avatar
hartman committed
606
607

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

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

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

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

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

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

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

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

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

785
786
787
                    playlist_item_t *p_parent = p_sys->p_node;
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
788
                    if( !p_parent )
789
                        p_parent = p_playlist->p_local_onelevel;
790

Rafaël Carré's avatar
Rafaël Carré committed
791
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
792
793
                        p_parent = p_parent->p_parent;

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

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

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

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

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