cli.c 30.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*****************************************************************************
 * cli.c : remote control stdin/stdout module for vlc
 *****************************************************************************
 * Copyright (C) 2004-2009 the VideoLAN team
 *
 * Author: Peter Surda <shurdeek@panorama.sth.ac.at>
 *         Jean-Paul Saman <jpsaman #_at_# m2x _replaceWith#dot_ nl>
 *
 * 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.
 *
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <errno.h>                                                 /* ENOMEM */
#include <assert.h>
#include <math.h>
#include <sys/types.h>
36
#include <sys/stat.h>
37
#include <fcntl.h>
38
#include <unistd.h>
39
40
41
#ifdef HAVE_WORDEXP_H
#include <wordexp.h>
#endif
42
43
44
#ifdef HAVE_SEARCH_H
#include <search.h>
#endif
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_interface.h>
#include <vlc_player.h>
#include <vlc_actions.h>
#include <vlc_fs.h>
#include <vlc_network.h>
#include <vlc_url.h>
#include <vlc_charset.h>

#if defined(PF_UNIX) && !defined(PF_LOCAL)
#    define PF_LOCAL PF_UNIX
#endif

#if defined(AF_LOCAL) && ! defined(_WIN32)
#    include <sys/un.h>
#endif

#include "cli.h"

67
68
69
70
71
72
73
struct intf_sys_t
{
    vlc_thread_t thread;
    void *commands;
    void *player_cli;

#ifndef _WIN32
74
75
    vlc_mutex_t clients_lock;
    struct vlc_list clients;
76
77
78
79
80
81
82
83
#else
    HANDLE hConsoleIn;
    bool b_quiet;
    int i_socket;
#endif
    int *pi_socket_listen;
};

84
85
#define MAX_LINE_LENGTH 1024

86
87
88
89
90
struct command {
    union {
        const char *name;
        struct cli_handler handler;
    };
91
    void *data;
92
93
};

94
95
96
97
98
99
100
101
102
static int cmdcmp(const void *a, const void *b)
{
    const char *const *na = a;
    const char *const *nb = b;

    return strcmp(*na, *nb);
}

void RegisterHandlers(intf_thread_t *intf, const struct cli_handler *handlers,
103
                      size_t count, void *opaque)
104
105
106
107
108
{
    intf_sys_t *sys = intf->p_sys;

    for (size_t i = 0; i < count; i++)
    {
109
110
111
        struct command *cmd = malloc(sizeof (*cmd));
        if (unlikely(cmd == NULL))
            break;
112

113
        cmd->handler = handlers[i];
114
        cmd->data = opaque;
115

116
        struct command **pp = tsearch(&cmd->name, &sys->commands, cmdcmp);
117
        if (unlikely(pp == NULL))
118
119
        {
            free(cmd);
120
            continue;
121
        }
122

123
        assert(*pp == cmd); /* Fails if duplicate command */
124
125
126
    }
}

127
static int Help(struct cli_client *cl, const char *const *args, size_t count,
128
                void *data)
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
{
    msg_rc("%s", _("+----[ Remote control commands ]"));
    msg_rc(  "| ");
    msg_rc("%s", _("| add XYZ  . . . . . . . . . . . . add XYZ to playlist"));
    msg_rc("%s", _("| enqueue XYZ  . . . . . . . . . queue XYZ to playlist"));
    msg_rc("%s", _("| playlist . . . . .  show items currently in playlist"));
    msg_rc("%s", _("| play . . . . . . . . . . . . . . . . . . play stream"));
    msg_rc("%s", _("| stop . . . . . . . . . . . . . . . . . . stop stream"));
    msg_rc("%s", _("| next . . . . . . . . . . . . . .  next playlist item"));
    msg_rc("%s", _("| prev . . . . . . . . . . . .  previous playlist item"));
    msg_rc("%s", _("| goto . . . . . . . . . . . . . .  goto item at index"));
    msg_rc("%s", _("| repeat [on|off] . . . .  toggle playlist item repeat"));
    msg_rc("%s", _("| loop [on|off] . . . . . . . . . toggle playlist loop"));
    msg_rc("%s", _("| random [on|off] . . . . . . .  toggle random jumping"));
    msg_rc("%s", _("| clear . . . . . . . . . . . . . . clear the playlist"));
    msg_rc("%s", _("| status . . . . . . . . . . . current playlist status"));
    msg_rc("%s", _("| title [X]  . . . . . . set/get title in current item"));
    msg_rc("%s", _("| title_n  . . . . . . . .  next title in current item"));
    msg_rc("%s", _("| title_p  . . . . . .  previous title in current item"));
    msg_rc("%s", _("| chapter [X]  . . . . set/get chapter in current item"));
    msg_rc("%s", _("| chapter_n  . . . . . .  next chapter in current item"));
    msg_rc("%s", _("| chapter_p  . . . .  previous chapter in current item"));
    msg_rc(  "| ");
    msg_rc("%s", _("| seek X . . . seek in seconds, for instance `seek 12'"));
    msg_rc("%s", _("| pause  . . . . . . . . . . . . . . . .  toggle pause"));
    msg_rc("%s", _("| fastforward  . . . . . . . .  .  set to maximum rate"));
    msg_rc("%s", _("| rewind  . . . . . . . . . . . .  set to minimum rate"));
    msg_rc("%s", _("| faster . . . . . . . . . .  faster playing of stream"));
    msg_rc("%s", _("| slower . . . . . . . . . .  slower playing of stream"));
    msg_rc("%s", _("| normal . . . . . . . . . .  normal playing of stream"));
    msg_rc("%s", _("| frame. . . . . . . . . .  play frame by frame"));
    msg_rc("%s", _("| f [on|off] . . . . . . . . . . . . toggle fullscreen"));
    msg_rc("%s", _("| info . . . . .  information about the current stream"));
    msg_rc("%s", _("| stats  . . . . . . . .  show statistical information"));
    msg_rc("%s", _("| get_time . . seconds elapsed since stream's beginning"));
    msg_rc("%s", _("| is_playing . . . .  1 if a stream plays, 0 otherwise"));
    msg_rc("%s", _("| get_title . . . . .  the title of the current stream"));
    msg_rc("%s", _("| get_length . . . .  the length of the current stream"));
    msg_rc(  "| ");
    msg_rc("%s", _("| volume [X] . . . . . . . . . .  set/get audio volume"));
    msg_rc("%s", _("| volup [X]  . . . . . . .  raise audio volume X steps"));
    msg_rc("%s", _("| voldown [X]  . . . . . .  lower audio volume X steps"));
    msg_rc("%s", _("| adev [device]  . . . . . . . .  set/get audio device"));
    msg_rc("%s", _("| achan [X]. . . . . . . . . .  set/get audio channels"));
    msg_rc("%s", _("| atrack [X] . . . . . . . . . . . set/get audio track"));
    msg_rc("%s", _("| vtrack [X] . . . . . . . . . . . set/get video track"));
    msg_rc("%s", _("| vratio [X]  . . . . . . . set/get video aspect ratio"));
    msg_rc("%s", _("| vcrop [X]  . . . . . . . . . . .  set/get video crop"));
    msg_rc("%s", _("| vzoom [X]  . . . . . . . . . . .  set/get video zoom"));
    msg_rc("%s", _("| snapshot . . . . . . . . . . . . take video snapshot"));
    msg_rc("%s", _("| record [on|off] . . . . . . . . . . toggle recording"));
    msg_rc("%s", _("| strack [X] . . . . . . . . .  set/get subtitle track"));
    msg_rc("%s", _("| key [hotkey name] . . . . . .  simulate hotkey press"));
    msg_rc(  "| ");
    msg_rc("%s", _("| help . . . . . . . . . . . . . . . this help message"));
    msg_rc("%s", _("| logout . . . . . . .  exit (if in socket connection)"));
    msg_rc("%s", _("| quit . . . . . . . . . . . . . . . . . . .  quit vlc"));
    msg_rc(  "| ");
    msg_rc("%s", _("+----[ end of help ]"));
188
    (void) args; (void) count; (void) data;
189
    return 0;
190
191
}

192
static int Intf(struct cli_client *cl, const char *const *args, size_t count,
193
                void *data)
194
{
195
196
197
198
    intf_thread_t *intf = data;

    (void) cl;

199
    return intf_Create(vlc_object_instance(intf), count == 1 ? "" : args[1]);
200
201
}

202
static int Quit(struct cli_client *cl, const char *const *args, size_t count,
203
                void *data)
204
{
205
206
    intf_thread_t *intf = data;

207
    libvlc_Quit(vlc_object_instance(intf));
208
    (void) cl; (void) args; (void) count;
209
    return 0;
210
211
}

212
static int LogOut(struct cli_client *cl, const char *const *args, size_t count,
213
                  void *data)
214
215
{
    /* Close connection */
216
#ifndef _WIN32
217
218
219
220
221
222
223
224
225
226
    /* Force end-of-file on the file descriptor. */
    int fd = vlc_open("/dev/null", O_RDONLY);
    if (fd != -1)
    {   /* POSIX requires flushing before, and seeking after, replacing a
         * file descriptor underneath an I/O stream.
         */
        int fd2 = fileno(cl->stream);

        fflush(cl->stream);
        if (fd2 != 1)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
227
            vlc_dup2(fd, fd2);
228
229
230
231
        else
            dup2(fd, fd2);
        fseek(cl->stream, 0, SEEK_SET);
        vlc_close(fd);
232
    }
233
    (void) data;
234
#else
235
236
237
    intf_thread_t *intf = data;
    intf_sys_t *sys = intf->p_sys;

238
239
240
241
242
    if (sys->i_socket != -1)
    {
        net_Close(sys->i_socket);
        sys->i_socket = -1;
    }
243
#endif
244
    (void) args; (void) count;
245
    return 0;
246
247
}

248
static int KeyAction(struct cli_client *cl, const char *const *args, size_t n,
249
                     void *data)
250
{
251
    intf_thread_t *intf = data;
252
253
    vlc_object_t *vlc = VLC_OBJECT(vlc_object_instance(intf));

254
    if (n != 2)
255
256
257
        return VLC_EGENERIC; /* EINVAL */

    var_SetInteger(vlc, "key-action", vlc_actions_get_id(args[1]));
258
    (void) cl;
259
    return 0;
260
261
}

262
static const struct cli_handler cmds[] =
263
264
{
    { "longhelp", Help },
265
266
267
268
    { "h", Help },
    { "help", Help },
    { "H", Help },
    { "?", Help },
269
270
271
272
    { "logout", LogOut },
    { "quit", Quit },

    { "intf", Intf },
273
274
    { "key", KeyAction },
    { "hotkey", KeyAction },
275
276
};

277
static int Process(intf_thread_t *intf, struct cli_client *cl, const char *line)
278
{
279
    intf_sys_t *sys = intf->p_sys;
280
281
    /* Skip heading spaces */
    const char *cmd = line + strspn(line, " ");
282
    int ret;
283

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
284
    if (*cmd == '\0')
285
        return 0; /* Ignore empty line */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
286

287
#ifdef HAVE_WORDEXP
288
289
290
291
292
293
    wordexp_t we;
    int val = wordexp(cmd, &we, 0);

    if (val != 0)
    {
        if (val == WRDE_NOSPACE)
294
295
        {
            ret = VLC_ENOMEM;
296
error:      wordfree(&we);
297
298
299
300
        }
        else
            ret = VLC_EGENERIC;

301
        cli_printf(cl, N_("parse error"));
302
        return ret;
303
304
305
306
307
    }

    size_t count = we.we_wordc;
    const char **args = vlc_alloc(count, sizeof (*args));
    if (unlikely(args == NULL))
308
309
    {
        ret = VLC_ENOMEM;
310
        goto error;
311
    }
312
313
314
315

    for (size_t i = 0; i < we.we_wordc; i++)
        args[i] = we.we_wordv[i];
#else
316
317
318
    char *cmd_dup = strdup(cmd);
    if (unlikely(cmd_dup == NULL))
        return VLC_ENOMEM;
319
320
    /* Split psz_cmd at the first space and make sure that
     * psz_arg is valid */
321
    const char *args[] = { cmd_dup, NULL };
322
    size_t count = 1;
323
    char *arg = strchr(cmd_dup, ' ');
324
325
326
327
328

    if (arg != NULL)
    {
        *(arg++) = '\0';
        arg += strspn(arg, " ");
329
330

        if (*arg)
331
            args[count++] = arg;
332
    }
333
334
335
336
#endif

    if (count > 0)
    {
337
        const struct command **pp = tfind(&args[0], &sys->commands, cmdcmp);
338

339
340
341
        if (pp != NULL)
        {
            const struct command *c = *pp;;
342

343
            ret = c->handler.callback(cl, args, count, c->data);
344
345
346
        }
        else
        {
347
            cli_printf(cl, _("Unknown command `%s'. Type `help' for help."),
348
349
350
                      args[0]);
            ret = VLC_EGENERIC;
        }
351
    }
352

353
#ifdef HAVE_WORDEXP
354
355
    free(args);
    wordfree(&we);
356
357
#else
    free(cmd_dup);
358
#endif
359
    return ret;
360
361
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
362
#ifndef _WIN32
363
364
static ssize_t cli_writev(struct cli_client *cl,
                          const struct iovec *iov, unsigned iovlen)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
365
{
366
    ssize_t val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
367

368
369
370
371
372
373
374
375
    vlc_mutex_lock(&cl->output_lock);
    if (cl->fd != -1)
        val = vlc_writev(cl->fd, iov, iovlen);
    else
        errno = EPIPE, val = -1;
    vlc_mutex_unlock(&cl->output_lock);
    return val;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
376

377
378
379
380
static int cli_vprintf(struct cli_client *cl, const char *fmt, va_list args)
{
    char *msg;
    int len = vasprintf(&msg, fmt, args);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
381

382
383
    if (likely(len >= 0))
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
384
385
        struct iovec iov[2] = { { msg, len }, { (char *)"\n", 1 } };

386
387
        cli_writev(cl, iov, ARRAY_SIZE(iov));
        len++;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
388
389
        free(msg);
    }
390
    return len;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
391
392
}

393
int cli_printf(struct cli_client *cl, const char *fmt, ...)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
394
395
{
    va_list ap;
396
    int len;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
397
398

    va_start(ap, fmt);
399
    len = cli_vprintf(cl, fmt, ap);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
400
    va_end(ap);
401
    return len;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
402
403
}

404
static void msg_vprint(intf_thread_t *p_intf, const char *fmt, va_list args)
405
406
{
    intf_sys_t *sys = p_intf->p_sys;
407
    struct cli_client *cl;
408

409
410
411
412
    vlc_mutex_lock(&sys->clients_lock);
    vlc_list_foreach (cl, &sys->clients, node)
        cli_vprintf(cl, fmt, args);
    vlc_mutex_unlock(&sys->clients_lock);
413
414
415
}

void msg_print(intf_thread_t *intf, const char *fmt, ...)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
416
417
418
419
{
    va_list ap;

    va_start(ap, fmt);
420
    msg_vprint(intf, fmt, ap);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
421
422
423
    va_end(ap);
}

424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#ifdef __OS2__
static char *os2_fgets(char *buffer, int n, FILE *stream)
{
    if( stream == stdin )
    {
        /* stdin ? Then wait for a stroke before entering into fgets() for
         * cancellation. */
        KBDKEYINFO key;

        while( KbdPeek( &key, 0 ) || !( key.fbStatus & 0x40 ))
            vlc_tick_sleep( INTF_IDLE_SLEEP );
    }

    vlc_testcancel();

    return fgets(buffer, n, stream);
}

#define fgets(buffer, n, stream) os2_fgets(buffer, n, stream)
#endif

445
446
447
448
static void *cli_client_thread(void *data)
{
    struct cli_client *cl = data;
    intf_thread_t *intf = cl->intf;
449
    char cmd[MAX_LINE_LENGTH + 1];
450

451
452
    vlc_thread_set_name("vlc-cli-client");

453
    while (fgets(cmd, sizeof (cmd), cl->stream) != NULL)
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
    {
        int canc = vlc_savecancel();
        if (cmd[0] != '\0')
            cmd[strlen(cmd) - 1] = '\0'; /* remove trailing LF */
        Process(intf, cl, cmd);
        vlc_restorecancel(canc);
    }

    if (cl->stream == stdin)
    {
        int canc = vlc_savecancel();
        libvlc_Quit(vlc_object_instance(intf));
        vlc_restorecancel(canc);
    }

469
    atomic_store_explicit(&cl->zombie, true, memory_order_release);
470
471
472
    return NULL;
}

473
474
475
476
477
478
479
480
481
static struct cli_client *cli_client_new(intf_thread_t *intf, int fd,
                                         FILE *stream)
{
    struct cli_client *cl = malloc(sizeof (*cl));
    if (unlikely(cl == NULL))
        return NULL;

    cl->stream = stream;
    cl->fd = fd;
482
    atomic_init(&cl->zombie, false);
483
484
    cl->intf = intf;
    vlc_mutex_init(&cl->output_lock);
485

486
    if (vlc_clone(&cl->thread, cli_client_thread, cl))
487
488
489
490
    {
        free(cl);
        cl = NULL;
    }
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
    return cl;
}

/**
 * Creates a client from a file descriptor.
 *
 * This works with (pseudo-)terminals, stream sockets, serial ports, etc.
 */
static struct cli_client *cli_client_new_fd(intf_thread_t *intf, int fd)
{
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);

    FILE *stream = fdopen(fd, "r");
    if (stream == NULL)
    {
        vlc_close(fd);
        return NULL;
    }

    struct cli_client *cl = cli_client_new(intf, fd, stream);
    if (unlikely(cl == NULL))
        fclose(stream);
    return cl;
}

/**
 * Creates a client from the standard input and output.
 */
static struct cli_client *cli_client_new_std(intf_thread_t *intf)
{
    return cli_client_new(intf, 1, stdin);
}

static void cli_client_delete(struct cli_client *cl)
{
526
527
528
    vlc_cancel(cl->thread);
    vlc_join(cl->thread, NULL);

529
530
531
532
533
    if (cl->stream != stdin)
        fclose(cl->stream);
    free(cl);
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
534
535
536
537
538
static void *Run(void *data)
{
    intf_thread_t *intf = data;
    intf_sys_t *sys = intf->p_sys;

539
540
    vlc_thread_set_name("vlc-cli-server");

541
    assert(sys->pi_socket_listen != NULL);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
542

543
544
    for (;;)
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
545
546
547
548
549
550
551
552
553
554
555
556
557
        int fd = net_Accept(intf, sys->pi_socket_listen);
        if (fd == -1)
            continue;

        int canc = vlc_savecancel();
        struct cli_client *cl = cli_client_new_fd(intf, fd);

        if (cl != NULL)
        {
            vlc_mutex_lock(&sys->clients_lock);
            vlc_list_append(&cl->node, &sys->clients);
            vlc_mutex_unlock(&sys->clients_lock);
        }
558
559
560
561
562
563
564
565
566
567
568
569

        /* Reap any dead client */
        vlc_list_foreach (cl, &sys->clients, node)
            if (atomic_load_explicit(&cl->zombie, memory_order_acquire))
            {
                vlc_mutex_lock(&sys->clients_lock);
                vlc_list_remove(&cl->node);
                vlc_mutex_unlock(&sys->clients_lock);
                cli_client_delete(cl);
            }

        vlc_restorecancel(canc);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
570
571
572
573
    }
}

#else
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
static void msg_vprint(intf_thread_t *p_intf, const char *psz_fmt, va_list args)
{
    char fmt_eol[strlen (psz_fmt) + 3], *msg;
    int len;

    snprintf (fmt_eol, sizeof (fmt_eol), "%s\r\n", psz_fmt);
    len = vasprintf( &msg, fmt_eol, args );

    if( len < 0 )
        return;

    if( p_intf->p_sys->i_socket == -1 )
        utf8_fprintf( stdout, "%s", msg );
    else
        net_Write( p_intf, p_intf->p_sys->i_socket, msg, len );

    free( msg );
}

void msg_print(intf_thread_t *intf, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    msg_vprint(intf, fmt, ap);
    va_end(ap);
}

int cli_printf(struct cli_client *cl, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    msg_vprint(cl->intf, fmt, ap);
    va_end(ap);
    return VLC_SUCCESS;
}

612
#ifndef VLC_WINSTORE_APP
613
614
615
616
617
618
619
620
621
622
static bool ReadWin32( intf_thread_t *p_intf, unsigned char *p_buffer, int *pi_size )
{
    INPUT_RECORD input_record;
    DWORD i_dw;

    /* On Win32, select() only works on socket descriptors */
    while( WaitForSingleObjectEx( p_intf->p_sys->hConsoleIn,
                                MS_FROM_VLC_TICK(INTF_IDLE_SLEEP), TRUE ) == WAIT_OBJECT_0 )
    {
        // Prefer to fail early when there's not enough space to store a 4 bytes
Alexandre Janniaux's avatar
Alexandre Janniaux committed
623
        // UTF8 character. The function will be immediately called again and we won't
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
        // lose an input
        while( *pi_size < MAX_LINE_LENGTH - 4 &&
               ReadConsoleInput( p_intf->p_sys->hConsoleIn, &input_record, 1, &i_dw ) )
        {
            if( input_record.EventType != KEY_EVENT ||
                !input_record.Event.KeyEvent.bKeyDown ||
                input_record.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT ||
                input_record.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL||
                input_record.Event.KeyEvent.wVirtualKeyCode == VK_MENU ||
                input_record.Event.KeyEvent.wVirtualKeyCode == VK_CAPITAL )
            {
                /* nothing interesting */
                continue;
            }
            if( input_record.Event.KeyEvent.uChar.AsciiChar == '\n' ||
                input_record.Event.KeyEvent.uChar.AsciiChar == '\r' )
            {
                putc( '\n', stdout );
                break;
            }
            switch( input_record.Event.KeyEvent.uChar.AsciiChar )
            {
            case '\b':
                if ( *pi_size == 0 )
                    break;
                if ( *pi_size > 1 && (p_buffer[*pi_size - 1] & 0xC0) == 0x80 )
                {
                    // pi_size currently points to the character to be written, so
                    // we need to roll back from 2 bytes to start erasing the previous
                    // character
                    (*pi_size) -= 2;
                    unsigned int nbBytes = 1;
                    while( *pi_size > 0 && (p_buffer[*pi_size] & 0xC0) == 0x80 )
                    {
                        (*pi_size)--;
                        nbBytes++;
                    }
                    assert( clz( (unsigned char)~(p_buffer[*pi_size]) ) == nbBytes + 1 );
Lyndon Brown's avatar
Lyndon Brown committed
662
                    // The first utf8 byte will be overridden by a \0
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
                }
                else
                    (*pi_size)--;
                p_buffer[*pi_size] = 0;

                fputs( "\b \b", stdout );
                break;
            default:
            {
                WCHAR psz_winput[] = { input_record.Event.KeyEvent.uChar.UnicodeChar, L'\0' };
                char* psz_input = FromWide( psz_winput );
                int input_size = strlen(psz_input);
                if ( *pi_size + input_size > MAX_LINE_LENGTH )
                {
                    p_buffer[ *pi_size ] = 0;
                    return false;
                }
                strcpy( (char*)&p_buffer[*pi_size], psz_input );
                utf8_fprintf( stdout, "%s", psz_input );
                free(psz_input);
                *pi_size += input_size;
            }
            }
        }

        p_buffer[ *pi_size ] = 0;
        return true;
    }

    vlc_testcancel ();

    return false;
}
#endif

static bool ReadCommand(intf_thread_t *p_intf, char *p_buffer, int *pi_size)
{
700
#ifndef VLC_WINSTORE_APP
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
    if( p_intf->p_sys->i_socket == -1 && !p_intf->p_sys->b_quiet )
        return ReadWin32( p_intf, (unsigned char*)p_buffer, pi_size );
    else if( p_intf->p_sys->i_socket == -1 )
    {
        vlc_tick_sleep( INTF_IDLE_SLEEP );
        return false;
    }
#endif

    while( *pi_size < MAX_LINE_LENGTH )
    {
        if( p_intf->p_sys->i_socket == -1 )
        {
            if( read( 0/*STDIN_FILENO*/, p_buffer + *pi_size, 1 ) <= 0 )
            {   /* Standard input closed: exit */
                libvlc_Quit( vlc_object_instance(p_intf) );
                p_buffer[*pi_size] = 0;
                return true;
            }
        }
        else
        {   /* Connection closed */
            if( net_Read( p_intf, p_intf->p_sys->i_socket, p_buffer + *pi_size,
                          1 ) <= 0 )
            {
                net_Close( p_intf->p_sys->i_socket );
                p_intf->p_sys->i_socket = -1;
                p_buffer[*pi_size] = 0;
                return true;
            }
        }

        if( p_buffer[ *pi_size ] == '\r' || p_buffer[ *pi_size ] == '\n' )
            break;

        (*pi_size)++;
    }

    if( *pi_size == MAX_LINE_LENGTH ||
        p_buffer[ *pi_size ] == '\r' || p_buffer[ *pi_size ] == '\n' )
    {
        p_buffer[ *pi_size ] = 0;
        return true;
    }

    return false;
}

/*****************************************************************************
 * 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( void *data )
{
    intf_thread_t *p_intf = data;
    intf_sys_t *p_sys = p_intf->p_sys;

760
761
    vlc_thread_set_name("vlc-cli-server");

762
763
764
765
766
767
768
    char p_buffer[ MAX_LINE_LENGTH + 1 ];

    int  i_size = 0;
    int  canc = vlc_savecancel( );

    p_buffer[0] = 0;

769
#ifndef VLC_WINSTORE_APP
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
    /* Get the file descriptor of the console input */
    p_intf->p_sys->hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
    if( p_intf->p_sys->hConsoleIn == INVALID_HANDLE_VALUE )
    {
        msg_Err( p_intf, "couldn't find user input handle" );
        return NULL;
    }
#endif

    for( ;; )
    {
        bool b_complete;

        vlc_restorecancel( canc );

        if( p_sys->pi_socket_listen != NULL && p_sys->i_socket == -1 )
        {
            p_sys->i_socket =
                net_Accept( p_intf, p_sys->pi_socket_listen );
            if( p_sys->i_socket == -1 ) continue;
        }

        b_complete = ReadCommand( p_intf, p_buffer, &i_size );
        canc = vlc_savecancel( );

        /* Is there something to do? */
        if( !b_complete ) continue;

798
799
800
        struct cli_client cl = { p_intf };

        Process(p_intf, &cl, p_buffer);
801
802
803
804
805

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
806
    vlc_assert_unreachable();
807
}
808
809
810
811

#undef msg_rc
#define msg_rc(...)  msg_print(p_intf, __VA_ARGS__)
#include "../intromsg.h"
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
812
#endif
813
814
815
816
817
818
819

/*****************************************************************************
 * Activate: initialize and create stuff
 *****************************************************************************/
static int Activate( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;
820
    struct cli_client *cl;
821
    char *psz_host;
822
823
    int  *pi_socket = NULL;

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
    intf_sys_t *p_sys = vlc_obj_malloc(p_this, sizeof (*p_sys));
    if (unlikely(p_sys == NULL))
        return VLC_ENOMEM;

    p_intf->p_sys = p_sys;
    p_sys->commands = NULL;
#ifndef _WIN32
    vlc_mutex_init(&p_sys->clients_lock);
    vlc_list_init(&p_sys->clients);
#endif
    RegisterHandlers(p_intf, cmds, ARRAY_SIZE(cmds), p_intf);

    p_sys->player_cli = RegisterPlayer(p_intf);
    if (unlikely(p_sys->player_cli == NULL))
        goto error;

    RegisterPlaylist(p_intf);

842
#ifndef _WIN32
843
844
845
846
847
# ifndef HAVE_ISATTY
#  define isatty(fd) (fd, 0)
# endif
    /* Start CLI on the standard input if it is an actual console */
    if (isatty(fileno(stdin)) || var_InheritBool(p_intf, "rc-fake-tty"))
848
    {
849
850
851
852
        cl = cli_client_new_std(p_intf);
        if (cl == NULL)
            goto error;
        vlc_list_append(&cl->node, &p_sys->clients);
853
    }
854

855
#ifdef AF_LOCAL
856
    char *psz_unix_path = var_InheritString(p_intf, "rc-unix");
857
858
859
    if( psz_unix_path )
    {
        int i_socket;
860
        struct sockaddr_un addr = { .sun_family = AF_LOCAL };
861
        struct stat st;
862
863
864
865
866

        msg_Dbg( p_intf, "trying UNIX socket" );

        /* The given unix path cannot be longer than sun_path - 1 to take into
         * account the terminated null character. */
867
868
        size_t len = strlen(psz_unix_path);
        if (len >= sizeof (addr.sun_path))
869
870
        {
            msg_Err( p_intf, "rc-unix value is longer than expected" );
871
            goto error;
872
        }
873
        memcpy(addr.sun_path, psz_unix_path, len + 1);
874
        free(psz_unix_path);
875
876
877
878

        if( (i_socket = vlc_socket( PF_LOCAL, SOCK_STREAM, 0, false ) ) < 0 )
        {
            msg_Warn( p_intf, "can't open socket: %s", vlc_strerror_c(errno) );
879
            goto error;
880
881
        }

882
        if (vlc_stat(addr.sun_path, &st) == 0 && S_ISSOCK(st.st_mode))
883
        {
884
885
            msg_Dbg(p_intf, "unlinking old %s socket", addr.sun_path);
            unlink(addr.sun_path);
886
887
        }

888
889
        if (bind(i_socket, (struct sockaddr *)&addr, sizeof (addr))
         || listen(i_socket, 1))
890
891
892
893
        {
            msg_Warn (p_intf, "can't listen on socket: %s",
                      vlc_strerror_c(errno));
            net_Close( i_socket );
894
            goto error;
895
896
897
898
899
900
901
        }

        /* FIXME: we need a core function to merge listening sockets sets */
        pi_socket = calloc( 2, sizeof( int ) );
        if( pi_socket == NULL )
        {
            net_Close( i_socket );
902
            goto error;
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
        }
        pi_socket[0] = i_socket;
        pi_socket[1] = -1;
    }
#endif /* AF_LOCAL */
#endif /* !_WIN32 */

    if( ( pi_socket == NULL ) &&
        ( psz_host = var_InheritString( p_intf, "rc-host" ) ) != NULL )
    {
        vlc_url_t url;

        vlc_UrlParse( &url, psz_host );
        if( url.psz_host == NULL )
        {
            vlc_UrlClean( &url );
            char *psz_backward_compat_host;
            if( asprintf( &psz_backward_compat_host, "//%s", psz_host ) < 0 )
            {
                free( psz_host );
923
                goto error;
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
            }
            free( psz_host );
            psz_host = psz_backward_compat_host;
            vlc_UrlParse( &url, psz_host );
        }

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

        pi_socket = net_ListenTCP(p_this, url.psz_host, url.i_port);
        if( pi_socket == NULL )
        {
            msg_Warn( p_intf, "can't listen to %s port %i",
                      url.psz_host, url.i_port );
            vlc_UrlClean( &url );
            free( psz_host );
939
            goto error;
940
941
942
943
944
945
946
947
        }

        vlc_UrlClean( &url );
        free( psz_host );
    }

    p_sys->pi_socket_listen = pi_socket;

948
#ifndef _WIN32
949
    /* Line-buffered stdout */
950
    setvbuf(stdout, NULL, _IOLBF, 0);
951

952
    if (pi_socket != NULL)
953
954
#else
    p_sys->i_socket = -1;
955
#ifdef VLC_WINSTORE_APP
956
    p_sys->b_quiet = true;
957
#else
958
959
960
    p_sys->b_quiet = var_InheritBool( p_intf, "rc-quiet" );
    if( !p_sys->b_quiet )
        intf_consoleIntroMsg( p_intf );
961
#endif
962
#endif
963
    if( vlc_clone( &p_sys->thread, Run, p_intf ) )
964
965
        goto error;

966
967
    msg_print(p_intf, "%s",
             _("Remote control interface initialized. Type `help' for help."));
968
969
970
971

    return VLC_SUCCESS;

error:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
972
973
    if (p_sys->player_cli != NULL)
        DeregisterPlayer(p_intf, p_sys->player_cli);
974
975
976
977
#ifndef _WIN32
    vlc_list_foreach (cl, &p_sys->clients, node)
        cli_client_delete(cl);
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
978
    tdestroy(p_sys->commands, free);
979
980
981
982
983
984
985
986
987
988
989
990
    net_ListenClose( pi_socket );
    return VLC_EGENERIC;
}

/*****************************************************************************
 * Deactivate: uninitialize and cleanup
 *****************************************************************************/
static void Deactivate( vlc_object_t *p_this )
{
    intf_thread_t *p_intf = (intf_thread_t*)p_this;
    intf_sys_t *p_sys = p_intf->p_sys;

991
992
993
994
995
996
997
998
#ifndef _WIN32
    if (p_sys->pi_socket_listen != NULL)
#endif
    {
        vlc_cancel(p_sys->thread);
        vlc_join(p_sys->thread, NULL);
    }

999
    DeregisterPlayer(p_intf, p_sys->player_cli);
1000