Usando pipes nomeados para enviar pressionamentos de tecla para um programa interativo

4

Eu tenho um programa, o mpg123, que possui um modo interativo que permite que os comandos do teclado do stdin façam coisas úteis, como o volume de controle. Eu estou tentando iniciar o mpg123 para que ele leia comandos de um pipe nomeado; Dessa forma, posso ter outros programas interagindo com ele.

Em um terminal eu faço o seguinte:

mkfifo pipe
tail -n1 -f pipe | mpg123 -vC /some/song.mp3

E em outro terminal eu faço o seguinte:

cat > pipe
-

Agora, eu estava esperando que o - fosse enviado para o programa mpg123 exatamente da mesma forma como se eu estivesse sentado naquele terminal batendo na tecla - , mas não é isso que acontece. Alguém pode me dizer o que estou fazendo incorretamente?

    
por virosa 29.04.2015 / 03:08

1 resposta

4

Parece que -C faz o mpg123 ler a partir do terminal, não do stdin. Eu vejo isso, no entanto, na minha versão da man page do mpg123:

-R, --remote
       Activate  generic  control interface.  mpg123 will then read and
       execute commands from stdin. Basic usage is ''load <filename> ''
       to  play some file and the obvious ''pause'', ''command.  ''jump
       <frame>'' will jump/seek to a given point (MPEG  frame  number).
       Issue ''help'' to get a full list of commands and syntax.

Isso pode ser o que você está procurando; tente mpg123 -vR <pipe . o interação em seu exemplo se tornaria algo como o seguinte (isso define o volume para 30%):

$ cat >pipe
load /some/song.mp3
volume 30

Mas então, o que -C faz que -R não resulta na modo anterior não consegue ler de stdin quando um pipe nomeado, em vez de um terminal está conectado?

Uma rápida olhada no código-fonte mpg123 indica que ele usa o instalações da termios para ler as teclas pressionadas do terminal, usando tcsetattr para colocá-lo no chamado "modo não canônico", onde keypresses são transmitidos para o leitor sem processamento adicional (em particular, sem esperar que uma linha completa tenha sido digitado):

struct termios tio = *pattern;
(...)

tio.c_lflag &= ~(ICANON|ECHO);
(...)

return tcsetattr(0,TCSANOW,&tio);

(Este é o mesmo que o exemplo de código GNU libc .)

Em seguida, em um loop, uma função get_key é chamada, que usa select para dizer se o descritor de arquivo 0 (stdin) tem dados disponíveis e, se então, lê um byte dele ( read(0,val,1) ). Mas isso ainda não explique por que um terminal funciona, mas um cano não funciona! A resposta está em o código de inicialização do terminal:

/* initialze terminal */
void term_init(void)
{
    debug("term_init");

    term_enable = 0;

    if(tcgetattr(0,&old_tio) < 0)
    {
        fprintf(stderr,"Can't get terminal attributes\n");
        return;
    }
    if(term_setup(&old_tio) < 0)
    {
        fprintf(stderr,"Can't set terminal attributes\n");
        return;
    }

    term_enable = 1;
}

Observe que, se tcgetattr ou term_setup falhar, term_enable está definido como 0. (A função para ler as chaves do terminal começa com if(!term_enable) return 0; .) E, de fato, quando stdin não é um terminal, tcgetattr falha, a mensagem de erro correspondente é impressa e a O código de manipulação de teclas é ignorado:

$ mpg123 -C ~/input.mp3 <pipe
(...)
Can't get terminal attributes

Isso explica por que a tentativa de enviar comandos pelo piping para mpg123 -C falha. Essa é uma escolha discutível pelos implementadores; presumivelmente simplesmente permitindo que tcgetattr / tcsetattr falhe (talvez usando um interruptor para esse propósito), em vez de desabilitar o manuseio de manipulação de código, sua tentativa teria funcionado.

    
por 29.04.2015 / 03:15

Tags