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
#include <vlc/vlc.h>
42
#include <vlc_plugin.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
43

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

49
50
#include <ncurses.h>

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

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

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

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

80
81
82
#define SEARCH_CHAIN_SIZE 20
#define OPEN_CHAIN_SIZE 50

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

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

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

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

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

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

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

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

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

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

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

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

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

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

206
    int             b_box_cleared;
207

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

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

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

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

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

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

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

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

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

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

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

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

    clear();

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

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

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

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

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

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

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

330
    return VLC_SUCCESS;
331
332
333
334
335
336
}

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

342
343
    PlaylistDestroy( p_intf );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    playlist_t *p_playlist = pl_Yield( p_intf );
583

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            default:
                break;
        }
    }
898
899
    else if( p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain )
    {