3

(in Linux)

The methods I found all use signal .

Is there no other way? Is there anything I can do to make the terminal put it into the input buffer?

ipnah
  • 125
  • 7
  • 4
    Using `signal` is the correct way. Why can you not use it? It's not difficult. – Bib Nov 23 '21 at 11:51
  • 4
    No, there isn't. Also, see [What is the XY problem?](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – molbdnilo Nov 23 '21 at 11:51
  • @Bib Because I think it seems troublesome – ipnah Nov 23 '21 at 11:55
  • 1
    It's not troublesome. It's incredibly easy. It take just a few lines. Remember, you learn by doing. – Bib Nov 23 '21 at 11:56
  • There *are* alternatives – [`sigaction`](https://man7.org/linux/man-pages/man2/sigaction.2.html) and on linux actually [`eventfd`](https://man7.org/linux/man-pages/man2/eventfd.2.html) – none of these are as simple to use as `signal`, though. In any case, all three catch the signal that is sent to the process when you press Ctrl+C. – Aconcagua Nov 23 '21 at 11:58
  • signal is an interrupt. It has to be handled by using the relevant system calls. – kjohri Nov 23 '21 at 12:04
  • 2
    You could try putting the terminal in raw mode, as described here: https://stackoverflow.com/a/4217250/2485966 – Ruud Helderman Nov 23 '21 at 12:04
  • Linux [man pages](https://linux.die.net/man/2/signal) actually recommend to avoid `signal` in favour of `sigaction`. – Aconcagua Nov 23 '21 at 12:05
  • Be aware that catching signals is really easy – handling them comes with some pitfalls you need to be aware of, though. For instance you should only call [signal safe functions](https://man7.org/linux/man-pages/man7/signal-safety.7.html). Signals work pretty much like yet another thread, but they won't be preempted by those normal threads. This means, for instance, that you shouldn't ever wait on mutexes either, because if they are locked, there's no-one left to unlock them again, so deadlock... – Aconcagua Nov 23 '21 at 12:13
  • @Bib and all the others recommending catching the signal: Yes, it's easy, but it is *not* the "only way"! And if you really want to read control-C as an ordinary input character, it's completely the wrong way to do it. Turning off the interrupt character in the terminal driver, or putting the terminal driver in raw mode, are the right ways to do it. – Steve Summit Nov 23 '21 at 12:16
  • I have voted to reopen this question. The OP *specifically asked* for a way to read control-C without catching it as a signal. Someone closed the question as a duplicate, pointing at a former question explaining how to catch control-C as a signal. I have seen no indication that this is an XY program, that the OP is wrong, that the OP should be wanting to catch the signal instead. – Steve Summit Nov 23 '21 at 12:47

2 Answers2

5

In order to "read CTL+C as input" instead of having it generate a SIGINT it is necessary to use tcsetattr() to either clear cc_c[VINTR] or clear the ISIG flag as described in the manual page that I linked to, here.

You will need to use tcgetattr, first, to read the current terminal settings, adjust them accordingly, then tcsetattr to set them.

You should make sure that the terminal settings get reset to their original defaults when your program terminates, it is not a given that the shell will reset them to default, for you.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 2
    While this is true, it is not very likely to be of service to OP. In spite of asking to read control-C as a character, that is probably not going to do them much good. Especially by itself—once those flags are cleared, what character does the control-C key combination send, and, given other default settings, does it cause the buffer to be sent or does one need to make other changes to cause characters to be sent immediately? (Because, if it is not sent immediately, the user will press control-C and then expect some immediate program response, but the program will still be waiting for input.) – Eric Postpischil Nov 23 '21 at 12:35
  • 1
    Perhaps, @EricPostpischil, the OP wants to reimplement emacs, where ^C is a valid key command, or a part of one, and there's nothing wrong with reading it from standard input and acting on it accordingly. In any case, whether or not it's useful to read ^C will be evaluated on its own merits once ^C becomes readable as an ordinary character. Even if it proves to be not, the original question that was asked is a perfectly reasonable, valid question, with an answer that precisely addresses what was asked. – Sam Varshavchik Nov 23 '21 at 15:14
  • You don't need to do that. This willl make your program to handle in a different way when the input comes from a tty, while the ttty has an escape character for this purpose. Read my answer for a solution that requires no programming at all. – Luis Colorado Nov 24 '21 at 13:20
0

Nope, signal() is not the way. You should need to configure the tty driver to do raw input (no input char processing in the driver), so it passes all characters untouched. But this requires considering the tty input device as special and write special code to treat that case (this requires you to issue several ioctl system calls). But this is not recommended, for the reasons explained below.

Second, there's another, simpler way that doesn't require to use raw mode. You can escape the Ctrl-C character by prepending the tty escape character. In Linux and BSD system, this is normally tied to the Ctrl-V character, so pressing Ctrl-V + Ctrl-C allows you to input a single Ctrl-C char. I have just checked it with the hd command:

$ hd
^C
00000000 : 03 0a                                           : ..
00000002
$ _

Next question is, then, how to input a Ctrl-V? well, just double it! (but we are out of scope now, just continue reading)

The advantage of this approach is that it doesn't require programming in your program and will work the same way when reading from a file, pipe, fifo, socket, etc. (to which Ctrl-C has no special meaning, as the tty driver is out of scene) The only device that generates an interrupt when detecting Ctrl-C is the tty driver (more exactly, the controlling tty, in it's code generic part, so all ttys do this) and it also has a escape character (by the same reason, all ttys have it) to invalidate the special meaning of the next character.

You can check which character is the escape character with the stty(1) command, configured as the lnext entry. But I can almost ensure you that it will be Ctrl-V.

Note

This also applies to any other special character (like Ctrl-D, Ctrl-H, etc. that the terminal uses for special purposes)

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31