pull/2173/head
nick black 3 years ago
commit e4a50f0143

@ -496,6 +496,7 @@ foreach(f ${POCSRCS})
get_filename_component(fe "${f}" NAME_WE)
add_executable(${fe} ${f})
target_include_directories(${fe}
BEFORE
PRIVATE include src "${TERMINFO_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/include"
)
@ -511,6 +512,7 @@ foreach(f ${POCPPSRCS})
get_filename_component(fe "${f}" NAME_WE)
add_executable(${fe} ${f})
target_include_directories(${fe}
BEFORE
PRIVATE include src "${TERMINFO_INCLUDE_DIRS}"
"${PROJECT_BINARY_DIR}/include"
)

@ -4,6 +4,14 @@ rearrangements of Notcurses.
* 2.4.2 (not yet released)
* You can now set a resize callback on the standard plane.
* Added `notcurses_getvec()`, providing batched input.
* Added `NCOPTION_DRAIN_INPUT`. Notcurses now launches a thread to process
input, so that it can respond to terminal messages with minimal latency.
Input read from `stdin` intended for the client is buffered until
retrieved. If your client never intends to read this input, provide this
flag to eliminate unnecessary processing, and ensure Notcurses can always
retrieve terminal messages (if buffers are full, Notcurses cannot
continue reading). Likewise added `NCDIRECT_OPTION_DRAIN_INPUT`.
* Removed a bunch of deprecated `static inline` functions from the headers.
* 2.4.1 (2021-09-12)
* `notcurses_check_pixel_support()` still returns 0 if there is no support

@ -248,6 +248,25 @@ If things break or seem otherwise lackluster, **please** consult the
more importantly, it will link against minimal Notcurses installations.
</details>
<details>
<summary>Does it work with hardware terminals?</summary>
With the correct `TERM` value, many hardware terminals are supported. The VT100
is sadly unsupported due to its extensive need for delays. In general, if the
terminfo database entry indicates mandatory delays, Notcurses will not currently
support that terminal properly. It's known that Notcurses can drive the VT320
and VT340, including Sixel graphics on the latter.
</details>
<details>
<summary>What happens if I try blitting bitmap graphics on a terminal which
doesn't support them?</summary>
Notcurses will not make use of bitmap protocols unless the terminal positively
indicates support for them, even if `NCBLIT_PIXEL` has been requested. Likewise,
sextants (`NCBLIT_3x2`) won't be used without Unicode 13 support, etc.
`ncvisual_render()` will use the best blitter available, unless
`NCVISUAL_OPTION_NODEGRADE` is provided (in which case it will fail).
</details>
<details>
<summary>Notcurses looks like absolute crap in <code>screen</code>.</summary>
<code>screen</code> doesn't support RGB colors (at least as of 4.08.00);

@ -95,7 +95,7 @@ typedef enum {
// to do this, pass NCOPTION_NO_CLEAR_BITMAPS. Note that they might still
// get cleared even if this is set, and they might not get cleared even if
// this is not set. It's a tough world out there.
#define NCOPTION_NO_CLEAR_BITMAPS 0x0002ull
#define NCOPTION_NO_CLEAR_BITMAPS 0x0002
// We typically install a signal handler for SIGWINCH that generates a resize
// event in the notcurses_get() queue. Set to inhibit this handler.
@ -110,7 +110,7 @@ typedef enum {
// at context creation time. Together with NCOPTION_NO_ALTERNATE_SCREEN and a
// scrolling standard plane, this facilitates easy scrolling-style programs in
// rendered mode.
#define NCOPTION_PRESERVE_CURSOR 0x0010ull
#define NCOPTION_PRESERVE_CURSOR 0x0010
// Notcurses typically prints version info in notcurses_init() and performance
// info in notcurses_stop(). This inhibits that output.
@ -120,6 +120,17 @@ typedef enum {
// of the "alternate screen". This flag inhibits use of smcup/rmcup.
#define NCOPTION_NO_ALTERNATE_SCREEN 0x0040
// Do not modify the font. Notcurses might attempt to change the font slightly,
// to support certain glyphs (especially on the Linux console). If this is set,
// no such modifications will be made. Note that font changes will not affect
// anything but the virtual console/terminal in which Notcurses is running.
#define NCOPTION_NO_FONT_CHANGES 0x0080
// Input may be freely dropped. This ought be provided when the program does not
// intend to handle input. Otherwise, input can accumulate in internal buffers,
// eventually preventing Notcurses from processing terminal messages.
#define NCOPTION_DRAIN_INPUT 0x0100
// Configuration for notcurses_init().
typedef struct notcurses_options {
// The name of the terminfo database entry describing this terminal. If NULL,
@ -378,11 +389,23 @@ struct ncdirect* ncdirect_core_init(const char* termtype, FILE* fp, uint64_t fla
// echo and line buffering are turned off.
#define NCDIRECT_OPTION_INHIBIT_CBREAK 0x0002ull
// Input may be freely dropped. This ought be provided when the program does not
// intend to handle input. Otherwise, input can accumulate in internal buffers,
// eventually preventing Notcurses from processing terminal messages.
#define NCDIRECT_OPTION_DRAIN_INPUT 0x0004ull
// We typically install a signal handler for SIG{INT, SEGV, ABRT, QUIT} that
// restores the screen, and then calls the old signal handler. Set to inhibit
// registration of these signal handlers. Chosen to match fullscreen mode.
#define NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS 0x0008ull
// Enable logging (to stderr) at the NCLOGLEVEL_WARNING level.
#define NCDIRECT_OPTION_VERBOSE 0x0010ull
// Enable logging (to stderr) at the NCLOGLEVEL_TRACE level. This will enable
// all diagnostics, a superset of NCDIRECT_OPTION_VERBOSE (which this implies).
#define NCDIRECT_OPTION_VERY_VERBOSE 0x0020ull
// Release 'nc' and any associated resources. 0 on success, non-0 on failure.
int ncdirect_stop(struct ncdirect* nc);
```
@ -671,7 +694,12 @@ typedef struct ncinput {
// event is processed, the return value is the 'id' field from that event.
// 'ni' may be NULL.
uint32_t notcurses_get(struct notcurses* n, const struct timespec* ts,
ncinput* ni)
ncinput* ni);
// Acquire up to 'vcount' ncinputs at the vector 'ni'. The number read will be
// returned, or -1 on error without any reads, 0 on timeout.
int notcurses_getvec(struct notcurses* n, const struct timespec* ts,
ncinput* ni, int vcount);
// 'ni' may be NULL if the caller is uninterested in event details. If no event
// is ready, returns 0.

@ -169,6 +169,11 @@ The following flags are defined:
will place the terminal into cbreak mode (i.e. disabling echo and line
buffering; see **tcgetattr(3)**).
* **NCDIRECT_OPTION_DRAIN_INPUT**: Standard input may be freely discarded. If
you do not intend to process input, pass this flag. Otherwise, input can
buffer up, eventually preventing Notcurses from processing terminal
messages. It will furthermore avoid wasting time processing useless input.
* **NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS**: A signal handler will usually be installed
for **SIGABRT**, **SIGFPE**, **SIGILL**, **SIGINT**, **SIGQUIT**,
**SIGSEGV**, and **SIGTERM**, cleaning up the terminal on such exceptions.

@ -19,6 +19,7 @@ notcurses_init - initialize a notcurses instance
#define NCOPTION_SUPPRESS_BANNERS 0x0020ull
#define NCOPTION_NO_ALTERNATE_SCREEN 0x0040ull
#define NCOPTION_NO_FONT_CHANGES 0x0080ull
#define NCOPTION_DRAIN_INPUT 0x0100ull
typedef enum {
NCLOGLEVEL_SILENT, // print nothing once fullscreen service begins
@ -139,6 +140,11 @@ zero. The following flags are defined:
* **NCOPTION_NO_FONT_CHANGES**: Do not touch the font. Notcurses might
otherwise attempt to extend the font, especially in the Linux console.
* **NCOPTION_DRAIN_INPUT**: Standard input may be freely discarded. If you do not
intend to process input, pass this flag. Otherwise, input can buffer up, and
eventually prevent Notcurses from processing messages from the terminal. It
will furthermore avoid wasting time processing useless input.
## Fatal signals
It is important to reset the terminal before exiting, whether terminating due

@ -31,6 +31,8 @@ typedef struct ncinput {
**uint32_t notcurses_get(struct notcurses* ***n***, const struct timespec* ***ts***, ncinput* ***ni***);**
**int notcurses_getvec(struct notcurses* ***n***, const struct timespec* ***ts***, ncinput* ***ni***, int vcount);**
**uint32_t notcurses_getc_nblock(struct notcurses* ***n***, ncinput* ***ni***);**
**uint32_t notcurses_getc_blocking(struct notcurses* ***n***, ncinput* ***ni***);**
@ -65,9 +67,8 @@ to fill whenever it reads.
**notcurses_get** allows a **struct timespec** to be specified as a timeout.
If **ts** is **NULL**, **notcurses_get** will block until it reads input, or
is interrupted by a signal. If its values are zeroes, there will be no blocking.
Otherwise, **ts** specifies a minimum time to wait for input before giving up.
On timeout, 0 is returned. Signals in **sigmask** will be masked and blocked in
the same manner as a call to **ppoll(2)**. **sigmask** may be **NULL**. Event
Otherwise, **ts** specifies an absolute deadline (using the same source and
timezone as **gettimeofday(2)**). On timeout, 0 is returned. Event
details will be reported in **ni**, unless **ni** is NULL.
**notcurses_inputready_fd** provides a file descriptor suitable for use with
@ -128,6 +129,10 @@ temporary one (especially e.g. **EINTR**), **notcurses_get** probably cannot
be usefully called forthwith. On a timeout, 0 is returned. Otherwise, the
UCS-32 value of a Unicode codepoint, or a synthesized event, is returned.
If an error is encountered before **notcurses_getvec** has read any input,
it will return -1. If it times out before reading any input, it will return
0. Otherwise, it returns the number of **ncinput** objects written back.
**notcurses_mouse_enable** returns 0 on success, and non-zero on failure, as
does **notcurses_mouse_disable**.
@ -175,6 +180,7 @@ are resolved.
# SEE ALSO
**gettimeofday(2)**,
**poll(2)**,
**notcurses(3)**,
**notcurses_refresh(3)**,

@ -24,6 +24,11 @@ extern "C" {
// echo and input's line buffering are turned off.
#define NCDIRECT_OPTION_INHIBIT_CBREAK 0x0002ull
// Input may be freely dropped. This ought be provided when the program does not
// intend to handle input. Otherwise, input can accumulate in internal buffers,
// eventually preventing Notcurses from processing terminal messages.
#define NCDIRECT_OPTION_DRAIN_INPUT 0x0004ull
// We typically install a signal handler for SIG{INT, SEGV, ABRT, QUIT} that
// restores the screen, and then calls the old signal handler. Set to inhibit
// registration of these signal handlers. Chosen to match fullscreen mode.
@ -489,11 +494,6 @@ ncdirect_canbraille(const struct ncdirect* nc){
API bool ncdirect_canget_cursor(const struct ncdirect* nc)
__attribute__ ((nonnull (1)));
// Deprecated, to be removed for ABI3. Use ncdirect_get() in new code.
API uint32_t ncdirect_getc(struct ncdirect* n, const struct timespec* ts,
const void* unused, ncinput* ni)
__attribute__ ((deprecated)) __attribute__ ((nonnull (1)));
#undef ALLOC
#undef API

@ -48,6 +48,7 @@ struct nctablet; // grouped item within an ncreel
struct ncreel; // hierarchical block-based data browser
struct nctab; // grouped item within an nctabbed
struct nctabbed; // widget with one tab visible at a time
struct ncdirect; // direct mode context
// we never blit full blocks, but instead spaces (more efficient) with the
// background set to the desired foreground. these need be kept in the same
@ -900,6 +901,11 @@ typedef enum {
// anything but the virtual console/terminal in which Notcurses is running.
#define NCOPTION_NO_FONT_CHANGES 0x0080ull
// Input may be freely dropped. This ought be provided when the program does not
// intend to handle input. Otherwise, input can accumulate in internal buffers,
// eventually preventing Notcurses from processing terminal messages.
#define NCOPTION_DRAIN_INPUT 0x0100ull
// "CLI mode" is just NCOPTION_NO_CLEAR_BITMAPS | NCOPTION_NO_ALTERNATE_SCREEN |
// NCOPTION_PRESERVE_CURSOR, plus enabling scrolling on the standard plane.
@ -1064,11 +1070,18 @@ ncinput_equal_p(const ncinput* n1, const ncinput* n2){
// timespec to bound blocking. Returns a single Unicode code point, or
// (uint32_t)-1 on error. 'sigmask' may be NULL. Returns 0 on a timeout. If an
// event is processed, the return value is the 'id' field from that event.
// 'ni' may be NULL.
// 'ni' may be NULL. 'ts' is an *absolute* time relative to gettimeofday()
// (see pthread_cond_timedwait(3)).
API uint32_t notcurses_get(struct notcurses* n, const struct timespec* ts,
ncinput* ni)
__attribute__ ((nonnull (1)));
// Acquire up to 'vcount' ncinputs at the vector 'ni'. The number read will be
// returned, or -1 on error without any reads, 0 on timeout.
API int notcurses_getvec(struct notcurses* n, const struct timespec* ts,
ncinput* ni, int vcount)
__attribute__ ((nonnull (1, 3)));
// Get a file descriptor suitable for input event poll()ing. When this
// descriptor becomes available, you can call notcurses_getc_nblock(),
// and input ought be ready. This file descriptor is *not* necessarily
@ -4019,17 +4032,19 @@ API int notcurses_render_to_file(struct notcurses* nc, FILE* fp)
API void notcurses_debug_caps(const struct notcurses* nc, FILE* debugfp)
__attribute__ ((deprecated)) __attribute__ ((nonnull (1, 2)));
// Backwards-compatibility wrapper; this will be removed for ABI3.
// Use notcurses_get() in new code.
API uint32_t notcurses_getc(struct notcurses* n, const struct timespec* ts,
const void* unused, ncinput* ni)
__attribute__ ((deprecated)) __attribute__ ((nonnull (1)));
__attribute__ ((deprecated)) API int nccell_width(const struct ncplane* n, const nccell* c);
API ALLOC char* ncvisual_subtitle(const struct ncvisual* ncv)
__attribute__ ((nonnull (1))) __attribute__ ((deprecated));
API uint32_t notcurses_getc(struct notcurses* nc, const struct timespec* ts,
const void* unused, ncinput* ni)
__attribute__ ((nonnull (1))) __attribute__ ((deprecated));
API uint32_t ncdirect_getc(struct ncdirect* nc, const struct timespec *ts,
const void* unused, ncinput* ni)
__attribute__ ((nonnull (1))) __attribute__ ((deprecated));
#undef ALLOC
#undef API

@ -210,7 +210,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
ncplane_printf_yx(n, 1, 4, " %03dx%03d (%d/%d) ", maxx, maxy, num + 1, total);
ncplane_off_styles(n, NCSTYLE_ITALIC);
ncplane_set_fg_rgb8(n, 224, 128, 224);
ncplane_putstr_yx(n, 3, 1, " 🎆🔥 unicode 13, resize awareness, 24b truecolor…🔥🎆 ");
ncplane_putstr_yx(n, 3, 1, " 🎆🔥 unicode 14, resize awareness, 24b truecolor…🔥🎆 ");
ncplane_set_fg_rgb8(n, 255, 255, 255);
return 0;
}

@ -664,7 +664,9 @@ ncneofetch(struct notcurses* nc){
fi.hostname = notcurses_hostname();
fi.username = notcurses_accountname();
fetch_env_vars(nc, &fi);
fetch_x_props(&fi);
if(kern != NCNEO_XNU && kern != NCNEO_WINDOWS){
fetch_x_props(&fi);
}
if(kern == NCNEO_LINUX){
fetch_cpu_info(&fi);
}else{
@ -695,7 +697,8 @@ int main(int argc, char** argv){
.flags = NCOPTION_SUPPRESS_BANNERS
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_NO_CLEAR_BITMAPS
| NCOPTION_PRESERVE_CURSOR,
| NCOPTION_PRESERVE_CURSOR
| NCOPTION_DRAIN_INPUT,
};
if(argc > 2){
usage(argv[0], stderr);

@ -429,7 +429,8 @@ int main(int argc, const char** argv){
notcurses_options nopts = {
.flags = NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_PRESERVE_CURSOR
| NCOPTION_NO_CLEAR_BITMAPS,
| NCOPTION_NO_CLEAR_BITMAPS
| NCOPTION_DRAIN_INPUT,
};
if(argc > 2){
usage(*argv, stderr, EXIT_FAILURE);

@ -218,13 +218,13 @@ int input_demo(ncpp::NotCurses* nc) {
}
struct ncplane_options nopts = {
.y = dimy - PLOTHEIGHT - 1,
.x = 0,
.x = NCALIGN_CENTER,
.rows = PLOTHEIGHT,
.cols = cols,
.cols = cols / 2,
.userptr = nullptr,
.name = "plot",
.resizecb = nullptr, // FIXME
.flags = 0,
.flags = NCPLANE_OPTION_HORALIGNED,
.margin_b = 0,
.margin_r = 0,
};
@ -356,6 +356,10 @@ int main(int argc, char** argv){
return EXIT_FAILURE;
}
notcurses_options nopts{};
nopts.margin_t = 2;
nopts.margin_l = 2;
nopts.margin_r = 2;
nopts.margin_b = 2;
nopts.loglevel = NCLOGLEVEL_ERROR;
if(argc > 2){
usage(argv[0], stderr);

@ -179,77 +179,16 @@ int ncdirect_cursor_disable(ncdirect* nc){
}
static int
cursor_yx_get(int ttyfd, const char* u7, int* y, int* x){
if(tty_emit(u7, ttyfd)){
cursor_yx_get(ncdirect* n, int ttyfd, const char* u7, int* y, int* x){
struct inputctx* ictx = n->tcache.ictx;
if(ncdirect_flush(n)){
return -1;
}
bool done = false;
enum { // what we expect now
CURSOR_ESC, // 27 (0x1b)
CURSOR_LSQUARE,
CURSOR_ROW, // delimited by a semicolon
CURSOR_COLUMN,
CURSOR_R,
} state = CURSOR_ESC;
int row = 0, column = 0;
int r;
char in;
do{
#ifndef __MINGW64__
while((r = read(ttyfd, &in, 1)) == 1){
#else
while((r = getc(stdin)) >= 0){ // FIXME
in = r;
#endif
bool valid = false;
switch(state){
case CURSOR_ESC: valid = (in == NCKEY_ESC); state = CURSOR_LSQUARE; break;
case CURSOR_LSQUARE: valid = (in == '['); state = CURSOR_ROW; break;
case CURSOR_ROW:
if(isdigit(in)){
row *= 10;
row += in - '0';
valid = true;
}else if(in == ';'){
state = CURSOR_COLUMN;
valid = true;
}
break;
case CURSOR_COLUMN:
if(isdigit(in)){
column *= 10;
column += in - '0';
valid = true;
}else if(in == 'R'){
state = CURSOR_R;
valid = true;
}
break;
case CURSOR_R: default: // logical error, whoops
break;
}
if(!valid){
logerror("Unexpected result (%c, %d) from terminal\n", in, in);
break;
}
if(state == CURSOR_R){
done = true;
break;
}
}
// need to loop 0 to handle slow terminals, see for instance screen =[
}while(!done && (r >= 0 || (errno == EINTR || errno == EAGAIN || errno == EBUSY)));
if(!done){
logerror("Error reading cursor location\n");
if(tty_emit(u7, ttyfd)){
return -1;
}
if(y){
*y = row;
}
if(x){
*x = column;
}
loginfo("cursor at y=%d x=%d\n", row, column);
get_cursor_location(ictx, y, x);
loginfo("cursor at y=%d x=%d\n", *y, *x);
return 0;
}
@ -266,7 +205,7 @@ int ncdirect_cursor_move_yx(ncdirect* n, int y, int x){
if(hpa){
return term_emit(tiparm(hpa, x), n->ttyfp, false);
}else if(n->tcache.ttyfd >= 0 && u7){
if(cursor_yx_get(n->tcache.ttyfd, u7, &y, NULL)){
if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){
return -1;
}
}else{
@ -276,7 +215,7 @@ int ncdirect_cursor_move_yx(ncdirect* n, int y, int x){
if(!vpa){
return term_emit(tiparm(vpa, y), n->ttyfp, false);
}else if(n->tcache.ttyfd >= 0 && u7){
if(cursor_yx_get(n->tcache.ttyfd, u7, NULL, &x)){
if(cursor_yx_get(n, n->tcache.ttyfd, u7, NULL, &x)){
return -1;
}
}else{
@ -295,6 +234,7 @@ int ncdirect_cursor_move_yx(ncdirect* n, int y, int x){
return -1; // we will not be moving the cursor today
}
/*
// an algorithm to detect inverted cursor reporting on terminals 2x2 or larger:
// * get initial cursor position / push cursor position
// * move right using cursor-independent routines
@ -408,6 +348,7 @@ detect_cursor_inversion_wrapper(ncdirect* n, const char* u7, int* y, int* x){
// of ttyfd, as needed by cursor interrogation.
return detect_cursor_inversion(n, u7, toty, totx, y, x);
}
*/
// no terminfo capability for this. dangerous--it involves writing controls to
// the terminal, and then reading a response. many things can distupt this
@ -444,24 +385,7 @@ int ncdirect_cursor_yx(ncdirect* n, int* y, int* x){
if(!x){
x = &xval;
}
if(!n->tcache.detected_cursor_inversion){
ret = detect_cursor_inversion_wrapper(n, u7, y, x);
}else{
ret = cursor_yx_get(n->tcache.ttyfd, u7, y, x);
}
if(ret == 0){
if(n->tcache.inverted_cursor){
int tmp = *y;
*y = *x;
*x = tmp;
}else{
// we use 0-based coordinates, but known terminals use 1-based
// coordinates. the only known exception is kmscon, which is
// incidentally the only one which inverts its response.
--*y;
--*x;
}
}
ret = cursor_yx_get(n, n->tcache.ttyfd, u7, y, x);
if(tcsetattr(n->tcache.ttyfd, TCSANOW, &oldtermios)){
fprintf(stderr, "Couldn't restore terminal mode on %d (%s)\n",
n->tcache.ttyfd, strerror(errno)); // don't return error for this
@ -510,7 +434,7 @@ ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){
if(np->sprite){
int y;
const char* u7 = get_escape(&n->tcache, ESCAPE_U7);
if(cursor_yx_get(n->tcache.ttyfd, u7, &y, NULL)){
if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){
return -1;
}
if(ncdirect_cursor_move_yx(n, y, xoff)){
@ -522,10 +446,10 @@ ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){
return -1;
}
fbuf f = {};
if(fbuf_init(&f)){
if(cursor_yx_get(n, n->tcache.ttyfd, u7, &y, NULL)){
return -1;
}
if(cursor_yx_get(n->tcache.ttyfd, u7, &y, NULL)){
if(fbuf_init(&f)){
return -1;
}
if(toty - dimy < y){
@ -538,21 +462,16 @@ ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){
// perform our scrolling outside of the fbuf framework, as we need it
// to happen immediately for fbdcon
if(ncdirect_cursor_move_yx(n, y, xoff)){
fbuf_free(&f);
return -1;
}
const char* indn = get_escape(&n->tcache, ESCAPE_IND);
if(scrolls > 1 && indn){
if(term_emit(tiparm(indn, scrolls), stdout, true) < 0){
return -1;
}
}
while(scrolls--){
if(ncfputc('\v', stdout) < 0){
return -1;
}
if(emit_scrolls(&n->tcache, scrolls, &f) < 0){
fbuf_free(&f);
return -1;
}
}
if(sprite_draw(&n->tcache, NULL, np->sprite, &f, y, xoff) < 0){
fbuf_free(&f);
return -1;
}
if(sprite_commit(&n->tcache, &f, np->sprite, true)){
@ -864,18 +783,14 @@ ncdirect_stop_minimal(void* vnc){
ret |= fbuf_finalize(&f, stdout);
}
if(nc->tcache.ttyfd >= 0){
if(nc->tcache.kittykbd){
ret |= tty_emit("\x1b[<u", nc->tcache.ttyfd);
}
ret |= tty_emit("\x1b[<u", nc->tcache.ttyfd);
const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
if(cnorm && tty_emit(cnorm, nc->tcache.ttyfd)){
ret = -1;
}
ret |= tcsetattr(nc->tcache.ttyfd, TCSANOW, nc->tcache.tpreserved);
ret |= close(nc->tcache.ttyfd);
}
ret |= ncdirect_flush(nc);
free_terminfo_cache(&nc->tcache);
return ret;
}
@ -883,7 +798,7 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
if(outfp == NULL){
outfp = stdout;
}
if(flags > (NCDIRECT_OPTION_VERY_VERBOSE << 1)){ // allow them through with warning
if(flags > (NCDIRECT_OPTION_DRAIN_INPUT << 1)){ // allow them through with warning
logwarn("Passed unsupported flags 0x%016jx\n", (uintmax_t)flags);
}
ncdirect* ret = malloc(sizeof(ncdirect));
@ -891,6 +806,10 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
return ret;
}
memset(ret, 0, sizeof(*ret));
if(pthread_mutex_init(&ret->stats.lock, NULL)){
free(ret);
return NULL;
}
ret->flags = flags;
ret->ttyfp = outfp;
if(!(flags & NCDIRECT_OPTION_INHIBIT_SETLOCALE)){
@ -903,6 +822,7 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
}
if(setup_signals(ret, (flags & NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS),
true, ncdirect_stop_minimal)){
pthread_mutex_destroy(&ret->stats.lock);
free(ret);
return NULL;
}
@ -917,9 +837,10 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
}
int cursor_y = -1;
int cursor_x = -1;
if(interrogate_terminfo(&ret->tcache, termtype, ret->ttyfp, utf8,
1, flags & NCDIRECT_OPTION_INHIBIT_CBREAK,
TERMINAL_UNKNOWN, &cursor_y, &cursor_x, NULL)){
if(interrogate_terminfo(&ret->tcache, termtype, ret->ttyfp, utf8, 1,
flags & NCDIRECT_OPTION_INHIBIT_CBREAK,
0, &cursor_y, &cursor_x, &ret->stats, 0, 0,
flags & NCDIRECT_OPTION_DRAIN_INPUT)){
goto err;
}
if(cursor_y >= 0){
@ -927,6 +848,7 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
// unaffected by any query spill (unconsumed control sequences). move
// us back to that location, in case there was any such spillage.
if(ncdirect_cursor_move_yx(ret, cursor_y, cursor_x)){
free_terminfo_cache(&ret->tcache);
goto err;
}
}
@ -942,6 +864,7 @@ err:
(void)tcsetattr(ret->tcache.ttyfd, TCSANOW, ret->tcache.tpreserved);
}
drop_signals(ret);
pthread_mutex_destroy(&ret->stats.lock);
free(ret);
return NULL;
}
@ -950,6 +873,11 @@ int ncdirect_stop(ncdirect* nc){
int ret = 0;
if(nc){
ret |= ncdirect_stop_minimal(nc);
free_terminfo_cache(&nc->tcache);
if(nc->tcache.ttyfd >= 0){
ret |= close(nc->tcache.ttyfd);
}
pthread_mutex_destroy(&nc->stats.lock);
free(nc);
}
return ret;

@ -135,8 +135,7 @@ int ncfdplane_destroy(ncfdplane* n){
n->destroyed = true; // ncfdplane_destroy_inner() is called on thread exit
}else{
void* vret = NULL;
pthread_cancel(n->tid);
ret |= pthread_join(n->tid, &vret);
ret |= cancel_and_join("fdplane", n->tid, &vret);
ret |= ncfdplane_destroy_inner(n);
}
}

@ -32,7 +32,7 @@ gpmwatcher(void* vti){
logwarn("input overflowed %hd %hd\n", gev.x, gev.y);
continue;
}
ncinput_shovel(&ti->input, cmdbuf, strlen(cmdbuf));
ncinput_shovel(ti->ictx, cmdbuf, strlen(cmdbuf));
}
return NULL;
}
@ -66,13 +66,8 @@ int gpm_read(tinfo* ti, ncinput* ni){
}
int gpm_close(tinfo* ti){
if(pthread_cancel(ti->gpmthread)){
logerror("couldn't cancel gpm thread\n"); // daemon might have died
}
void* thrres;
if(pthread_join(ti->gpmthread, &thrres)){
logerror("error joining gpm thread\n");
}
cancel_and_join("gpm", ti->gpmthread, &thrres);
Gpm_Close();
memset(&gpmconn, 0, sizeof(gpmconn));
return 0;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,84 @@
#ifndef NOTCURSES_IN
#define NOTCURSES_IN
#ifdef __cplusplus
extern "C" {
#endif
// internal header, not installed
#include <stdio.h>
struct tinfo;
struct inputctx;
struct ncsharedstats;
int init_inputlayer(struct tinfo* ti, FILE* infp, int lmargin, int tmargin,
struct ncsharedstats* stats, unsigned drain)
__attribute__ ((nonnull (1, 2, 5)));
int stop_inputlayer(struct tinfo* ti);
int inputready_fd(const struct inputctx* ictx)
__attribute__ ((nonnull (1)));
// allow another source provide raw input for distribution to client code.
// drops input if there is no room in appropriate output queue.
int ncinput_shovel(struct inputctx* ictx, const void* buf, int len)
__attribute__ ((nonnull (1, 2)));
typedef enum {
TERMINAL_UNKNOWN, // no useful information from queries; use termname
// the very limited linux VGA/serial console, or possibly the (deprecated,
// pixel-drawable, RGBA8888) linux framebuffer console. *not* fbterm.
TERMINAL_LINUX, // ioctl()s
// the linux KMS/DRM console, *not* kmscon, but DRM direct dumb buffers
TERMINAL_LINUXDRM, // ioctl()s
TERMINAL_XTERM, // XTVERSION == 'XTerm(ver)'
TERMINAL_VTE, // TDA: "~VTE"
TERMINAL_KITTY, // XTGETTCAP['TN'] == 'xterm-kitty'
TERMINAL_FOOT, // TDA: "\EP!|464f4f54\E\\"
TERMINAL_MLTERM, // XTGETTCAP['TN'] == 'mlterm'
TERMINAL_TMUX, // XTVERSION == "tmux ver"
TERMINAL_WEZTERM, // XTVERSION == 'WezTerm *'
TERMINAL_ALACRITTY, // can't be detected; match TERM+DA2
TERMINAL_CONTOUR, // XTVERSION == 'contour ver'
TERMINAL_ITERM, // XTVERSION == 'iTerm2 [ver]'
TERMINAL_TERMINOLOGY, // TDA: "~~TY"
TERMINAL_APPLE, // Terminal.App, determined by TERM_PROGRAM + macOS
TERMINAL_MSTERMINAL, // Microsoft Windows Terminal
TERMINAL_MINTTY, // XTVERSION == 'mintty ver' MinTTY (Cygwin, MSYS2)
} queried_terminals_e;
// after spawning the input layer, send initial queries to the terminal. its
// responses will be built up herein. it's dangerous to go alone! take this!
struct initial_responses {
int cursory; // cursor location
int cursorx; // cursor location
unsigned appsync_supported; // is application-synchronized mode supported?
queried_terminals_e qterm; // determined terminal
unsigned kitty_graphics; // kitty graphics supported
uint32_t bg; // default background
int pixx; // screen geometry in pixels
int pixy; // screen geometry in pixels
int dimx; // screen geometry in cells
int dimy; // screen geometry in cells
int color_registers; // sixel color registers
int sixely; // maximum sixel height
int sixelx; // maximum sixel width
char* version; // version string, heap-allocated
};
// Blocking call. Waits until the input thread has processed all responses to
// our initial queries, and returns them.
struct initial_responses* inputlayer_get_responses(struct inputctx* ictx)
__attribute__ ((nonnull (1)));
int get_cursor_location(struct inputctx* ictx, int* y, int* x)
__attribute__ ((nonnull (1)));
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

@ -1,69 +0,0 @@
#ifndef NOTCURSES_INPUT
#define NOTCURSES_INPUT
#ifdef __cplusplus
extern "C" {
#endif
// internal header, not installed
#include <stdio.h>
struct tinfo;
struct termios;
struct ncinputlayer;
struct ncsharedstats;
typedef enum {
TERMINAL_UNKNOWN, // no useful information from queries; use termname
// the very limited linux VGA/serial console, or possibly the (deprecated,
// pixel-drawable, RGBA8888) linux framebuffer console. *not* fbterm.
TERMINAL_LINUX, // ioctl()s
// the linux KMS/DRM console, *not* kmscon, but DRM direct dumb buffers
TERMINAL_LINUXDRM, // ioctl()s
TERMINAL_XTERM, // XTVERSION == 'XTerm(ver)'
TERMINAL_VTE, // TDA: "~VTE"
TERMINAL_KITTY, // XTGETTCAP['TN'] == 'xterm-kitty'
TERMINAL_FOOT, // TDA: "\EP!|464f4f54\E\\"
TERMINAL_MLTERM, // XTGETTCAP['TN'] == 'mlterm'
TERMINAL_TMUX, // XTVERSION == "tmux ver"
TERMINAL_WEZTERM, // XTVERSION == 'WezTerm *'
TERMINAL_ALACRITTY, // can't be detected; match TERM+DA2
TERMINAL_CONTOUR, // XTVERSION == 'contour ver'
TERMINAL_ITERM, // XTVERSION == 'iTerm2 [ver]'
TERMINAL_TERMINOLOGY, // TDA: "~~TY"
TERMINAL_APPLE, // Terminal.App, determined by TERM_PROGRAM + macOS
TERMINAL_MSTERMINAL, // Microsoft Windows Terminal
TERMINAL_MINTTY, // XTVERSION == 'mintty ver' MinTTY (Cygwin, MSYS2)
} queried_terminals_e;
// sets up the input layer, building a trie of escape sequences and their
// nckey equivalents. if we are connected to a tty, this also completes the
// terminal detection sequence (we ought have already written our initial
// queries, ideally as early as possible). if we are able to determine the
// terminal conclusively, it will be written to |detected|. if the terminal
// advertised support for application-sychronized updates, |appsync| will be
// non-zero.
int ncinputlayer_init(struct tinfo* tcache, FILE* infp,
queried_terminals_e* detected, unsigned* appsync,
int* cursor_y, int* cursor_x,
struct ncsharedstats* stats,
unsigned* kittygraphs);
void ncinputlayer_stop(struct ncinputlayer* nilayer);
// FIXME absorb into ncinputlayer_init()
int cbreak_mode(struct tinfo* ti);
// assuming the user context is not active, go through current data looking
// for a cursor location report. if we find none, block on input, and read if
// appropriate. we can be interrupted by a new user context.
void ncinput_extract_clrs(struct tinfo* ti);
int ncinput_shovel(struct ncinputlayer* ni, const char* buf, size_t len);
#ifdef __cplusplus
}
#endif
#endif

@ -9,6 +9,7 @@ extern "C" {
#include "builddef.h"
#include "compat/compat.h"
#include "notcurses/notcurses.h"
#include "notcurses/direct.h"
// KEY_EVENT is defined by both ncurses.h and wincon.h. since we don't use
// either definition, kill it before inclusion of ncurses.h.
@ -271,6 +272,15 @@ typedef struct nctabbed {
nctabbed_options opts; // copied in nctabbed_create()
} nctabbed;
// various moving parts within a notcurses context (and the user) might need to
// access the stats object, so throw a lock on it. we don't want the lock in
// the actual structure since (a) it's usually unnecessary and (b) it breaks
// memset() and memcpy().
typedef struct ncsharedstats {
pthread_mutex_t lock;
ncstats s;
} ncsharedstats;
typedef struct ncdirect {
ncpalette palette; // 256-indexed palette can be used instead of/with RGB
FILE* ttyfp; // FILE* for output tty
@ -279,6 +289,7 @@ typedef struct ncdirect {
uint16_t stylemask; // current styles
bool initialized_readline; // have we initialized Readline?
uint64_t flags; // copied in ncdirect_init() from param
ncsharedstats stats; // stats! not as broadly used as in notcurses
} ncdirect;
// Extracellular state for a cell during the render process. There is one
@ -329,15 +340,6 @@ typedef struct ncpile {
sprixel* sprixelcache; // list of sprixels
} ncpile;
// various moving parts within a notcurses context (and the user) might need to
// access the stats object, so throw a lock on it. we don't want the lock in
// the actual structure since (a) it's usually unnecessary and (b) it breaks
// memset() and memcpy().
typedef struct ncsharedstats {
pthread_mutex_t lock;
ncstats s;
} ncsharedstats;
// the standard pile can be reached through ->stdplane.
typedef struct notcurses {
ncplane* stdplane; // standard plane, covers screen
@ -375,7 +377,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;
@ -1745,6 +1746,44 @@ tty_check(int fd){
return isatty(fd);
}
// attempt to cancel the specified thread (not an error if we can't; it might
// have already exited), and then join it (an error here is propagated).
static inline int
cancel_and_join(const char* name, pthread_t tid, void** res){
if(pthread_cancel(tid)){
logerror("couldn't cancel %s thread\n", name); // tid might have died
}
if(pthread_join(tid, res)){
logerror("error joining %s thread\n", name);
return -1;
}
return 0;
}
static inline int
emit_scrolls(const tinfo* ti, int count, fbuf* f){
if(count > 1){
const char* indn = get_escape(ti, ESCAPE_INDN);
if(indn){
if(fbuf_emit(f, tiparm(indn, count)) < 0){
return -1;
}
return 0;
}
}
const char* ind = get_escape(ti, ESCAPE_IND);
if(ind == NULL){
ind = "\v";
}
while(count > 0){
if(fbuf_emit(f, ind) < 0){
return -1;
}
--count;
}
return 0;
}
#undef ALLOC
#undef API

@ -406,7 +406,7 @@ program_line_drawing_chars(int fd, struct unimapdesc* map){
return 0;
}
if(ioctl(fd, PIO_UNIMAP, map)){
logwarn("Error setting kernel unicode map (%s)\n", strerror(errno));
logwarn("error setting kernel unicode map (%s)\n", strerror(errno));
return -1;
}
loginfo("Successfully added %d kernel unicode mapping%s\n",
@ -533,12 +533,12 @@ program_block_drawing_chars(tinfo* ti, int fd, struct console_font_op* cfo,
}
}
if(candidate == 0){
logwarn("Ran out of replaceable glyphs for U+%04lx\n", (long)half[s].w);
logwarn("ran out of replaceable glyphs for U+%04lx\n", (long)half[s].w);
// FIXME maybe don't want to error out here?
return -1;
}
if(shim_quad_block(cfo, candidate, half[s].qbits)){
logwarn("Error replacing glyph for U+%04lx at %u\n", (long)half[s].w, candidate);
logwarn("error replacing glyph for U+%04lx at %u\n", (long)half[s].w, candidate);
return -1;
}
if(add_to_map(map, half[s].w, candidate)){
@ -555,12 +555,12 @@ program_block_drawing_chars(tinfo* ti, int fd, struct console_font_op* cfo,
}
}
if(candidate == 0){
logwarn("Ran out of replaceable glyphs for U+%04lx\n", (long)quads[s].w);
logwarn("ran out of replaceable glyphs for U+%04lx\n", (long)quads[s].w);
// FIXME maybe don't want to error out here?
return -1;
}
if(shim_quad_block(cfo, candidate, quads[s].qbits)){
logwarn("Error replacing glyph for U+%04lx at %u\n", (long)quads[s].w, candidate);
logwarn("error replacing glyph for U+%04lx at %u\n", (long)quads[s].w, candidate);
return -1;
}
if(add_to_map(map, quads[s].w, candidate)){
@ -577,11 +577,11 @@ program_block_drawing_chars(tinfo* ti, int fd, struct console_font_op* cfo,
}
}
if(candidate == 0){
logwarn("Ran out of replaceable glyphs for U+%04lx\n", (long)eighths[s].w);
logwarn("ran out of replaceable glyphs for U+%04lx\n", (long)eighths[s].w);
return -1;
}
if(shim_lower_eighths(cfo, candidate, eighths[s].qbits)){
logwarn("Error replacing glyph for U+%04lx at %u\n", (long)eighths[s].w, candidate);
logwarn("error replacing glyph for U+%04lx at %u\n", (long)eighths[s].w, candidate);
return -1;
}
if(add_to_map(map, eighths[s].w, candidate)){
@ -600,12 +600,12 @@ program_block_drawing_chars(tinfo* ti, int fd, struct console_font_op* cfo,
}
cfo->op = KD_FONT_OP_SET;
if(ioctl(fd, KDFONTOP, cfo)){
logwarn("Error programming kernel font (%s)\n", strerror(errno));
logwarn("error programming kernel font (%s)\n", strerror(errno));
kill_fbcopy(&fbdup);
return -1;
}
if(ioctl(fd, PIO_UNIMAP, map)){
logwarn("Error setting kernel unicode map (%s)\n", strerror(errno));
logwarn("error setting kernel unicode map (%s)\n", strerror(errno));
kill_fbcopy(&fbdup);
return -1;
}
@ -636,7 +636,7 @@ reprogram_linux_font(tinfo* ti, int fd, struct console_font_op* cfo,
struct unimapdesc* map, unsigned no_font_changes,
bool* halfblocks, bool* quadrants){
if(ioctl(fd, KDFONTOP, cfo)){
logwarn("Error reading Linux kernelfont (%s)\n", strerror(errno));
logwarn("error reading Linux kernelfont (%s)\n", strerror(errno));
return -1;
}
loginfo("Kernel font size (glyphcount): %hu\n", cfo->charcount);
@ -646,7 +646,7 @@ reprogram_linux_font(tinfo* ti, int fd, struct console_font_op* cfo,
return -1;
}
if(ioctl(fd, GIO_UNIMAP, map)){
logwarn("Error reading Linux unimap (%s)\n", strerror(errno));
logwarn("error reading Linux unimap (%s)\n", strerror(errno));
return -1;
}
loginfo("Kernel Unimap size: %hu/%hu\n", map->entry_ct, USHRT_MAX);
@ -676,7 +676,7 @@ int reprogram_console_font(tinfo* ti, unsigned no_font_changes,
size_t totsize = 128 * cfo.charcount; // FIXME enough?
cfo.data = malloc(totsize);
if(cfo.data == NULL){
logwarn("Error acquiring %zub for font descriptors (%s)\n", totsize, strerror(errno));
logwarn("error acquiring %zub for font descriptors (%s)\n", totsize, strerror(errno));
return -1;
}
struct unimapdesc map = {};
@ -684,7 +684,7 @@ int reprogram_console_font(tinfo* ti, unsigned no_font_changes,
totsize = map.entry_ct * sizeof(struct unipair);
map.entries = malloc(totsize);
if(map.entries == NULL){
logwarn("Error acquiring %zub for Unicode font map (%s)\n", totsize, strerror(errno));
logwarn("error acquiring %zub for Unicode font map (%s)\n", totsize, strerror(errno));
free(cfo.data);
return -1;
}
@ -704,10 +704,10 @@ bool is_linux_console(int fd){
}
int mode;
if(ioctl(fd, KDGETMODE, &mode)){
logdebug("Not a Linux console, KDGETMODE failed\n");
logdebug("not a Linux console, KDGETMODE failed\n");
return false;
}
loginfo("Verified Linux console, mode %d\n", mode);
loginfo("verified Linux console, mode %d\n", mode);
return true;
}
@ -721,7 +721,7 @@ int get_linux_fb_pixelgeom(tinfo* ti, unsigned* ypix, unsigned *xpix){
}
struct fb_var_screeninfo fbi = {};
if(ioctl(ti->linux_fb_fd, FBIOGET_VSCREENINFO, &fbi)){
logwarn("No framebuffer info from %s (%s?)\n", ti->linux_fb_dev, strerror(errno));
logwarn("no framebuffer info from %s (%s?)\n", ti->linux_fb_dev, strerror(errno));
return -1;
}
loginfo("Linux %s geometry: %dx%d\n", ti->linux_fb_dev, fbi.yres, fbi.xres);

@ -1,4 +1,3 @@
#include "input.h"
#include "linux.h"
#include "version.h"
#include "egcpool.h"
@ -107,10 +106,8 @@ notcurses_stop_minimal(void* vnc){
if(nc->tcache.tpreserved){
ret |= tcsetattr(nc->tcache.ttyfd, TCSAFLUSH, nc->tcache.tpreserved);
}
if(nc->tcache.kittykbd){
if(tty_emit("\x1b[<u", nc->tcache.ttyfd)){
ret = -1;
}
if(tty_emit("\x1b[<u", nc->tcache.ttyfd)){
ret = -1;
}
if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
if(sprite_clear_all(&nc->tcache, f)){ // send this to f
@ -1000,7 +997,7 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
fprintf(stderr, "Provided an illegal negative margin, refusing to start\n");
return NULL;
}
if(opts->flags >= (NCOPTION_NO_FONT_CHANGES << 1u)){
if(opts->flags >= (NCOPTION_DRAIN_INPUT << 1u)){
fprintf(stderr, "Warning: unknown Notcurses options %016" PRIu64 "\n", opts->flags);
}
notcurses* ret = malloc(sizeof(*ret));
@ -1092,7 +1089,9 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
if(interrogate_terminfo(&ret->tcache, opts->termtype, ret->ttyfp, utf8,
opts->flags & NCOPTION_NO_ALTERNATE_SCREEN, 0,
opts->flags & NCOPTION_NO_FONT_CHANGES,
cursory, cursorx, &ret->stats)){
cursory, cursorx, &ret->stats,
ret->margin_l, ret->margin_t,
opts->flags & NCOPTION_DRAIN_INPUT)){
fbuf_free(&ret->rstate.f);
pthread_mutex_destroy(&ret->pilelock);
pthread_mutex_destroy(&ret->stats.lock);
@ -1149,9 +1148,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
@ -1246,7 +1242,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);
@ -2678,11 +2673,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

@ -988,26 +988,7 @@ rasterize_scrolls(const ncpile* p, fbuf* f){
return -1;
}
}
if(scrolls > 1){
const char* indn = get_escape(&p->nc->tcache, ESCAPE_INDN);
if(indn){
if(fbuf_emit(f, tiparm(indn, scrolls)) < 0){
return -1;
}
return 0;
}
}
const char* ind = get_escape(&p->nc->tcache, ESCAPE_IND);
if(ind == NULL){
ind = "\v";
}
while(scrolls > 0){
if(fbuf_emit(f, ind) < 0){
return -1;
}
--scrolls;
}
return 0;
return emit_scrolls(&p->nc->tcache, scrolls, f);
}
// second sprixel pass in rasterization. by this time, all sixels are handled

@ -5,7 +5,6 @@
#endif
#include "internal.h"
#include "windows.h"
#include "input.h"
#include "linux.h"
// there does not exist any true standard terminal size. with that said, we
@ -167,7 +166,7 @@ match_termname(const char* termname, queried_terminals_e* qterm){
}
void free_terminfo_cache(tinfo* ti){
ncinputlayer_stop(&ti->input);
stop_inputlayer(ti);
free(ti->termversion);
free(ti->esctable);
#ifdef __linux__
@ -720,7 +719,8 @@ macos_early_matches(void){
// full round trip before getting the reply, which is likely to pace init.
int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned utf8,
unsigned noaltscreen, unsigned nocbreak, unsigned nonewfonts,
int* cursor_y, int* cursor_x, ncsharedstats* stats){
int* cursor_y, int* cursor_x, ncsharedstats* stats,
int lmargin, int tmargin, unsigned draininput){
int foolcursor_x, foolcursor_y;
if(!cursor_x){
cursor_x = &foolcursor_x;
@ -790,6 +790,9 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
}
tname = termname(); // longname() is also available
#endif
if(init_inputlayer(ti, stdin, lmargin, tmargin, stats, draininput)){
goto err;
}
ti->sprixel_scale_height = 1;
get_default_geometry(ti);
ti->caps.utf8 = utf8;
@ -916,16 +919,52 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
goto err;
}
}
unsigned appsync_advertised = 0;
unsigned kittygraphs = 0;
if(ncinputlayer_init(ti, stdin, &ti->qterm, &appsync_advertised,
cursor_y, cursor_x, stats, &kittygraphs)){
goto err;
unsigned kitty_graphics = 0;
if(ti->ttyfd >= 0){
struct initial_responses* iresp;
if((iresp = inputlayer_get_responses(ti->ictx)) == NULL){
goto err;
}
if(iresp->appsync_supported){
if(add_appsync_escapes_sm(ti, &tablelen, &tableused)){
free(iresp->version);
free(iresp);
goto err;
}
}
if(iresp->qterm != TERMINAL_UNKNOWN){
ti->qterm = iresp->qterm;
}
*cursor_y = iresp->cursory;
*cursor_x = iresp->cursorx;
ti->termversion = iresp->version;
if(iresp->dimy && iresp->dimx){
// FIXME probably oughtn't be setting the defaults, as this is just some
// random transient measurement?
ti->default_rows = iresp->dimy;
ti->default_cols = iresp->dimx;
}
if(iresp->pixy && iresp->pixx){
ti->pixy = iresp->pixy;
ti->pixx = iresp->pixx;
}
if(ti->default_rows && ti->default_cols){
ti->cellpixy = ti->pixy / ti->default_rows;
ti->cellpixx = ti->pixx / ti->default_cols;
}
ti->bg_collides_default = iresp->bg;
// kitty trumps sixel, when both are available
if((kitty_graphics = iresp->kitty_graphics) == 0){
ti->color_registers = iresp->color_registers;
ti->sixel_maxy = iresp->sixely;
ti->sixel_maxx = iresp->sixelx;
}
free(iresp);
}
if(nocbreak){
if(ti->ttyfd >= 0){
// FIXME do this in input later, upon signaling completion?
if(tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved)){
ncinputlayer_stop(&ti->input);
goto err;
}
}
@ -935,20 +974,14 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
goto err;
}
}
if(appsync_advertised){
if(add_appsync_escapes_sm(ti, &tablelen, &tableused)){
goto err;
}
}
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);
if(ti->pixel_draw == NULL && ti->pixel_draw_late == NULL){
if(kittygraphs){
if(kitty_graphics){
setup_kitty_bitmaps(ti, ti->ttyfd, NCPIXEL_KITTY_ANIMATED);
}
// our current sixel quantization algorithm requires at least 64 color
@ -967,6 +1000,7 @@ err:
free(ti->tpreserved);
ti->tpreserved = NULL;
}
stop_inputlayer(ti);
free(ti->esctable);
free(ti->termversion);
del_curterm(cur_term);
@ -992,48 +1026,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,47 +1050,54 @@ 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);
}
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);
*cursor_y = clr->y;
*cursor_x = clr->x;
if(ti->inverted_cursor){
int tmp = *cursor_y;
*cursor_y = *cursor_x;
*cursor_x = tmp;
}
free(clr);
int fd = ti->ttyfd;
if(tty_emit(u7, fd)){
return -1;
}
get_cursor_location(ti->ictx, cursor_y, cursor_x);
loginfo("got a report from %d %d/%d\n", fd, *cursor_y, *cursor_x);
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;
}

@ -9,12 +9,12 @@ extern "C" {
#include "version.h"
#include "builddef.h"
#include "input.h"
#include <stdint.h>
#include <pthread.h>
#include <stdbool.h>
#include <notcurses/notcurses.h>
#include "fbuf.h"
#include "in.h"
struct ncpile;
struct sprixel;
@ -88,43 +88,6 @@ typedef struct cursorreport {
struct cursorreport* next;
} cursorreport;
// we read input from one or two places. if stdin is connected to our
// controlling tty, we read only from that file descriptor. if it is
// connected to something else, and we have a controlling tty, we will
// read data only from stdin and control only from the tty. if we have
// no connected tty, only data is available.
typedef struct ncinputlayer {
// only allow one reader at a time, whether it's the user trying to do so,
// or our desire for a cursor report competing with the user.
pthread_mutex_t lock;
// must be held to operate on the cursor report queue shared between pure
// input and the control layer.
pthread_cond_t creport_cond;
// ttyfd is only valid if we are connected to a tty, *and* stdin is not
// connected to that tty (this usually means stdin was redirected). in that
// case, we read control sequences only from ttyfd.
int ttyfd; // file descriptor for connected tty
int infd; // file descriptor for processing input, from stdin
unsigned char inputbuf[BUFSIZ];
unsigned char csibuf[BUFSIZ]; // running buffer while parsing CSIs
// we keep a wee ringbuffer of input queued up for delivery. if
// inputbuf_occupied == sizeof(inputbuf), there is no room. otherwise, data
// can be read to inputbuf_write_at until we fill up. the first datum
// available for the app is at inputbuf_valid_starts iff inputbuf_occupied is
// not 0. the main purpose is working around bad predictions of escapes.
unsigned inputbuf_occupied;
unsigned inputbuf_valid_starts;
unsigned inputbuf_write_at;
// number of input events seen. does not belong in ncstats, since it must not
// be reset (semantics are relied upon by widgets for mouse click detection).
uint64_t input_events;
struct esctrie* inputescapes; // trie of input escapes -> ncspecial_keys
cursorreport* creport_queue; // queue of cursor reports
bool user_wants_data; // a user context is active
bool inner_wants_data; // if we're blocking on input
struct ncsharedstats* stats; // notcurses sharedstats object
} ncinputlayer;
// terminal interface description. most of these are acquired from terminfo(5)
// (using a database entry specified by TERM). some are determined via
// heuristics based off terminal interrogation or the TERM environment
@ -198,7 +161,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
@ -206,8 +170,6 @@ typedef struct tinfo {
int default_rows; // LINES environment var / lines terminfo / 24
int default_cols; // COLUMNS environment var / cols terminfo / 80
unsigned kittykbd; // kitty keyboard support level
int gpmfd; // connection to GPM daemon
pthread_t gpmthread; // thread handle for GPM watcher
#ifdef __linux__
@ -220,10 +182,6 @@ typedef struct tinfo {
HANDLE outhandle;
#endif
// some terminals (e.g. kmscon) return cursor coordinates inverted from the
// typical order. we detect it the first time ncdirect_cursor_yx() is called.
bool detected_cursor_inversion; // have we performed inversion testing?
bool inverted_cursor; // does the terminal return inverted coordinates?
bool bce; // is the bce property advertised?
bool in_alt_screen; // are we in the alternate screen?
} tinfo;
@ -252,7 +210,9 @@ term_supported_styles(const tinfo* ti){
int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out,
unsigned utf8, unsigned noaltscreen, unsigned nocbreak,
unsigned nonewfonts, int* cursor_y, int* cursor_x,
struct ncsharedstats* stats);
struct ncsharedstats* stats, int lmargin, int tmargin,
unsigned draininput)
__attribute__ ((nonnull (1, 2, 3, 10)));
void free_terminfo_cache(tinfo* ti);
@ -382,6 +342,8 @@ leave_alternate_screen(FILE* fp, tinfo* ti){
return 0;
}
int cbreak_mode(tinfo* ti);
#ifdef __cplusplus
}
#endif

@ -149,6 +149,7 @@ wipebitmap(struct notcurses* nc){
int main(void){
struct notcurses_options opts = {
.loglevel = NCLOGLEVEL_TRACE,
.flags = NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
if(notcurses_check_pixel_support(nc) < 1){

@ -14,7 +14,8 @@ int main(int argc, char** argv){
return EXIT_FAILURE;
}
struct notcurses_options nopts = {
.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN,
.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_init(&nopts, NULL);
if(nc == NULL){

@ -7,6 +7,7 @@ int main(void){
notcurses_options nopts = {
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT
| NCOPTION_PRESERVE_CURSOR,
};
struct notcurses* nc = notcurses_core_init(&nopts, NULL);

@ -29,7 +29,8 @@ int main(void){
if(rendered_cursor()){
return EXIT_FAILURE;
}
uint64_t flags = NCDIRECT_OPTION_VERY_VERBOSE;
uint64_t flags = NCDIRECT_OPTION_VERY_VERBOSE
| NCDIRECT_OPTION_DRAIN_INPUT;
struct ncdirect* n = ncdirect_core_init(NULL, stdout, flags);
if(n == NULL){
return EXIT_FAILURE;

@ -52,7 +52,8 @@ int main(void){
if(!setlocale(LC_ALL, "")){
return EXIT_FAILURE;
}
struct ncdirect* nc = ncdirect_core_init(NULL, stdout, 0);
uint64_t flags = NCDIRECT_OPTION_DRAIN_INPUT;
struct ncdirect* nc = ncdirect_core_init(NULL, stdout, flags);
if(!nc){
return EXIT_FAILURE;
}

@ -6,7 +6,8 @@ int main(void){
if(!setlocale(LC_ALL, "")){
return EXIT_FAILURE;
}
struct ncdirect* n = ncdirect_core_init(NULL, stdout, 0);
uint64_t flags = NCDIRECT_OPTION_DRAIN_INPUT;
struct ncdirect* n = ncdirect_core_init(NULL, stdout, flags);
putchar('\n');
for(int i = 0 ; i < 15 ; ++i){
uint64_t c1 = 0, c2 = 0;

@ -18,7 +18,8 @@ int main(int argc, char** argv){
| NCOPTION_NO_CLEAR_BITMAPS
| NCOPTION_SUPPRESS_BANNERS
| NCOPTION_NO_FONT_CHANGES
| NCOPTION_PRESERVE_CURSOR,
| NCOPTION_PRESERVE_CURSOR
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_init(&nopts, NULL);
if(nc == NULL){

@ -43,8 +43,9 @@ int main(int argc, char** argv){
}
setlocale(LC_ALL, "");
notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE |
NCOPTION_SUPPRESS_BANNERS,
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_SUPPRESS_BANNERS
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
struct ncplane* n = notcurses_stdplane(nc);

@ -70,7 +70,8 @@ int main(void){
return EXIT_FAILURE;
}
struct notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE,
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
if(nc == NULL){

@ -95,6 +95,7 @@ interp(struct notcurses* nc, int cellpixy, int cellpixx){
int main(void){
struct notcurses_options nopts = {
// .loglevel = NCLOGLEVEL_TRACE,
.flags = NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_init(&nopts, NULL);
if(nc == NULL){

@ -49,6 +49,7 @@ int main(int argc, char** argv){
.margin_l = 2,
.margin_b = 2,
.margin_r = 2,
.flags = NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_init(&opts, NULL);
if(notcurses_check_pixel_support(nc) <= 0){

@ -43,8 +43,9 @@ int main(int argc, char** argv){
}
setlocale(LC_ALL, "");
notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE |
NCOPTION_SUPPRESS_BANNERS,
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_SUPPRESS_BANNERS
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
if(nc == NULL){

@ -25,7 +25,9 @@ int main(int argc, const char** argv){
return EXIT_FAILURE;
}
struct notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN,
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_init(&opts, NULL);
int dimy, dimx;

@ -9,7 +9,9 @@ int main(void){
return EXIT_FAILURE;
}
struct notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN,
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
if(nc == NULL){

@ -22,7 +22,9 @@ int main(int argc, char** argv){
}
notcurses_options opts = {};
//opts.loglevel = NCLOGLEVEL_TRACE;
opts.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN;
opts.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT;
struct notcurses* nc;
if((nc = notcurses_init(&opts, NULL)) == NULL){
return EXIT_FAILURE;

@ -3,7 +3,8 @@
int main(void){
struct notcurses_options opts = {
.flags = NCOPTION_NO_ALTERNATE_SCREEN,
.flags = NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
if(!nc){

@ -5,8 +5,10 @@
int main(void){
setlocale(LC_ALL, "");
struct notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_PRESERVE_CURSOR,
.flags = NCOPTION_INHIBIT_SETLOCALE
| NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_PRESERVE_CURSOR
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&opts, NULL);
if(nc == NULL){

@ -6,7 +6,8 @@ int main(void){
struct notcurses_options nopts = {
.flags = NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_SUPPRESS_BANNERS
| NCOPTION_PRESERVE_CURSOR,
| NCOPTION_PRESERVE_CURSOR
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&nopts, NULL);
if(nc == NULL){

@ -115,7 +115,8 @@ int main(int argc, char **argv){
}
char** a = argv + 1;
struct notcurses_options opts = {
.flags = NCOPTION_NO_ALTERNATE_SCREEN,
.flags = NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_DRAIN_INPUT,
//.loglevel = NCLOGLEVEL_TRACE,
};
struct notcurses* nc = notcurses_init(&opts, NULL);

@ -3,7 +3,9 @@
int main(void){
struct notcurses_options nops = {
.flags = NCOPTION_NO_ALTERNATE_SCREEN | NCOPTION_SUPPRESS_BANNERS,
.flags = NCOPTION_NO_ALTERNATE_SCREEN
| NCOPTION_SUPPRESS_BANNERS
| NCOPTION_DRAIN_INPUT,
};
struct notcurses* nc = notcurses_core_init(&nops, NULL);
if(nc == NULL){

@ -42,7 +42,7 @@ TEST_CASE("Cell") {
CHECK(9 == nccell_load(n_, &c, "นี้"));
WARN(1 == nccell_cols(&c));
// type-3 woman playing water polo, 17 bytes (5 characters)
// type-3 woman playing water polo, 17 bytes (5 characters, 2 columns)
#ifdef __linux__
CHECK(17 == nccell_load(n_, &c, "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f"));
WARN(2 == nccell_cols(&c));

Loading…
Cancel
Save