ncurses.c 70.6 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
    set_callbacks( Open, Close );
117
    set_program( "nvlc" );
118
    add_shortcut( "curses" );
hartman's avatar
hartman committed
119
    add_directory( "browse-dir", NULL, NULL, BROWSE_TEXT, BROWSE_LONGTEXT, VLC_FALSE );
120
121
122
123
124
vlc_module_end();

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

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

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

173
    int             b_box_cleared;
174

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

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

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

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

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

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

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

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

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

    /* Initialize the curses library */
228
229
    p_sys->w = initscr();
    keypad( p_sys->w, TRUE );
230
231
232
233
234
235
236
237
238
239
240
241
    /* 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();

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

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

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

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

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

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

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

285
    return VLC_SUCCESS;
286
287
288
289
290
291
}

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

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

    PlaylistDestroy( p_intf );

hartman's avatar
hartman committed
303
304
305
306
307
308
309
310
    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;
311

hartman's avatar
hartman committed
312
    if( p_sys->psz_current_dir ) free( p_sys->psz_current_dir );
313
314
315
316
    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 );

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

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

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

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

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

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

    int i_key;
346
347
348
349
350
351
352
    time_t t_last_refresh;

    /*
     * force drawing the interface for the first time
     */
    t_last_refresh = ( time( 0 ) - 1);

353
    while( !intf_ShouldDie( p_intf ) )
354
355
356
    {
        msleep( INTF_IDLE_SLEEP );

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

382
        if( p_sys->b_box_plidx_follow && p_playlist->status.p_item )
383
        {
384
            FindIndex( p_intf );
385
        }
386

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

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

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

430
#ifdef HAVE_NCURSESW
431
432
    psz_utf8 = strdup( psz_part );
#else
433
434
435
436
    psz_utf8 = FromLocaleDup( psz_part );

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

    memset( psz_part, 0, 6 );
    return psz_utf8;
457
458
459
460
461
462
463
464
465
466
467
}

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

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

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

474
    if( p_sys->i_box_type == BOX_PLAYLIST )
475
    {
476
        int b_ret = VLC_TRUE;
477
        vlc_bool_t b_box_plidx_follow = VLC_FALSE;
478

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

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

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

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

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

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

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

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

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

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

673
674
675
                    playlist_item_t *p_parent = p_sys->p_node;
                    if( !p_parent )
                    p_parent = p_playlist->status.p_node;
676
                    if( !p_parent )
677
                        p_parent = p_playlist->p_local_onelevel;
678

Rafaël Carré's avatar
Rafaël Carré committed
679
                    while( p_parent->p_parent && p_parent->p_parent->p_parent )
680
681
                        p_parent = p_parent->p_parent;

682
683
                    playlist_Add( p_playlist, psz_uri, NULL, PLAYLIST_APPEND,
                                  PLAYLIST_END,
Rafaël Carré's avatar
Rafaël Carré committed
684
                                  p_parent->p_input == 
685
                                    p_playlist->p_local_onelevel->p_input
686
                                  , VLC_FALSE );
687

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

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

hartman's avatar
hartman committed
699
700
701
702
703
704
                    p_sys->psz_current_dir = strdup( psz_uri );
                    ReadDir( p_intf );
                    free( psz_uri );
                }
                break;
            default:
705
                b_ret = VLC_FALSE;
hartman's avatar
hartman committed
706
                break;
707
708
709
        }
        if( b_ret )
        {
710
711
            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
712
713
714
            return 1;
        }
    }
715
716
    else if( p_sys->i_box_type == BOX_HELP || p_sys->i_box_type == BOX_INFO ||
             p_sys->i_box_type == BOX_META )
717
718
719
720
721
722
    {
        switch( i_key )
        {
            case KEY_HOME:
                p_sys->i_box_start = 0;
                return 1;
Rafaël Carré's avatar
Rafaël Carré committed
723
724
725
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
726
727
728
729
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
            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;
759
                ManageSlider( p_intf );
760
                return 1;
Rafaël Carré's avatar
Rafaël Carré committed
761
762
763
#ifdef __FreeBSD__
            case KEY_SELECT:
#endif
764
765
            case KEY_END:
                p_sys->f_slider = 99.9;
766
                ManageSlider( p_intf );
767
768
                return 1;
            case KEY_UP:
769
                p_sys->f_slider += 5.0;
770
                if( p_sys->f_slider >= 99.0 ) p_sys->f_slider = 99.0;
771
                ManageSlider( p_intf );
772
773
                return 1;
            case KEY_DOWN:
774
                p_sys->f_slider -= 5.0;
775
                if( p_sys->f_slider < 0.0 ) p_sys->f_slider = 0.0;
776
                ManageSlider( p_intf );
777
778
779
780
781
782
                return 1;

            default:
                break;
        }
    }
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
    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:
809
                RemoveLastUTF8Entity( p_sys->psz_search_chain, i_chain_len );
810
811
                break;
            default:
812
            {
813
                char *psz_utf8 = KeyToUTF8( i_key, p_sys->psz_partial_keys );
814

815
                if( psz_utf8 != NULL )
816
                {
817
818
819
820
821
822
                    if( i_chain_len + strlen( psz_utf8 ) < SEARCH_CHAIN_SIZE )
                    {
                        strcpy( p_sys->psz_search_chain + i_chain_len,
                                psz_utf8 );
                    }
                    free( psz_utf8 );
823
824
                }
                break;
825
            }
826
827
828
829
830
831
832
833
834
835
836
        }
        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 )
    {
837
        int i_chain_len = strlen( p_sys->psz_open_chain );
838
839
840
841
842
843
844
845

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

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

864
865
866
867
868
869
870
871
                    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:
872
                RemoveLastUTF8Entity( p_sys->psz_open_chain, i_chain_len );
873
874
                break;
            default:
875
            {
876
                char *psz_utf8 = KeyToUTF8( i_key, p_sys->psz_partial_keys );
877

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

893
894

    /* Common keys */
895
896
897
898
    switch( i_key )
    {
        case 'q':
        case 'Q':
899
        case 0x1b:  /* Esc */
900
            vlc_object_kill( p_intf->p_libvlc );
901
902
            return 0;

903
904
905
906
907
908
909
910
        /* 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;
911
912
913
914
915
916
917
        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
918
        case 'L':
919
920
921
922
923
924
925
926
927
928
929
            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
930
931
932
933
934
935
        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;
936
937
938
939
940
941
942
        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;
943
            return 1;
944
945
946
947
948
949
950
951
952
953
954
955
956
        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
957
        case 'A': /* Open */
958
959
960
961
962
963
964
965
966
967
            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;
968

969
970
        /* Navigation */
        case KEY_RIGHT:
971
            p_sys->f_slider += 1.0;
972
            if( p_sys->f_slider > 99.9 ) p_sys->f_slider = 99.9;
973
            ManageSlider( p_intf );
974
975
            return 1;

976
        case KEY_LEFT:
977
            p_sys->f_slider -= 1.0;
978
            if( p_sys->f_slider < 0.0 ) p_sys->f_slider = 0.0;
979
            ManageSlider( p_intf );
980
981
            return 1;

982
983
984
        /* Common control */
        case 'f':
        {
sigmunau's avatar
sigmunau committed
985
            if( p_intf->p_sys->p_input )
986
            {