ncurses.c 70.8 KB
Newer Older
1
/*****************************************************************************
2
 * ncurses.c : NCurses interface for vlc
3
 *****************************************************************************
4
 * Copyright (C) 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
28
29
30
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
31
32
#include <vlc/vlc.h>

33
34
35
#include <errno.h>                                                 /* ENOMEM */
#include <time.h>

36
37
#ifdef HAVE_NCURSESW
#   define _XOPEN_SOURCE_EXTENDED 1
38
#endif
39

40
41
#include <ncurses.h>

zorglub's avatar
zorglub committed
42
#include <vlc_interface.h>
43
44
#include <vlc_vout.h>
#include <vlc_aout.h>
zorglub's avatar
zorglub committed
45
#include <vlc_charset.h>
46
47
#include <vlc_input.h>
#include <vlc_playlist.h>
48
#include <vlc_meta.h>
49

hartman's avatar
hartman committed
50
51
52
53
54
55
56
57
#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

58
59
#ifdef HAVE_CDDAX
#define CDDA_MRL "cddax://"
60
#else
61
62
63
64
65
66
#define CDDA_MRL "cdda://"
#endif

#ifdef HAVE_VCDX
#define VCD_MRL "vcdx://"
#else
67
#define VCD_MRL "vcd://"
68
69
#endif

70
71
72
#define SEARCH_CHAIN_SIZE 20
#define OPEN_CHAIN_SIZE 50

73
74
75
76
/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
static int  Open           ( vlc_object_t * );
77
static void Close          ( vlc_object_t * );
78

79
static void Run            ( intf_thread_t * );
80
static void PlayPause      ( intf_thread_t * );
81
82
83
static void Eject          ( intf_thread_t * );

static int  HandleKey      ( intf_thread_t *, int );
84
static void Redraw         ( intf_thread_t *, time_t * );
85
86

static playlist_item_t *PlaylistGetRoot( intf_thread_t * );
87
static void PlaylistRebuild( intf_thread_t * );
Laurent Aimar's avatar
Laurent Aimar committed
88
static void PlaylistAddNode( intf_thread_t *, playlist_item_t *, int, const char *);
89
90
91
static void PlaylistDestroy( intf_thread_t * );
static int  PlaylistChanged( vlc_object_t *, const char *, vlc_value_t,
                             vlc_value_t, void * );
92
93
static inline vlc_bool_t PlaylistIsPlaying( intf_thread_t *,
                                            playlist_item_t * );
94
static void FindIndex      ( intf_thread_t * );
95
static void SearchPlaylist ( intf_thread_t *, char * );
96
static int  SubSearchPlaylist( intf_thread_t *, char *, int, int );
97

98
static void ManageSlider   ( intf_thread_t * );
hartman's avatar
hartman committed
99
static void ReadDir        ( intf_thread_t * );
100
101
102
103

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
hartman's avatar
hartman committed
104
105
106

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

110
vlc_module_begin();
111
    set_shortname( "Ncurses" );
112
    set_description( _("Ncurses interface") );
113
    set_capability( "interface", 10 );
zorglub's avatar
zorglub committed
114
    set_category( CAT_INTERFACE );
zorglub's avatar
zorglub committed
115
    set_subcategory( SUBCAT_INTERFACE_MAIN );
116
117
    set_callbacks( Open, Close );
    add_shortcut( "curses" );
hartman's avatar
hartman committed
118
    add_directory( "browse-dir", NULL, NULL, BROWSE_TEXT, BROWSE_LONGTEXT, VLC_FALSE );
119
120
121
122
123
vlc_module_end();

/*****************************************************************************
 * intf_sys_t: description and status of ncurses interface
 *****************************************************************************/
124
125
126
127
128
129
enum
{
    BOX_NONE,
    BOX_HELP,
    BOX_INFO,
    BOX_LOG,
130
131
    BOX_PLAYLIST,
    BOX_SEARCH,
hartman's avatar
hartman committed
132
    BOX_OPEN,
133
134
    BOX_BROWSE,
    BOX_META
hartman's avatar
hartman committed
135
};
136
137
138
139
140
enum
{
    VIEW_CATEGORY,
    VIEW_ONELEVEL
};
hartman's avatar
hartman committed
141
142
143
144
struct dir_entry_t
{
    vlc_bool_t  b_file;
    char        *psz_path;
145
};
146
147
148
149
150
struct pl_item_t
{
    playlist_item_t *p_item;
    char            *psz_display;
};
151
152
struct intf_sys_t
{
153
154
155
156
157
158
159
160
161
162
163
164
165
    input_thread_t *p_input;

    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;

166
167
    int             i_box_plidx;    /* Playlist index */
    int             b_box_plidx_follow;
168
    int             i_box_bidx;     /* browser index */
169

170
171
    playlist_item_t *p_node;        /* current node */

172
    int             b_box_cleared;
173

174
    msg_subscription_t* p_sub;                  /* message bank subscription */
175
176
177
178
179
180

    char            *psz_search_chain;          /* for playlist searching    */
    char            *psz_old_search;            /* for searching next        */
    int             i_before_search;

    char            *psz_open_chain;
181
    char             psz_partial_keys[7];
182

hartman's avatar
hartman committed
183
184
185
    char            *psz_current_dir;
    int             i_dir_entries;
    struct dir_entry_t  **pp_dir_entries;
186
    vlc_bool_t      b_show_hidden_files;
187
188
189
190

    int             i_current_view;             /* playlist view             */
    struct pl_item_t    **pp_plist;
    int             i_plist_entries;
191
192
193
    vlc_bool_t      b_need_update;              /* for playlist view         */

    int             i_verbose;                  /* stores verbosity level    */
194
195
};

196
static void DrawBox( WINDOW *win, int y, int x, int h, int w, const char *title );
197
198
199
static void DrawLine( WINDOW *win, int y, int x, int w );
static void DrawEmptyLine( WINDOW *win, int y, int x, int w );

200
201
202
203
/*****************************************************************************
 * Open: initialize and create window
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
204
{
205
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
206
    intf_sys_t    *p_sys;
207
    vlc_value_t    val;
208
209

    /* Allocate instance and initialize some members */
210
    p_sys = p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
211
    p_sys->p_node = NULL;
212
213
214
215
216
217
218
    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;
219
    p_sys->b_box_plidx_follow = VLC_TRUE;
220
    p_sys->b_box_cleared = VLC_FALSE;
221
    p_sys->i_box_plidx = 0;
222
    p_sys->i_box_bidx = 0;
223
    p_sys->p_sub = msg_Subscribe( p_intf, MSG_QUEUE_NORMAL );
224
    memset( p_sys->psz_partial_keys, 0, sizeof( p_sys->psz_partial_keys ) );
225
226

    /* Initialize the curses library */
227
228
    p_sys->w = initscr();
    keypad( p_sys->w, TRUE );
229
230
231
232
233
234
235
236
237
238
239
240
    /* Don't do NL -> CR/NL */
    nonl();
    /* Take input chars one at a time */
    cbreak();
    /* Don't echo */
    noecho();

    curs_set(0);
    timeout(0);

    clear();

241
242
243
    /* exported function */
    p_intf->pf_run = Run;

244
245
246
    /* Remember verbosity level */
    var_Get( p_intf->p_libvlc, "verbose", &val );
    p_sys->i_verbose = val.i_int;
247
248
    /* Set quiet mode */
    val.i_int = -1;
249
    var_Set( p_intf->p_libvlc, "verbose", val );
250

251
    /* Set defaul playlist view */
zorglub's avatar
zorglub committed
252
    p_sys->i_current_view = VIEW_CATEGORY;
253
254
255
256
    p_sys->pp_plist = NULL;
    p_sys->i_plist_entries = 0;
    p_sys->b_need_update = VLC_FALSE;

257
258
259
260
261
262
263
    /* 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 );
264

hartman's avatar
hartman committed
265
266
267
    /* Initialize browser options */
    var_Create( p_intf, "browse-dir", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
    var_Get( p_intf, "browse-dir", &val);
268

hartman's avatar
hartman committed
269
270
    if( val.psz_string && *val.psz_string )
    {
271
        p_sys->psz_current_dir = strdup( val.psz_string );
hartman's avatar
hartman committed
272
273
274
275
        free( val.psz_string );
    }
    else
    {
276
        p_sys->psz_current_dir = strdup( p_intf->p_libvlc->psz_homedir );
hartman's avatar
hartman committed
277
    }
278

hartman's avatar
hartman committed
279
    p_sys->i_dir_entries = 0;
280
    p_sys->pp_dir_entries = NULL;
281
    p_sys->b_show_hidden_files = VLC_FALSE;
hartman's avatar
hartman committed
282
    ReadDir( p_intf );
283

284
    return VLC_SUCCESS;
285
286
287
288
289
290
}

/*****************************************************************************
 * Close: destroy interface window
 *****************************************************************************/
static void Close( vlc_object_t *p_this )
291
{
292
    intf_thread_t *p_intf = (intf_thread_t *)p_this;
293
    intf_sys_t    *p_sys = p_intf->p_sys;
294
    playlist_t    *p_playlist = pl_Get( p_intf );
hartman's avatar
hartman committed
295
    int i;
296

297
298
    var_DelCallback( p_playlist, "intf-change", PlaylistChanged, p_intf );
    var_DelCallback( p_playlist, "item-append", PlaylistChanged, p_intf );
299
300
301

    PlaylistDestroy( p_intf );

hartman's avatar
hartman committed
302
303
304
305
306
307
308
309
    for( i = 0; i < p_sys->i_dir_entries; i++ )
    {
        struct dir_entry_t *p_dir_entry = p_sys->pp_dir_entries[i];
        if( p_dir_entry->psz_path ) free( p_dir_entry->psz_path );
        REMOVE_ELEM( p_sys->pp_dir_entries, p_sys->i_dir_entries, i );
        if( p_dir_entry ) free( p_dir_entry );
    }
    p_sys->pp_dir_entries = NULL;
310

hartman's avatar
hartman committed
311
    if( p_sys->psz_current_dir ) free( p_sys->psz_current_dir );
312
313
314
315
    if( p_sys->psz_search_chain ) free( p_sys->psz_search_chain );
    if( p_sys->psz_old_search ) free( p_sys->psz_old_search );
    if( p_sys->psz_open_chain ) free( p_sys->psz_open_chain );

316
317
318
319
    if( p_sys->p_input )
    {
        vlc_object_release( p_sys->p_input );
    }
320
    pl_Release( p_intf );
321
322
323
324

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

325
326
    msg_Unsubscribe( p_intf, p_sys->p_sub );

327
328
329
330
331
    /* Restores initial verbose setting */
    vlc_value_t val;
    val.i_int = p_sys->i_verbose;
    var_Set( p_intf->p_libvlc, "verbose", val );

332
    /* Destroy structure */
333
    free( p_sys );
334
335
336
337
338
339
340
}

/*****************************************************************************
 * Run: ncurses thread
 *****************************************************************************/
static void Run( intf_thread_t *p_intf )
{
341
    intf_sys_t    *p_sys = p_intf->p_sys;
342
    playlist_t    *p_playlist = pl_Yield( p_intf );
343
344

    int i_key;
345
346
347
348
349
350
    time_t t_last_refresh;

    /*
     * force drawing the interface for the first time
     */
    t_last_refresh = ( time( 0 ) - 1);
351
352
353
354
    /*
     * force building of the playlist array
     */
    PlaylistRebuild( p_intf );
355

356
    while( !intf_ShouldDie( p_intf ) )
357
358
359
    {
        msleep( INTF_IDLE_SLEEP );

360
        /* Update the input */
361
362
363
364
365
        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 )
366
        {
367
368
            p_sys->p_input = p_playlist->p_input;
            if( p_sys->p_input )
369
            {
370
                if( !p_sys->p_input->b_dead )
371
                {
372
                    vlc_object_yield( p_sys->p_input );
373
                }
374
            }
375
        }
376
377
378
379
380
381
382
383
        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;
384

385
        if( p_sys->b_box_plidx_follow && p_playlist->status.p_item )
386
        {
387
            FindIndex( p_intf );
388
        }
389

390
        while( ( i_key = getch() ) != -1 )
391
392
393
394
        {
            /*
             * HandleKey returns 1 if the screen needs to be redrawn
             */
395
            if( HandleKey( p_intf, i_key ) )
396
397
398
399
            {
                Redraw( p_intf, &t_last_refresh );
            }
        }
400
401
402
403
404
405
406
        /* 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;
        }
407
408
409
410

        /*
         * redraw the screen every second
         */
411
        if( (time(0) - t_last_refresh) >= 1 )
412
        {
413
            ManageSlider( p_intf );
414
415
416
417
418
419
            Redraw( p_intf, &t_last_refresh );
        }
    }
}

/* following functions are local */
420
static char *KeyToUTF8( int i_key, char *psz_part )
421
{
422
    char *psz_utf8;
423
424
425
426
427
428
429
430
431
432
    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;

433
#ifdef HAVE_NCURSESW
434
435
    psz_utf8 = strdup( psz_part );
#else
436
437
438
439
    psz_utf8 = FromLocaleDup( psz_part );

    /* Ugly check for incomplete bytes sequences
     * (in case of non-UTF8 multibyte local encoding) */
440
    char *psz;
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
    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;
    }
456
#endif
457
458
459

    memset( psz_part, 0, 6 );
    return psz_utf8;
460
461
462
463
464
465
466
467
468
469
470
}

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

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

471
472
static int HandleKey( intf_thread_t *p_intf, int i_key )
{
473
474
    intf_sys_t *p_sys = p_intf->p_sys;
    vlc_value_t val;
475
    playlist_t *p_playlist = pl_Get( p_intf );
476

477
    if( p_sys->i_box_type == BOX_PLAYLIST )
478
    {
479
        int b_ret = VLC_TRUE;
480
        vlc_bool_t b_box_plidx_follow = VLC_FALSE;
481

482
483
        switch( i_key )
        {
hartman's avatar
hartman committed
484
485
            vlc_value_t val;
            /* Playlist Settings */
486
            case 'r':
487
                var_Get( p_playlist, "random", &val );
hartman's avatar
hartman committed
488
                val.b_bool = !val.b_bool;
489
                var_Set( p_playlist, "random", val );
hartman's avatar
hartman committed
490
491
                return 1;
            case 'l':
492
                var_Get( p_playlist, "loop", &val );
hartman's avatar
hartman committed
493
                val.b_bool = !val.b_bool;
494
                var_Set( p_playlist, "loop", val );
495
                return 1;
hartman's avatar
hartman committed
496
            case 'R':
497
                var_Get( p_playlist, "repeat", &val );
hartman's avatar
hartman committed
498
                val.b_bool = !val.b_bool;
499
                var_Set( p_playlist, "repeat", val );
hartman's avatar
hartman committed
500
501
502
                return 1;

            /* Playlist sort */
503
            case 'o':
504
                playlist_RecursiveNodeSort( p_playlist,
505
506
507
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_NORMAL );
                p_sys->b_need_update = VLC_TRUE;
508
509
                return 1;
            case 'O':
510
                playlist_RecursiveNodeSort( p_playlist,
511
512
513
                                            PlaylistGetRoot( p_intf ),
                                            SORT_TITLE_NODES_FIRST, ORDER_REVERSE );
                p_sys->b_need_update = VLC_TRUE;
514
515
                return 1;

516
517
518
519
520
            /* Playlist view */
            case 'v':
                switch( p_sys->i_current_view )
                {
                    case VIEW_CATEGORY:
521
                        p_sys->i_current_view = VIEW_ONELEVEL;
522
523
                        break;
                    default:
zorglub's avatar
zorglub committed
524
                        p_sys->i_current_view = VIEW_CATEGORY;
525
                }
526
                //p_sys->b_need_update = VLC_TRUE;
527
528
529
                PlaylistRebuild( p_intf );
                return 1;

530
            /* Playlist navigation */
531
532
533
            case 'g':
                FindIndex( p_intf );
                break;
534
            case KEY_HOME:
535
536
                p_sys->i_box_plidx = 0;
                break;
Rafaël Carré's avatar
Rafaël Carré committed
537
538
539
540
541
#ifdef __FreeBSD__
/* workaround for FreeBSD + xterm:
 * see http://www.nabble.com/curses-vs.-xterm-key-mismatch-t3574377.html */
            case KEY_SELECT:
#endif
542
            case KEY_END:
543
                p_sys->i_box_plidx = p_playlist->items.i_size - 1;
544
                break;
545
            case KEY_UP:
546
547
                p_sys->i_box_plidx--;
                break;
548
            case KEY_DOWN:
549
550
                p_sys->i_box_plidx++;
                break;
551
            case KEY_PPAGE:
552
553
                p_sys->i_box_plidx -= p_sys->i_box_lines;
                break;
554
            case KEY_NPAGE:
555
556
                p_sys->i_box_plidx += p_sys->i_box_lines;
                break;
hartman's avatar
hartman committed
557
            case 'D':
558
559
560
            case KEY_BACKSPACE:
            case KEY_DC:
            {
561
                playlist_item_t *p_item;
562

563
                PL_LOCK;
564
565
566
                p_item = p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
                if( p_item->i_children == -1 )
                {
567
                    playlist_DeleteFromInput( p_playlist,
zorglub's avatar
zorglub committed
568
                                              p_item->p_input->i_id, VLC_TRUE );
569
570
                }
                else
571
                {
572
573
                    playlist_NodeDelete( p_playlist, p_item,
                                         VLC_TRUE , VLC_FALSE );
574
                }
575
                PL_UNLOCK;
576
                PlaylistRebuild( p_intf );
577
                break;
578
579
            }

580
581
            case KEY_ENTER:
            case 0x0d:
582
                if( !p_sys->pp_plist[p_sys->i_box_plidx] )
583
584
585
586
                {
                    b_ret = VLC_FALSE;
                    break;
                }
587
588
                if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == -1 )
589
                {
590
591
                    playlist_item_t *p_item, *p_parent;
                    p_item = p_parent =
592
                            p_sys->pp_plist[p_sys->i_box_plidx]->p_item;
593

594
                    if( !p_parent )
595
                        p_parent = p_playlist->p_root_onelevel;
596
597
                    while( p_parent->p_parent )
                        p_parent = p_parent->p_parent;
598
                    playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
599
                                      VLC_TRUE, p_parent, p_item );
600
                }
Rafaël Carré's avatar
Rafaël Carré committed
601
602
603
                else if( p_sys->pp_plist[p_sys->i_box_plidx]->p_item->i_children
                        == 0 )
                {   /* We only want to set the current node */
604
605
                    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
606
                }
607
                else
Rafaël Carré's avatar
Rafaël Carré committed
608
                {
609
610
611
                    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 );
612
                }
613
                b_box_plidx_follow = VLC_TRUE;
614
                break;
615
            default:
616
                b_ret = VLC_FALSE;
617
618
                break;
        }
619
620
621

        if( b_ret )
        {
622
623
            int i_max = p_sys->i_plist_entries;
            if( p_sys->i_box_plidx >= i_max ) p_sys->i_box_plidx = i_max - 1;
624
            if( p_sys->i_box_plidx < 0 ) p_sys->i_box_plidx = 0;
625
626
627
628
            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;
629
630
            return 1;
        }
631
    }
hartman's avatar
hartman committed
632
633
    if( p_sys->i_box_type == BOX_BROWSE )
    {
634
        vlc_bool_t b_ret = VLC_TRUE;
hartman's avatar
hartman committed
635
636
637
638
        /* Browser navigation */
        switch( i_key )
        {
            case KEY_HOME:
639
                p_sys->i_box_bidx = 0;
hartman's avatar
hartman committed
640
                break;
Rafaël Carré's avatar
Rafaël Carré committed
641
642
643
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
hartman's avatar
hartman committed
644
            case KEY_END:
645
                p_sys->i_box_bidx = p_sys->i_dir_entries - 1;
hartman's avatar
hartman committed
646
647
                break;
            case KEY_UP:
648
                p_sys->i_box_bidx--;
hartman's avatar
hartman committed
649
650
                break;
            case KEY_DOWN:
651
                p_sys->i_box_bidx++;
hartman's avatar
hartman committed
652
653
                break;
            case KEY_PPAGE:
654
                p_sys->i_box_bidx -= p_sys->i_box_lines;
hartman's avatar
hartman committed
655
656
                break;
            case KEY_NPAGE:
657
                p_sys->i_box_bidx += p_sys->i_box_lines;
hartman's avatar
hartman committed
658
                break;
659
660
661
662
663
            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
664
665
666

            case KEY_ENTER:
            case 0x0d:
667
668
            case ' ':
                if( p_sys->pp_dir_entries[p_sys->i_box_bidx]->b_file || i_key == ' ' )
hartman's avatar
hartman committed
669
                {
670
671
                    int i_size_entry = strlen( "directory://" ) +
                                       strlen( p_sys->psz_current_dir ) +
672
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
673
674
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

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

677
678
679
                    playlist_item_t *p_parent = p_sys->p_node;
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
680
                    if( !p_parent )
681
                        p_parent = p_playlist->p_local_onelevel;
682

Rafaël Carré's avatar
Rafaël Carré committed
683
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
684
685
                        p_parent = p_parent->p_parent;

686
687
                    playlist_Add( p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
                                  PLAYLIST_END,
Rafaël Carré's avatar
Rafaël Carré committed
688
                                  p_parent->p_input == 
689
                                    p_playlist->p_local_onelevel->p_input
690
                                  , VLC_FALSE );
691

hartman's avatar
hartman committed
692
693
694
695
696
697
                    p_sys->i_box_type = BOX_PLAYLIST;
                    free( psz_uri );
                }
                else
                {
                    int i_size_entry = strlen( p_sys->psz_current_dir ) +
698
                                       strlen( p_sys->pp_dir_entries[p_sys->i_box_bidx]->psz_path ) + 2;
hartman's avatar
hartman committed
699
700
                    char *psz_uri = (char *)malloc( sizeof(char)*i_size_entry);

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

hartman's avatar
hartman committed
703
704
705
706
707
708
                    p_sys->psz_current_dir = strdup( psz_uri );
                    ReadDir( p_intf );
                    free( psz_uri );
                }
                break;
            default:
709
                b_ret = VLC_FALSE;
hartman's avatar
hartman committed
710
                break;
711
712
713
        }
        if( b_ret )
        {
714
715
            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
716
717
718
            return 1;
        }
    }
719
720
    else if( p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
             p_sys->i_box_type == BOX_META )
721
722
723
724
725
726
    {
        switch( i_key )
        {
            case KEY_HOME:
                p_sys->i_box_start = 0;
                return 1;
Rafaël Carré's avatar
Rafaël Carré committed
727
728
729
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
            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;
763
                ManageSlider( p_intf );
764
                return 1;
Rafaël Carré's avatar
Rafaël Carré committed
765
766
767
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
768
769
            case KEY_END:
                p_sys->f_slider = 99.9;
770
                ManageSlider( p_intf );
771
772
                return 1;
            case KEY_UP:
773
                p_sys->f_slider += 5.0;
774
                if( p_sys->f_slider >= 99.0 ) p_sys->f_slider = 99.0;
775
                ManageSlider( p_intf );
776
777
                return 1;
            case KEY_DOWN:
778
                p_sys->f_slider -= 5.0;
779
                if( p_sys->f_slider < 0.0 ) p_sys->f_slider = 0.0;
780
                ManageSlider( p_intf );
781
782
783
784
785
786
                return 1;

            default:
                break;
        }
    }
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
    else if( p_sys->i_box_type == BOX_SEARCH && p_sys->psz_search_chain )
    {
        int i_chain_len;
        i_chain_len = strlen( p_sys->psz_search_chain );
        switch( i_key )
        {
            case 0x0c:      /* ^l */
                clear();
                return 1;
            case KEY_ENTER:
            case 0x0d:
                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;
            case 0x1b:      /* Esc. */
                p_sys->i_box_plidx = p_sys->i_before_search;
                p_sys->i_box_type = BOX_PLAYLIST;
                return 1;
            case KEY_BACKSPACE:
813
                RemoveLastUTF8Entity( p_sys->psz_search_chain, i_chain_len );
814
815
                break;
            default:
816
            {
817
                char *psz_utf8 = KeyToUTF8( i_key, p_sys->psz_partial_keys );
818

819
                if( psz_utf8 != NULL )
820
                {
821
822
823
824
825
826
                    if( i_chain_len + strlen( psz_utf8 ) < SEARCH_CHAIN_SIZE )
                    {
                        strcpy( p_sys->psz_search_chain + i_chain_len,
                                psz_utf8 );
                    }
                    free( psz_utf8 );
827
828
                }
                break;
829
            }
830
831
832
833
834
835
836
837
838
839
840
        }
        if( p_sys->psz_old_search )
        {
            free( p_sys->psz_old_search );
            p_sys->psz_old_search = NULL;
        }
        SearchPlaylist( p_intf, p_sys->psz_search_chain );
        return 1;
    }
    else if( p_sys->i_box_type == BOX_OPEN && p_sys->psz_open_chain )
    {
841
        int i_chain_len = strlen( p_sys->psz_open_chain );
842
843
844
845
846
847
848
849

        switch( i_key )
        {
            case 0x0c:      /* ^l */
                clear();
                return 1;
            case KEY_ENTER:
            case 0x0d:
850
                if( i_chain_len > 0 )
851
                {
852
853
854
855
                    playlist_item_t *p_parent = p_sys->p_node;
                   
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
856
                    if( !p_parent )
857
                        p_parent = p_playlist->p_local_onelevel;
858

Rafaël Carré's avatar
Rafaël Carré committed
859
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
860
861
862
863
                        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
864
                                  p_parent->p_input == 
865
                                    p_playlist->p_local_onelevel->p_input
866
                                  , VLC_FALSE );
867

868
869
870
871
872
873
874
875
                    p_sys->b_box_plidx_follow = VLC_TRUE;
                }
                p_sys->i_box_type = BOX_PLAYLIST;
                return 1;
            case 0x1b:      /* Esc. */
                p_sys->i_box_type = BOX_PLAYLIST;
                return 1;
            case KEY_BACKSPACE:
876
                RemoveLastUTF8Entity( p_sys->psz_open_chain, i_chain_len );
877
878
                break;
            default:
879
            {
880
                char *psz_utf8 = KeyToUTF8( i_key, p_sys->psz_partial_keys );
881

882
                if( psz_utf8 != NULL )
883
                {
884
885
886
887
888
889
                    if( i_chain_len + strlen( psz_utf8 ) < OPEN_CHAIN_SIZE )
                    {
                        strcpy( p_sys->psz_open_chain + i_chain_len,
                                psz_utf8 );
                    }
                    free( psz_utf8 );
890
891
                }
                break;
892
            }
893
894
895
896
        }
        return 1;
    }

897
898

    /* Common keys */
899
900
901
902
    switch( i_key )
    {
        case 'q':
        case 'Q':
903
        case 0x1b:  /* Esc */
904
            vlc_object_kill( p_intf->p_libvlc );
905
906
            return 0;

907
908
909
910
911
912
913
914
        /* Box switching */
        case 'i':
            if( p_sys->i_box_type == BOX_INFO )
                p_sys->i_box_type = BOX_NONE;
            else
                p_sys->i_box_type = BOX_INFO;
            p_sys->i_box_lines_total = 0;
            return 1;
915
916
917
918
919
920
921
        case 'm':
            if( p_sys->i_box_type == BOX_META )
                p_sys->i_box_type = BOX_NONE;
            else
                p_sys->i_box_type = BOX_META;
            p_sys->i_box_lines_total = 0;
            return 1;
hartman's avatar
hartman committed
922
        case 'L':
923
924
925
926
927
928
929
930
931
932
933
            if( p_sys->i_box_type == BOX_LOG )
                p_sys->i_box_type = BOX_NONE;
            else
                p_sys->i_box_type = BOX_LOG;
            return 1;
        case 'P':
            if( p_sys->i_box_type == BOX_PLAYLIST )
                p_sys->i_box_type = BOX_NONE;
            else
                p_sys->i_box_type = BOX_PLAYLIST;
            return 1;
hartman's avatar
hartman committed
934
935
936
937
938
939
        case 'B':
            if( p_sys->i_box_type == BOX_BROWSE )
                p_sys->i_box_type = BOX_NONE;
            else
                p_sys->i_box_type = BOX_BROWSE;
            return 1;
940
941
942
943
944
945
946
        case 'h':
        case 'H':
            if( p_sys->i_box_type == BOX_HELP )
                p_sys->i_box_type = BOX_NONE;
            else
                p_sys->i_box_type = BOX_HELP;
            p_sys->i_box_lines_total = 0;
947
            return 1;
948
949
950
951
952
953
954
955
956
957
958
959
960
        case '/':
            if( p_sys->i_box_type != BOX_SEARCH )
            {
                if( p_sys->psz_search_chain == NULL )
                {
                    return 1;
                }
                p_sys->psz_search_chain[0] = '\0';
                p_sys->b_box_plidx_follow = VLC_FALSE;
                p_sys->i_before_search = p_sys->i_box_plidx;
                p_sys->i_box_type = BOX_SEARCH;
            }
            return 1;
hartman's avatar
hartman committed
961
        case 'A': /* Open */
962
963
964
965
966
967
968
969
970
971
            if( p_sys->i_box_type != BOX_OPEN )
            {
                if( p_sys->psz_open_chain == NULL )
                {
                    return 1;
                }
                p_sys->psz_open_chain[0] = '\0';
                p_sys->i_box_type = BOX_OPEN;
            }
            return 1;
972

973
974
        /* Navigation */
        case KEY_RIGHT:
975
            p_sys->f_slider += 1.0;
976
            if( p_sys->f_slider > 99.9 ) p_sys->f_slider = 99.9;
977
            ManageSlider( p_intf );
978
979
            return 1;

980
        case KEY_LEFT:
981
            p_sys->f_slider -= 1.0;
982
            if( p_sys->f_slider < 0.0 ) p_sys->f_slider = 0.0;
983
            ManageSlider( p_intf );
984
985
            return 1;

986
987
988
        /* Common control */
        case 'f':
        {