Readline C: принудительный возврат определенного текста в readline () - PullRequest
0 голосов
/ 06 ноября 2018

Я пытаюсь разрешить прерыванию вызвать определенное значение в readline. Вот минимальный пример:

#include <stdio.h>
#include <signal.h>
#include <readline/readline.h>

void handler (int status)
{
   rl_replace_line("word",0);
   rl_redisplay();
   rl_done = 1;
}

int main (int argc, char** argv)
{
   char* entry;
   signal(SIGINT,handler);
   entry = readline("");

   printf("\nEntry was: %s\n", entry);
   return 0;
}

Если я запускаю этот код и нажимаю Control-C, после того, как я нажму ENTER, он обязательно напечатает «Entry was: word». Но я бы хотел, чтобы пользователь не нажимал клавишу ВВОД. Я просто хочу установить запись в «слово» при получении сигнала прерывания, заканчивая функцию readline. Мне не удалось найти какую-либо документацию о том, как просто завершить цикл readline и вернуть определенное значение (я уверен, что оно есть, но я его не нашел).

Одна вещь, которую я пытался добавить

 (*rl_named_function("accept-line"))(1,0);

в конце обработчика, но он не сразу отправил текст в "entry".

Ответы [ 4 ]

0 голосов
/ 06 ноября 2018

Этого можно добиться с помощью альтернативного интерфейса , где ваш код выполняет цикл обработки событий и вызывает функции libreadline каждый раз, когда символ должен быть прочитан с терминала. В цикле обработки событий вы можете обрабатывать все дополнительные асинхронные события, такие как сигналы (но не только это - например, приложение для терминального чата, куда сообщения поступают асинхронно из сети).

Вот как это может выглядеть:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <readline/readline.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

static volatile sig_atomic_t my_signal_flag = 0;
static int done_loop = 0;

void my_signal_handler (int status)
{
   my_signal_flag = 1;                       // set a volaatile sig-atomic_t var
                                             // and exit, just as the standard says
}

void my_rlhandler(char* line)                // all your app is in this function
                                             // called each time a line is ready
{
    if (line && strcmp(line, "quit"))
        printf("Entry was: %s\n", line);
    else
    {
       done_loop = 1;
       rl_set_prompt("");
    }
    free(line);
}

void my_event_loop()                         // event loop
                                             // handle all async events here
                                             // signals, network, threads, whatever
{
    rl_callback_handler_install("w00t>", my_rlhandler);

    do
    {
        signal(SIGINT, my_signal_handler);   // readline may override this
                                             // better do it here each time
        fd_set readfds;                      // prepare the select
        FD_ZERO(&readfds);
        FD_SET(0, &readfds);

        if (select(1, &readfds, NULL, NULL, NULL) > 0)
        {
            rl_callback_read_char();         // character ready, let readline eat it
        }
        else if (my_signal_flag )
        {
            my_signal_flag = 0;              // can get here only after a signal
            rl_replace_line("word",0);      
            rl_done = 1;
            rl_redisplay();
            rl_pending_input = '\n';         // not sure why it's needed
            rl_callback_read_char();
        }
    }
    while (!done_loop);

    rl_callback_handler_remove();
}


int main (int argc, char** argv)
{
    char* entry;
    signal(SIGINT, my_signal_handler);

    my_event_loop();

    return 0;
}

Хотя это может показаться более сложным, чем другие методы, интерфейс обратного вызова больше подходит для реальных программ, которые должны обрабатывать различные события.

0 голосов
/ 06 ноября 2018

Я думаю, у меня есть то, что вы хотите запустить здесь.

#include <stdio.h>
#include <signal.h>
#include <readline/readline.h>
int event(void) { }
void handler (int status)
{
   rl_replace_line("word",0);
   rl_redisplay();
   rl_done = 1;
}

int main (int argc, char** argv)
{
   char* entry;
   rl_event_hook=event;
   signal(SIGINT,handler);
   entry = readline("");

   printf("\nEntry was: %s\n", entry);
   return 0;
}

Секрет rl_done проверяется только в цикле событий. Когда вы даете ему нулевую функцию перехвата событий, она проверяет rl_done и завершает работу.

0 голосов
/ 06 ноября 2018

Я не верю, что есть какая-либо гарантия того, что вы можете вызывать функции readline из асинхронного обработчика сигнала. (Тот факт, что он «работает», не гарантирует, что время от времени он не будет иметь катастрофических сбоев.) В общем, вы должны сделать абсолютный минимум в обработчике сигнала, например, установить флаг, чтобы указать, что сигнал имеет был получен.

Библиотека readline предоставляет переменную rl_signal_event_hook, значением которой является функция, которая будет вызываться, когда вызов readline прерывается сигналом. Вероятно, было бы целесообразно поместить в такую ​​функцию любой код, который изменяет состояние readline.

Но, похоже, самым безопасным решением здесь было бы организовать передачу символа Control-C непосредственно на readline без запуска SIGINT. Вы можете создать пользовательскую настройку терминала на основе структуры termios, возвращаемой tcgetattr, которая отключает отображение Ctrl-C на функцию INTR, либо сняв флаг ISIG (который будет также отключите другие символы прерывания, включая Ctrl-Z) или изменив c_cc[VINTR] на _POSIX_VDISABLE (или какую-либо другую клавишу).

Если вы работаете в Windows и не используете Cygwin, которая включает эмуляцию termios, вы можете использовать нативные API для включения и отключения обработки Control-C.

Затем вы можете использовать rl_bind_key, чтобы связать Ctrl-C (то есть 3) с вашей собственной функцией. Функция должна соответствовать typedef rl_command_func_t, который равен int(*)(int, int). Функция должна вернуть 0; в вашем простом случае вы, вероятно, можете игнорировать аргументы, но для записи первым является «count» (числовой аргумент, вводимый путем ввода числа, удерживая нажатой клавишу Alt), а второй - сам ключ .

Вам, вероятно, следует сделать копию структуры termios, прежде чем изменять ее, чтобы после сброса можно было сбросить настройки терминала. Как правило, вы хотите устанавливать и восстанавливать настройки терминала при каждом вызове на readline (что также делает сам readline).

0 голосов
/ 06 ноября 2018

CTRL + C должен передавать сигнал SIGINT или аналогичный сигнал прерывания вашей программе. Должны быть способы переопределить обработку, см., Например, здесь .

...