mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-10 01:29:05 -04:00
more work on input thread
This commit is contained in:
parent
e54661f425
commit
79610b2efa
81
src/lib/in.c
81
src/lib/in.c
@ -2,6 +2,13 @@
|
|||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "in.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.
|
// data collected from responses to our terminal queries.
|
||||||
typedef struct termqueries {
|
typedef struct termqueries {
|
||||||
int celly, cellx; // cell geometry on startup
|
int celly, cellx; // cell geometry on startup
|
||||||
@ -19,10 +26,10 @@ typedef struct cursorloc {
|
|||||||
|
|
||||||
// local state for the input thread
|
// local state for the input thread
|
||||||
typedef struct inputctx {
|
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
|
int termfd; // terminal fd: -1 with no controlling terminal, or
|
||||||
// if stdin is a terminal, and on Windows Terminal.
|
// 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__
|
#ifdef __MINGW64__
|
||||||
HANDLE stdinhandle; // handle to input terminal
|
HANDLE stdinhandle; // handle to input terminal
|
||||||
#endif
|
#endif
|
||||||
@ -45,24 +52,28 @@ typedef struct inputctx {
|
|||||||
} inputctx;
|
} inputctx;
|
||||||
|
|
||||||
static inline inputctx*
|
static inline inputctx*
|
||||||
create_inputctx(tinfo* ti){
|
create_inputctx(tinfo* ti, FILE* infp){
|
||||||
inputctx* i = malloc(sizeof(*i));
|
inputctx* i = malloc(sizeof(*i));
|
||||||
if(i){
|
if(i){
|
||||||
i->csize = 64;
|
i->csize = 64;
|
||||||
if( (i->csrs = malloc(sizeof(*i->csrs) * i->csize)) ){
|
if( (i->csrs = malloc(sizeof(*i->csrs) * i->csize)) ){
|
||||||
i->isize = BUFSIZ;
|
i->isize = BUFSIZ;
|
||||||
if( (i->inputs = malloc(sizeof(*i->inputs) * i->isize)) ){
|
if( (i->inputs = malloc(sizeof(*i->inputs) * i->isize)) ){
|
||||||
// FIXME set up infd/handle
|
if((i->stdinfd = fileno(infp)) >= 0){
|
||||||
i->termfd = -1;
|
if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){
|
||||||
i->stdinfd = -1;
|
i->termfd = tty_check(i->stdinfd) ? -1 : get_tty_fd(infp);
|
||||||
i->ti = ti;
|
i->ti = ti;
|
||||||
i->cvalid = i->ivalid = 0;
|
i->cvalid = i->ivalid = 0;
|
||||||
i->cwrite = i->iwrite = 0;
|
i->cwrite = i->iwrite = 0;
|
||||||
i->cread = i->iread = 0;
|
i->cread = i->iread = 0;
|
||||||
i->ibufvalid = i->ibufwrite = 0;
|
i->ibufvalid = i->ibufwrite = 0;
|
||||||
i->ibufread = 0;
|
i->ibufread = 0;
|
||||||
|
logdebug("input descriptors: %d/%d\n", i->stdinfd, i->termfd);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
free(i->inputs);
|
||||||
|
}
|
||||||
free(i->csrs);
|
free(i->csrs);
|
||||||
}
|
}
|
||||||
free(i);
|
free(i);
|
||||||
@ -73,7 +84,10 @@ create_inputctx(tinfo* ti){
|
|||||||
static inline void
|
static inline void
|
||||||
free_inputctx(inputctx* i){
|
free_inputctx(inputctx* i){
|
||||||
if(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.
|
// do not kill the thread here, either.
|
||||||
free(i->inputs);
|
free(i->inputs);
|
||||||
free(i->csrs);
|
free(i->csrs);
|
||||||
@ -160,8 +174,8 @@ input_thread(void* vmarshall){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int init_inputlayer(tinfo* ti){
|
int init_inputlayer(tinfo* ti, FILE* infp){
|
||||||
inputctx* ictx = create_inputctx(ti);
|
inputctx* ictx = create_inputctx(ti, infp);
|
||||||
if(ictx == NULL){
|
if(ictx == NULL){
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -180,9 +194,58 @@ int stop_inputlayer(tinfo* ti){
|
|||||||
if(ti->ictx){
|
if(ti->ictx){
|
||||||
loginfo("tearing down input thread\n");
|
loginfo("tearing down input thread\n");
|
||||||
ret |= cancel_and_join("input", ti->ictx->tid, NULL);
|
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);
|
free_inputctx(ti->ictx);
|
||||||
ti->ictx = NULL;
|
ti->ictx = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
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;
|
int loglevel;
|
||||||
ncpalette palette; // 256-indexed palette can be used instead of/with RGB
|
ncpalette palette; // 256-indexed palette can be used instead of/with RGB
|
||||||
bool palette_damage[NCPALETTESIZE];
|
bool palette_damage[NCPALETTESIZE];
|
||||||
unsigned stdio_blocking_save; // was stdio blocking at entry? restore on stop.
|
|
||||||
uint64_t flags; // copied from notcurses_options
|
uint64_t flags; // copied from notcurses_options
|
||||||
} notcurses;
|
} notcurses;
|
||||||
|
|
||||||
|
@ -1150,9 +1150,6 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
|
|||||||
ncplane_cursor_move_yx(ret->stdplane, ret->rstate.logendy, ret->rstate.logendx);
|
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)){
|
if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){
|
||||||
// perform an explicit clear since the alternate screen was requested
|
// perform an explicit clear since the alternate screen was requested
|
||||||
// (smcup *might* clear, but who knows? and it might not have been
|
// (smcup *might* clear, but who knows? and it might not have been
|
||||||
@ -1247,7 +1244,6 @@ int notcurses_stop(notcurses* nc){
|
|||||||
goto_location(nc, &nc->rstate.f, targy, 0);
|
goto_location(nc, &nc->rstate.f, targy, 0);
|
||||||
fbuf_finalize(&nc->rstate.f, stdout);
|
fbuf_finalize(&nc->rstate.f, stdout);
|
||||||
}
|
}
|
||||||
ret |= set_fd_nonblocking(nc->tcache.input.infd, nc->stdio_blocking_save, NULL);
|
|
||||||
if(nc->stdplane){
|
if(nc->stdplane){
|
||||||
notcurses_drop_planes(nc);
|
notcurses_drop_planes(nc);
|
||||||
free_plane(nc->stdplane);
|
free_plane(nc->stdplane);
|
||||||
@ -2682,11 +2678,11 @@ int notcurses_lex_margins(const char* op, notcurses_options* opts){
|
|||||||
}
|
}
|
||||||
|
|
||||||
int notcurses_inputready_fd(notcurses* n){
|
int notcurses_inputready_fd(notcurses* n){
|
||||||
return n->tcache.input.infd;
|
return inputready_fd(n->tcache.ictx);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ncdirect_inputready_fd(ncdirect* n){
|
int ncdirect_inputready_fd(ncdirect* n){
|
||||||
return n->tcache.input.infd;
|
return inputready_fd(n->tcache.ictx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME speed this up, PoC
|
// FIXME speed this up, PoC
|
||||||
|
@ -168,7 +168,6 @@ match_termname(const char* termname, queried_terminals_e* qterm){
|
|||||||
|
|
||||||
void free_terminfo_cache(tinfo* ti){
|
void free_terminfo_cache(tinfo* ti){
|
||||||
stop_inputlayer(ti);
|
stop_inputlayer(ti);
|
||||||
ncinputlayer_stop(&ti->input);
|
|
||||||
free(ti->termversion);
|
free(ti->termversion);
|
||||||
free(ti->esctable);
|
free(ti->esctable);
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@ -919,17 +918,18 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
|
|||||||
}
|
}
|
||||||
unsigned appsync_advertised = 0;
|
unsigned appsync_advertised = 0;
|
||||||
unsigned kittygraphs = 0;
|
unsigned kittygraphs = 0;
|
||||||
if(init_inputlayer(ti)){
|
if(init_inputlayer(ti, stdin)){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
if(ncinputlayer_init(ti, stdin, &ti->qterm, &appsync_advertised,
|
if(ncinputlayer_init(ti, stdin, &ti->qterm, &appsync_advertised,
|
||||||
cursor_y, cursor_x, stats, &kittygraphs)){
|
cursor_y, cursor_x, stats, &kittygraphs)){
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
if(nocbreak){
|
if(nocbreak){
|
||||||
if(ti->ttyfd >= 0){
|
if(ti->ttyfd >= 0){
|
||||||
if(tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved)){
|
if(tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved)){
|
||||||
ncinputlayer_stop(&ti->input);
|
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -947,7 +947,6 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
|
|||||||
bool invertsixel = false;
|
bool invertsixel = false;
|
||||||
if(apply_term_heuristics(ti, tname, ti->qterm, &tablelen, &tableused,
|
if(apply_term_heuristics(ti, tname, ti->qterm, &tablelen, &tableused,
|
||||||
&invertsixel, nonewfonts)){
|
&invertsixel, nonewfonts)){
|
||||||
ncinputlayer_stop(&ti->input);
|
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
build_supported_styles(ti);
|
build_supported_styles(ti);
|
||||||
@ -997,48 +996,6 @@ char* termdesc_longterm(const tinfo* ti){
|
|||||||
return ret;
|
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
|
// 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
|
// is valid, we can just camp there. otherwise, we need dance with potential
|
||||||
// user input looking at infd.
|
// user input looking at infd.
|
||||||
@ -1063,39 +1020,16 @@ int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){
|
|||||||
logwarn("No support in terminfo\n");
|
logwarn("No support in terminfo\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if(ti->ttyfd >= 0){
|
if(ti->ttyfd < 0){
|
||||||
return locate_cursor_simple(ti, u7, cursor_y, cursor_x);
|
|
||||||
}
|
|
||||||
int fd = ti->input.infd;
|
|
||||||
if(fd < 0){
|
|
||||||
logwarn("No valid path for cursor report\n");
|
logwarn("No valid path for cursor report\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
bool emitted_u7 = false; // only want to send one max
|
int fd = ti->ttyfd;
|
||||||
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)){
|
if(tty_emit(u7, fd)){
|
||||||
pthread_mutex_unlock(&ti->input.lock);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
emitted_u7 = true;
|
// FIXME get that report
|
||||||
}
|
/*
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
ti->input.creport_queue = clr->next;
|
|
||||||
pthread_mutex_unlock(&ti->input.lock);
|
|
||||||
loginfo("Got a report from %d %d/%d\n", fd, clr->y, clr->x);
|
loginfo("Got a report from %d %d/%d\n", fd, clr->y, clr->x);
|
||||||
*cursor_y = clr->y;
|
*cursor_y = clr->y;
|
||||||
*cursor_x = clr->x;
|
*cursor_x = clr->x;
|
||||||
@ -1105,5 +1039,45 @@ int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){
|
|||||||
*cursor_x = tmp;
|
*cursor_x = tmp;
|
||||||
}
|
}
|
||||||
free(clr);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -199,8 +199,8 @@ typedef struct tinfo {
|
|||||||
queried_terminals_e qterm; // detected terminal class
|
queried_terminals_e qterm; // detected terminal class
|
||||||
// we heap-allocate this one (if we use it), as it's not fully defined on Windows
|
// we heap-allocate this one (if we use it), as it's not fully defined on Windows
|
||||||
struct termios *tpreserved;// terminal state upon entry
|
struct termios *tpreserved;// terminal state upon entry
|
||||||
ncinputlayer input; // input layer
|
|
||||||
struct inputctx* ictx; // new 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
|
// 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
|
// replace these values. note that LINES/COLUMNS cannot be used to limit
|
||||||
|
Loading…
x
Reference in New Issue
Block a user