mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 04:13:08 +00:00
318 lines
7.7 KiB
C++
318 lines
7.7 KiB
C++
/** \file input_common.c
|
|
|
|
Implementation file for the low level input library
|
|
|
|
*/
|
|
#include "config.h"
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <wchar.h>
|
|
#include <stack>
|
|
#include <list>
|
|
#include <queue>
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
|
|
#include "fallback.h"
|
|
#include "util.h"
|
|
|
|
#include "common.h"
|
|
#include "wutil.h"
|
|
#include "input_common.h"
|
|
#include "env_universal_common.h"
|
|
#include "iothread.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 10
|
|
|
|
/** Characters that have been read and returned by the sequence matching code */
|
|
static std::stack<wint_t, std::vector<wint_t> > lookahead_list;
|
|
|
|
/* Queue of pairs of (function pointer, argument) to be invoked. Expected to be mostly empty. */
|
|
typedef std::pair<void (*)(void *), void *> callback_info_t;
|
|
typedef std::queue<callback_info_t, std::list<callback_info_t> > callback_queue_t;
|
|
static callback_queue_t callback_queue;
|
|
static void input_flush_callbacks(void);
|
|
|
|
static bool has_lookahead(void)
|
|
{
|
|
return ! lookahead_list.empty();
|
|
}
|
|
|
|
static wint_t lookahead_pop(void)
|
|
{
|
|
wint_t result = lookahead_list.top();
|
|
lookahead_list.pop();
|
|
return result;
|
|
}
|
|
|
|
static void lookahead_push(wint_t c)
|
|
{
|
|
lookahead_list.push(c);
|
|
}
|
|
|
|
static wint_t lookahead_top(void)
|
|
{
|
|
return lookahead_list.top();
|
|
}
|
|
|
|
/** Callback function for handling interrupts on reading */
|
|
static int (*interrupt_handler)();
|
|
|
|
void input_common_init(int (*ih)())
|
|
{
|
|
interrupt_handler = ih;
|
|
}
|
|
|
|
void input_common_destroy()
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
Internal function used by input_common_readch to read one byte from fd 0. This function should only be called by
|
|
input_common_readch().
|
|
*/
|
|
static wint_t readb()
|
|
{
|
|
/* do_loop must be set on every path through the loop; leaving it uninitialized allows the static analyzer to assist in catching mistakes. */
|
|
unsigned char arr[1];
|
|
bool do_loop;
|
|
|
|
do
|
|
{
|
|
/* Flush callbacks */
|
|
input_flush_callbacks();
|
|
|
|
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 = maxi(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 = maxi(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)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EINTR:
|
|
case EAGAIN:
|
|
{
|
|
if (interrupt_handler)
|
|
{
|
|
int res = interrupt_handler();
|
|
if (res)
|
|
{
|
|
return res;
|
|
}
|
|
if (has_lookahead())
|
|
{
|
|
return lookahead_pop();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
do_loop = true;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
/*
|
|
The terminal has been closed. Save and exit.
|
|
*/
|
|
return R_EOF;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Assume we loop unless we see a character in stdin */
|
|
do_loop = true;
|
|
|
|
/* 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)
|
|
{
|
|
env_universal_barrier();
|
|
}
|
|
|
|
if (ioport > 0 && FD_ISSET(ioport, &fdset))
|
|
{
|
|
iothread_service_completion();
|
|
if (has_lookahead())
|
|
{
|
|
return lookahead_pop();
|
|
}
|
|
}
|
|
|
|
if (FD_ISSET(STDIN_FILENO, &fdset))
|
|
{
|
|
if (read_blocked(0, arr, 1) != 1)
|
|
{
|
|
/* The teminal has been closed. Save and exit. */
|
|
return R_EOF;
|
|
}
|
|
|
|
/* We read from stdin, so don't loop */
|
|
do_loop = false;
|
|
}
|
|
}
|
|
}
|
|
while (do_loop);
|
|
|
|
return arr[0];
|
|
}
|
|
|
|
wchar_t input_common_readch(int timed)
|
|
{
|
|
if (! has_lookahead())
|
|
{
|
|
if (timed)
|
|
{
|
|
int count;
|
|
fd_set fds;
|
|
struct timeval tm=
|
|
{
|
|
0,
|
|
1000 * WAIT_ON_ESCAPE
|
|
}
|
|
;
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(0, &fds);
|
|
count = select(1, &fds, 0, 0, &tm);
|
|
|
|
switch (count)
|
|
{
|
|
case 0:
|
|
return WEOF;
|
|
|
|
case -1:
|
|
return WEOF;
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
wchar_t res;
|
|
mbstate_t state = {};
|
|
|
|
while (1)
|
|
{
|
|
wint_t b = readb();
|
|
char bb;
|
|
|
|
size_t sz;
|
|
|
|
if ((b >= R_NULL) && (b < R_NULL + 1000))
|
|
return b;
|
|
|
|
bb=b;
|
|
|
|
sz = mbrtowc(&res, &bb, 1, &state);
|
|
|
|
switch (sz)
|
|
{
|
|
case (size_t)(-1):
|
|
memset(&state, '\0', sizeof(state));
|
|
debug(2, L"Illegal input");
|
|
return R_NULL;
|
|
case (size_t)(-2):
|
|
break;
|
|
case 0:
|
|
return 0;
|
|
default:
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!timed)
|
|
{
|
|
while (has_lookahead() && lookahead_top() == WEOF)
|
|
lookahead_pop();
|
|
if (! has_lookahead())
|
|
return input_common_readch(0);
|
|
}
|
|
|
|
return lookahead_pop();
|
|
}
|
|
}
|
|
|
|
|
|
void input_common_unreadch(wint_t ch)
|
|
{
|
|
lookahead_push(ch);
|
|
}
|
|
|
|
void input_common_add_callback(void (*callback)(void *), void *arg)
|
|
{
|
|
ASSERT_IS_MAIN_THREAD();
|
|
callback_queue.push(callback_info_t(callback, arg));
|
|
}
|
|
|
|
static void input_flush_callbacks(void)
|
|
{
|
|
/* Nothing to do if nothing to do */
|
|
if (callback_queue.empty())
|
|
return;
|
|
|
|
/* We move the queue into a local variable, so that events queued up during a callback don't get fired until next round. */
|
|
callback_queue_t local_queue;
|
|
std::swap(local_queue, callback_queue);
|
|
while (! local_queue.empty())
|
|
{
|
|
const callback_info_t &callback = local_queue.front();
|
|
callback.first(callback.second); //cute
|
|
local_queue.pop();
|
|
}
|
|
}
|