rc.c 73.4 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
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
28
29
#include <vlc/vlc.h>

30
31
#include <errno.h>                                                 /* ENOMEM */
#include <ctype.h>
32
#include <signal.h>
33

zorglub's avatar
zorglub committed
34
35
36
#include <vlc_interface.h>
#include <vlc_aout.h>
#include <vlc_vout.h>
37
#include <vlc_osd.h>
zorglub's avatar
zorglub committed
38
#include <vlc_playlist.h>
39
#include <vlc_update.h>
40
41
42
43
44
45
46
47
48
49

#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
50
#include <vlc_network.h>
51
#include "vlc_url.h"
52

zorglub's avatar
zorglub committed
53
#include <vlc_charset.h>
54

55
56
57
58
#if defined(AF_UNIX) && !defined(AF_LOCAL)
#    define AF_LOCAL AF_UNIX
#endif

damienf's avatar
damienf committed
59
#if defined(AF_LOCAL) && ! defined(WIN32)
60
61
#    include <sys/un.h>
#endif
62

63
#define MAX_LINE_LENGTH 256
64
#define STATUS_CHANGE "status change: "
65
66
67
68
69

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  Activate     ( vlc_object_t * );
70
71
72
static void Deactivate   ( vlc_object_t * );
static void Run          ( intf_thread_t * );

73
static void Help         ( intf_thread_t *, vlc_bool_t );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
74
static void RegisterCallbacks( intf_thread_t * );
75

76
static vlc_bool_t ReadCommand( intf_thread_t *, char *, int * );
77

78
static input_item_t *parse_MRL( intf_thread_t *, char * );
79

80
81
static int  Input        ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
gbazin's avatar
   
gbazin committed
82
83
84
85
86
87
88
89
90
91
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 * );
92
93
static int  VideoConfig  ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
gbazin's avatar
   
gbazin committed
94
95
static int  AudioConfig  ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
96
97
static int  Menu         ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
98
99
100
101
static int  Statistics   ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );

static int updateStatistics( intf_thread_t *, input_item_t *);
102
#ifdef UPDATE_CHECK
103
static void checkUpdates( intf_thread_t *p_intf );
104
#endif
105
106
107
108
109
110
111
112
113
114

/* 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 * );
115

116
117
struct intf_sys_t
{
118
    int *pi_socket_listen;
119
    int i_socket;
120
    char *psz_unix_path;
121

122
123
124
    /* status changes */
    vlc_mutex_t       status_lock;
    playlist_status_t i_last_state;
125
    vlc_bool_t        b_statistics;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
126

127
128
#ifdef WIN32
    HANDLE hConsoleIn;
129
    vlc_bool_t b_quiet;
130
131
132
133
#endif
};

#ifdef HAVE_VARIADIC_MACROS
134
135
#   define msg_rc( psz_format, args... ) \
      __msg_rc( p_intf, psz_format, ## args )
136
137
#endif

138
static void __msg_rc( intf_thread_t *p_intf, const char *psz_fmt, ... )
139
140
141
{
    va_list args;
    va_start( args, psz_fmt );
142

143
    if( p_intf->p_sys->i_socket == -1 )
144
    {
145
        utf8_vfprintf( stdout, psz_fmt, args );
146
        printf( "\r\n" );
147
    }
148
    else
149
150
    {
        net_vaPrintf( p_intf, p_intf->p_sys->i_socket, NULL, psz_fmt, args );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
151
        net_Write( p_intf, p_intf->p_sys->i_socket, NULL, (uint8_t*)"\r\n", 2 );
152
    }
153
154
155
    va_end( args );
}

156
157
158
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Christophe Massiot's avatar
Christophe Massiot committed
159
#define POS_TEXT N_("Show stream position")
160
161
#define POS_LONGTEXT N_("Show the current position in seconds within the " \
                        "stream from time to time." )
162

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

166
#define UNIX_TEXT N_("UNIX socket command input")
167
168
#define UNIX_LONGTEXT N_("Accept commands over a Unix socket rather than " \
                         "stdin." )
169

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

174
175
176
177
178
179
180
181
182
#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

183
vlc_module_begin();
184
    set_shortname( _("RC"));
zorglub's avatar
zorglub committed
185
    set_category( CAT_INTERFACE );
zorglub's avatar
zorglub committed
186
    set_subcategory( SUBCAT_INTERFACE_MAIN );
gbazin's avatar
   
gbazin committed
187
    set_description( _("Remote control interface") );
188
    add_bool( "rc-show-pos", 0, NULL, POS_TEXT, POS_LONGTEXT, VLC_TRUE );
189
190
191

#ifdef WIN32
    add_bool( "rc-quiet", 0, NULL, QUIET_TEXT, QUIET_LONGTEXT, VLC_FALSE );
192
193
194
195
196
#else
#if defined (HAVE_ISATTY)
    add_bool( "rc-fake-tty", 0, NULL, TTY_TEXT, TTY_LONGTEXT, VLC_TRUE );
#endif
    add_string( "rc-unix", 0, NULL, UNIX_TEXT, UNIX_LONGTEXT, VLC_TRUE );
197
#endif
198
    add_string( "rc-host", 0, NULL, HOST_TEXT, HOST_LONGTEXT, VLC_TRUE );
199

200
    set_capability( "interface", 20 );
201

202
    set_callbacks( Activate, Deactivate );
203
204
205
206
207
208
209
210
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;
211
    char *psz_host, *psz_unix_path;
212
    int  *pi_socket = NULL;
213

214
215
#ifndef WIN32
#if defined(HAVE_ISATTY)
216
    /* Check that stdin is a TTY */
217
    if( !config_GetInt( p_intf, "rc-fake-tty" ) && !isatty( 0 ) )
218
219
    {
        msg_Warn( p_intf, "fd 0 is not a TTY" );
220
        return VLC_EGENERIC;
221
222
223
    }
#endif

224
225
    psz_unix_path = config_GetPsz( p_intf, "rc-unix" );
    if( psz_unix_path )
226
    {
227
        int i_socket;
228

229
#ifndef AF_LOCAL
230
        msg_Warn( p_intf, "your OS doesn't support filesystem sockets" );
231
        free( psz_unix_path );
232
233
234
235
236
237
238
239
        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
240
        if( (i_socket = socket( AF_LOCAL, SOCK_STREAM, 0 ) ) < 0 )
241
        {
242
            msg_Warn( p_intf, "can't open socket: %m" );
243
            free( psz_unix_path );
244
245
246
            return VLC_EGENERIC;
        }

247
248
249
        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';
250

251
252
253
254
        if (bind (i_socket, (struct sockaddr *)&addr, sizeof (addr))
         && (errno == EADDRINUSE)
         && connect (i_socket, (struct sockaddr *)&addr, sizeof (addr))
         && (errno == ECONNREFUSED))
255
        {
256
257
258
259
            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)))
260
            {
261
262
                msg_Err (p_intf, "cannot bind UNIX socket at %s: %m",
                         psz_unix_path);
263
264
                free (psz_unix_path);
                net_Close (i_socket);
265
266
                return VLC_EGENERIC;
            }
267
268
        }

269
        if( listen( i_socket, 1 ) )
270
        {
271
            msg_Warn( p_intf, "can't listen on socket: %m");
272
273
            free( psz_unix_path );
            net_Close( i_socket );
274
275
            return VLC_EGENERIC;
        }
276
277
278
279
280
281
282
283
284
285
286

        /* 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;
287
#endif /* AF_LOCAL */
288
    }
289
#endif /* !WIN32 */
290

291
    if( ( pi_socket == NULL ) &&
292
        ( psz_host = config_GetPsz( p_intf, "rc-host" ) ) != NULL )
293
294
295
    {
        vlc_url_t url;

296
297
        vlc_UrlParse( &url, psz_host, 0 );

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

300
301
        pi_socket = net_ListenTCP(p_this, url.psz_host, url.i_port);
        if( pi_socket == NULL )
302
        {
303
304
            msg_Warn( p_intf, "can't listen to %s port %i",
                      url.psz_host, url.i_port );
305
306
307
308
309
310
            vlc_UrlClean( &url );
            free( psz_host );
            return VLC_EGENERIC;
        }

        vlc_UrlClean( &url );
311
        free( psz_host );
312
313
314
315
316
317
318
319
320
    }

    p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
    if( !p_intf->p_sys )
    {
        msg_Err( p_intf, "no memory" );
        return VLC_ENOMEM;
    }

321
    p_intf->p_sys->pi_socket_listen = pi_socket;
322
    p_intf->p_sys->i_socket = -1;
323
    p_intf->p_sys->psz_unix_path = psz_unix_path;
324
325
    vlc_mutex_init( p_intf, &p_intf->p_sys->status_lock );
    p_intf->p_sys->i_last_state = PLAYLIST_STOPPED;
326
    p_intf->p_sys->b_statistics = VLC_FALSE;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
327

328
329
330
331
332
    /* Non-buffered stdout */
    setvbuf( stdout, (char *)NULL, _IOLBF, 0 );

    p_intf->pf_run = Run;

333
#ifdef WIN32
334
335
    p_intf->p_sys->b_quiet = config_GetInt( p_intf, "rc-quiet" );
    if( !p_intf->p_sys->b_quiet ) { CONSOLE_INTRO_MSG; }
336
#else
gbazin's avatar
   
gbazin committed
337
    CONSOLE_INTRO_MSG;
338
#endif
339

dionoea's avatar
dionoea committed
340
    msg_rc( _("Remote control interface initialized. Type `help' for help.") );
341
    return VLC_SUCCESS;
342
343
}

344
345
346
347
348
349
350
/*****************************************************************************
 * Deactivate: uninitialize and cleanup
 *****************************************************************************/
static void Deactivate( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;

351
    net_ListenClose( p_intf->p_sys->pi_socket_listen );
352
353
    if( p_intf->p_sys->i_socket != -1 )
        net_Close( p_intf->p_sys->i_socket );
354
355
    if( p_intf->p_sys->psz_unix_path != NULL )
    {
damienf's avatar
damienf committed
356
#if defined(AF_LOCAL) && !defined(WIN32)
357
358
359
360
        unlink( p_intf->p_sys->psz_unix_path );
#endif
        free( p_intf->p_sys->psz_unix_path );
    }
361
    vlc_mutex_destroy( &p_intf->p_sys->status_lock );
362
363
364
    free( p_intf->p_sys );
}

365
/*****************************************************************************
Jean-Paul Saman's avatar
Jean-Paul Saman committed
366
 * RegisterCallbacks: Register callbacks to dynamic variables
367
 *****************************************************************************/
Jean-Paul Saman's avatar
Jean-Paul Saman committed
368
static void RegisterCallbacks( intf_thread_t *p_intf )
369
{
370
    /* Register commands that will be cleaned up upon object destruction */
dionoea's avatar
dionoea committed
371
372
373
374
375
376
377
378
379
#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
380
    ADD( "random", STRING, Playlist )
dionoea's avatar
dionoea committed
381
382
383
384
385
386
387
388
389
390
    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 )
391

392
    /* OSD menu commands */
dionoea's avatar
dionoea committed
393
    ADD(  "menu", STRING, Menu )
394
395

    /* DVD commands */
dionoea's avatar
dionoea committed
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
    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 )
414

415
    /* video commands */
dionoea's avatar
dionoea committed
416
417
418
    ADD( "vratio", STRING, VideoConfig )
    ADD( "vcrop", STRING, VideoConfig )
    ADD( "vzoom", STRING, VideoConfig )
419
    ADD( "snapshot", VOID, VideoConfig )
420

421
    /* audio commands */
dionoea's avatar
dionoea committed
422
423
424
425
426
427
    ADD( "volume", STRING, Volume )
    ADD( "volup", STRING, VolumeMove )
    ADD( "voldown", STRING, VolumeMove )
    ADD( "adev", STRING, AudioConfig )
    ADD( "achan", STRING, AudioConfig )

428
429
430
    /* misc menu commands */
    ADD( "stats", BOOL, Statistics )

dionoea's avatar
dionoea committed
431
#undef ADD
Jean-Paul Saman's avatar
Jean-Paul Saman committed
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
}

/*****************************************************************************
 * 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 ];
    vlc_bool_t b_showpos = config_GetInt( p_intf, "rc-show-pos" );
    vlc_bool_t b_longhelp = VLC_FALSE;

    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 );
459

460
461
    /* status callbacks */
    /* Listen to audio volume updates */
462
    var_AddCallback( p_intf->p_libvlc, "volume-change", VolumeChanged, p_intf );
463

gbazin's avatar
   
gbazin committed
464
465
#ifdef WIN32
    /* Get the file descriptor of the console input */
466
467
    p_intf->p_sys->hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
    if( p_intf->p_sys->hConsoleIn == INVALID_HANDLE_VALUE )
gbazin's avatar
   
gbazin committed
468
    {
dionoea's avatar
dionoea committed
469
        msg_Err( p_intf, "couldn't find user input handle" );
470
        vlc_object_kill( p_intf );
gbazin's avatar
   
gbazin committed
471
472
473
    }
#endif

474
    while( !intf_ShouldDie( p_intf ) )
475
    {
476
477
        char *psz_cmd, *psz_arg;
        vlc_bool_t b_complete;
478

479
        if( p_intf->p_sys->pi_socket_listen != NULL &&
480
            p_intf->p_sys->i_socket == -1 )
481
        {
482
            p_intf->p_sys->i_socket =
483
484
485
                net_Accept( p_intf, p_intf->p_sys->pi_socket_listen,
                            INTF_IDLE_SLEEP );
            if( p_intf->p_sys->i_socket == -1 ) continue;
486
487
        }

488
489
        b_complete = ReadCommand( p_intf, p_buffer, &i_size );

490
491
492
        /* Manage the input part */
        if( p_input == NULL )
        {
493
494
495
496
497
498
499
500
501
            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 );
502
                if( p_input )
503
504
505
506
507
                {
                    p_playlist = vlc_object_find( p_input, VLC_OBJECT_PLAYLIST,
                                                           FIND_PARENT );
                }
            }
508
509
510
511
512
            /* New input has been registered */
            if( p_input )
            {
                if( !p_input->b_dead || !p_input->b_die )
                {
513
                    char *psz_uri =
514
                            input_item_GetURI( input_GetItem( p_input ) );
515
516
                    msg_rc( STATUS_CHANGE "( new input: %s )", psz_uri );
                    free( psz_uri );
dionoea's avatar
dionoea committed
517
518
                    msg_rc( STATUS_CHANGE "( audio volume: %d )",
                            config_GetInt( p_intf, "volume" ));
519
520
521
522
523
                }
                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
524
525
                var_AddCallback( p_input, "time-offset", TimeOffsetChanged,
                                 p_intf );
526
            }
527
528
529
        }
        else if( p_input->b_dead )
        {
530
531
532
533
            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
534
535
            var_DelCallback( p_input, "time-offset", TimeOffsetChanged,
                             p_intf );
536
537
            vlc_object_release( p_input );
            p_input = NULL;
538
539
540
541
542

            if( p_playlist )
            {
                vlc_mutex_lock( &p_playlist->object_lock );
                p_intf->p_sys->i_last_state = (int) PLAYLIST_STOPPED;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
543
                msg_rc( STATUS_CHANGE "( stop state: 0 )" );
544
545
546
                vlc_mutex_unlock( &p_playlist->object_lock );
            }
        }
547

548
549
550
551
552
553
554
555
        if( (p_input != NULL) && !p_input->b_dead && !p_input->b_die &&
            (p_playlist != NULL) )
        {
            vlc_mutex_lock( &p_playlist->object_lock );
            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;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
556
                msg_rc( STATUS_CHANGE "( stop state: 0 )" );
557
            }
dionoea's avatar
dionoea committed
558
559
            else if(
                (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
560
561
562
                (p_playlist->status.i_status == PLAYLIST_RUNNING) )
            {
                p_intf->p_sys->i_last_state = p_playlist->status.i_status;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
563
                msg_rc( STATUS_CHANGE "( play state: 1 )" );
564
            }
dionoea's avatar
dionoea committed
565
566
            else if(
                (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
567
568
569
                (p_playlist->status.i_status == PLAYLIST_PAUSED) )
            {
                p_intf->p_sys->i_last_state = p_playlist->status.i_status;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
570
                msg_rc( STATUS_CHANGE "( pause state: 2 )" );
571
572
            }
            vlc_mutex_unlock( &p_playlist->object_lock );
573
574
        }

575
        if( p_input && b_showpos )
576
        {
Laurent Aimar's avatar
Laurent Aimar committed
577
578
            i_newpos = 100 * var_GetFloat( p_input, "position" );
            if( i_oldpos != i_newpos )
579
            {
Laurent Aimar's avatar
Laurent Aimar committed
580
                i_oldpos = i_newpos;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
581
                msg_rc( "pos: %d%%", i_newpos );
582
583
584
            }
        }

585
586
587
588
589
590
591
        if( p_input && p_intf->p_sys->b_statistics )
        {
            vlc_mutex_lock( &input_GetItem(p_input)->lock );
            updateStatistics( p_intf, input_GetItem(p_input) );
            vlc_mutex_unlock( &input_GetItem(p_input)->lock );
        }

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

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

602
603
604
605
606
607
608
        /* 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 == ' ' )
609
            {
610
                psz_arg++;
611
            }
612
613
614
615
616
617
        }
        else
        {
            psz_arg = "";
        }

618
619
620
621
622
623
624
        /* 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, ' ' );
625
            char *psz_msg;
626

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

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

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

653
654
            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
655
            msg_rc( "%s: returned %i (%s)",
656
657
658
                    psz_cmd, i_ret, vlc_error( i_ret ) );
        }
        /* Or maybe it's a global command */
659
        else if( var_Type( p_intf->p_libvlc, psz_cmd ) & VLC_VAR_ISCOMMAND )
660
661
662
663
664
665
        {
            vlc_value_t val;
            int i_ret;

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

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

            Help( p_intf, b_longhelp );
768
        }
769
#ifdef UPDATE_CHECK
770
771
        else if( !strcmp( psz_cmd, "check-updates" ) )
        {
772
            checkUpdates( p_intf );
773
        }
774
#endif
775
776
777
778
779
        else if( !strcmp( psz_cmd, "key" ) || !strcmp( psz_cmd, "hotkey" ) )
        {
            var_SetInteger( p_intf->p_libvlc, "key-pressed",
                            config_GetInt( p_intf, psz_arg ) );
        }
780
781
782
783
784
785
786
787
788
789
790
791
        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 )
                {
792
                    vlc_value_t val;
793
                    vlc_bool_t b_update = VLC_FALSE;
794
795
                    var_Get( p_vout, "fullscreen", &val );
                    val.b_bool = !val.b_bool;
dionoea's avatar
dionoea committed
796
797
                    if( !strncmp( psz_arg, "on", 2 )
                        && ( val.b_bool == VLC_TRUE ) )
798
799
800
801
                    {
                        b_update = VLC_TRUE;
                        val.b_bool = VLC_TRUE;
                    }
dionoea's avatar
dionoea committed
802
803
                    else if( !strncmp( psz_arg, "off", 3 )
                             && ( val.b_bool == VLC_FALSE ) )
804
805
806
807
                    {
                        b_update = VLC_TRUE;
                        val.b_bool = VLC_FALSE;
                    }
dionoea's avatar
dionoea committed
808
809
                    else if( strncmp( psz_arg, "off", 3 )
                             && strncmp( psz_arg, "on", 2 ) )
810
811
                        b_update = VLC_TRUE;
                    if( b_update ) var_Set( p_vout, "fullscreen", val );
812
813
814
815
816
817
818
819
                    vlc_object_release( p_vout );
                }
            }
            break;

        case 's':
        case 'S':
            ;
820
821
822
823
824
825
826
            break;

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

        default:
dionoea's avatar
dionoea committed
827
            msg_rc(_("Unknown command `%s'. Type `help' for help."), psz_cmd);
828
            break;
829
        }
830
831
832

        /* Command processed */
        i_size = 0; p_buffer[0] = 0;
833
834
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
835
836
    msg_rc( STATUS_CHANGE "( stop state: 0 )" );
    msg_rc( STATUS_CHANGE "( quit )" );
837

838
839
    if( p_input )
    {
840
841
842
843
844
        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 );
845
846
        vlc_object_release( p_input );
        p_input = NULL;
847
    }
848

849
850
851
852
    if( p_playlist )
    {
        vlc_object_release( p_playlist );
        p_playlist = NULL;
853
    }
854

855
    var_DelCallback( p_intf->p_libvlc, "volume-change", VolumeChanged, p_intf );
856
857
858
}

static void Help( intf_thread_t *p_intf, vlc_bool_t b_longhelp)
859
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
860
861
    msg_rc(_("+----[ Remote control commands ]"));
    msg_rc(  "| ");
862
863
864
865
866
867
868
869
870
    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
871
872
873
    msg_rc(_("| loop [on|off] . . . . . . . . . toggle playlist loop"));
    msg_rc(_("| random [on|off] . . . . . . .  toggle random jumping"));
    msg_rc(_("| clear . . . . . . . . . . . . . . clear the playlist"));
874
875
876
877
878
879
880
    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
881
    msg_rc(  "| ");
882
883
884
885
886
887
888
889
890
    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"));
891
    msg_rc(_("| stats  . . . . . . . .  show statistical information"));
892
    msg_rc(_("| get_time . . seconds elapsed since stream's beginning"));
893
894
895
    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
896
    msg_rc(  "| ");
897
898
899
900
901
902
903
904
905
906
    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"));
907
    msg_rc(_("| snapshot . . . . . . . . . . . . take video snapshot"));
908
909
910
    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
911
    msg_rc(  "| ");
912

913
914
    if (b_longhelp)
    {
915
916
917
918
919
920
921
922
        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
923
        msg_rc(  "| ");
924
925
926
927
928
        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
929
        msg_rc(  "| ");
930
931
932
933
934
935
936
937
938
939
940
941
942
943
        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"));
944
#ifdef UPDATE_CHECK
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
945
        msg_rc(  "| ");
946
947
        msg_rc(_("| check-updates [newer] [equal] [older]\n"
                 "|               [undef] [info] [source] [binary] [plugin]"));
948
#endif
949
        msg_rc(  "| ");
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
950
    }
951
952
953
954
    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
955
956
    msg_rc(  "| ");
    msg_rc(_("+----[ end of help ]"));
957
958
959
960
961
962
963
964
965
966
}

/********************************************************************
 * 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 )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_data;
    input_thread_t *p_input = NULL;
967

968
969
970
971
    vlc_mutex_lock( &p_intf->p_sys->status_lock );
    p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
    if( p_input )
    {
dionoea's avatar
dionoea committed
972
973
        msg_rc( STATUS_CHANGE "( time-offset: %d )",
                var_GetInteger( p_input, "time-offset" ) );
974
975
976
977
978
979
980
981
982
983
        vlc_object_release( p_input );
    }
    vlc_mutex_unlock( &p_intf->p_sys->status_lock );
    return VLC_SUCCESS;
}

static int VolumeChanged( vlc_object_t *p_this, char const *psz_cmd,
    vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_data;
984

985
    vlc_mutex_lock( &p_intf->p_sys->status_lock );
dionoea's avatar
dionoea committed
986
987
    msg_rc( STATUS_CHANGE "( audio volume: %d )",
            config_GetInt( p_this, "volume") );
988
989
990
991
992
993
994
995
996
997
    vlc_mutex_unlock( &p_intf->p_sys->status_lock );
    return VLC_SUCCESS;
}

static int StateChanged( vlc_object_t *p_this, char const *psz_cmd,
    vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_data;
    playlist_t    *p_playlist = NULL;
    input_thread_t *p_input = NULL;
998

999
1000
1001
1002
1003
1004
1005
    vlc_mutex_lock( &p_intf->p_sys->status_lock );
    p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_ANYWHERE );
    if( p_input )
    {
        p_playlist = vlc_object_find( p_input, VLC_OBJECT_PLAYLIST, FIND_PARENT );
        if( p_playlist )
        {
1006
            char cmd[6];
1007
1008
1009
            switch( p_playlist->status.i_status )
            {
            case PLAYLIST_STOPPED:
1010
                strcpy( cmd, "stop" );
1011
1012
                break;
            case PLAYLIST_RUNNING:
1013
                strcpy( cmd, "play" );
1014
1015
                break;
            case PLAYLIST_PAUSED:
1016
                strcpy( cmd, "pause" );
1017
                break;
Rémi Denis-Courmont's avatar