]> git.sesse.net Git - vlc/blob - modules/control/rc.c
Improvements to preferences
[vlc] / modules / control / rc.c
1 /*****************************************************************************
2  * rc.c : remote control stdin/stdout module for vlc
3  *****************************************************************************
4  * Copyright (C) 2004 VideoLAN
5  * $Id$
6  *
7  * Author: Peter Surda <shurdeek@panorama.sth.ac.at>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29
30 #include <errno.h>                                                 /* ENOMEM */
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <signal.h>
34
35 #include <vlc/vlc.h>
36 #include <vlc/intf.h>
37 #include <vlc/aout.h>
38 #include <vlc/vout.h>
39
40 #ifdef HAVE_UNISTD_H
41 #    include <unistd.h>
42 #endif
43
44 #ifdef HAVE_SYS_TIME_H
45 #    include <sys/time.h>
46 #endif
47 #include <sys/types.h>
48
49 #include "vlc_error.h"
50 #include "network.h"
51
52 #if defined(PF_UNIX) && !defined(PF_LOCAL)
53 #    define PF_LOCAL PF_UNIX
54 #endif
55 #if defined(AF_UNIX) && !defined(AF_LOCAL)
56 #    define AF_LOCAL AF_UNIX
57 #endif
58
59 #ifdef PF_LOCAL
60 #    include <sys/un.h>
61 #endif
62
63 #define MAX_LINE_LENGTH 256
64
65 /*****************************************************************************
66  * Local prototypes
67  *****************************************************************************/
68 static int  Activate     ( vlc_object_t * );
69 static void Deactivate   ( vlc_object_t * );
70 static void Run          ( intf_thread_t * );
71
72 static vlc_bool_t ReadCommand( intf_thread_t *, char *, int * );
73
74 static playlist_item_t *parse_MRL( intf_thread_t *, char * );
75
76 static int  Input        ( vlc_object_t *, char const *,
77                            vlc_value_t, vlc_value_t, void * );
78 static int  Playlist     ( vlc_object_t *, char const *,
79                            vlc_value_t, vlc_value_t, void * );
80 static int  Other        ( vlc_object_t *, char const *,
81                            vlc_value_t, vlc_value_t, void * );
82 static int  Quit         ( vlc_object_t *, char const *,
83                            vlc_value_t, vlc_value_t, void * );
84 static int  Intf         ( vlc_object_t *, char const *,
85                            vlc_value_t, vlc_value_t, void * );
86 static int  Volume       ( vlc_object_t *, char const *,
87                            vlc_value_t, vlc_value_t, void * );
88 static int  VolumeMove   ( vlc_object_t *, char const *,
89                            vlc_value_t, vlc_value_t, void * );
90 static int  AudioConfig  ( vlc_object_t *, char const *,
91                            vlc_value_t, vlc_value_t, void * );
92
93 struct intf_sys_t
94 {
95     int i_socket_listen;
96     int i_socket;
97     char *psz_unix_path;
98     vlc_bool_t b_extend;
99     
100 #ifdef WIN32
101     HANDLE hConsoleIn;
102     vlc_bool_t b_quiet;
103 #endif
104 };
105
106 #ifdef HAVE_VARIADIC_MACROS
107 #   define msg_rc( psz_format, args... ) \
108       __msg_rc( p_intf, psz_format, ## args )
109 #endif
110
111 void __msg_rc( intf_thread_t *p_intf, const char *psz_fmt, ... )
112 {
113     va_list args;
114     va_start( args, psz_fmt );
115     if( p_intf->p_sys->i_socket == -1 ) vprintf( psz_fmt, args );
116     else
117     { net_vaPrintf( p_intf, p_intf->p_sys->i_socket, NULL, psz_fmt, args );
118       net_Printf( VLC_OBJECT(p_intf), p_intf->p_sys->i_socket, NULL, "\r" ); }
119     va_end( args );
120 }
121
122 /*****************************************************************************
123  * Module descriptor
124  *****************************************************************************/
125 #define POS_TEXT N_("Show stream position")
126 #define POS_LONGTEXT N_("Show the current position in seconds within the " \
127                         "stream from time to time." )
128
129 #define TTY_TEXT N_("Fake TTY")
130 #define TTY_LONGTEXT N_("Force the rc module to use stdin as if it was a TTY.")
131
132 #define UNIX_TEXT N_("UNIX socket command input")
133 #define UNIX_LONGTEXT N_("Accept commands over a Unix socket rather than " \
134                          "stdin." )
135
136 #define HOST_TEXT N_("TCP command input")
137 #define HOST_LONGTEXT N_("Accept commands over a socket rather than stdin. " \
138             "You can set the address and port the interface will bind to." )
139 #define EXTEND_TEXT N_("Extended help")
140 #define EXTEND_LONGTEXT N_("List additional commands.")
141             
142
143 #ifdef WIN32
144 #define QUIET_TEXT N_("Do not open a DOS command box interface")
145 #define QUIET_LONGTEXT N_( \
146     "By default the rc interface plugin will start a DOS command box. " \
147     "Enabling the quiet mode will not bring this command box but can also " \
148     "be pretty annoying when you want to stop VLC and no video window is " \
149     "open." )
150 #endif
151
152 vlc_module_begin();
153     set_category( CAT_INTERFACE );
154     set_subcategory( SUBCAT_INTERFACE_CONTROL );
155     set_description( _("Remote control interface") );
156     add_bool( "rc-show-pos", 0, NULL, POS_TEXT, POS_LONGTEXT, VLC_TRUE );
157 #ifdef HAVE_ISATTY
158     add_bool( "rc-fake-tty", 0, NULL, TTY_TEXT, TTY_LONGTEXT, VLC_TRUE );
159 #endif
160     add_string( "rc-unix", 0, NULL, UNIX_TEXT, UNIX_LONGTEXT, VLC_TRUE );
161     add_string( "rc-host", 0, NULL, HOST_TEXT, HOST_LONGTEXT, VLC_TRUE );
162
163 #ifdef WIN32
164     add_bool( "rc-quiet", 0, NULL, QUIET_TEXT, QUIET_LONGTEXT, VLC_FALSE );
165 #endif
166     add_bool( "rc-extend", 0, NULL, EXTEND_TEXT, EXTEND_LONGTEXT, VLC_FALSE );
167
168     set_capability( "interface", 20 );
169     set_callbacks( Activate, Deactivate );
170 vlc_module_end();
171
172 /*****************************************************************************
173  * Activate: initialize and create stuff
174  *****************************************************************************/
175 static int Activate( vlc_object_t *p_this )
176 {
177     intf_thread_t *p_intf = (intf_thread_t*)p_this;
178     char *psz_host, *psz_unix_path;
179     int i_socket = -1;
180
181 #if defined(HAVE_ISATTY) && !defined(WIN32)
182     /* Check that stdin is a TTY */
183     if( !config_GetInt( p_intf, "rc-fake-tty" ) && !isatty( 0 ) )
184     {
185         msg_Warn( p_intf, "fd 0 is not a TTY" );
186         return VLC_EGENERIC;
187     }
188 #endif
189
190     psz_unix_path = config_GetPsz( p_intf, "rc-unix" );
191     if( psz_unix_path )
192     {
193 #ifndef PF_LOCAL
194         msg_Warn( p_intf, "your OS doesn't support filesystem sockets" );
195         free( psz_unix_path );
196         return VLC_EGENERIC;
197 #else
198         struct sockaddr_un addr;
199         int i_ret;
200
201         memset( &addr, 0, sizeof(struct sockaddr_un) );
202
203         msg_Dbg( p_intf, "trying UNIX socket" );
204
205         if( (i_socket = socket( PF_LOCAL, SOCK_STREAM, 0 ) ) < 0 )
206         {
207             msg_Warn( p_intf, "can't open socket: %s", strerror(errno) );
208             free( psz_unix_path );
209             return VLC_EGENERIC;
210         }
211
212         addr.sun_family = AF_LOCAL;
213         strncpy( addr.sun_path, psz_unix_path, sizeof( addr.sun_path ) );
214         addr.sun_path[sizeof( addr.sun_path ) - 1] = '\0';
215
216         if( (i_ret = bind( i_socket, (struct sockaddr*)&addr,
217                            sizeof(struct sockaddr_un) ) ) < 0 )
218         {
219             msg_Warn( p_intf, "couldn't bind socket to address: %s",
220                       strerror(errno) );
221             free( psz_unix_path );
222             net_Close( i_socket );
223             return VLC_EGENERIC;
224         }
225
226         if( ( i_ret = listen( i_socket, 1 ) ) < 0 )
227         {
228             msg_Warn( p_intf, "can't listen on socket: %s", strerror(errno));
229             free( psz_unix_path );
230             net_Close( i_socket );
231             return VLC_EGENERIC;
232         }
233 #endif
234     }
235
236     if( ( i_socket == -1) &&
237         ( psz_host = config_GetPsz( p_intf, "rc-host" ) ) != NULL )
238     {
239         vlc_url_t url;
240
241         vlc_UrlParse( &url, psz_host, 0 );
242
243         msg_Dbg( p_intf, "base %s port %d", url.psz_host, url.i_port );
244
245         if( (i_socket = net_ListenTCP(p_this, url.psz_host, url.i_port)) == -1)
246         {
247             msg_Warn( p_intf, "can't listen to %s port %i",
248                       url.psz_host, url.i_port );
249             vlc_UrlClean( &url );
250             free( psz_host );
251             return VLC_EGENERIC;
252         }
253
254         vlc_UrlClean( &url );
255         free( psz_host );
256     }
257
258     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
259     if( !p_intf->p_sys )
260     {
261         msg_Err( p_intf, "no memory" );
262         return VLC_ENOMEM;
263     }
264
265     p_intf->p_sys->i_socket_listen = i_socket;
266     p_intf->p_sys->i_socket = -1;
267     p_intf->p_sys->psz_unix_path = psz_unix_path;
268
269     /* Non-buffered stdout */
270     setvbuf( stdout, (char *)NULL, _IOLBF, 0 );
271
272     p_intf->pf_run = Run;
273
274 #ifdef WIN32
275     p_intf->p_sys->b_quiet = config_GetInt( p_intf, "rc-quiet" );
276     if( !p_intf->p_sys->b_quiet ) { CONSOLE_INTRO_MSG; }
277 #else
278     CONSOLE_INTRO_MSG;
279 #endif
280
281     msg_rc( _("Remote control interface initialized, `h' for help\n") );
282     return VLC_SUCCESS;
283 }
284
285 /*****************************************************************************
286  * Deactivate: uninitialize and cleanup
287  *****************************************************************************/
288 static void Deactivate( vlc_object_t *p_this )
289 {
290     intf_thread_t *p_intf = (intf_thread_t*)p_this;
291
292     if( p_intf->p_sys->i_socket_listen != -1 )
293         net_Close( p_intf->p_sys->i_socket_listen );
294     if( p_intf->p_sys->i_socket != -1 )
295         net_Close( p_intf->p_sys->i_socket );
296     if( p_intf->p_sys->psz_unix_path != NULL )
297     {
298 #ifdef PF_LOCAL
299         unlink( p_intf->p_sys->psz_unix_path );
300 #endif
301         free( p_intf->p_sys->psz_unix_path );
302     }
303     free( p_intf->p_sys );
304 }
305
306 /*****************************************************************************
307  * Run: rc thread
308  *****************************************************************************
309  * This part of the interface is in a separate thread so that we can call
310  * exec() from within it without annoying the rest of the program.
311  *****************************************************************************/
312 static void Run( intf_thread_t *p_intf )
313 {
314     input_thread_t * p_input;
315     playlist_t *     p_playlist;
316
317     char       p_buffer[ MAX_LINE_LENGTH + 1 ];
318     vlc_bool_t b_showpos = config_GetInt( p_intf, "rc-show-pos" );
319
320     int        i_size = 0;
321     int        i_oldpos = 0;
322     int        i_newpos;
323
324     p_buffer[0] = 0;
325     p_input = NULL;
326     p_playlist = NULL;
327  
328     p_intf->p_sys->b_extend = config_GetInt( p_intf, "rc-extend" );
329     /* Register commands that will be cleaned up upon object destruction */
330     var_Create( p_intf, "quit", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
331     var_AddCallback( p_intf, "quit", Quit, NULL );
332     var_Create( p_intf, "intf", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
333     var_AddCallback( p_intf, "intf", Intf, NULL );
334
335     var_Create( p_intf, "add", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
336     var_AddCallback( p_intf, "add", Playlist, NULL );
337     var_Create( p_intf, "playlist", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
338     var_AddCallback( p_intf, "playlist", Playlist, NULL );
339     var_Create( p_intf, "play", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
340     var_AddCallback( p_intf, "play", Playlist, NULL );
341     var_Create( p_intf, "stop", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
342     var_AddCallback( p_intf, "stop", Playlist, NULL );
343     var_Create( p_intf, "prev", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
344     var_AddCallback( p_intf, "prev", Playlist, NULL );
345     var_Create( p_intf, "next", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
346     var_AddCallback( p_intf, "next", Playlist, NULL );
347   
348     var_Create( p_intf, "marq-marquee", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
349     var_AddCallback( p_intf, "marq-marquee", Other, NULL );
350     var_Create( p_intf, "marq-x", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
351     var_AddCallback( p_intf, "marq-x", Other, NULL );
352     var_Create( p_intf, "marq-y", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
353     var_AddCallback( p_intf, "marq-y", Other, NULL );
354     var_Create( p_intf, "marq-timeout", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
355     var_AddCallback( p_intf, "marq-timeout", Other, NULL );
356
357     var_Create( p_intf, "pause", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
358     var_AddCallback( p_intf, "pause", Input, NULL );
359     var_Create( p_intf, "seek", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
360     var_AddCallback( p_intf, "seek", Input, NULL );
361     var_Create( p_intf, "title", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
362     var_AddCallback( p_intf, "title", Input, NULL );
363     var_Create( p_intf, "title_n", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
364     var_AddCallback( p_intf, "title_n", Input, NULL );
365     var_Create( p_intf, "title_p", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
366     var_AddCallback( p_intf, "title_p", Input, NULL );
367     var_Create( p_intf, "chapter", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
368     var_AddCallback( p_intf, "chapter", Input, NULL );
369     var_Create( p_intf, "chapter_n", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
370     var_AddCallback( p_intf, "chapter_n", Input, NULL );
371     var_Create( p_intf, "chapter_p", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
372     var_AddCallback( p_intf, "chapter_p", Input, NULL );
373
374     var_Create( p_intf, "volume", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
375     var_AddCallback( p_intf, "volume", Volume, NULL );
376     var_Create( p_intf, "volup", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
377     var_AddCallback( p_intf, "volup", VolumeMove, NULL );
378     var_Create( p_intf, "voldown", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
379     var_AddCallback( p_intf, "voldown", VolumeMove, NULL );
380     var_Create( p_intf, "adev", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
381     var_AddCallback( p_intf, "adev", AudioConfig, NULL );
382     var_Create( p_intf, "achan", VLC_VAR_STRING | VLC_VAR_ISCOMMAND );
383     var_AddCallback( p_intf, "achan", AudioConfig, NULL );
384
385 #ifdef WIN32
386     /* Get the file descriptor of the console input */
387     p_intf->p_sys->hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
388     if( p_intf->p_sys->hConsoleIn == INVALID_HANDLE_VALUE )
389     {
390         msg_Err( p_intf, "Couldn't open STD_INPUT_HANDLE" );
391         p_intf->b_die = VLC_TRUE;
392     }
393 #endif
394
395     while( !p_intf->b_die )
396     {
397         char *psz_cmd, *psz_arg;
398         vlc_bool_t b_complete;
399
400         if( p_intf->p_sys->i_socket_listen != - 1 &&
401             p_intf->p_sys->i_socket == -1 )
402         {
403             p_intf->p_sys->i_socket =
404                 net_Accept( p_intf, p_intf->p_sys->i_socket_listen, 0 );
405         }
406
407         b_complete = ReadCommand( p_intf, p_buffer, &i_size );
408
409         /* Manage the input part */
410         if( p_input == NULL )
411         {
412             if( p_playlist )
413             {
414                 p_input = vlc_object_find( p_playlist, VLC_OBJECT_INPUT,
415                                                        FIND_CHILD );
416             }
417             else
418             {
419                 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
420                                                    FIND_ANYWHERE );
421                 if( p_input )
422                 {
423                     p_playlist = vlc_object_find( p_input, VLC_OBJECT_PLAYLIST,
424                                                            FIND_PARENT );
425                 }
426             }
427         }
428         else if( p_input->b_dead )
429         {
430             vlc_object_release( p_input );
431             p_input = NULL;
432         }
433
434         if( p_input && b_showpos )
435         {
436             i_newpos = 100 * var_GetFloat( p_input, "position" );
437             if( i_oldpos != i_newpos )
438             {
439                 i_oldpos = i_newpos;
440                 msg_rc( "pos: %d%%\n", i_newpos );
441             }
442         }
443
444         /* Is there something to do? */
445         if( !b_complete ) continue;
446
447
448         /* Skip heading spaces */
449         psz_cmd = p_buffer;
450         while( *psz_cmd == ' ' )
451         {
452             psz_cmd++;
453         }
454
455         /* Split psz_cmd at the first space and make sure that
456          * psz_arg is valid */
457         psz_arg = strchr( psz_cmd, ' ' );
458         if( psz_arg )
459         {
460             *psz_arg++ = 0;
461             while( *psz_arg == ' ' )
462             {
463                 psz_arg++;
464             }
465         }
466         else
467         {
468             psz_arg = "";
469         }
470
471         /* If the user typed a registered local command, try it */
472         if( var_Type( p_intf, psz_cmd ) & VLC_VAR_ISCOMMAND )
473         {
474             vlc_value_t val;
475             int i_ret;
476
477             val.psz_string = psz_arg;
478             i_ret = var_Set( p_intf, psz_cmd, val );
479             msg_rc( "%s: returned %i (%s)\n",
480                     psz_cmd, i_ret, vlc_error( i_ret ) );
481         }
482         /* Or maybe it's a global command */
483         else if( var_Type( p_intf->p_libvlc, psz_cmd ) & VLC_VAR_ISCOMMAND )
484         {
485             vlc_value_t val;
486             int i_ret;
487
488             val.psz_string = psz_arg;
489             /* FIXME: it's a global command, but we should pass the
490              * local object as an argument, not p_intf->p_libvlc. */
491             i_ret = var_Set( p_intf->p_libvlc, psz_cmd, val );
492             if( i_ret != 0 )
493             {
494                 msg_rc( "%s: returned %i (%s)\n",
495                          psz_cmd, i_ret, vlc_error( i_ret ) );
496             }
497         }
498         else if( !strcmp( psz_cmd, "logout" ) )
499         {
500             /* Close connection */
501             if( p_intf->p_sys->i_socket != -1 )
502             {
503                 net_Close( p_intf->p_sys->i_socket );
504             }
505             p_intf->p_sys->i_socket = -1;
506         }
507         else if( !strcmp( psz_cmd, "info" ) )
508         {
509             if( p_input )
510             {
511                 int i, j;
512                 vlc_mutex_lock( &p_input->input.p_item->lock );
513                 for ( i = 0; i < p_input->input.p_item->i_categories; i++ )
514                 {
515                     info_category_t *p_category =
516                         p_input->input.p_item->pp_categories[i];
517
518                     msg_rc( "+----[ %s ]\n", p_category->psz_name );
519                     msg_rc( "| \n" );
520                     for ( j = 0; j < p_category->i_infos; j++ )
521                     {
522                         info_t *p_info = p_category->pp_infos[j];
523                         msg_rc( "| %s: %s\n", p_info->psz_name,
524                                 p_info->psz_value );
525                     }
526                     msg_rc( "| \n" );
527                 }
528                 msg_rc( "+----[ end of stream info ]\n" );
529                 vlc_mutex_unlock( &p_input->input.p_item->lock );
530             }
531             else
532             {
533                 msg_rc( "no input\n" );
534             }
535         }
536         else if( !strcmp( psz_cmd, "is_playing" ) )
537         {
538             if( ! p_input )
539             {
540                 msg_rc( "0\n" );
541             }
542             else
543             {
544                 msg_rc( "1\n" );
545             }
546         }
547         else if( !strcmp( psz_cmd, "get_time" ) )
548         {
549             if( ! p_input )
550             {
551                 msg_rc("0\n");
552             }
553             else
554             {
555                 vlc_value_t time;
556                 var_Get( p_input, "time", &time );
557                 msg_rc( "%i\n", time.i_time / 1000000);
558             }
559         }
560         else if( !strcmp( psz_cmd, "get_length" ) )
561         {
562             if( ! p_input )
563             {
564                 msg_rc("0\n");
565             }
566             else
567             {
568                 vlc_value_t time;
569                 var_Get( p_input, "length", &time );
570                 msg_rc( "%i\n", time.i_time / 1000000);
571             }
572         }
573         else if( !strcmp( psz_cmd, "get_title" ) )
574         {
575             if( ! p_input )
576             {
577                 msg_rc("\n");
578             }
579             else
580             {
581                 msg_rc( "%s\n", p_input->input.p_item->psz_name );
582             }
583         }
584         else switch( psz_cmd[0] )
585         {
586         case 'f':
587         case 'F':
588             if( p_input )
589             {
590                 vout_thread_t *p_vout;
591                 p_vout = vlc_object_find( p_input,
592                                           VLC_OBJECT_VOUT, FIND_CHILD );
593
594                 if( p_vout )
595                 {
596                     p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
597                     vlc_object_release( p_vout );
598                 }
599             }
600             break;
601
602         case 's':
603         case 'S':
604             ;
605             break;
606
607         case '?':
608         case 'h':
609         case 'H':
610             msg_rc(_("+----[ Remote control commands ]\n"));
611             msg_rc("| \n");
612             msg_rc(_("| add XYZ  . . . . . . . . . . add XYZ to playlist\n"));
613             msg_rc(_("| playlist . . .  show items currently in playlist\n"));
614             msg_rc(_("| play . . . . . . . . . . . . . . . . play stream\n"));
615             msg_rc(_("| stop . . . . . . . . . . . . . . . . stop stream\n"));
616             msg_rc(_("| next . . . . . . . . . . . .  next playlist item\n"));
617             msg_rc(_("| prev . . . . . . . . . .  previous playlist item\n"));
618             msg_rc(_("| title [X]  . . . . set/get title in current item\n"));
619             msg_rc(_("| title_n  . . . . . .  next title in current item\n"));
620             msg_rc(_("| title_p  . . . .  previous title in current item\n"));
621             msg_rc(_("| chapter [X]  . . set/get chapter in current item\n"));
622             msg_rc(_("| chapter_n  . . . .  next chapter in current item\n"));
623             msg_rc(_("| chapter_p  . .  previous chapter in current item\n"));
624             msg_rc("| \n");
625             msg_rc(_("| seek X . seek in seconds, for instance `seek 12'\n"));
626             msg_rc(_("| pause  . . . . . . . . . . . . . .  toggle pause\n"));
627             msg_rc(_("| f  . . . . . . . . . . . . . . toggle fullscreen\n"));
628             msg_rc(_("| info . . .  information about the current stream\n"));
629             msg_rc("| \n");
630             msg_rc(_("| volume [X] . . . . . . . .  set/get audio volume\n"));
631             msg_rc(_("| volup [X]  . . . . .  raise audio volume X steps\n"));
632             msg_rc(_("| voldown [X]  . . . .  lower audio volume X steps\n"));
633             msg_rc(_("| adev [X] . . . . . . . . .  set/get audio device\n"));
634             msg_rc(_("| achan [X]. . . . . . . .  set/get audio channels\n"));
635             msg_rc("| \n");
636             if (p_intf->p_sys->b_extend)
637             {
638                    msg_rc(_("| marq-marquee STRING  . . overlay STRING in video\n"));
639                msg_rc(_("| marq-x X . . . . . .offset of marquee, from left\n"));
640                msg_rc(_("| marq-y Y . . . . . . offset of marquee, from top\n"));
641                msg_rc(_("| marq-timeout T. . . . .timeout of marquee, in ms\n"));
642                msg_rc("| \n");
643             }    
644             msg_rc(_("| help . . . . . . . . . . . . . this help message\n"));
645             msg_rc(_("| logout . . . . . .exit (if in socket connection)\n"));
646             msg_rc(_("| quit . . . . . . . . . . . . . . . . .  quit vlc\n"));
647             msg_rc("| \n");
648             msg_rc(_("+----[ end of help ]\n"));
649             break;
650
651         case '\0':
652             /* Ignore empty lines */
653             break;
654
655         default:
656             msg_rc(_("unknown command `%s', type `help' for help\n"), psz_cmd);
657             break;
658         }
659
660         /* Command processed */
661         i_size = 0; p_buffer[0] = 0;
662     }
663
664     if( p_input )
665     {
666         vlc_object_release( p_input );
667         p_input = NULL;
668     }
669
670     if( p_playlist )
671     {
672         vlc_object_release( p_playlist );
673         p_playlist = NULL;
674     }
675 }
676
677 static int Input( vlc_object_t *p_this, char const *psz_cmd,
678                   vlc_value_t oldval, vlc_value_t newval, void *p_data )
679 {
680     intf_thread_t *p_intf = (intf_thread_t*)p_this;
681     input_thread_t *p_input;
682     vlc_value_t     val;
683
684     p_input = vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_ANYWHERE );
685     if( !p_input ) return VLC_ENOOBJ;
686
687     /* Parse commands that only require an input */
688     if( !strcmp( psz_cmd, "pause" ) )
689     {
690         val.i_int = PAUSE_S;
691
692         var_Set( p_input, "state", val );
693         vlc_object_release( p_input );
694         return VLC_SUCCESS;
695     }
696     else if( !strcmp( psz_cmd, "seek" ) )
697     {
698         if( strlen( newval.psz_string ) > 0 &&
699             newval.psz_string[strlen( newval.psz_string ) - 1] == '%' )
700         {
701             val.f_float = (float)atoi( newval.psz_string ) / 100.0;
702             var_Set( p_input, "position", val );
703         }
704         else
705         {
706             val.i_time = ((int64_t)atoi( newval.psz_string )) * 1000000;
707             var_Set( p_input, "time", val );
708         }
709         vlc_object_release( p_input );
710         return VLC_SUCCESS;
711     }
712    
713     else if( !strcmp( psz_cmd, "chapter" ) ||
714              !strcmp( psz_cmd, "chapter_n" ) ||
715              !strcmp( psz_cmd, "chapter_p" ) )
716     {
717         if( !strcmp( psz_cmd, "chapter" ) )
718         {
719             if ( *newval.psz_string )
720             {
721                 /* Set. */
722                 val.i_int = atoi( newval.psz_string );
723                 var_Set( p_input, "chapter", val );
724             }
725             else
726             {
727                 vlc_value_t val_list;
728
729                 /* Get. */
730                 var_Get( p_input, "chapter", &val );
731                 var_Change( p_input, "chapter", VLC_VAR_GETCHOICES,
732                             &val_list, NULL );
733                 msg_rc( "Currently playing chapter %d/%d\n",
734                         val.i_int, val_list.p_list->i_count );
735                 var_Change( p_this, "chapter", VLC_VAR_FREELIST,
736                             &val_list, NULL );
737             }
738         }
739         else if( !strcmp( psz_cmd, "chapter_n" ) )
740         {
741             val.b_bool = VLC_TRUE;
742             var_Set( p_input, "next-chapter", val );
743         }
744         else if( !strcmp( psz_cmd, "chapter_p" ) )
745         {
746             val.b_bool = VLC_TRUE;
747             var_Set( p_input, "prev-chapter", val );
748         }
749
750         vlc_object_release( p_input );
751         return VLC_SUCCESS;
752     }
753     else if( !strcmp( psz_cmd, "title" ) ||
754              !strcmp( psz_cmd, "title_n" ) ||
755              !strcmp( psz_cmd, "title_p" ) )
756     {
757         if( !strcmp( psz_cmd, "title" ) )
758         {
759             if ( *newval.psz_string )
760             {
761                 /* Set. */
762                 val.i_int = atoi( newval.psz_string );
763                 var_Set( p_input, "title", val );
764             }
765             else
766             {
767                 vlc_value_t val_list;
768
769                 /* Get. */
770                 var_Get( p_input, "title", &val );
771                 var_Change( p_input, "title", VLC_VAR_GETCHOICES,
772                             &val_list, NULL );
773                 msg_rc( "Currently playing title %d/%d\n",
774                         val.i_int, val_list.p_list->i_count );
775                 var_Change( p_this, "title", VLC_VAR_FREELIST,
776                             &val_list, NULL );
777             }
778         }
779         else if( !strcmp( psz_cmd, "title_n" ) )
780         {
781             val.b_bool = VLC_TRUE;
782             var_Set( p_input, "next-title", val );
783         }
784         else if( !strcmp( psz_cmd, "title_p" ) )
785         {
786             val.b_bool = VLC_TRUE;
787             var_Set( p_input, "prev-title", val );
788         }
789
790         vlc_object_release( p_input );
791         return VLC_SUCCESS;
792     }
793
794     /* Never reached. */
795     return VLC_EGENERIC;
796 }
797
798 static int Playlist( vlc_object_t *p_this, char const *psz_cmd,
799                      vlc_value_t oldval, vlc_value_t newval, void *p_data )
800 {
801     intf_thread_t *p_intf = (intf_thread_t*)p_this;
802     playlist_t *p_playlist;
803
804     p_playlist = vlc_object_find( p_this, VLC_OBJECT_PLAYLIST,
805                                            FIND_ANYWHERE );
806     if( !p_playlist )
807     {
808         return VLC_ENOOBJ;
809     }
810
811     /* Parse commands that require a playlist */
812     if( !strcmp( psz_cmd, "prev" ) )
813     {
814         playlist_Prev( p_playlist );
815     }
816     else if( !strcmp( psz_cmd, "next" ) )
817     {
818         playlist_Next( p_playlist );
819     }
820     else if( !strcmp( psz_cmd, "play" ) )
821     {
822         playlist_Play( p_playlist );
823     }
824     else if( !strcmp( psz_cmd, "stop" ) )
825     {
826         playlist_Stop( p_playlist );
827     }
828     else if( !strcmp( psz_cmd, "add" ) &&
829              newval.psz_string && *newval.psz_string )
830     {
831         playlist_item_t *p_item = parse_MRL( p_intf, newval.psz_string );
832
833         if( p_item )
834         {
835             msg_rc( "trying to add %s to playlist\n", newval.psz_string );
836             playlist_AddItem( p_playlist, p_item,
837                               PLAYLIST_GO|PLAYLIST_APPEND, PLAYLIST_END );
838         }
839     }
840     else if( !strcmp( psz_cmd, "playlist" ) )
841     {
842         int i;
843         for ( i = 0; i < p_playlist->i_size; i++ )
844         {
845             msg_rc( "|%s%s   %s|\n", i == p_playlist->i_index?"*":" ",
846                     p_playlist->pp_items[i]->input.psz_name,
847                     p_playlist->pp_items[i]->input.psz_uri );
848         }
849         if ( i == 0 )
850         {
851             msg_rc( "| no entries\n" );
852         }
853     }
854  
855     /*
856      * sanity check
857      */
858     else
859     {
860         msg_rc( "unknown command!\n" );
861     }
862
863     vlc_object_release( p_playlist );
864     return VLC_SUCCESS;
865 }
866
867 static int Other( vlc_object_t *p_this, char const *psz_cmd,
868                      vlc_value_t oldval, vlc_value_t newval, void *p_data )
869 {
870     intf_thread_t *p_intf = (intf_thread_t*)p_this;
871     vlc_object_t *p_pl;
872     vlc_value_t     val;
873     vlc_object_t *p_inp;
874
875     p_pl = vlc_object_find( p_this, VLC_OBJECT_PLAYLIST,
876                                            FIND_ANYWHERE );
877     if( !p_pl )
878     {
879         return VLC_ENOOBJ;
880     }
881     
882     p_inp = vlc_object_find( p_this, VLC_OBJECT_INPUT,
883                                            FIND_ANYWHERE );
884     if( !p_inp )
885     {
886         return VLC_ENOOBJ;
887     }
888
889     /* Parse miscellaneous commands */
890     if( !strcmp( psz_cmd, "marq-marquee" ) )
891     {
892         if( strlen( newval.psz_string ) > 0 )
893         {
894             val.psz_string = newval.psz_string;
895             var_Set( p_inp->p_libvlc, "marq-marquee", val );
896         }
897         else 
898         {
899                 val.psz_string = "";
900                 var_Set( p_inp->p_libvlc, "marq-marquee", val);
901         }
902     }
903     else if( !strcmp( psz_cmd, "marq-x" ) )
904     {
905         if( strlen( newval.psz_string ) > 0) 
906         {
907             val.i_int = atoi( newval.psz_string );
908             var_Set( p_inp->p_libvlc, "marq-x", val );
909         }
910     }
911     else if( !strcmp( psz_cmd, "marq-y" ) )
912     {
913         if( strlen( newval.psz_string ) > 0) 
914         {
915             val.i_int = atoi( newval.psz_string );
916             var_Set( p_inp->p_libvlc, "marq-y", val );
917         }
918     }
919     else if( !strcmp( psz_cmd, "marq-timeout" ) )
920     {
921         if( strlen( newval.psz_string ) > 0) 
922         {
923             val.i_int = atoi( newval.psz_string );
924             var_Set( p_inp, "marq-timeout", val );
925         }
926     }
927  
928     /*
929      * sanity check
930      */
931     else
932     {
933         msg_rc( "unknown command!\n" );
934     }
935
936     vlc_object_release( p_pl );
937     vlc_object_release( p_inp );
938     return VLC_SUCCESS;
939 }
940
941 static int Quit( vlc_object_t *p_this, char const *psz_cmd,
942                  vlc_value_t oldval, vlc_value_t newval, void *p_data )
943 {
944     p_this->p_vlc->b_die = VLC_TRUE;
945     return VLC_SUCCESS;
946 }
947
948 static int Intf( vlc_object_t *p_this, char const *psz_cmd,
949                  vlc_value_t oldval, vlc_value_t newval, void *p_data )
950 {
951     intf_thread_t *p_newintf;
952
953     p_newintf = intf_Create( p_this->p_vlc, newval.psz_string );
954
955     if( p_newintf )
956     {
957         p_newintf->b_block = VLC_FALSE;
958         if( intf_RunThread( p_newintf ) )
959         {
960             vlc_object_detach( p_newintf );
961             intf_Destroy( p_newintf );
962         }
963     }
964
965     return VLC_SUCCESS;
966 }
967
968 static int Volume( vlc_object_t *p_this, char const *psz_cmd,
969                    vlc_value_t oldval, vlc_value_t newval, void *p_data )
970 {
971     intf_thread_t *p_intf = (intf_thread_t*)p_this;
972     int i_error;
973
974     if ( *newval.psz_string )
975     {
976         /* Set. */
977         audio_volume_t i_volume = atoi( newval.psz_string );
978         if ( i_volume > AOUT_VOLUME_MAX )
979         {
980             msg_rc( "Volume must be in the range %d-%d\n", AOUT_VOLUME_MIN,
981                     AOUT_VOLUME_MAX );
982             i_error = VLC_EBADVAR;
983         }
984         else i_error = aout_VolumeSet( p_this, i_volume );
985     }
986     else
987     {
988         /* Get. */
989         audio_volume_t i_volume;
990         if ( aout_VolumeGet( p_this, &i_volume ) < 0 )
991         {
992             i_error = VLC_EGENERIC;
993         }
994         else
995         {
996             msg_rc( "Volume is %d\n", i_volume );
997             i_error = VLC_SUCCESS;
998         }
999     }
1000
1001     return i_error;
1002 }
1003
1004 static int VolumeMove( vlc_object_t *p_this, char const *psz_cmd,
1005                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
1006 {
1007     intf_thread_t *p_intf = (intf_thread_t*)p_this;
1008     audio_volume_t i_volume;
1009     int i_nb_steps = atoi(newval.psz_string);
1010     int i_error = VLC_SUCCESS;
1011
1012     if ( i_nb_steps <= 0 || i_nb_steps > (AOUT_VOLUME_MAX/AOUT_VOLUME_STEP) )
1013     {
1014         i_nb_steps = 1;
1015     }
1016
1017     if ( !strcmp(psz_cmd, "volup") )
1018     {
1019         if ( aout_VolumeUp( p_this, i_nb_steps, &i_volume ) < 0 )
1020             i_error = VLC_EGENERIC;
1021     }
1022     else
1023     {
1024         if ( aout_VolumeDown( p_this, i_nb_steps, &i_volume ) < 0 )
1025             i_error = VLC_EGENERIC;
1026     }
1027
1028     if ( !i_error ) msg_rc( "Volume is %d\n", i_volume );
1029     return i_error;
1030 }
1031
1032 static int AudioConfig( vlc_object_t *p_this, char const *psz_cmd,
1033                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
1034 {
1035     intf_thread_t *p_intf = (intf_thread_t*)p_this;
1036     aout_instance_t * p_aout;
1037     const char * psz_variable;
1038     vlc_value_t val_name;
1039     int i_error;
1040
1041     p_aout = vlc_object_find( p_this, VLC_OBJECT_AOUT, FIND_ANYWHERE );
1042     if ( p_aout == NULL ) return VLC_ENOOBJ;
1043
1044     if ( !strcmp( psz_cmd, "adev" ) )
1045     {
1046         psz_variable = "audio-device";
1047     }
1048     else
1049     {
1050         psz_variable = "audio-channels";
1051     }
1052
1053     /* Get the descriptive name of the variable */
1054     var_Change( (vlc_object_t *)p_aout, psz_variable, VLC_VAR_GETTEXT,
1055                  &val_name, NULL );
1056     if( !val_name.psz_string ) val_name.psz_string = strdup(psz_variable);
1057
1058     if ( !*newval.psz_string )
1059     {
1060         /* Retrieve all registered ***. */
1061         vlc_value_t val, text;
1062         int i, i_value;
1063
1064         if ( var_Get( (vlc_object_t *)p_aout, psz_variable, &val ) < 0 )
1065         {
1066             vlc_object_release( (vlc_object_t *)p_aout );
1067             return VLC_EGENERIC;
1068         }
1069         i_value = val.i_int;
1070
1071         if ( var_Change( (vlc_object_t *)p_aout, psz_variable,
1072                          VLC_VAR_GETLIST, &val, &text ) < 0 )
1073         {
1074             vlc_object_release( (vlc_object_t *)p_aout );
1075             return VLC_EGENERIC;
1076         }
1077
1078         msg_rc( "+----[ %s ]\n", val_name.psz_string );
1079         for ( i = 0; i < val.p_list->i_count; i++ )
1080         {
1081             if ( i_value == val.p_list->p_values[i].i_int )
1082                 msg_rc( "| %i - %s *\n", val.p_list->p_values[i].i_int,
1083                         text.p_list->p_values[i].psz_string );
1084             else
1085                 msg_rc( "| %i - %s\n", val.p_list->p_values[i].i_int,
1086                         text.p_list->p_values[i].psz_string );
1087         }
1088         var_Change( (vlc_object_t *)p_aout, psz_variable, VLC_VAR_FREELIST,
1089                     &val, &text );
1090         msg_rc( "+----[ end of %s ]\n", val_name.psz_string );
1091
1092         if( val_name.psz_string ) free( val_name.psz_string );
1093         i_error = VLC_SUCCESS;
1094     }
1095     else
1096     {
1097         vlc_value_t val;
1098         val.i_int = atoi( newval.psz_string );
1099
1100         i_error = var_Set( (vlc_object_t *)p_aout, psz_variable, val );
1101     }
1102     vlc_object_release( (vlc_object_t *)p_aout );
1103
1104     return i_error;
1105 }
1106
1107 #ifdef WIN32
1108 vlc_bool_t ReadWin32( intf_thread_t *p_intf, char *p_buffer, int *pi_size )
1109 {
1110     INPUT_RECORD input_record;
1111     DWORD i_dw;
1112
1113     /* On Win32, select() only works on socket descriptors */
1114     while( WaitForSingleObject( p_intf->p_sys->hConsoleIn,
1115                                 INTF_IDLE_SLEEP/1000 ) == WAIT_OBJECT_0 )
1116     {
1117         while( !p_intf->b_die && *pi_size < MAX_LINE_LENGTH &&
1118                ReadConsoleInput( p_intf->p_sys->hConsoleIn, &input_record,
1119                                  1, &i_dw ) )
1120         {
1121             if( input_record.EventType != KEY_EVENT ||
1122                 !input_record.Event.KeyEvent.bKeyDown ||
1123                 input_record.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT ||
1124                 input_record.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL||
1125                 input_record.Event.KeyEvent.wVirtualKeyCode == VK_MENU ||
1126                 input_record.Event.KeyEvent.wVirtualKeyCode == VK_CAPITAL )
1127             {
1128                 /* nothing interesting */
1129                 continue;
1130             }
1131
1132             p_buffer[ *pi_size ] = input_record.Event.KeyEvent.uChar.AsciiChar;
1133
1134             /* Echo out the command */
1135             putc( p_buffer[ *pi_size ], stdout );
1136
1137             /* Handle special keys */
1138             if( p_buffer[ *pi_size ] == '\r' || p_buffer[ *pi_size ] == '\n' )
1139             {
1140                 putc( '\n', stdout );
1141                 break;
1142             }
1143             switch( p_buffer[ *pi_size ] )
1144             {
1145             case '\b':
1146                 if( *pi_size )
1147                 {
1148                     *pi_size -= 2;
1149                     putc( ' ', stdout );
1150                     putc( '\b', stdout );
1151                 }
1152                 break;
1153             case '\r':
1154                 (*pi_size) --;
1155                 break;
1156             }
1157
1158             (*pi_size)++;
1159         }
1160
1161         if( *pi_size == MAX_LINE_LENGTH ||
1162             p_buffer[ *pi_size ] == '\r' || p_buffer[ *pi_size ] == '\n' )
1163         {
1164             p_buffer[ *pi_size ] = 0;
1165             return VLC_TRUE;
1166         }
1167     }
1168
1169     return VLC_FALSE;
1170 }
1171 #endif
1172
1173 vlc_bool_t ReadCommand( intf_thread_t *p_intf, char *p_buffer, int *pi_size )
1174 {
1175     int i_read = 0;
1176
1177 #ifdef WIN32
1178     if( p_intf->p_sys->i_socket == -1 && !p_intf->p_sys->b_quiet )
1179         return ReadWin32( p_intf, p_buffer, pi_size );
1180     else if( p_intf->p_sys->i_socket == -1 )
1181     {
1182         msleep( INTF_IDLE_SLEEP );
1183         return VLC_FALSE;
1184     }
1185 #endif
1186
1187     while( !p_intf->b_die && *pi_size < MAX_LINE_LENGTH &&
1188            (i_read = net_ReadNonBlock( p_intf, p_intf->p_sys->i_socket == -1 ?
1189                        0 /*STDIN_FILENO*/ : p_intf->p_sys->i_socket, NULL,
1190                        p_buffer + *pi_size, 1, INTF_IDLE_SLEEP ) ) > 0 )
1191     {
1192         if( p_buffer[ *pi_size ] == '\r' || p_buffer[ *pi_size ] == '\n' )
1193             break;
1194
1195         (*pi_size)++;
1196     }
1197
1198     /* Connection closed */
1199     if( i_read == -1 )
1200     {
1201         p_intf->p_sys->i_socket = -1;
1202         p_buffer[ *pi_size ] = 0;
1203         return VLC_TRUE;
1204     }
1205
1206     if( *pi_size == MAX_LINE_LENGTH ||
1207         p_buffer[ *pi_size ] == '\r' || p_buffer[ *pi_size ] == '\n' )
1208     {
1209         p_buffer[ *pi_size ] = 0;
1210         return VLC_TRUE;
1211     }
1212
1213     return VLC_FALSE;
1214 }
1215
1216 /*****************************************************************************
1217  * parse_MRL: build a playlist item from a full mrl
1218  *****************************************************************************
1219  * MRL format: "simplified-mrl [:option-name[=option-value]]"
1220  * We don't check for '"' or '\'', we just assume that a ':' that follows a
1221  * space is a new option. Should be good enough for our purpose.
1222  *****************************************************************************/
1223 static playlist_item_t *parse_MRL( intf_thread_t *p_intf, char *psz_mrl )
1224 {
1225 #define SKIPSPACE( p ) { while( *p && ( *p == ' ' || *p == '\t' ) ) p++; }
1226 #define SKIPTRAILINGSPACE( p, d ) \
1227     { char *e=d; while( e > p && (*(e-1)==' ' || *(e-1)=='\t') ){e--;*e=0;} }
1228
1229     playlist_item_t *p_item = NULL;
1230     char *psz_item = NULL, *psz_item_mrl = NULL, *psz_orig;
1231     char **ppsz_options = NULL;
1232     int i, i_options = 0;
1233
1234     if( !psz_mrl ) return 0;
1235
1236     psz_mrl = psz_orig = strdup( psz_mrl );
1237     while( *psz_mrl )
1238     {
1239         SKIPSPACE( psz_mrl );
1240         psz_item = psz_mrl;
1241
1242         for( ; *psz_mrl; psz_mrl++ )
1243         {
1244             if( (*psz_mrl == ' ' || *psz_mrl == '\t') && psz_mrl[1] == ':' )
1245             {
1246                 /* We have a complete item */
1247                 break;
1248             }
1249             if( (*psz_mrl == ' ' || *psz_mrl == '\t') &&
1250                 (psz_mrl[1] == '"' || psz_mrl[1] == '\'') && psz_mrl[2] == ':')
1251             {
1252                 /* We have a complete item */
1253                 break;
1254             }
1255         }
1256
1257         if( *psz_mrl ) { *psz_mrl = 0; psz_mrl++; }
1258         SKIPTRAILINGSPACE( psz_item, psz_item + strlen( psz_item ) );
1259
1260         /* Remove '"' and '\'' if necessary */
1261         if( *psz_item == '"' && psz_item[strlen(psz_item)-1] == '"' )
1262         { psz_item++; psz_item[strlen(psz_item)-1] = 0; }
1263         if( *psz_item == '\'' && psz_item[strlen(psz_item)-1] == '\'' )
1264         { psz_item++; psz_item[strlen(psz_item)-1] = 0; }
1265
1266         if( !psz_item_mrl ) psz_item_mrl = psz_item;
1267         else if( *psz_item )
1268         {
1269             i_options++;
1270             ppsz_options = realloc( ppsz_options, i_options * sizeof(char *) );
1271             ppsz_options[i_options - 1] = &psz_item[1];
1272         }
1273
1274         if( *psz_mrl ) SKIPSPACE( psz_mrl );
1275     }
1276
1277     /* Now create a playlist item */
1278     if( psz_item_mrl )
1279     {
1280         p_item = playlist_ItemNew( p_intf, psz_item_mrl, psz_item_mrl );
1281         for( i = 0; i < i_options; i++ )
1282         {
1283             playlist_ItemAddOption( p_item, ppsz_options[i] );
1284         }
1285     }
1286
1287     if( i_options ) free( ppsz_options );
1288     free( psz_orig );
1289
1290     return p_item;
1291 }