ncurses.c 85.1 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
#endif
46

47
48
#include <ncurses.h>

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

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

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

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

78
79
80
#define SEARCH_CHAIN_SIZE 20
#define OPEN_CHAIN_SIZE 50

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

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

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

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

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

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

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

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

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

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

ivoire's avatar
ivoire committed
183
184
185
    vlc_bool_t      b_color;
    vlc_bool_t      b_color_started;

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

197
198
    int             i_box_plidx;    /* Playlist index */
    int             b_box_plidx_follow;
199
    int             i_box_bidx;     /* browser index */
200

201
202
    playlist_item_t *p_node;        /* current node */

203
    int             b_box_cleared;
204

205
    msg_subscription_t* p_sub;                  /* message bank subscription */
206
207
208
209
210
211

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

hartman's avatar
hartman committed
216
217
218
    char            *psz_current_dir;
    int             i_dir_entries;
    struct dir_entry_t  **pp_dir_entries;
219
    vlc_bool_t      b_show_hidden_files;
220
221
222
223

    int             i_current_view;             /* playlist view             */
    struct pl_item_t    **pp_plist;
    int             i_plist_entries;
224
225
226
    vlc_bool_t      b_need_update;              /* for playlist view         */

    int             i_verbose;                  /* stores verbosity level    */
227
228
};

ivoire's avatar
ivoire committed
229
static void DrawBox( WINDOW *win, int y, int x, int h, int w, const char *title, vlc_bool_t b_color );
230
231
232
static void DrawLine( WINDOW *win, int y, int x, int w );
static void DrawEmptyLine( WINDOW *win, int y, int x, int w );

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

    /* Allocate instance and initialize some members */
243
    p_sys = p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
244
    p_sys->p_node = NULL;
245
246
247
248
249
250
251
    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;
252
    p_sys->b_box_plidx_follow = VLC_TRUE;
253
    p_sys->b_box_cleared = VLC_FALSE;
254
    p_sys->i_box_plidx = 0;
255
    p_sys->i_box_bidx = 0;
256
    p_sys->p_sub = msg_Subscribe( p_intf, MSG_QUEUE_NORMAL );
ivoire's avatar
ivoire committed
257
258
259
260
    p_sys->b_color = p_this->p_libvlc->b_color;
    p_sys->b_color_started = VLC_FALSE;

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

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

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

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

    clear();

284
285
286
    /* exported function */
    p_intf->pf_run = Run;

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

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

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

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

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

hartman's avatar
hartman committed
322
    p_sys->i_dir_entries = 0;
323
    p_sys->pp_dir_entries = NULL;
324
    p_sys->b_show_hidden_files = VLC_FALSE;
hartman's avatar
hartman committed
325
    ReadDir( p_intf );
326

327
    return VLC_SUCCESS;
328
329
330
331
332
333
}

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

340
341
    var_DelCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
    var_DelCallback( p_playlist, "item-append", PlaylistChanged, p_intf );
342
343
344

    PlaylistDestroy( p_intf );

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

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

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

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

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

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

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

/*****************************************************************************
 * Run: ncurses thread
 *****************************************************************************/
static void Run( intf_thread_t *p_intf )
{
384
    intf_sys_t    *p_sys = p_intf->p_sys;
385
    playlist_t    *p_playlist = pl_Yield( p_intf );
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
    while( !intf_ShouldDie( p_intf ) )
400
401
402
    {
        msleep( INTF_IDLE_SLEEP );

403
        /* Update the input */
404
405
406
407
408
        var_AddCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
        var_AddCallback( p_playlist, "item-append", PlaylistChanged, p_intf );

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

428
        if( p_sys->b_box_plidx_follow && p_playlist->status.p_item )
429
        {
430
            FindIndex( p_intf );
431
        }
ivoire's avatar
ivoire committed
432
433
    
        while( ( i_key = wgetch( p_sys->w ) ) != -1 )
434
435
436
437
        {
            /*
             * HandleKey returns 1 if the screen needs to be redrawn
             */
438
            if( HandleKey( p_intf, i_key ) )
439
440
441
442
            {
                Redraw( p_intf, &t_last_refresh );
            }
        }
443
444
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 );
            p_sys->b_box_cleared = VLC_TRUE;
        }
450
451
452
453

        /*
         * redraw the screen every second
         */
454
        if( (time(0) - t_last_refresh) >= 1 )
455
        {
456
            ManageSlider( p_intf );
457
458
459
460
461
462
            Redraw( p_intf, &t_last_refresh );
        }
    }
}

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

    p_intf->p_sys->b_color_started = VLC_TRUE;
}

#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;
568
    playlist_t *p_playlist = pl_Get( p_intf );
569

570
    if( p_sys->i_box_type == BOX_PLAYLIST )
571
    {
572
        int b_ret = VLC_TRUE;
573
        vlc_bool_t b_box_plidx_follow = VLC_FALSE;
574

575
576
        switch( i_key )
        {
hartman's avatar
hartman committed
577
578
            vlc_value_t val;
            /* Playlist Settings */
579
            case 'r':
580
                var_Get( p_playlist, "random", &val );
hartman's avatar
hartman committed
581
                val.b_bool = !val.b_bool;
582
                var_Set( p_playlist, "random", val );
hartman's avatar
hartman committed
583
584
                return 1;
            case 'l':
585
                var_Get( p_playlist, "loop", &val );
hartman's avatar
hartman committed
586
                val.b_bool = !val.b_bool;
587
                var_Set( p_playlist, "loop", val );
588
                return 1;
hartman's avatar
hartman committed
589
            case 'R':
590
                var_Get( p_playlist, "repeat", &val );
hartman's avatar
hartman committed
591
                val.b_bool = !val.b_bool;
592
                var_Set( p_playlist, "repeat", val );
hartman's avatar
hartman committed
593
594
595
                return 1;

            /* Playlist sort */
596
            case 'o':
597
                playlist_RecursiveNodeSort( p_playlist,
598
599
600
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_NORMAL );
                p_sys->b_need_update = VLC_TRUE;
601
602
                return 1;
            case 'O':
603
                playlist_RecursiveNodeSort( p_playlist,
604
605
606
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_REVERSE );
                p_sys->b_need_update = VLC_TRUE;
607
608
                return 1;

609
610
611
612
613
            /* Playlist view */
            case 'v':
                switch( p_sys->i_current_view )
                {
                    case VIEW_CATEGORY:
614
                        p_sys->i_current_view = VIEW_ONELEVEL;
615
616
                        break;
                    default:
zorglub's avatar
zorglub committed
617
                        p_sys->i_current_view = VIEW_CATEGORY;
618
                }
619
                //p_sys->b_need_update = VLC_TRUE;
620
621
622
                PlaylistRebuild( p_intf );
                return 1;

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

657
                PL_LOCK;
658
659
660
                p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
                if( p_item->i_children == -1 )
                {
661
                    playlist_DeleteFromInput( p_playlist,
zorglub's avatar
zorglub committed
662
                                              p_item->p_input->i_id, VLC_TRUE );
663
664
                }
                else
665
                {
666
667
                    playlist_NodeDelete( p_playlist, p_item,
                                         VLC_TRUE , VLC_FALSE );
668
                }
669
                PL_UNLOCK;
670
                PlaylistRebuild( p_intf );
671
                break;
672
673
            }

674
            case KEY_ENTER:
ivoire's avatar
ivoire committed
675
676
            case '\r':
            case '\n':
677
                if( !p_sys->pp_plist[p_sys->i_box_plidx] )
678
679
680
681
                {
                    b_ret = VLC_FALSE;
                    break;
                }
682
683
                if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == -1 )
684
                {
685
686
                    playlist_item_t *p_item, *p_parent;
                    p_item = p_parent =
687
                            p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
688

689
                    if( !p_parent )
690
                        p_parent = p_playlist->p_root_onelevel;
691
692
                    while( p_parent->p_parent )
                        p_parent = p_parent->p_parent;
693
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
694
                                      VLC_TRUE, p_parent, p_item );
695
                }
Rafaël Carré's avatar
Rafaël Carré committed
696
697
698
                else if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == 0 )
                {   /* We only want to set the current node */
699
700
                    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
701
                }
702
                else
Rafaël Carré's avatar
Rafaël Carré committed
703
                {
704
705
706
                    p_sys->p_node = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, VLC_TRUE,
                        p_sys->pp_plist[p_sys->i_box_plidx]->p_item, NULL );
707
                }
708
                b_box_plidx_follow = VLC_TRUE;
709
                break;
710
            default:
711
                b_ret = VLC_FALSE;
712
713
                break;
        }
714
715
716

        if( b_ret )
        {
717
718
            int i_max = p_sys->i_plist_entries;
            if( p_sys->i_box_plidx >= i_max ) p_sys->i_box_plidx = i_max - 1;
719
            if( p_sys->i_box_plidx < 0 ) p_sys->i_box_plidx = 0;
720
721
722
723
            if( PlaylistIsPlaying( p_intf,
                    p_sys->pp_plist[p_sys->i_box_plidx]->p_item ) )
                b_box_plidx_follow = VLC_TRUE;
            p_sys->b_box_plidx_follow = b_box_plidx_follow;
724
725
            return 1;
        }
726
    }
hartman's avatar
hartman committed
727
728
    if( p_sys->i_box_type == BOX_BROWSE )
    {
729
        vlc_bool_t b_ret = VLC_TRUE;
hartman's avatar
hartman committed
730
731
732
733
        /* Browser navigation */
        switch( i_key )
        {
            case KEY_HOME:
734
                p_sys->i_box_bidx = 0;
hartman's avatar
hartman committed
735
                break;
Rafaël Carré's avatar
Rafaël Carré committed
736
737
738
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
hartman's avatar
hartman committed
739
            case KEY_END:
740
                p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
hartman's avatar
hartman committed
741
742
                break;
            case KEY_UP:
743
                p_sys->i_box_bidx--;
hartman's avatar
hartman committed
744
745
                break;
            case KEY_DOWN:
746
                p_sys->i_box_bidx++;
hartman's avatar
hartman committed
747
748
                break;
            case KEY_PPAGE:
749
                p_sys->i_box_bidx -= p_sys->i_box_lines;
hartman's avatar
hartman committed
750
751
                break;
            case KEY_NPAGE:
752
                p_sys->i_box_bidx += p_sys->i_box_lines;
hartman's avatar
hartman committed
753
                break;
754
755
756
757
758
            case '.': /* Toggle show hidden files */
                p_sys->b_show_hidden_files = ( p_sys->b_show_hidden_files ==
                    VLC_TRUE ? VLC_FALSE : VLC_TRUE );
                ReadDir( p_intf );
                break;
hartman's avatar
hartman committed
759
760

            case KEY_ENTER:
ivoire's avatar
ivoire committed
761
762
            case '\r':
            case '\n':
763
764
            case ' ':
                if( p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ' )
hartman's avatar
hartman committed
765
                {
766
767
                    int i_size_entry = strlen( "directory://" ) +
                                       strlen( p_sys->psz_current_dir ) +
768
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
769
770
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

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

773
774
775
                    playlist_item_t *p_parent = p_sys->p_node;
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
776
                    if( !p_parent )
777
                        p_parent = p_playlist->p_local_onelevel;
778

Rafaël Carré's avatar
Rafaël Carré committed
779
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
780
781
                        p_parent = p_parent->p_parent;

782
783
                    playlist_Add( p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
                                  PLAYLIST_END,
Rafaël Carré's avatar
Rafaël Carré committed
784
                                  p_parent->p_input == 
785
                                    p_playlist->p_local_onelevel->p_input
786
                                  , VLC_FALSE );
787

hartman's avatar
hartman committed
788
789
790
791
792
793
                    p_sys->i_box_type = BOX_PLAYLIST;
                    free( psz_uri );
                }
                else
                {
                    int i_size_entry = strlen( p_sys->psz_current_dir ) +
794
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
795
796
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

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

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

            default:
                break;
        }
    }
884
885
    else if( p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain )
    {
ivoire's avatar
ivoire committed
886
        int i_chain_len = strlen( p_sys->psz_search_chain );
887
888
        switch( i_key )
        {
ivoire's avatar
ivoire committed
889
            case KEY_CLEAR:
890
891
892
893
            case 0x0c:      /* ^l */
                clear();
                return 1;
            case KEY_ENTER:
ivoire's avatar
ivoire committed
894
895
            case '\r':
            case '\n':
896
897
898
899
900
901
902
903
904
905
                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;
                return 1;
ivoire's avatar
ivoire committed
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
            case 0x1b: /* ESC */
                /* Alt+key combinations return 2 keys in the terminal keyboard:
                 * ESC, and the 2nd key.
                 * If some other key is available immediately (where immediately
                 * means after wgetch() 1 second delay ), that means that the
                 * ESC key was not pressed.
                 *
                 * man 3X curs_getch says:
                 *
                 * Use of the escape key by a programmer for a single
                 * character function is discouraged, as it will cause a delay 
                 * of up to one second while the keypad code looks for a
                 * following function-key sequence.
                 *
                 */
                if( wgetch( p_sys->w ) != ERR )
                    return 0;
923
924
925
926
                p_sys->i_box_plidx = p_sys->i_before_search;
                p_sys->i_box_type = BOX_PLAYLIST;
                return 1;
            case KEY_BACKSPACE:
ivoire's avatar
ivoire committed
927
            case 0x7f:
928
                RemoveLastUTF8Entity( p_sys->psz_search_chain, i_chain_len );
929
930
                break;
            default:
931
            {
ivoire's avatar
ivoire committed
932
933
934
935
936
937
938
#ifdef HAVE_NCURSESW
                if( i_chain_len + 1 < SEARCH_CHAIN_SIZE )
                {
                    p_sys->psz_search_chain[i_chain_len] = (char) i_key;
                    p_sys->psz_search_chain[i_chain_len + 1] = '\0';
                }
#else
939
                char *psz_utf8 = KeyToUTF8( i_key, p_sys->psz_partial_keys );
940

941
                if( psz_utf8 != NULL )
942
                {
943
944
945
946
947
948
                    if( i_chain_len + strlen( psz_utf8 ) < SEARCH_CHAIN_SIZE )
                    {
                        strcpy( p_sys->psz_search_chain + i_chain_len,
                                psz_utf8 );
                    }
                    free( psz_utf8 );
949
                }
ivoire's avatar
ivoire committed
950
#endif
951
                break;
952
            }
953
        }
954
955
        free( p_sys->psz_old_search );
        p_sys->psz_old_search = NULL;
956
957
958
959
960
        SearchPlaylist( p_intf, p_sys->psz_search_chain );
        return 1;
    }
    else if( p_sys->i_box_type == BOX_OPEN && p_sys->psz_open_chain )
    {
961
        int i_chain_len = strlen( p_sys->psz_open_chain );
962
963
964

        switch( i_key )
        {
ivoire's avatar
ivoire committed
965
966
            case KEY_CLEAR:
            case 0x0c:          /* ^l */
967
968
969
                clear();
                return 1;
            case KEY_ENTER:
ivoire's avatar
ivoire committed
970
971
            case '\r': 
            case '\n':
972
                if( i_chain_len > 0 )
973
                {
974
975
976
977
                    playlist_item_t *p_parent = p_sys->p_node;
                   
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
978
                    if( !p_parent )
979
                        p_parent = p_playlist->p_local_onelevel;
980

Rafaël Carré's avatar
Rafaël Carré committed
981
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
982
983
984
985
                        p_parent = p_parent->p_parent;

                    playlist_Add( p_playlist, p_sys->psz_open_chain, NULL,
                                  PLAYLIST_APPEND|PLAYLIST_GO, PLAYLIST_END,
Rafaël Carré's avatar
Rafaël Carré committed
986
                                  p_parent->p_input == 
987
                                    p_playlist->p_local_onelevel->p_input
988
                                  , VLC_FALSE );
989