rc.c 78.1 KB
Newer Older
1
/*****************************************************************************
Anil Daoud's avatar
Anil Daoud committed
2
 * rc.c : remote control stdin/stdout module for vlc
3
 *****************************************************************************
4
 * Copyright (C) 2004 - 2005 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 28 29 30 31 32 33
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>                                      /* malloc(), free() */
#include <string.h>

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

#include <vlc/vlc.h>
#include <vlc/intf.h>
38
#include <vlc/aout.h>
39
#include <vlc/vout.h>
40
#include <vlc_video.h>
41
#include <vlc_osd.h>
42
#include <vlc_update.h>
43 44 45 46 47 48 49 50 51 52

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

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

53
#include "vlc_error.h"
54
#include "network.h"
55
#include "vlc_url.h"
56

57 58 59 60
#if defined(AF_UNIX) && !defined(AF_LOCAL)
#    define AF_LOCAL AF_UNIX
#endif

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

65
#define MAX_LINE_LENGTH 256
66
#define STATUS_CHANGE "status change: "
67 68 69 70 71

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

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

78
static vlc_bool_t ReadCommand( intf_thread_t *, char *, int * );
79

80 81
static playlist_item_t *parse_MRL( intf_thread_t *, char * );

82 83
static int  Input        ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
gbazin's avatar
 
gbazin committed
84 85
static int  Playlist     ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
86 87
static int  Other        ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
gbazin's avatar
 
gbazin committed
88 89 90 91 92 93 94 95 96 97
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 * );
static int  AudioConfig  ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
98 99
static int  Menu         ( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
100
static void checkUpdates( intf_thread_t *p_intf, char *psz_arg );
101 102 103 104 105 106 107 108 109 110

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

112 113
struct intf_sys_t
{
114
    int *pi_socket_listen;
115
    int i_socket;
116
    char *psz_unix_path;
117

118 119 120
    /* status changes */
    vlc_mutex_t       status_lock;
    playlist_status_t i_last_state;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
121

122 123
#ifdef WIN32
    HANDLE hConsoleIn;
124
    vlc_bool_t b_quiet;
125 126 127 128
#endif
};

#ifdef HAVE_VARIADIC_MACROS
129 130
#   define msg_rc( psz_format, args... ) \
      __msg_rc( p_intf, psz_format, ## args )
131 132
#endif

133
void __msg_rc( intf_thread_t *p_intf, const char *psz_fmt, ... )
134 135 136
{
    va_list args;
    va_start( args, psz_fmt );
137

138
    if( p_intf->p_sys->i_socket == -1 )
139
    {
140
        vprintf( psz_fmt, args );
141
        printf( "\r\n" );
142
    }
143
    else
144 145
    {
        net_vaPrintf( p_intf, p_intf->p_sys->i_socket, NULL, psz_fmt, args );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
146
        net_Write( p_intf, p_intf->p_sys->i_socket, NULL, (uint8_t*)"\r\n", 2 );
147
    }
148 149 150
    va_end( args );
}

151 152 153
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Christophe Massiot's avatar
Christophe Massiot committed
154
#define POS_TEXT N_("Show stream position")
155 156
#define POS_LONGTEXT N_("Show the current position in seconds within the " \
                        "stream from time to time." )
157

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

161
#define UNIX_TEXT N_("UNIX socket command input")
162 163
#define UNIX_LONGTEXT N_("Accept commands over a Unix socket rather than " \
                         "stdin." )
164

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

169 170 171 172 173 174 175 176 177
#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

178
vlc_module_begin();
179
    set_shortname( _("RC"));
zorglub's avatar
zorglub committed
180
    set_category( CAT_INTERFACE );
zorglub's avatar
zorglub committed
181
    set_subcategory( SUBCAT_INTERFACE_MAIN );
gbazin's avatar
 
gbazin committed
182
    set_description( _("Remote control interface") );
183
    add_bool( "rc-show-pos", 0, NULL, POS_TEXT, POS_LONGTEXT, VLC_TRUE );
Sam Hocevar's avatar
Sam Hocevar committed
184
#ifdef HAVE_ISATTY
185
    add_bool( "rc-fake-tty", 0, NULL, TTY_TEXT, TTY_LONGTEXT, VLC_TRUE );
Sam Hocevar's avatar
Sam Hocevar committed
186
#endif
187
    add_string( "rc-unix", 0, NULL, UNIX_TEXT, UNIX_LONGTEXT, VLC_TRUE );
188
    add_string( "rc-host", 0, NULL, HOST_TEXT, HOST_LONGTEXT, VLC_TRUE );
189 190 191 192 193

#ifdef WIN32
    add_bool( "rc-quiet", 0, NULL, QUIET_TEXT, QUIET_LONGTEXT, VLC_FALSE );
#endif

194
    set_capability( "interface", 20 );
195
    set_callbacks( Activate, Deactivate );
196 197 198 199 200 201 202 203
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;
204
    playlist_t *p_playlist;
205
    char *psz_host, *psz_unix_path;
206
    int  *pi_socket = NULL;
207

gbazin's avatar
 
gbazin committed
208
#if defined(HAVE_ISATTY) && !defined(WIN32)
209
    /* Check that stdin is a TTY */
210
    if( !config_GetInt( p_intf, "rc-fake-tty" ) && !isatty( 0 ) )
211 212
    {
        msg_Warn( p_intf, "fd 0 is not a TTY" );
213
        return VLC_EGENERIC;
214 215 216
    }
#endif

217 218
    psz_unix_path = config_GetPsz( p_intf, "rc-unix" );
    if( psz_unix_path )
219
    {
220 221
        int i_socket;

damienf's avatar
damienf committed
222
#if !defined(AF_LOCAL) || defined(WIN32)
223
        msg_Warn( p_intf, "your OS doesn't support filesystem sockets" );
224
        free( psz_unix_path );
225 226 227 228 229 230 231 232 233
        return VLC_EGENERIC;
#else
        struct sockaddr_un addr;
        int i_ret;

        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
234
        if( (i_socket = socket( AF_LOCAL, SOCK_STREAM, 0 ) ) < 0 )
235 236
        {
            msg_Warn( p_intf, "can't open socket: %s", strerror(errno) );
237
            free( psz_unix_path );
238 239 240
            return VLC_EGENERIC;
        }

241 242 243
        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';
244 245 246 247 248 249

        if( (i_ret = bind( i_socket, (struct sockaddr*)&addr,
                           sizeof(struct sockaddr_un) ) ) < 0 )
        {
            msg_Warn( p_intf, "couldn't bind socket to address: %s",
                      strerror(errno) );
250 251
            free( psz_unix_path );
            net_Close( i_socket );
252 253 254 255 256 257
            return VLC_EGENERIC;
        }

        if( ( i_ret = listen( i_socket, 1 ) ) < 0 )
        {
            msg_Warn( p_intf, "can't listen on socket: %s", strerror(errno));
258 259
            free( psz_unix_path );
            net_Close( i_socket );
260 261
            return VLC_EGENERIC;
        }
262 263 264 265 266 267 268 269 270 271 272

        /* 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;
273 274
#endif
    }
275

276
    if( ( pi_socket == NULL ) &&
277
        ( psz_host = config_GetPsz( p_intf, "rc-host" ) ) != NULL )
278 279 280
    {
        vlc_url_t url;

281 282 283 284
        vlc_UrlParse( &url, psz_host, 0 );

        msg_Dbg( p_intf, "base %s port %d", url.psz_host, url.i_port );

285 286
        pi_socket = net_ListenTCP(p_this, url.psz_host, url.i_port);
        if( pi_socket == NULL )
287
        {
288 289
            msg_Warn( p_intf, "can't listen to %s port %i",
                      url.psz_host, url.i_port );
290 291 292 293 294 295
            vlc_UrlClean( &url );
            free( psz_host );
            return VLC_EGENERIC;
        }

        vlc_UrlClean( &url );
296
        free( psz_host );
297 298 299 300 301 302 303 304 305
    }

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

306
    p_intf->p_sys->pi_socket_listen = pi_socket;
307
    p_intf->p_sys->i_socket = -1;
308
    p_intf->p_sys->psz_unix_path = psz_unix_path;
309 310
    vlc_mutex_init( p_intf, &p_intf->p_sys->status_lock );
    p_intf->p_sys->i_last_state = PLAYLIST_STOPPED;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
311

312 313 314 315 316
    /* Non-buffered stdout */
    setvbuf( stdout, (char *)NULL, _IOLBF, 0 );

    p_intf->pf_run = Run;

317
#ifdef WIN32
318 319
    p_intf->p_sys->b_quiet = config_GetInt( p_intf, "rc-quiet" );
    if( !p_intf->p_sys->b_quiet ) { CONSOLE_INTRO_MSG; }
320
#else
gbazin's avatar
 
gbazin committed
321
    CONSOLE_INTRO_MSG;
322
#endif
323

324 325 326 327 328 329 330 331 332 333 334
    /* Force "no-view" mode */
    p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
                                                 FIND_ANYWHERE );
    if( p_playlist )
    {
        vlc_mutex_lock( &p_playlist->object_lock );
        p_playlist->status.i_view = -1;
        vlc_mutex_unlock( &p_playlist->object_lock );
        vlc_object_release( p_playlist );
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
335
    msg_rc( _("Remote control interface initialized, `h' for help") );
336
    return VLC_SUCCESS;
337 338
}

339 340 341 342 343 344 345
/*****************************************************************************
 * Deactivate: uninitialize and cleanup
 *****************************************************************************/
static void Deactivate( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;

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

360
/*****************************************************************************
Jean-Paul Saman's avatar
Jean-Paul Saman committed
361
 * RegisterCallbacks: Register callbacks to dynamic variables
362
 *****************************************************************************/
Jean-Paul Saman's avatar
Jean-Paul Saman committed
363
static void RegisterCallbacks( intf_thread_t *p_intf )
364
{
365
    /* Register commands that will be cleaned up upon object destruction */
gbazin's avatar
 
gbazin committed
366 367 368 369 370
    var_Create( p_intf, "quit", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "quit", Quit, NULL );
    var_Create( p_intf, "intf", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "intf", Intf, NULL );

371 372 373 374
    var_Create( p_intf, "add", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "add", Playlist, NULL );
    var_Create( p_intf, "playlist", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "playlist", Playlist, NULL );
gbazin's avatar
 
gbazin committed
375 376 377 378
    var_Create( p_intf, "play", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "play", Playlist, NULL );
    var_Create( p_intf, "stop", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "stop", Playlist, NULL );
379 380
    var_Create( p_intf, "clear", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "clear", Playlist, NULL );
gbazin's avatar
 
gbazin committed
381 382 383 384
    var_Create( p_intf, "prev", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "prev", Playlist, NULL );
    var_Create( p_intf, "next", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "next", Playlist, NULL );
hartman's avatar
hartman committed
385 386
    var_Create( p_intf, "goto", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "goto", Playlist, NULL );
387 388 389
    var_Create( p_intf, "status", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "status", Playlist, NULL );

390
    /* marquee on the fly items */
391 392
    var_Create( p_intf, "marq-marquee", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "marq-marquee", Other, NULL );
393
    var_Create( p_intf, "marq-x", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
394
    var_AddCallback( p_intf, "marq-x", Other, NULL );
395
    var_Create( p_intf, "marq-y", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
396
    var_AddCallback( p_intf, "marq-y", Other, NULL );
397 398 399 400 401 402
    var_Create( p_intf, "marq-position", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "marq-position", Other, NULL );
    var_Create( p_intf, "marq-color", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "marq-color", Other, NULL );
    var_Create( p_intf, "marq-opacity", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "marq-opacity", Other, NULL );
zorglub's avatar
zorglub committed
403
    var_Create( p_intf, "marq-timeout", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
404
    var_AddCallback( p_intf, "marq-timeout", Other, NULL );
405 406
    var_Create( p_intf, "marq-size", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "marq-size", Other, NULL );
gbazin's avatar
 
gbazin committed
407

408 409 410 411 412 413 414 415 416 417
    var_Create( p_intf, "mosaic-alpha", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-alpha", Other, NULL );
    var_Create( p_intf, "mosaic-height", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-height", Other, NULL );
    var_Create( p_intf, "mosaic-width", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-width", Other, NULL );
    var_Create( p_intf, "mosaic-xoffset", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-xoffset", Other, NULL );
    var_Create( p_intf, "mosaic-yoffset", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-yoffset", Other, NULL );
418 419
    var_Create( p_intf, "mosaic-align", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-align", Other, NULL );
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
    var_Create( p_intf, "mosaic-vborder", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-vborder", Other, NULL );
    var_Create( p_intf, "mosaic-hborder", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-hborder", Other, NULL );
    var_Create( p_intf, "mosaic-position",
                     VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-position", Other, NULL );
    var_Create( p_intf, "mosaic-rows", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-rows", Other, NULL );
    var_Create( p_intf, "mosaic-cols", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-cols", Other, NULL );
    var_Create( p_intf, "mosaic-keep-aspect-ratio",
                     VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "mosaic-keep-aspect-ratio", Other, NULL );

435 436 437 438 439 440 441 442 443 444 445 446 447
    /* time on the fly items */
    var_Create( p_intf, "time-format", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-format", Other, NULL );
    var_Create( p_intf, "time-x", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-x", Other, NULL );
    var_Create( p_intf, "time-y", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-y", Other, NULL );
    var_Create( p_intf, "time-position", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-position", Other, NULL );
    var_Create( p_intf, "time-color", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-color", Other, NULL );
    var_Create( p_intf, "time-opacity", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-opacity", Other, NULL );
448 449
    var_Create( p_intf, "time-size", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "time-size", Other, NULL );
450

451
    /* logo on the fly items */
Mark Moriarty's avatar
Mark Moriarty committed
452 453
    var_Create( p_intf, "logo-file", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "logo-file", Other, NULL );
454 455 456 457 458 459 460 461
    var_Create( p_intf, "logo-x", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "logo-x", Other, NULL );
    var_Create( p_intf, "logo-y", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "logo-y", Other, NULL );
    var_Create( p_intf, "logo-position", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "logo-position", Other, NULL );
    var_Create( p_intf, "logo-transparency", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "logo-transparency", Other, NULL );
462 463 464

    /* OSD menu commands */
    var_Create( p_intf, "menu", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
465
    var_AddCallback( p_intf, "menu", Menu, NULL );
466 467

    /* DVD commands */
468 469 470 471
    var_Create( p_intf, "pause", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "pause", Input, NULL );
    var_Create( p_intf, "seek", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "seek", Input, NULL );
gbazin's avatar
 
gbazin committed
472
    var_Create( p_intf, "title", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
473
    var_AddCallback( p_intf, "title", Input, NULL );
gbazin's avatar
 
gbazin committed
474
    var_Create( p_intf, "title_n", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
475
    var_AddCallback( p_intf, "title_n", Input, NULL );
gbazin's avatar
 
gbazin committed
476
    var_Create( p_intf, "title_p", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
477
    var_AddCallback( p_intf, "title_p", Input, NULL );
gbazin's avatar
 
gbazin committed
478
    var_Create( p_intf, "chapter", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
479
    var_AddCallback( p_intf, "chapter", Input, NULL );
gbazin's avatar
 
gbazin committed
480
    var_Create( p_intf, "chapter_n", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
481
    var_AddCallback( p_intf, "chapter_n", Input, NULL );
gbazin's avatar
 
gbazin committed
482
    var_Create( p_intf, "chapter_p", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
483
    var_AddCallback( p_intf, "chapter_p", Input, NULL );
gbazin's avatar
 
gbazin committed
484

hartman's avatar
hartman committed
485 486 487 488
    var_Create( p_intf, "fastforward", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "fastforward", Input, NULL );
    var_Create( p_intf, "rewind", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "rewind", Input, NULL );
489 490 491 492 493 494 495 496
    var_Create( p_intf, "faster", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "faster", Input, NULL );
    var_Create( p_intf, "slower", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "slower", Input, NULL );
    var_Create( p_intf, "normal", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "normal", Input, NULL );

    /* audio commands */
gbazin's avatar
 
gbazin committed
497 498 499 500 501 502 503 504 505 506
    var_Create( p_intf, "volume", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "volume", Volume, NULL );
    var_Create( p_intf, "volup", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "volup", VolumeMove, NULL );
    var_Create( p_intf, "voldown", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "voldown", VolumeMove, NULL );
    var_Create( p_intf, "adev", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "adev", AudioConfig, NULL );
    var_Create( p_intf, "achan", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
    var_AddCallback( p_intf, "achan", AudioConfig, NULL );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
}

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

535 536
    /* status callbacks */
    /* Listen to audio volume updates */
537
    var_AddCallback( p_intf->p_vlc, "volume-change", VolumeChanged, p_intf );
538

gbazin's avatar
 
gbazin committed
539 540
#ifdef WIN32
    /* Get the file descriptor of the console input */
541 542
    p_intf->p_sys->hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
    if( p_intf->p_sys->hConsoleIn == INVALID_HANDLE_VALUE )
gbazin's avatar
 
gbazin committed
543
    {
544
        msg_Err( p_intf, "Couldn't open STD_INPUT_HANDLE" );
gbazin's avatar
 
gbazin committed
545
        p_intf->b_die = VLC_TRUE;
gbazin's avatar
 
gbazin committed
546 547 548
    }
#endif

549 550
    while( !p_intf->b_die )
    {
551 552
        char *psz_cmd, *psz_arg;
        vlc_bool_t b_complete;
553

554
        if( p_intf->p_sys->pi_socket_listen != NULL &&
555
            p_intf->p_sys->i_socket == -1 )
556
        {
557
            p_intf->p_sys->i_socket =
558
                net_Accept( p_intf, p_intf->p_sys->pi_socket_listen, 0 );
559 560
        }

561 562
        b_complete = ReadCommand( p_intf, p_buffer, &i_size );

563 564 565
        /* Manage the input part */
        if( p_input == NULL )
        {
566 567 568 569 570 571 572 573 574
            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 );
575
                if( p_input )
576 577 578 579 580
                {
                    p_playlist = vlc_object_find( p_input, VLC_OBJECT_PLAYLIST,
                                                           FIND_PARENT );
                }
            }
581 582 583 584 585
            /* New input has been registered */
            if( p_input )
            {
                if( !p_input->b_dead || !p_input->b_die )
                {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
586 587
                    msg_rc( STATUS_CHANGE "( New input: %s )", p_input->input.p_item->psz_uri );
                    msg_rc( STATUS_CHANGE "( audio volume: %d )", config_GetInt( p_intf, "volume" ));
588 589 590 591 592 593 594
                }
                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 );
                var_AddCallback( p_input, "time-offset", TimeOffsetChanged, p_intf );
            }
595 596 597
        }
        else if( p_input->b_dead )
        {
598 599 600 601 602
            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 );
603 604
            vlc_object_release( p_input );
            p_input = NULL;
605 606 607 608 609

            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
610
                msg_rc( STATUS_CHANGE "( stop state: 0 )" );
611 612 613
                vlc_mutex_unlock( &p_playlist->object_lock );
            }
        }
614

615 616 617 618 619 620 621 622
        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
623
                msg_rc( STATUS_CHANGE "( stop state: 0 )" );
624 625 626 627 628
            }
            else if( (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
                (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
629
                msg_rc( STATUS_CHANGE "( play state: 1 )" );
630 631 632 633 634
            }
            else if( (p_intf->p_sys->i_last_state != p_playlist->status.i_status) &&
                (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
635
                msg_rc( STATUS_CHANGE "( pause state: 2 )" );
636 637
            }
            vlc_mutex_unlock( &p_playlist->object_lock );
638 639
        }

640
        if( p_input && b_showpos )
641
        {
Laurent Aimar's avatar
Laurent Aimar committed
642 643
            i_newpos = 100 * var_GetFloat( p_input, "position" );
            if( i_oldpos != i_newpos )
644
            {
Laurent Aimar's avatar
Laurent Aimar committed
645
                i_oldpos = i_newpos;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
646
                msg_rc( "pos: %d%%", i_newpos );
647 648 649 650
            }
        }

        /* Is there something to do? */
651 652 653 654 655 656
        if( !b_complete ) continue;


        /* Skip heading spaces */
        psz_cmd = p_buffer;
        while( *psz_cmd == ' ' )
657
        {
658 659
            psz_cmd++;
        }
660

661 662 663 664 665 666 667
        /* 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 == ' ' )
668
            {
669
                psz_arg++;
670
            }
671 672 673 674 675 676 677 678 679 680 681
        }
        else
        {
            psz_arg = "";
        }

        /* If the user typed a registered local command, try it */
        if( var_Type( p_intf, psz_cmd ) & VLC_VAR_ISCOMMAND )
        {
            vlc_value_t val;
            int i_ret;
682

683 684
            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
685
            msg_rc( "%s: returned %i (%s)",
686 687 688 689 690 691 692 693 694 695 696 697
                    psz_cmd, i_ret, vlc_error( i_ret ) );
        }
        /* Or maybe it's a global command */
        else if( var_Type( p_intf->p_libvlc, psz_cmd ) & VLC_VAR_ISCOMMAND )
        {
            vlc_value_t val;
            int i_ret;

            val.psz_string = psz_arg;
            /* FIXME: it's a global command, but we should pass the
             * local object as an argument, not p_intf->p_libvlc. */
            i_ret = var_Set( p_intf->p_libvlc, psz_cmd, val );
698 699
            if( i_ret != 0 )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
700
                msg_rc( "%s: returned %i (%s)",
701 702
                         psz_cmd, i_ret, vlc_error( i_ret ) );
            }
703
        }
zorglub's avatar
zorglub committed
704 705 706 707 708 709 710 711 712
        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;
        }
713 714 715
        else if( !strcmp( psz_cmd, "info" ) )
        {
            if( p_input )
716
            {
717 718 719
                int i, j;
                vlc_mutex_lock( &p_input->input.p_item->lock );
                for ( i = 0; i < p_input->input.p_item->i_categories; i++ )
720
                {
721 722 723
                    info_category_t *p_category =
                        p_input->input.p_item->pp_categories[i];

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
724 725
                    msg_rc( "+----[ %s ]", p_category->psz_name );
                    msg_rc( "| " );
726 727 728
                    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
729
                        msg_rc( "| %s: %s", p_info->psz_name,
730 731
                                p_info->psz_value );
                    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
732
                    msg_rc( "| " );
733
                }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
734
                msg_rc( "+----[ end of stream info ]" );
735
                vlc_mutex_unlock( &p_input->input.p_item->lock );
736
            }
737
            else
738
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
739
                msg_rc( "no input" );
Sam Hocevar's avatar
Sam Hocevar committed
740
            }
741
        }
742 743 744 745
        else if( !strcmp( psz_cmd, "is_playing" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
746
                msg_rc( "0" );
747 748 749
            }
            else
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
750
                msg_rc( "1" );
751 752 753 754 755 756
            }
        }
        else if( !strcmp( psz_cmd, "get_time" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
757
                msg_rc("0");
758 759 760 761 762
            }
            else
            {
                vlc_value_t time;
                var_Get( p_input, "time", &time );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
763
                msg_rc( "%i", time.i_time / 1000000);
764 765 766 767 768 769
            }
        }
        else if( !strcmp( psz_cmd, "get_length" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
770
                msg_rc("0");
771 772 773 774 775
            }
            else
            {
                vlc_value_t time;
                var_Get( p_input, "length", &time );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
776
                msg_rc( "%i", time.i_time / 1000000);
777 778 779 780 781 782
            }
        }
        else if( !strcmp( psz_cmd, "get_title" ) )
        {
            if( ! p_input )
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
783
                msg_rc("");
784 785 786
            }
            else
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
787
                msg_rc( "%s", p_input->input.p_item->psz_name );
788 789
            }
        }
790 791
        else if( !strcmp( psz_cmd, "longhelp" ) || !strncmp( psz_cmd, "h", 1 )
                 || !strncmp( psz_cmd, "H", 1 ) || !strncmp( psz_cmd, "?", 1 ) )
792
        {
793 794 795
            if( !strcmp( psz_cmd, "longhelp" ) || !strncmp( psz_cmd, "H", 1 ) )
                 b_longhelp = VLC_TRUE;
            else b_longhelp = VLC_FALSE;
796 797

            Help( p_intf, b_longhelp );
798
        }
799 800 801 802
        else if( !strcmp( psz_cmd, "check-updates" ) )
        {
            checkUpdates( p_intf, psz_arg );
        }
803 804 805 806 807 808 809 810 811 812 813 814
        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 )
                {
815
                    vlc_value_t val;
816
                    vlc_bool_t b_update = VLC_FALSE;
817 818
                    var_Get( p_vout, "fullscreen", &val );
                    val.b_bool = !val.b_bool;
819 820 821 822 823 824 825 826 827 828 829 830 831
                    if( !strncmp(psz_arg, "on", 2) && (val.b_bool == VLC_TRUE) )
                    {
                        b_update = VLC_TRUE;
                        val.b_bool = VLC_TRUE;
                    }
                    else if( !strncmp(psz_arg, "off", 3)  && (val.b_bool == VLC_FALSE) )
                    {
                        b_update = VLC_TRUE;
                        val.b_bool = VLC_FALSE;
                    }
                    else if( strncmp(psz_arg, "off", 3) && strncmp(psz_arg, "on", 2) )
                        b_update = VLC_TRUE;
                    if( b_update ) var_Set( p_vout, "fullscreen", val );
832 833 834 835 836 837 838 839
                    vlc_object_release( p_vout );
                }
            }
            break;

        case 's':
        case 'S':
            ;
840 841 842 843 844 845 846
            break;

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

        default:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
847
            msg_rc(_("unknown command `%s', type `help' for help"), psz_cmd);
848
            break;
849
        }
850 851 852

        /* Command processed */
        i_size = 0; p_buffer[0] = 0;
853 854
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
855 856
    msg_rc( STATUS_CHANGE "( stop state: 0 )" );
    msg_rc( STATUS_CHANGE "( quit )" );
857

858 859
    if( p_input )
    {
860 861 862 863 864
        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 );
865 866
        vlc_object_release( p_input );
        p_input = NULL;
867
    }
868

869 870 871 872
    if( p_playlist )
    {
        vlc_object_release( p_playlist );
        p_playlist = NULL;
873
    }
874

875
    var_DelCallback( p_intf->p_vlc, "volume-change", VolumeChanged, p_intf );
876 877 878
}

static void Help( intf_thread_t *p_intf, vlc_bool_t b_longhelp)
879
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
880 881 882 883 884 885 886 887 888
    msg_rc(_("+----[ Remote control commands ]"));
    msg_rc(  "| ");
    msg_rc(_("| add XYZ  . . . . . . . . . . add 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"));
889
    msg_rc(_("| clear . . . . . . . . . . .   clear the playlist"));
890
    msg_rc(_("| status . . . . . . . . . current playlist status"));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
891 892 893 894 895 896 897 898 899 900 901 902 903