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

41
#include <vlc_common.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
    var_AddCallback( p_playlist, "item-change", PlaylistChanged, p_intf );
403

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

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

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

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

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

/* following functions are local */
ivoire's avatar
ivoire committed
468
469
470
471
472
473
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() )
    {
474
        p_intf->p_sys->b_color = false;
ivoire's avatar
ivoire committed
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
515
516
        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 );

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

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

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

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

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

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

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

    playlist_t *p_playlist = pl_Yield( p_intf );
587

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            default:
                break;
        }
    }
902
903
    else if( p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain )
    {
ivoire's avatar
ivoire committed
904
        int i_chain_len = strlen( p_sys->psz_search_chain );
905
906
        switch( i_key )
        {
ivoire's avatar
ivoire committed
907
            case KEY_CLEAR:
908
909
            case 0x0c:      /* ^l */
                clear();
Felix Paul Kühne's avatar
Felix Paul Kühne committed
910
                ReturnTrue;
911
            case KEY_ENTER:
ivoire's avatar
ivoire committed
912
913
            case '\r':
            case '\n':
914
915
916
917
918
919
920
921
922
                if( i_chain_len > 0 )
                {
                    p_sys->psz_old_search = strdup( p_sys->psz_search_chain );
                }
                else if( p_sys->psz_old_search )
                {
                    SearchPlaylist( p_intf, p_sys->psz_old_search );
                }
                p_sys->i_box_type = BOX_PLAYLIST;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
923
                ReturnTrue;
ivoire's avatar
ivoire committed
924