// Implementation file for the low level input library. #include "config.h" #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "env.h" #include "env_universal_common.h" #include "fallback.h" // IWYU pragma: keep #include "global_safety.h" #include "input_common.h" #include "iothread.h" #include "wutil.h" /// Time in milliseconds to wait for another byte to be available for reading /// after \x1B is read before assuming that escape key was pressed, and not an /// escape sequence. #define WAIT_ON_ESCAPE_DEFAULT 30 static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT; /// Callback function for handling interrupts on reading. static interrupt_func_t interrupt_handler; void input_common_init(interrupt_func_t func) { interrupt_handler = func; } /// Internal function used by input_common_readch to read one byte from fd 0. This function should /// only be called by input_common_readch(). char_event_t input_event_queue_t::readb() { for (;;) { fd_set fdset; int fd_max = 0; int ioport = iothread_port(); int res; FD_ZERO(&fdset); FD_SET(0, &fdset); if (ioport > 0) { FD_SET(ioport, &fdset); fd_max = std::max(fd_max, ioport); } // Get our uvar notifier. universal_notifier_t ¬ifier = universal_notifier_t::default_notifier(); // Get the notification fd (possibly none). int notifier_fd = notifier.notification_fd(); if (notifier_fd > 0) { FD_SET(notifier_fd, &fdset); fd_max = std::max(fd_max, notifier_fd); } // Get its suggested delay (possibly none). struct timeval tv = {}; const unsigned long usecs_delay = notifier.usec_delay_between_polls(); if (usecs_delay > 0) { unsigned long usecs_per_sec = 1000000; tv.tv_sec = (int)(usecs_delay / usecs_per_sec); tv.tv_usec = (int)(usecs_delay % usecs_per_sec); } res = select(fd_max + 1, &fdset, 0, 0, usecs_delay > 0 ? &tv : NULL); if (res == -1) { if (errno == EINTR || errno == EAGAIN) { if (interrupt_handler) { if (auto interrupt_evt = interrupt_handler()) { return *interrupt_evt; } else if (auto mc = pop_discard_timeouts()) { return *mc; } } } else { // The terminal has been closed. return char_event_type_t::eof; } } else { // Check to see if we want a universal variable barrier. bool barrier_from_poll = notifier.poll(); bool barrier_from_readability = false; if (notifier_fd > 0 && FD_ISSET(notifier_fd, &fdset)) { barrier_from_readability = notifier.notification_fd_became_readable(notifier_fd); } if (barrier_from_poll || barrier_from_readability) { if (env_universal_barrier()) { // A variable change may have triggered a repaint, etc. if (auto mc = pop_discard_timeouts()) { return *mc; } } } if (FD_ISSET(STDIN_FILENO, &fdset)) { unsigned char arr[1]; if (read_blocked(0, arr, 1) != 1) { // The teminal has been closed. return char_event_type_t::eof; } // We read from stdin, so don't loop. return arr[0]; } // Check for iothread completions only if there is no data to be read from the stdin. // This gives priority to the foreground. if (ioport > 0 && FD_ISSET(ioport, &fdset)) { iothread_service_completion(); if (auto mc = pop_discard_timeouts()) { return *mc; } } } } } // Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being // set. void update_wait_on_escape_ms(const environment_t &vars) { auto escape_time_ms = vars.get(L"fish_escape_delay_ms"); if (escape_time_ms.missing_or_empty()) { wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT; return; } long tmp = fish_wcstol(escape_time_ms->as_string().c_str()); if (errno || tmp < 10 || tmp >= 5000) { std::fwprintf(stderr, L"ignoring fish_escape_delay_ms: value '%ls' " L"is not an integer or is < 10 or >= 5000 ms\n", escape_time_ms->as_string().c_str()); } else { wait_on_escape_ms = (int)tmp; } } char_event_t input_event_queue_t::pop() { auto result = queue_.front(); queue_.pop_front(); return result; } maybe_t input_event_queue_t::pop_discard_timeouts() { while (has_lookahead()) { auto evt = pop(); if (!evt.is_timeout()) { return evt; } } return none(); } char_event_t input_event_queue_t::readch() { ASSERT_IS_MAIN_THREAD(); if (auto mc = pop_discard_timeouts()) { return *mc; } wchar_t res; mbstate_t state = {}; while (1) { auto evt = readb(); if (!evt.is_char()) { return evt; } wint_t b = evt.get_char(); if (MB_CUR_MAX == 1) { return b; // single-byte locale, all values are legal } char bb = b; size_t sz = std::mbrtowc(&res, &bb, 1, &state); switch (sz) { case (size_t)(-1): { std::memset(&state, '\0', sizeof(state)); debug(2, L"Illegal input"); return char_event_type_t::check_exit; } case (size_t)(-2): { break; } case 0: { return 0; } default: { return res; } } } } char_event_t input_event_queue_t::readch_timed(bool dequeue_timeouts) { char_event_t result{char_event_type_t::timeout}; if (has_lookahead()) { result = pop(); } else { fd_set fds; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); struct timeval tm = {wait_on_escape_ms / 1000, 1000 * (wait_on_escape_ms % 1000)}; if (select(1, &fds, 0, 0, &tm) > 0) { result = readch(); } } // If we got a timeout, either through dequeuing or creating, ensure it stays on the queue. if (result.is_timeout()) { if (!dequeue_timeouts) queue_.push_front(char_event_type_t::timeout); return char_event_type_t::timeout; } return result; } void input_event_queue_t::push_back(char_event_t ch) { queue_.push_back(ch); } void input_event_queue_t::push_front(char_event_t ch) { queue_.push_front(ch); }