5

I am trying to allow an interrupt to cause a certain value to be returned by readline. Here is a minimal example:

#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;
}

If I run this code and press Control-C, after I hit ENTER, sure enough it prints "Entry was: word". But I would like it to do so without the user needing to press ENTER. I basically just want to set entry to "word" when the interrupt signal is received, ending the readline function. I have been unable to find any documentation for how to just end the readline loop and return a certain value (I'm sure it's out there, but I haven't found it).

One thing I tried was adding

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

at the end of handler, but it didn't send the text to "entry" immediately.

Zach
  • 53
  • 4

4 Answers4

4

I think I have what you want running here.

#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;
}

The secret is the rl_done is only checked in the event loop. When you give it a null event hook function, it checks the rl_done and exits.

user1683793
  • 1,213
  • 13
  • 18
2

I don't believe there is any guarantee that you can call back into readline functions from an asynchronous signal handler. (The fact that it "seems to" work does not guarantee that it will not fail disastrously from time to time.) In general, you should do the absolute minimum in a signal handler, such as setting a flag to indicate that the signal has been received.

The readline library provides the variable rl_signal_event_hook, whose value is a function which will be called when a readline call is interrupted by a signal. It would probably be wise to put any code which modifies the readline state into such a function.

But it seems like the safest solution here would be to arrange for the Control-C character to be passed directly to readline without triggering a SIGINT. You could create a custom terminal setting based on the termios struct returned by tcgetattr which turns off the mapping of Ctrl-C to the INTR function, either by unsetting the ISIG flag (which will also turn off other interrupt characters, including Ctrl-Z) or by changing c_cc[VINTR] to _POSIX_VDISABLE (or to some other key).

If you are on Windows and you are not using Cygwin, which includes termios emulation, you can use native APIs to enable and disable Control-C handling.

Then you can use rl_bind_key to bind Ctrl-C (which is 3) to your own function. The function needs to match the rl_command_func_t typedef, which is int(*)(int, int). The function should return 0; in your simple case, you can probably ignore the arguments, but for the record the first one is a "count" (the numeric argument, entered by typing a number while holding down the Alt key), and the second one is the key itself.

You should probably make a copy of the termios structure before you modify it so that you can reset the terminal settings once you're done. Generally, you would want to install and restore the terminal settings around every call to readline (which is what readline itself does, as well).

rici
  • 234,347
  • 28
  • 237
  • 341
  • Not simple and not scalable. Termios? I'm on Windows, what do I do? :) What if you need to process other signals, not from the keyboard? What if your users prefers INTR to be mapped to Ctrl+Break? – n. m. could be an AI Nov 06 '18 at 11:49
  • @n.m.: does readline work on Windows? This question is about readline, not general signal handling. – rici Nov 06 '18 at 12:20
  • Yes there is a port for Windows with the bulk of the API working (not all of it). – n. m. could be an AI Nov 06 '18 at 14:42
  • My user doesn't use Windows and uses default keys to send interrupts like a good user :). So this answer works for me - thanks! – Zach Nov 06 '18 at 15:15
0

CTRL+C should pass a SIGINT, or similar interrupt signal to your program. There should be ways to override the handling, see here for example.

p0licat
  • 45
  • 1
  • 10
  • Yes (see my example in the question with "signal(SIGINT,handler);"), I am looking for specifically how to have readline() return a certain text on the interrupt. – Zach Nov 06 '18 at 05:02
0

You can achieve this by using the alternate interface, where your code is doing the event loop and calls libreadline functions each time a character needs to be read from the terminal. In the event loop you can handle all extra asynchronous events like signals (but not only that --- think a terminal chat application where messages arrive asynchronously from the network).

Here's how it could look like:

#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;
}

While this may seem more complicated that other methods, the callback interface is more appropriate for real-life programs that need to handle a variety of events.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243