mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
more work on input thread
This commit is contained in:
parent
36b5975fa6
commit
107f6e01dc
95
src/lib/in.c
95
src/lib/in.c
@ -2,6 +2,13 @@
|
||||
#include "internal.h"
|
||||
#include "in.h"
|
||||
|
||||
static sig_atomic_t resize_seen;
|
||||
|
||||
// called for SIGWINCH and SIGCONT
|
||||
void sigwinch_handler(int signo){
|
||||
resize_seen = signo;
|
||||
}
|
||||
|
||||
// data collected from responses to our terminal queries.
|
||||
typedef struct termqueries {
|
||||
int celly, cellx; // cell geometry on startup
|
||||
@ -19,10 +26,10 @@ typedef struct cursorloc {
|
||||
|
||||
// local state for the input thread
|
||||
typedef struct inputctx {
|
||||
// we do not close any of these file descriptors; we don't own them!
|
||||
int termfd; // terminal fd: -1 with no controlling terminal, or
|
||||
// if stdin is a terminal, and on Windows Terminal.
|
||||
int stdinfd; // stdin fd. always 0.
|
||||
int stdinfd; // bulk in fd. always >= 0 (almost always 0). we do not
|
||||
// own this descriptor, and must not close() it.
|
||||
#ifdef __MINGW64__
|
||||
HANDLE stdinhandle; // handle to input terminal
|
||||
#endif
|
||||
@ -45,23 +52,27 @@ typedef struct inputctx {
|
||||
} inputctx;
|
||||
|
||||
static inline inputctx*
|
||||
create_inputctx(tinfo* ti){
|
||||
create_inputctx(tinfo* ti, FILE* infp){
|
||||
inputctx* i = malloc(sizeof(*i));
|
||||
if(i){
|
||||
i->csize = 64;
|
||||
if( (i->csrs = malloc(sizeof(*i->csrs) * i->csize)) ){
|
||||
i->isize = BUFSIZ;
|
||||
if( (i->inputs = malloc(sizeof(*i->inputs) * i->isize)) ){
|
||||
// FIXME set up infd/handle
|
||||
i->termfd = -1;
|
||||
i->stdinfd = -1;
|
||||
i->ti = ti;
|
||||
i->cvalid = i->ivalid = 0;
|
||||
i->cwrite = i->iwrite = 0;
|
||||
i->cread = i->iread = 0;
|
||||
i->ibufvalid = i->ibufwrite = 0;
|
||||
i->ibufread = 0;
|
||||
return i;
|
||||
if((i->stdinfd = fileno(infp)) >= 0){
|
||||
if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){
|
||||
i->termfd = tty_check(i->stdinfd) ? -1 : get_tty_fd(infp);
|
||||
i->ti = ti;
|
||||
i->cvalid = i->ivalid = 0;
|
||||
i->cwrite = i->iwrite = 0;
|
||||
i->cread = i->iread = 0;
|
||||
i->ibufvalid = i->ibufwrite = 0;
|
||||
i->ibufread = 0;
|
||||
logdebug("input descriptors: %d/%d\n", i->stdinfd, i->termfd);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
free(i->inputs);
|
||||
}
|
||||
free(i->csrs);
|
||||
}
|
||||
@ -73,7 +84,10 @@ create_inputctx(tinfo* ti){
|
||||
static inline void
|
||||
free_inputctx(inputctx* i){
|
||||
if(i){
|
||||
// we *do not* own any file descriptors or handles; don't close them!
|
||||
// we *do not* own stdinfd; don't close() it! we do own termfd.
|
||||
if(i->termfd >= 0){
|
||||
close(i->termfd);
|
||||
}
|
||||
// do not kill the thread here, either.
|
||||
free(i->inputs);
|
||||
free(i->csrs);
|
||||
@ -160,8 +174,8 @@ input_thread(void* vmarshall){
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int init_inputlayer(tinfo* ti){
|
||||
inputctx* ictx = create_inputctx(ti);
|
||||
int init_inputlayer(tinfo* ti, FILE* infp){
|
||||
inputctx* ictx = create_inputctx(ti, infp);
|
||||
if(ictx == NULL){
|
||||
return -1;
|
||||
}
|
||||
@ -180,9 +194,58 @@ int stop_inputlayer(tinfo* ti){
|
||||
if(ti->ictx){
|
||||
loginfo("tearing down input thread\n");
|
||||
ret |= cancel_and_join("input", ti->ictx->tid, NULL);
|
||||
ret |= set_fd_nonblocking(ti->ictx->stdinfd, ti->stdio_blocking_save, NULL);
|
||||
free_inputctx(ti->ictx);
|
||||
ti->ictx = NULL;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int inputready_fd(const inputctx* ictx){
|
||||
return ictx->stdinfd;
|
||||
}
|
||||
|
||||
// infp has already been set non-blocking
|
||||
uint32_t notcurses_get(notcurses* nc, const struct timespec* ts, ncinput* ni){
|
||||
/*
|
||||
uint32_t r = ncinputlayer_prestamp(&nc->tcache, ts, ni,
|
||||
nc->margin_l, nc->margin_t);
|
||||
if(r != (uint32_t)-1){
|
||||
uint64_t stamp = nc->tcache.input.input_events++; // need increment even if !ni
|
||||
if(ni){
|
||||
ni->seqnum = stamp;
|
||||
}
|
||||
++nc->stats.s.input_events;
|
||||
}
|
||||
return r;
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t notcurses_getc(notcurses* nc, const struct timespec* ts,
|
||||
const void* unused, ncinput* ni){
|
||||
(void)unused; // FIXME remove for abi3
|
||||
return notcurses_get(nc, ts, ni);
|
||||
}
|
||||
|
||||
uint32_t ncdirect_get(struct ncdirect* n, const struct timespec* ts, ncinput* ni){
|
||||
/*
|
||||
uint32_t r = ncinputlayer_prestamp(&n->tcache, ts, ni, 0, 0);
|
||||
if(r != (uint32_t)-1){
|
||||
uint64_t stamp = n->tcache.input.input_events++; // need increment even if !ni
|
||||
if(ni){
|
||||
ni->seqnum = stamp;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t ncdirect_getc(ncdirect* nc, const struct timespec *ts,
|
||||
const void* unused, ncinput* ni){
|
||||
(void)unused; // FIXME remove for abi3
|
||||
return ncdirect_get(nc, ts, ni);
|
||||
}
|
||||
|
||||
|
1887
src/lib/input.c
1887
src/lib/input.c
File diff suppressed because it is too large
Load Diff
@ -375,7 +375,6 @@ typedef struct notcurses {
|
||||
int loglevel;
|
||||
ncpalette palette; // 256-indexed palette can be used instead of/with RGB
|
||||
bool palette_damage[NCPALETTESIZE];
|
||||
unsigned stdio_blocking_save; // was stdio blocking at entry? restore on stop.
|
||||
uint64_t flags; // copied from notcurses_options
|
||||
} notcurses;
|
||||
|
||||
|
@ -1153,9 +1153,6 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
|
||||
ncplane_cursor_move_yx(ret->stdplane, ret->rstate.logendy, ret->rstate.logendx);
|
||||
}
|
||||
}
|
||||
if(set_fd_nonblocking(ret->tcache.input.infd, 1, &ret->stdio_blocking_save)){
|
||||
goto err;
|
||||
}
|
||||
if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){
|
||||
// perform an explicit clear since the alternate screen was requested
|
||||
// (smcup *might* clear, but who knows? and it might not have been
|
||||
@ -1250,7 +1247,6 @@ int notcurses_stop(notcurses* nc){
|
||||
goto_location(nc, &nc->rstate.f, targy, 0);
|
||||
fbuf_finalize(&nc->rstate.f, stdout);
|
||||
}
|
||||
ret |= set_fd_nonblocking(nc->tcache.input.infd, nc->stdio_blocking_save, NULL);
|
||||
if(nc->stdplane){
|
||||
notcurses_drop_planes(nc);
|
||||
free_plane(nc->stdplane);
|
||||
@ -2685,11 +2681,11 @@ int notcurses_lex_margins(const char* op, notcurses_options* opts){
|
||||
}
|
||||
|
||||
int notcurses_inputready_fd(notcurses* n){
|
||||
return n->tcache.input.infd;
|
||||
return inputready_fd(n->tcache.ictx);
|
||||
}
|
||||
|
||||
int ncdirect_inputready_fd(ncdirect* n){
|
||||
return n->tcache.input.infd;
|
||||
return inputready_fd(n->tcache.ictx);
|
||||
}
|
||||
|
||||
// FIXME speed this up, PoC
|
||||
|
@ -163,7 +163,6 @@ match_termname(const char* termname, queried_terminals_e* qterm){
|
||||
|
||||
void free_terminfo_cache(tinfo* ti){
|
||||
stop_inputlayer(ti);
|
||||
ncinputlayer_stop(&ti->input);
|
||||
free(ti->termversion);
|
||||
free(ti->esctable);
|
||||
#ifdef __linux__
|
||||
@ -914,17 +913,18 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
|
||||
}
|
||||
unsigned appsync_advertised = 0;
|
||||
unsigned kittygraphs = 0;
|
||||
if(init_inputlayer(ti)){
|
||||
if(init_inputlayer(ti, stdin)){
|
||||
goto err;
|
||||
}
|
||||
/*
|
||||
if(ncinputlayer_init(ti, stdin, &ti->qterm, &appsync_advertised,
|
||||
cursor_y, cursor_x, stats, &kittygraphs)){
|
||||
goto err;
|
||||
}
|
||||
*/
|
||||
if(nocbreak){
|
||||
if(ti->ttyfd >= 0){
|
||||
if(tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved)){
|
||||
ncinputlayer_stop(&ti->input);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
@ -942,7 +942,6 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
|
||||
bool invertsixel = false;
|
||||
if(apply_term_heuristics(ti, tname, ti->qterm, &tablelen, &tableused,
|
||||
&invertsixel, nonewfonts)){
|
||||
ncinputlayer_stop(&ti->input);
|
||||
goto err;
|
||||
}
|
||||
build_supported_styles(ti);
|
||||
@ -992,48 +991,6 @@ char* termdesc_longterm(const tinfo* ti){
|
||||
return ret;
|
||||
}
|
||||
|
||||
// when we have input->ttyfd, everything's simple -- we're reading from a
|
||||
// different source than the user is, so we can just write the query, and block
|
||||
// on the response, easy peasy.
|
||||
// FIXME still, we ought reuse buffer, and pass on any excess reads...
|
||||
static int
|
||||
locate_cursor_simple(tinfo* ti, const char* u7, int* cursor_y, int* cursor_x){
|
||||
if(ti->qterm == TERMINAL_MSTERMINAL){
|
||||
return locate_cursor(ti, cursor_y, cursor_x);
|
||||
}
|
||||
char* buf = malloc(BUFSIZ);
|
||||
if(buf == NULL){
|
||||
return -1;
|
||||
}
|
||||
loginfo("sending cursor report request\n");
|
||||
if(tty_emit(u7, ti->ttyfd)){
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
ssize_t r;
|
||||
do{
|
||||
if((r = read(ti->input.infd, buf, BUFSIZ - 1)) > 0){
|
||||
buf[r] = '\0';
|
||||
if(sscanf(buf, "\e[%d;%dR", cursor_y, cursor_x) != 2){
|
||||
loginfo("not a cursor location report: %s\n", buf);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
--*cursor_y;
|
||||
--*cursor_x;
|
||||
break;
|
||||
}
|
||||
}while(errno == EAGAIN || errno == EWOULDBLOCK || errno == EBUSY || errno == EINTR);
|
||||
if(r < 0){
|
||||
logerror("error reading cursor location from %d (%s)\n", ti->input.infd, strerror(errno));
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
free(buf);
|
||||
loginfo("located cursor with %d: %d/%d\n", ti->ttyfd, *cursor_y, *cursor_x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// send a u7 request, and wait until we have a cursor report. if input's ttyfd
|
||||
// is valid, we can just camp there. otherwise, we need dance with potential
|
||||
// user input looking at infd.
|
||||
@ -1058,39 +1015,16 @@ int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){
|
||||
logwarn("No support in terminfo\n");
|
||||
return -1;
|
||||
}
|
||||
if(ti->ttyfd >= 0){
|
||||
return locate_cursor_simple(ti, u7, cursor_y, cursor_x);
|
||||
}
|
||||
int fd = ti->input.infd;
|
||||
if(fd < 0){
|
||||
if(ti->ttyfd < 0){
|
||||
logwarn("No valid path for cursor report\n");
|
||||
return -1;
|
||||
}
|
||||
bool emitted_u7 = false; // only want to send one max
|
||||
cursorreport* clr;
|
||||
loginfo("Acquiring input lock\n");
|
||||
pthread_mutex_lock(&ti->input.lock);
|
||||
while((clr = ti->input.creport_queue) == NULL){
|
||||
logdebug("No report yet\n");
|
||||
if(!emitted_u7){
|
||||
logdebug("Emitting u7\n");
|
||||
// FIXME i'd rather not do this while holding the lock =[
|
||||
if(tty_emit(u7, fd)){
|
||||
pthread_mutex_unlock(&ti->input.lock);
|
||||
return -1;
|
||||
}
|
||||
emitted_u7 = true;
|
||||
}
|
||||
// this can block. we must enter holding the input lock, and it will
|
||||
// return to us holding the input lock.
|
||||
ncinput_extract_clrs(ti);
|
||||
if( (clr = ti->input.creport_queue) ){
|
||||
break;
|
||||
}
|
||||
pthread_cond_wait(&ti->input.creport_cond, &ti->input.lock);
|
||||
int fd = ti->ttyfd;
|
||||
if(tty_emit(u7, fd)){
|
||||
return -1;
|
||||
}
|
||||
ti->input.creport_queue = clr->next;
|
||||
pthread_mutex_unlock(&ti->input.lock);
|
||||
// FIXME get that report
|
||||
/*
|
||||
loginfo("Got a report from %d %d/%d\n", fd, clr->y, clr->x);
|
||||
*cursor_y = clr->y;
|
||||
*cursor_x = clr->x;
|
||||
@ -1100,5 +1034,45 @@ int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){
|
||||
*cursor_x = tmp;
|
||||
}
|
||||
free(clr);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbreak_mode(tinfo* ti){
|
||||
#ifndef __MINGW64__
|
||||
int ttyfd = ti->ttyfd;
|
||||
if(ttyfd < 0){
|
||||
return 0;
|
||||
}
|
||||
// assume it's not a true terminal (e.g. we might be redirected to a file)
|
||||
struct termios modtermios;
|
||||
memcpy(&modtermios, ti->tpreserved, sizeof(modtermios));
|
||||
// see termios(3). disabling ECHO and ICANON means input will not be echoed
|
||||
// to the screen, input is made available without enter-based buffering, and
|
||||
// line editing is disabled. since we have not gone into raw mode, ctrl+c
|
||||
// etc. still have their typical effects. ICRNL maps return to 13 (Ctrl+M)
|
||||
// instead of 10 (Ctrl+J).
|
||||
modtermios.c_lflag &= (~ECHO & ~ICANON);
|
||||
modtermios.c_iflag &= ~ICRNL;
|
||||
if(tcsetattr(ttyfd, TCSANOW, &modtermios)){
|
||||
logerror("Error disabling echo / canonical on %d (%s)\n", ttyfd, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
// we don't yet have a way to take Cygwin/MSYS2 out of canonical mode. we'll
|
||||
// hit this stanza in MSYS2; allow the GetConsoleMode() to fail for now. this
|
||||
// means we'll need enter pressed after the query response, obviously an
|
||||
// unacceptable state of affairs...FIXME
|
||||
DWORD mode;
|
||||
if(!GetConsoleMode(ti->inhandle, &mode)){
|
||||
logerror("error acquiring input mode\n");
|
||||
return 0; // FIXME is it safe?
|
||||
}
|
||||
mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
|
||||
if(!SetConsoleMode(ti->inhandle, mode)){
|
||||
logerror("error setting input mode\n");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
@ -197,8 +197,8 @@ typedef struct tinfo {
|
||||
queried_terminals_e qterm; // detected terminal class
|
||||
// we heap-allocate this one (if we use it), as it's not fully defined on Windows
|
||||
struct termios *tpreserved;// terminal state upon entry
|
||||
ncinputlayer input; // input layer
|
||||
struct inputctx* ictx; // new input layer
|
||||
unsigned stdio_blocking_save; // was stdio blocking at entry? restore on stop.
|
||||
|
||||
// if we get a reply to our initial \e[18t cell geometry query, it will
|
||||
// replace these values. note that LINES/COLUMNS cannot be used to limit
|
||||
|
Loading…
x
Reference in New Issue
Block a user