rc.c 73.3 KB
Newer Older
1
/*****************************************************************************
Anil Daoud's avatar
Anil Daoud committed
2
 * rc.c : remote control stdin/stdout module for vlc
3
 *****************************************************************************
Rafaël Carré's avatar
Rafaël Carré committed
4
 * Copyright (C) 2004-2007 the VideoLAN team
5
 * $Id$
6
 *
7
 * Author: Peter Surda <shurdeek@panorama.sth.ac.at>
8
 *         Jean-Paul Saman <jpsaman #_at_# m2x _replaceWith#dot_ nl>
9
10
11
12
13
 *
 * 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.
14
 *
15
16
17
18
19
20
21
 * 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
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23
24
25
26
27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28
29
30
31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
34

35
36
#include <errno.h>                                                 /* ENOMEM */
#include <ctype.h>
37
#include <signal.h>
38

zorglub's avatar
zorglub committed
39
40
41
#include <vlc_interface.h>
#include <vlc_aout.h>
#include <vlc_vout.h>
42
#include <vlc_osd.h>
zorglub's avatar
zorglub committed
43
#include <vlc_playlist.h>
44
45
46
47
48
49
50
51
52
53

#ifdef HAVE_UNISTD_H
#    include <unistd.h>
#endif

#ifdef HAVE_SYS_TIME_H
#    include <sys/time.h>
#endif
#include <sys/types.h>

zorglub's avatar
zorglub committed
54
#include <vlc_network.h>
55
#include "vlc_url.h"
56

zorglub's avatar
zorglub committed
57
#include <vlc_charset.h>
58

59
60
61
62
#if defined(AF_UNIX) && !defined(AF_LOCAL)
#    define AF_LOCAL AF_UNIX
#endif

damienf's avatar
damienf committed
63
#if defined(AF_LOCAL) && ! defined(WIN32)
64
65
#    include <sys/un.h>
#endif
66

67
#define MAX_LINE_LENGTH 256
68
#define STATUS_CHANGE "status change: "
69

70
71
72
73
74
75
76
77
78
79
80
81
82
/* input_state_e from <vlc_input.h> */
static const char *ppsz_input_state[] = {
    N_("Initializing"),
    N_("Opening"),
    N_("Buffer"),
    N_("Play"),
    N_("Pause"),
    N_("Stop"),
    N_("Forward"),
    N_("Backward"),
    N_("End"),
    N_("Error"),
};
83

84
85
86
87
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  Activate     ( vlc_object_t * );
88
89
90
static void Deactivate   ( vlc_object_t * );
static void Run          ( intf_thread_t * );

91
static void Help         ( intf_thread_t *, bool );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
92
static void RegisterCallbacks( intf_thread_t * );
93

94
static bool ReadCommand( intf_thread_t *, char *, int * );
95

96
static input_item_t *parse_MRL( intf_thread_t *, char * );
97

98
99
static int  Input        ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
gbazin's avatar
   
gbazin committed
100
101
102
103
104
105
106
107
108
109
static int  Playlist     ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
static int  Quit         ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
static int  Intf         ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
static int  Volume       ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
static int  VolumeMove   ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
110
111
static int  VideoConfig  ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
gbazin's avatar
   
gbazin committed
112
113
static int  AudioConfig  ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
114
115
static int  Menu         ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
116
117
118
119
static int  Statistics   ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );

static int updateStatistics( intf_thread_t *, input_item_t *);
120
121
122
123
124
125
126
127
128
129

/* Status Callbacks */
static int TimeOffsetChanged( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t , void * );
static int VolumeChanged    ( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );
static int StateChanged     ( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );
static int RateChanged      ( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );
130

131
132
struct intf_sys_t
{
133
    int *pi_socket_listen;
134
    int i_socket;
135
    char *psz_unix_path;
136

137
138
139
    /* status changes */
    vlc_mutex_t       status_lock;
    playlist_status_t i_last_state;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
140

141
142
#ifdef WIN32
    HANDLE hConsoleIn;
143
    bool b_quiet;
144
145
146
#endif
};

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
147
#define msg_rc( ... ) __msg_rc( p_intf, __VA_ARGS__ )
148

149
static void __msg_rc( intf_thread_t *p_intf, const char *psz_fmt, ... )
150
151
152
{
    va_list args;
    va_start( args, psz_fmt );
153

154
    if( p_intf->p_sys->i_socket == -1 )
155
    {
156
        utf8_vfprintf( stdout, psz_fmt, args );
157
        printf( "\r\n" );
158
    }
159
    else
160
161
    {
        net_vaPrintf( p_intf, p_intf->p_sys->i_socket, NULL, psz_fmt, args );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
162
        net_Write( p_intf, p_intf->p_sys->i_socket, NULL, (uint8_t*)"\r\n", 2 );
163
    }
164
165
166
    va_end( args );
}

167
168
169
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Christophe Massiot's avatar
Christophe Massiot committed
170
#define POS_TEXT N_("Show stream position")
171
172
#define POS_LONGTEXT N_("Show the current position in seconds within the " \
                        "stream from time to time." )
173

Christophe Massiot's avatar
Christophe Massiot committed
174
#define TTY_TEXT N_("Fake TTY")
Anil Daoud's avatar
Anil Daoud committed
175
#define TTY_LONGTEXT N_("Force the rc module to use stdin as if it was a TTY.")
176

177
#define UNIX_TEXT N_("UNIX socket command input")
178
179
#define UNIX_LONGTEXT N_("Accept commands over a Unix socket rather than " \
                         "stdin." )
180

181
#define HOST_TEXT N_("TCP command input")
182
#define HOST_LONGTEXT N_("Accept commands over a socket rather than stdin. " \
183
184
            "You can set the address and port the interface will bind to." )

185
186
187
188
189
190
191
192
193
#ifdef WIN32
#define QUIET_TEXT N_("Do not open a DOS command box interface")
#define QUIET_LONGTEXT N_( \
    "By default the rc interface plugin will start a DOS command box. " \
    "Enabling the quiet mode will not bring this command box but can also " \
    "be pretty annoying when you want to stop VLC and no video window is " \
    "open." )
#endif

194
vlc_module_begin();
195
    set_shortname( N_("RC"));
zorglub's avatar
zorglub committed
196
    set_category( CAT_INTERFACE );
zorglub's avatar
zorglub committed
197
    set_subcategory( SUBCAT_INTERFACE_MAIN );
198
    set_description( N_("Remote control interface") );
199
    add_bool( "rc-show-pos", 0, NULL, POS_TEXT, POS_LONGTEXT, true );
200
201

#ifdef WIN32
202
    add_bool( "rc-quiet", 0, NULL, QUIET_TEXT, QUIET_LONGTEXT, false );
203
204
#else
#if defined (HAVE_ISATTY)
205
    add_bool( "rc-fake-tty", 0, NULL, TTY_TEXT, TTY_LONGTEXT, true );
206
#endif
207
    add_string( "rc-unix", 0, NULL, UNIX_TEXT, UNIX_LONGTEXT, true );
208
#endif
209
    add_string( "rc-host", 0, NULL, HOST_TEXT, HOST_LONGTEXT, true );
210

211
    set_capability( "interface", 20 );
212

213
    set_callbacks( Activate, Deactivate );
214
215
216
217
218
219
220
221
vlc_module_end();

/*****************************************************************************
 * Activate: initialize and create stuff
 *****************************************************************************/
static int Activate( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;
222
    char *psz_host, *psz_unix_path;
223
    int  *pi_socket = NULL;
224

225
226
#ifndef WIN32
#if defined(HAVE_ISATTY)
227
    /* Check that stdin is a TTY */
228
    if( !config_GetInt( p_intf, "rc-fake-tty" ) && !isatty( 0 ) )
229
230
    {
        msg_Warn( p_intf, "fd 0 is not a TTY" );
231
        return VLC_EGENERIC;
232
233
234
    }
#endif

235
236
    psz_unix_path = config_GetPsz( p_intf, "rc-unix" );
    if( psz_unix_path )
237
    {
238
        int i_socket;
239

240
#ifndef AF_LOCAL
241
        msg_Warn( p_intf, "your OS doesn't support filesystem sockets" );
242
        free( psz_unix_path );
243
244
245
246
247
248
249
250
        return VLC_EGENERIC;
#else
        struct sockaddr_un addr;

        memset( &addr, 0, sizeof(struct sockaddr_un) );

        msg_Dbg( p_intf, "trying UNIX socket" );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
251
        if( (i_socket = socket( AF_LOCAL, SOCK_STREAM, 0 ) ) < 0 )
252
        {
253
            msg_Warn( p_intf, "can't open socket: %m" );
254
            free( psz_unix_path );
255
256
257
            return VLC_EGENERIC;
        }

258
259
260
        addr.sun_family = AF_LOCAL;
        strncpy( addr.sun_path, psz_unix_path, sizeof( addr.sun_path ) );
        addr.sun_path[sizeof( addr.sun_path ) - 1] = '\0';
261

262
263
264
265
        if (bind (i_socket, (struct sockaddr *)&addr, sizeof (addr))
         && (errno == EADDRINUSE)
         && connect (i_socket, (struct sockaddr *)&addr, sizeof (addr))
         && (errno == ECONNREFUSED))
266
        {
267
268
269
270
            msg_Info (p_intf, "Removing dead UNIX socket: %s", psz_unix_path);
            unlink (psz_unix_path);

            if (bind (i_socket, (struct sockaddr *)&addr, sizeof (addr)))
271
            {
272
273
                msg_Err (p_intf, "cannot bind UNIX socket at %s: %m",
                         psz_unix_path);
274
275
                free (psz_unix_path);
                net_Close (i_socket);
276
277
                return VLC_EGENERIC;
            }
278
279
        }

280
        if( listen( i_socket, 1 ) )
281
        {
282
            msg_Warn( p_intf, "can't listen on socket: %m");
283
284
            free( psz_unix_path );
            net_Close( i_socket );
285
286
            return VLC_EGENERIC;
        }
287
288
289
290
291
292
293
294
295
296
297

        /* FIXME: we need a core function to merge listening sockets sets */
        pi_socket = calloc( 2, sizeof( int ) );
        if( pi_socket == NULL )
        {
            free( psz_unix_path );
            net_Close( i_socket );
            return VLC_ENOMEM;
        }
        pi_socket[0] = i_socket;
        pi_socket[1] = -1;
298
#endif /* AF_LOCAL */
299
    }
300
#endif /* !WIN32 */
301

302
    if( ( pi_socket == NULL ) &&
303
        ( psz_host = config_GetPsz( p_intf, "rc-host" ) ) != NULL )
304
305
306
    {
        vlc_url_t url;

307
308
        vlc_UrlParse( &url, psz_host, 0 );

dionoea's avatar
dionoea committed
309
        msg_Dbg( p_intf, "base: %s, port: %d", url.psz_host, url.i_port );
310

311
312
        pi_socket = net_ListenTCP(p_this, url.psz_host, url.i_port);
        if( pi_socket == NULL )
313
        {
314
315
            msg_Warn( p_intf, "can't listen to %s port %i",
                      url.psz_host, url.i_port );
316
317
318
319
320
321
            vlc_UrlClean( &url );
            free( psz_host );
            return VLC_EGENERIC;
        }

        vlc_UrlClean( &url );
322
        free( psz_host );
323
324
325
326
327
328
    }

    p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
    if( !p_intf->p_sys )
        return VLC_ENOMEM;

329
    p_intf->p_sys->pi_socket_listen = pi_socket;
330
    p_intf->p_sys->i_socket = -1;
331
    p_intf->p_sys->psz_unix_path = psz_unix_path;
332
    vlc_mutex_init( &p_intf->p_sys->status_lock );
333
    p_intf->p_sys->i_last_state = PLAYLIST_STOPPED;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
334

335
336
337
338
339
    /* Non-buffered stdout */
    setvbuf( stdout, (char *)NULL, _IOLBF, 0 );

    p_intf->pf_run = Run;

340
#ifdef WIN32
341
342
    p_intf->p_sys->b_quiet = config_GetInt( p_intf, "rc-quiet" );
    if( !p_intf->p_sys->b_quiet ) { CONSOLE_INTRO_MSG; }
343
#else
gbazin's avatar
   
gbazin committed
344
    CONSOLE_INTRO_MSG;
345
#endif
346

dionoea's avatar
dionoea committed
347
    msg_rc( _("Remote control interface initialized. Type `help' for help.") );
348
    return VLC_SUCCESS;
349
350
}

351
352
353
354
355
356
357
/*****************************************************************************
 * Deactivate: uninitialize and cleanup
 *****************************************************************************/
static void Deactivate( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;

358
    net_ListenClose( p_intf->p_sys->pi_socket_listen );
359
360
    if( p_intf->p_sys->i_socket != -1 )
        net_Close( p_intf->p_sys->i_socket );
361
362
    if( p_intf->p_sys->psz_unix_path != NULL )
    {
damienf's avatar
damienf committed
363
#if defined(AF_LOCAL) && !defined(WIN32)
364
365
366
367
        unlink( p_intf->p_sys->psz_unix_path );
#endif
        free( p_intf->p_sys->psz_unix_path );
    }
368
    vlc_mutex_destroy( &p_intf->p_sys->status_lock );
369
370
371
    free( p_intf->p_sys );
}

372
/*****************************************************************************
Jean-Paul Saman's avatar
Jean-Paul Saman committed
373
 * RegisterCallbacks: Register callbacks to dynamic variables
374
 *****************************************************************************/
Jean-Paul Saman's avatar
Jean-Paul Saman committed
375
static void RegisterCallbacks( intf_thread_t *p_intf )
376
{
377
    /* Register commands that will be cleaned up upon object destruction */
dionoea's avatar
dionoea committed
378
379
380
381
382
383
384
385
386
#define ADD( name, type, target )                                   \
    var_Create( p_intf, name, VLC_VAR_ ## type | VLC_VAR_ISCOMMAND ); \
    var_AddCallback( p_intf, name, target, NULL );
    ADD( "quit", VOID, Quit )
    ADD( "intf", STRING, Intf )

    ADD( "add", STRING, Playlist )
    ADD( "repeat", STRING, Playlist )
    ADD( "loop", STRING, Playlist )
Rafaël Carré's avatar
Rafaël Carré committed
387
    ADD( "random", STRING, Playlist )
dionoea's avatar
dionoea committed
388
389
390
391
392
393
394
395
396
397
    ADD( "enqueue", STRING, Playlist )
    ADD( "playlist", VOID, Playlist )
    ADD( "sort", VOID, Playlist )
    ADD( "play", VOID, Playlist )
    ADD( "stop", VOID, Playlist )
    ADD( "clear", VOID, Playlist )
    ADD( "prev", VOID, Playlist )
    ADD( "next", VOID, Playlist )
    ADD( "goto", INTEGER, Playlist )
    ADD( "status", INTEGER, Playlist )
398

399
    /* OSD menu commands */
dionoea's avatar
dionoea committed
400
    ADD(  "menu", STRING, Menu )
401
402

    /* DVD commands */
dionoea's avatar
dionoea committed
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
    ADD( "pause", VOID, Input )
    ADD( "seek", INTEGER, Input )
    ADD( "title", STRING, Input )
    ADD( "title_n", VOID, Input )
    ADD( "title_p", VOID, Input )
    ADD( "chapter", STRING, Input )
    ADD( "chapter_n", VOID, Input )
    ADD( "chapter_p", VOID, Input )

    ADD( "fastforward", VOID, Input )
    ADD( "rewind", VOID, Input )
    ADD( "faster", VOID, Input )
    ADD( "slower", VOID, Input )
    ADD( "normal", VOID, Input )

    ADD( "atrack", STRING, Input )
    ADD( "vtrack", STRING, Input )
    ADD( "strack", STRING, Input )
421

422
    /* video commands */
dionoea's avatar
dionoea committed
423
424
425
    ADD( "vratio", STRING, VideoConfig )
    ADD( "vcrop", STRING, VideoConfig )
    ADD( "vzoom", STRING, VideoConfig )
426
    ADD( "snapshot", VOID, VideoConfig )
427

428
    /* audio commands */
dionoea's avatar
dionoea committed
429
430
431
432
433
434
    ADD( "volume", STRING, Volume )
    ADD( "volup", STRING, VolumeMove )
    ADD( "voldown", STRING, VolumeMove )
    ADD( "adev", STRING, AudioConfig )
    ADD( "achan", STRING, AudioConfig )

435
436
437
    /* misc menu commands */
    ADD( "stats", BOOL, Statistics )

dionoea's avatar
dionoea committed
438
#undef ADD
Jean-Paul Saman's avatar
Jean-Paul Saman committed
439
440
441
442
443
444
445
446
447
448
449
450
451
452
}

/*****************************************************************************
 * Run: rc thread
 *****************************************************************************
 * This part of the interface is in a separate thread so that we can call
 * exec() from within it without annoying the rest of the program.
 *****************************************************************************/
static void Run( intf_thread_t *p_intf )
{
    input_thread_t * p_input;
    playlist_t *     p_playlist;

    char       p_buffer[ MAX_LINE_LENGTH + 1 ];
453
454
    bool b_showpos = config_GetInt( p_intf, "rc-show-pos" );
    bool b_longhelp = false;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
455
456
457
458
459
460
461
462
463
464
465

    int        i_size = 0;
    int        i_oldpos = 0;
    int        i_newpos;

    p_buffer[0] = 0;
    p_input = NULL;
    p_playlist = NULL;

    /* Register commands that will be cleaned up upon object destruction */
    RegisterCallbacks( p_intf );
466

467
468
    /* status callbacks */
    /* Listen to audio volume updates */
469
    var_AddCallback( p_intf->p_libvlc, "volume-change", VolumeChanged, p_intf );
470

gbazin's avatar
   
gbazin committed
471
472
#ifdef WIN32
    /* Get the file descriptor of the console input */
473
474
    p_intf->p_sys->hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
    if( p_intf->p_sys->hConsoleIn == INVALID_HANDLE_VALUE )
gbazin's avatar
   
gbazin committed
475
    {
dionoea's avatar
dionoea committed
476
        msg_Err( p_intf, "couldn't find user input handle" );
477
        vlc_object_kill( p_intf );
gbazin's avatar
   
gbazin committed
478
479
480
    }
#endif

481
    while( !intf_ShouldDie( p_intf ) )
482
    {
483
        char *psz_cmd, *psz_arg;
484
        bool b_complete;
485

486
        if( p_intf->p_sys->pi_socket_listen != NULL &&
487
            p_intf->p_sys->i_socket == -1 )
488
        {
489
            p_intf->p_sys->i_socket =
490
491
492
                net_Accept( p_intf, p_intf->p_sys->pi_socket_listen,
                            INTF_IDLE_SLEEP );
            if( p_intf->p_sys->i_socket == -1 ) continue;
493
494
        }

495
496
        b_complete = ReadCommand( p_intf, p_buffer, &i_size );

497
498
499
        /* Manage the input part */
        if( p_input == NULL )
        {
500
501
502
503
504
505
506
507
508
            if( p_playlist )
            {
                p_input = vlc_object_find( p_playlist, VLC_OBJECT_INPUT,
                                                       FIND_CHILD );
            }
            else
            {
                p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
                                                   FIND_ANYWHERE );
509
                if( p_input )
510
                {
511
                    p_playlist = pl_Yield( p_input );
512
513
                }
            }
514
515
516
            /* New input has been registered */
            if( p_input )
            {
517
                if( !p_input->b_dead || vlc_object_alive (p_input) )
518
                {
519
                    char *psz_uri =
520
                            input_item_GetURI( input_GetItem( p_input ) );
521
522
                    msg_rc( STATUS_CHANGE "( new input: %s )", psz_uri );
                    free( psz_uri );
dionoea's avatar
dionoea committed
523
524
                    msg_rc( STATUS_CHANGE "( audio volume: %d )",
                            config_GetInt( p_intf, "volume" ));
525
526
527
528
529
                }
                var_AddCallback( p_input, "state", StateChanged, p_intf );
                var_AddCallback( p_input, "rate-faster", RateChanged, p_intf );
                var_AddCallback( p_input, "rate-slower", RateChanged, p_intf );
                var_AddCallback( p_input, "rate", RateChanged, p_intf );
dionoea's avatar
dionoea committed
530
531
                var_AddCallback( p_input, "time-offset", TimeOffsetChanged,
                                 p_intf );
532
            }
533
534
535
        }
        else if( p_input->b_dead )
        {
536
537
538
539
            var_DelCallback( p_input, "state", StateChanged, p_intf );
            var_DelCallback( p_input, "rate-faster", RateChanged, p_intf );
            var_DelCallback( p_input, "rate-slower", RateChanged, p_intf );
            var_DelCallback( p_input, "rate", RateChanged, p_intf );
dionoea's avatar
dionoea committed
540
541
            var_DelCallback( p_input, "time-offset", TimeOffsetChanged,
                             p_intf );
542
543
            vlc_object_release( p_input );
            p_input = NULL;
544
545
546

            if( p_playlist )
            {
547
                vlc_object_lock( p_playlist );
548
                p_intf->p_sys->i_last_state = (int) PLAYLIST_STOPPED;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
549
                msg_rc( STATUS_CHANGE "( stop state: 0 )" );
550
                vlc_object_unlock( p_playlist );
551
552
            }
        }
553

554
        if( (p_input != NULL) && !p_input->b_dead && vlc_object_alive (p_input) &&
555
556
            (p_playlist != NULL) )
        {
557
            vlc_object_lock( p_playlist );
558
559
560
561
            if( (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
                (p_playlist->status.i_status == PLAYLIST_STOPPED) )
            {
                p_intf->p_sys->i_last_state = PLAYLIST_STOPPED;
562
                msg_rc( STATUS_CHANGE "( stop state: 5 )" );
563
            }
dionoea's avatar
dionoea committed
564
565
            else if(
                (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
566
567
568
                (p_playlist->status.i_status == PLAYLIST_RUNNING) )
            {
                p_intf->p_sys->i_last_state = p_playlist->status.i_status;
569
                 msg_rc( STATUS_CHANGE "( play state: 3 )" );
570
            }
dionoea's avatar
dionoea committed
571
572
            else if(
                (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
573
574
575
                (p_playlist->status.i_status == PLAYLIST_PAUSED) )
            {
                p_intf->p_sys->i_last_state = p_playlist->status.i_status;
576
                msg_rc( STATUS_CHANGE "( pause state: 4 )" );
577
            }
578
            vlc_object_unlock( p_playlist );
579
580
        }

581
        if( p_input && b_showpos )
582
        {
Laurent Aimar's avatar
Laurent Aimar committed
583
584
            i_newpos = 100 * var_GetFloat( p_input, "position" );
            if( i_oldpos != i_newpos )
585
            {
Laurent Aimar's avatar
Laurent Aimar committed
586
                i_oldpos = i_newpos;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
587
                msg_rc( "pos: %d%%", i_newpos );
588
589
590
591
            }
        }

        /* Is there something to do? */
592
593
594
595
596
        if( !b_complete ) continue;

        /* Skip heading spaces */
        psz_cmd = p_buffer;
        while( *psz_cmd == ' ' )
597
        {
598
599
            psz_cmd++;
        }
600

601
602
603
604
605
606
607
        /* Split psz_cmd at the first space and make sure that
         * psz_arg is valid */
        psz_arg = strchr( psz_cmd, ' ' );
        if( psz_arg )
        {
            *psz_arg++ = 0;
            while( *psz_arg == ' ' )
608
            {
609
                psz_arg++;
610
            }
611
612
613
        }
        else
        {
Rafaël Carré's avatar
Rafaël Carré committed
614
            psz_arg = (char*)"";
615
616
        }

617
618
619
620
621
622
623
        /* module specfic commands: @<module name> <command> <args...> */
        if( *psz_cmd == '@' && *psz_arg )
        {
            /* Parse miscellaneous commands */
            char *psz_alias = psz_cmd + 1;
            char *psz_mycmd = strdup( psz_arg );
            char *psz_myarg = strchr( psz_mycmd, ' ' );
624
            char *psz_msg;
625

dionoea's avatar
dionoea committed
626
627
628
629
630
631
632
633
            if( !psz_myarg )
            {
                msg_rc( "Not enough parameters." );
            }
            else
            {
                *psz_myarg = '\0';
                psz_myarg ++;
634

dionoea's avatar
dionoea committed
635
636
                var_Command( p_intf, psz_alias, psz_mycmd, psz_myarg,
                             &psz_msg );
637

dionoea's avatar
dionoea committed
638
639
640
641
642
                if( psz_msg )
                {
                    msg_rc( psz_msg );
                    free( psz_msg );
                }
643
644
645
            }
            free( psz_mycmd );
        }
646
        /* If the user typed a registered local command, try it */
647
        else if( var_Type( p_intf, psz_cmd ) & VLC_VAR_ISCOMMAND )
648
649
650
        {
            vlc_value_t val;
            int i_ret;
651

652
653
            val.psz_string = psz_arg;
            i_ret = var_Set( p_intf, psz_cmd, val );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
654
            msg_rc( "%s: returned %i (%s)",
655
656
657
                    psz_cmd, i_ret, vlc_error( i_ret ) );
        }
        /* Or maybe it's a global command */
658
        else if( var_Type( p_intf->p_libvlc, psz_cmd ) & VLC_VAR_ISCOMMAND )
659
660
661
662
663
664
        {
            vlc_value_t val;
            int i_ret;

            val.psz_string = psz_arg;
            /* FIXME: it's a global command, but we should pass the
665
666
             * local object as an argument, not p_intf->p_libvlc. */
            i_ret = var_Set( p_intf->p_libvlc, psz_cmd, val );
667
668
            if( i_ret != 0 )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
669
                msg_rc( "%s: returned %i (%s)",
670
671
                         psz_cmd, i_ret, vlc_error( i_ret ) );
            }
672
        }
zorglub's avatar
zorglub committed
673
674
675
676
677
678
679
680
681
        else if( !strcmp( psz_cmd, "logout" ) )
        {
            /* Close connection */
            if( p_intf->p_sys->i_socket != -1 )
            {
                net_Close( p_intf->p_sys->i_socket );
            }
            p_intf->p_sys->i_socket = -1;
        }
682
683
684
        else if( !strcmp( psz_cmd, "info" ) )
        {
            if( p_input )
685
            {
686
                int i, j;
zorglub's avatar
zorglub committed
687
688
                vlc_mutex_lock( &input_GetItem(p_input)->lock );
                for ( i = 0; i < input_GetItem(p_input)->i_categories; i++ )
689
                {
zorglub's avatar
zorglub committed
690
691
                    info_category_t *p_category = input_GetItem(p_input)
                                                        ->pp_categories[i];
692

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
693
694
                    msg_rc( "+----[ %s ]", p_category->psz_name );
                    msg_rc( "| " );
695
696
697
                    for ( j = 0; j < p_category->i_infos; j++ )
                    {
                        info_t *p_info = p_category->pp_infos[j];
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
698
                        msg_rc( "| %s: %s", p_info->psz_name,
699
700
                                p_info->psz_value );
                    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
701
                    msg_rc( "| " );
702
                }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
703
                msg_rc( "+----[ end of stream info ]" );
zorglub's avatar
zorglub committed
704
                vlc_mutex_unlock( &input_GetItem(p_input)->lock );
705
            }
706
            else
707
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
708
                msg_rc( "no input" );
Sam Hocevar's avatar
Sam Hocevar committed
709
            }
710
        }
711
712
713
714
        else if( !strcmp( psz_cmd, "is_playing" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
715
                msg_rc( "0" );
716
717
718
            }
            else
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
719
                msg_rc( "1" );
720
721
722
723
724
725
            }
        }
        else if( !strcmp( psz_cmd, "get_time" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
726
                msg_rc("0");
727
728
729
730
731
            }
            else
            {
                vlc_value_t time;
                var_Get( p_input, "time", &time );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
732
                msg_rc( "%i", time.i_time / 1000000);
733
734
735
736
737
738
            }
        }
        else if( !strcmp( psz_cmd, "get_length" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
739
                msg_rc("0");
740
741
742
743
744
            }
            else
            {
                vlc_value_t time;
                var_Get( p_input, "length", &time );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
745
                msg_rc( "%i", time.i_time / 1000000);
746
747
748
749
750
751
            }
        }
        else if( !strcmp( psz_cmd, "get_title" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
752
                msg_rc("");
753
754
755
            }
            else
            {
zorglub's avatar
zorglub committed
756
                msg_rc( "%s", input_GetItem(p_input)->psz_name );
757
758
            }
        }
759
760
        else if( !strcmp( psz_cmd, "longhelp" ) || !strncmp( psz_cmd, "h", 1 )
                 || !strncmp( psz_cmd, "H", 1 ) || !strncmp( psz_cmd, "?", 1 ) )
761
        {
762
            if( !strcmp( psz_cmd, "longhelp" ) || !strncmp( psz_cmd, "H", 1 ) )
763
764
                 b_longhelp = true;
            else b_longhelp = false;
765
766

            Help( p_intf, b_longhelp );
767
        }
768
769
770
771
772
        else if( !strcmp( psz_cmd, "key" ) || !strcmp( psz_cmd, "hotkey" ) )
        {
            var_SetInteger( p_intf->p_libvlc, "key-pressed",
                            config_GetInt( p_intf, psz_arg ) );
        }
773
774
775
776
777
778
779
780
781
782
783
784
        else switch( psz_cmd[0] )
        {
        case 'f':
        case 'F':
            if( p_input )
            {
                vout_thread_t *p_vout;
                p_vout = vlc_object_find( p_input,
                                          VLC_OBJECT_VOUT, FIND_CHILD );

                if( p_vout )
                {
785
                    vlc_value_t val;
786
                    bool b_update = false;
787
788
                    var_Get( p_vout, "fullscreen", &val );
                    val.b_bool = !val.b_bool;
dionoea's avatar
dionoea committed
789
                    if( !strncmp( psz_arg, "on", 2 )
790
                        && ( val.b_bool == true ) )
791
                    {
792
793
                        b_update = true;
                        val.b_bool = true;
794
                    }
dionoea's avatar
dionoea committed
795
                    else if( !strncmp( psz_arg, "off", 3 )
796
                             && ( val.b_bool == false ) )
797
                    {
798
799
                        b_update = true;
                        val.b_bool = false;
800
                    }
dionoea's avatar
dionoea committed
801
802
                    else if( strncmp( psz_arg, "off", 3 )
                             && strncmp( psz_arg, "on", 2 ) )
803
                        b_update = true;
804
                    if( b_update ) var_Set( p_vout, "fullscreen", val );
805
806
807
808
809
810
811
812
                    vlc_object_release( p_vout );
                }
            }
            break;

        case 's':
        case 'S':
            ;
813
814
815
816
817
818
819
            break;

        case '\0':
            /* Ignore empty lines */
            break;

        default:
dionoea's avatar
dionoea committed
820
            msg_rc(_("Unknown command `%s'. Type `help' for help."), psz_cmd);
821
            break;
822
        }
823
824
825

        /* Command processed */
        i_size = 0; p_buffer[0] = 0;
826
827
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
828
829
    msg_rc( STATUS_CHANGE "( stop state: 0 )" );
    msg_rc( STATUS_CHANGE "( quit )" );
830

831
832
    if( p_input )
    {
833
834
835
836
837
        var_DelCallback( p_input, "state", StateChanged, p_intf );
        var_DelCallback( p_input, "rate-faster", RateChanged, p_intf );
        var_DelCallback( p_input, "rate-slower", RateChanged, p_intf );
        var_DelCallback( p_input, "rate", RateChanged, p_intf );
        var_DelCallback( p_input, "time-offset", TimeOffsetChanged, p_intf );
838
839
        vlc_object_release( p_input );
        p_input = NULL;
840
    }
841

842
843
844
845
    if( p_playlist )
    {
        vlc_object_release( p_playlist );
        p_playlist = NULL;
846
    }
847

848
    var_DelCallback( p_intf->p_libvlc, "volume-change", VolumeChanged, p_intf );
849
850
}

851
static void Help( intf_thread_t *p_intf, bool b_longhelp)
852
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
853
854
    msg_rc(_("+----[ Remote control commands ]"));
    msg_rc(  "| ");
855
856
857
858
859
860
861
862
863
    msg_rc(_("| add XYZ  . . . . . . . . . . . . add XYZ to playlist"));
    msg_rc(_("| enqueue XYZ  . . . . . . . . . queue XYZ to playlist"));
    msg_rc(_("| playlist . . . . .  show items currently in playlist"));
    msg_rc(_("| play . . . . . . . . . . . . . . . . . . play stream"));
    msg_rc(_("| stop . . . . . . . . . . . . . . . . . . stop stream"));
    msg_rc(_("| next . . . . . . . . . . . . . .  next playlist item"));
    msg_rc(_("| prev . . . . . . . . . . . .  previous playlist item"));
    msg_rc(_("| goto . . . . . . . . . . . . . .  goto item at index"));
    msg_rc(_("| repeat [on|off] . . . .  toggle playlist item repeat"));
Rafaël Carré's avatar
Rafaël Carré committed
864
865
866
    msg_rc(_("| loop [on|off] . . . . . . . . . toggle playlist loop"));
    msg_rc(_("| random [on|off] . . . . . . .  toggle random jumping"));
    msg_rc(_("| clear . . . . . . . . . . . . . . clear the playlist"));
867
868
869
870
871
872
873
    msg_rc(_("| status . . . . . . . . . . . current playlist status"));
    msg_rc(_("| title [X]  . . . . . . set/get title in current item"));
    msg_rc(_("| title_n  . . . . . . . .  next title in current item"));
    msg_rc(_("| title_p  . . . . . .  previous title in current item"));
    msg_rc(_("| chapter [X]  . . . . set/get chapter in current item"));
    msg_rc(_("| chapter_n  . . . . . .  next chapter in current item"));
    msg_rc(_("| chapter_p  . . . .  previous chapter in current item"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
874
    msg_rc(  "| ");
875
876
877
878
879
880
881
882
883
    msg_rc(_("| seek X . . . seek in seconds, for instance `seek 12'"));
    msg_rc(_("| pause  . . . . . . . . . . . . . . . .  toggle pause"));
    msg_rc(_("| fastforward  . . . . . . . .  .  set to maximum rate"));
    msg_rc(_("| rewind  . . . . . . . . . . . .  set to minimum rate"));
    msg_rc(_("| faster . . . . . . . . . .  faster playing of stream"));
    msg_rc(_("| slower . . . . . . . . . .  slower playing of stream"));
    msg_rc(_("| normal . . . . . . . . . .  normal playing of stream"));
    msg_rc(_("| f [on|off] . . . . . . . . . . . . toggle fullscreen"));
    msg_rc(_("| info . . . . .  information about the current stream"));
884
    msg_rc(_("| stats  . . . . . . . .  show statistical information"));
885
    msg_rc(_("| get_time . . seconds elapsed since stream's beginning"));
886
887
888
    msg_rc(_("| is_playing . . . .  1 if a stream plays, 0 otherwise"));
    msg_rc(_("| get_title . . . . .  the title of the current stream"));
    msg_rc(_("| get_length . . . .  the length of the current stream"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
889
    msg_rc(  "| ");
890
891
892
893
894
895
896
897
898
899
    msg_rc(_("| volume [X] . . . . . . . . . .  set/get audio volume"));
    msg_rc(_("| volup [X]  . . . . . . .  raise audio volume X steps"));
    msg_rc(_("| voldown [X]  . . . . . .  lower audio volume X steps"));
    msg_rc(_("| adev [X] . . . . . . . . . . .  set/get audio device"));
    msg_rc(_("| achan [X]. . . . . . . . . .  set/get audio channels"));
    msg_rc(_("| atrack [X] . . . . . . . . . . . set/get audio track"));
    msg_rc(_("| vtrack [X] . . . . . . . . . . . set/get video track"));
    msg_rc(_("| vratio [X]  . . . . . . . set/get video aspect ratio"));
    msg_rc(_("| vcrop [X]  . . . . . . . . . . .  set/get video crop"));
    msg_rc(_("| vzoom [X]  . . . . . . . . . . .  set/get video zoom"));
900
    msg_rc(_("| snapshot . . . . . . . . . . . . take video snapshot"));
901
902
903
    msg_rc(_("| strack [X] . . . . . . . . . set/get subtitles track"));
    msg_rc(_("| key [hotkey name] . . . . . .  simulate hotkey press"));
    msg_rc(_("| menu . . [on|off|up|down|left|right|select] use menu"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
904
    msg_rc(  "| ");
905

906
907
    if (b_longhelp)
    {
908
909
910
911
912
913
914
915
        msg_rc(_("| @name marq-marquee  STRING  . . overlay STRING in video"));
        msg_rc(_("| @name marq-x X . . . . . . . . . . . .offset from left"));
        msg_rc(_("| @name marq-y Y . . . . . . . . . . . . offset from top"));
        msg_rc(_("| @name marq-position #. . .  .relative position control"));
        msg_rc(_("| @name marq-color # . . . . . . . . . . font color, RGB"));
        msg_rc(_("| @name marq-opacity # . . . . . . . . . . . . . opacity"));
        msg_rc(_("| @name marq-timeout T. . . . . . . . . . timeout, in ms"));
        msg_rc(_("| @name marq-size # . . . . . . . . font size, in pixels"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
916
        msg_rc(  "| ");
917
918
919
920
921
        msg_rc(_("| @name logo-file STRING . . .the overlay file path/name"));
        msg_rc(_("| @name logo-x X . . . . . . . . . . . .offset from left"));
        msg_rc(_("| @name logo-y Y . . . . . . . . . . . . offset from top"));
        msg_rc(_("| @name logo-position #. . . . . . . . relative position"));
        msg_rc(_("| @name logo-transparency #. . . . . . . . .transparency"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
922
        msg_rc(  "| ");
923
924
925
926
927
928
929
930
931
932
933
934
935
936
        msg_rc(_("| @name mosaic-alpha # . . . . . . . . . . . . . . alpha"));
        msg_rc(_("| @name mosaic-height #. . . . . . . . . . . . . .height"));
        msg_rc(_("| @name mosaic-width # . . . . . . . . . . . . . . width"));
        msg_rc(_("| @name mosaic-xoffset # . . . .top left corner position"));
        msg_rc(_("| @name mosaic-yoffset # . . . .top left corner position"));
        msg_rc(_("| @name mosaic-offsets x,y(,x,y)*. . . . list of offsets"));
        msg_rc(_("| @name mosaic-align 0..2,4..6,8..10. . .mosaic alignment"));
        msg_rc(_("| @name mosaic-vborder # . . . . . . . . vertical border"));
        msg_rc(_("| @name mosaic-hborder # . . . . . . . horizontal border"));
        msg_rc(_("| @name mosaic-position {0=auto,1=fixed} . . . .position"));
        msg_rc(_("| @name mosaic-rows #. . . . . . . . . . .number of rows"));
        msg_rc(_("| @name mosaic-cols #. . . . . . . . . . .number of cols"));
        msg_rc(_("| @name mosaic-order id(,id)* . . . . order of pictures "));
        msg_rc(_("| @name mosaic-keep-aspect-ratio {0,1} . . .aspect ratio"));
937
        msg_rc(  "| ");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
938
    }
939
940
941
942
    msg_rc(_("| help . . . . . . . . . . . . . . . this help message"));
    msg_rc(_("| longhelp . . . . . . . . . . . a longer help message"));
    msg_rc(_("| logout . . . . . . .  exit (if in socket connection)"));
    msg_rc(_("| quit . . . . . . . . . . . . . . . . . . .  quit vlc"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
943
944
    msg_rc(  "| ");
    msg_rc(_("+----[ end of help ]"));
945
946
947
948
949
950
951
952
}

/********************************************************************
 * Status callback routines
 ********************************************************************/
static int TimeOffsetChanged( vlc_object_t *p_this, char const *psz_cmd,
    vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
Rafaël Carré's avatar
Rafaël Carré committed
953
954
    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd);
    VLC_UNUSED(oldval); VLC_UNUSED(newval);
955
956
    intf_thread_t *p_intf = (intf_thread_t*)p_data;
    input_thread_t *p_input = NULL;
957

958
959
960
961
    vlc_mutex_lock( &p_intf->p_sys->status_lock );
    p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
    if( p_input )
    {