readch_timed to block signals

readch_timed is called after reading the escape character \x1b. The escape
char may be a standalone key press or part of an escape sequence; fish
waits for a little bit (per the fish_escape_delay_ms variable) to see if
something else arrives, before treating it as standalone escape-key press.

It may happen that a signal is delivered while fish waits. Prior to this
change we would treat this signal as a "nothing was read" event, causing
escape to be wrongly treated as standalone.

Avoid this by using pselect() with a full signal mask, to ensure this call
completes.
This commit is contained in:
ridiculousfish
2022-01-31 21:02:25 -08:00
parent 8a4ed096ed
commit 43e5004b6e

View File

@@ -2,6 +2,7 @@
#include "config.h"
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <cstring>
@@ -10,6 +11,7 @@
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -223,9 +225,27 @@ maybe_t<char_event_t> input_event_queue_t::readch_timed() {
if (auto evt = try_pop()) {
return evt;
}
const uint64_t usec_per_msec = 1000;
uint64_t timeout_usec = static_cast<uint64_t>(wait_on_escape_ms) * usec_per_msec;
if (fd_readable_set_t::is_fd_readable(in_, timeout_usec)) {
// We are not prepared to handle a signal immediately; we only want to know if we get input on
// our fd before the timeout. Use pselect to block all signals; we will handle signals
// before the next call to getch().
sigset_t sigs;
sigfillset(&sigs);
// pselect expects timeouts in nanoseconds.
const uint64_t nsec_per_msec = 1000 * 1000;
const uint64_t nsec_per_sec = nsec_per_msec * 1000;
const uint64_t wait_nsec = wait_on_escape_ms * nsec_per_msec;
struct timespec timeout;
timeout.tv_sec = (wait_nsec) / nsec_per_sec;
timeout.tv_nsec = (wait_nsec) % nsec_per_sec;
// We have one fd of interest.
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(in_, &fdset);
int res = pselect(in_ + 1, &fdset, nullptr, nullptr, &timeout, &sigs);
if (res > 0) {
return readch();
}
return none();