Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Readline C: force return of certain text in readline()

Tags:

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.

like image 720
Zach Avatar asked Nov 06 '18 04:11

Zach


2 Answers

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.

like image 60
user1683793 Avatar answered Oct 21 '22 04:10

user1683793


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).

like image 32
rici Avatar answered Oct 21 '22 04:10

rici