diff --git a/NEWS.md b/NEWS.md
index 5c5bf0147..5d680655b 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -5,6 +5,14 @@ rearrangements of Notcurses.
* `notcurses_check_pixel_support()` still returns 0 if there is no support
for bitmap graphics, but now returns an `ncpixelimple_e` to differentiate
the pixel backend otherwise. This result is strictly informative.
+ * 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.0 (2021-09-06)
* Mouse events in the Linux console are now reported from GPM when built
diff --git a/README.md b/README.md
index 3b5022d4c..c0c417381 100644
--- a/README.md
+++ b/README.md
@@ -248,6 +248,25 @@ If things break or seem otherwise lackluster, **please** consult the
more importantly, it will link against minimal Notcurses installations.
+
+ Does it work with hardware terminals?
+ 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.
+
+
+
+ What happens if I try blitting bitmap graphics on a terminal which
+ doesn't support them?
+ 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).
+
+
Notcurses looks like absolute crap in screen
.
screen
doesn't support RGB colors (at least as of 4.08.00);
diff --git a/USAGE.md b/USAGE.md
index 8dc2a0611..95e09cbd8 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -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.
diff --git a/doc/man/man3/notcurses_direct.3.md b/doc/man/man3/notcurses_direct.3.md
index 00b5f8330..1fe633ec9 100644
--- a/doc/man/man3/notcurses_direct.3.md
+++ b/doc/man/man3/notcurses_direct.3.md
@@ -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.
diff --git a/doc/man/man3/notcurses_init.3.md b/doc/man/man3/notcurses_init.3.md
index efcece480..b27d570ae 100644
--- a/doc/man/man3/notcurses_init.3.md
+++ b/doc/man/man3/notcurses_init.3.md
@@ -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
diff --git a/doc/man/man3/notcurses_input.3.md b/doc/man/man3/notcurses_input.3.md
index ba75a59c5..6f50564cf 100644
--- a/doc/man/man3/notcurses_input.3.md
+++ b/doc/man/man3/notcurses_input.3.md
@@ -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***);**
@@ -128,6 +130,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**.
diff --git a/include/notcurses/direct.h b/include/notcurses/direct.h
index 86651d39c..00bd10d30 100644
--- a/include/notcurses/direct.h
+++ b/include/notcurses/direct.h
@@ -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
diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h
index 8268be1b8..b3e4127fc 100644
--- a/include/notcurses/notcurses.h
+++ b/include/notcurses/notcurses.h
@@ -900,6 +900,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
+
// Configuration for notcurses_init().
typedef struct notcurses_options {
// The name of the terminfo database entry describing this terminal. If NULL,
@@ -1066,6 +1071,12 @@ 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
@@ -4244,233 +4255,6 @@ palette256_get_rgb8(const ncpalette* p, int idx, unsigned* RESTRICT r, unsigned*
API void palette256_free(ncpalette* p) __attribute__ ((deprecated));
-__attribute__ ((deprecated)) static inline unsigned
-channel_r(uint32_t channel){
- return ncchannel_r(channel);
-}
-
-// Extract the 8-bit green component from a 32-bit channel.
-__attribute__ ((deprecated)) static inline unsigned
-channel_g(uint32_t channel){
- return ncchannel_g(channel);
-}
-
-// Extract the 8-bit blue component from a 32-bit channel.
-__attribute__ ((deprecated)) static inline unsigned
-channel_b(uint32_t channel){
- return ncchannel_b(channel);
-}
-
-// Extract the three 8-bit R/G/B components from a 32-bit channel.
-__attribute__ ((deprecated)) static inline unsigned
-channel_rgb8(uint32_t channel, unsigned* RESTRICT r, unsigned* RESTRICT g,
- unsigned* RESTRICT b){
- return ncchannel_rgb8(channel, r, g, b);
-}
-
-// Set the three 8-bit components of a 32-bit channel, and mark it as not using
-// the default color. Retain the other bits unchanged.
-__attribute__ ((deprecated)) static inline int
-channel_set_rgb8(uint32_t* channel, int r, int g, int b){
- return ncchannel_set_rgb8(channel, r, g, b);
-}
-
-// Set the three 8-bit components of a 32-bit channel, and mark it as not using
-// the default color. Retain the other bits unchanged. r, g, and b will be
-// clipped to the range [0..255].
-__attribute__ ((deprecated)) static inline void
-channel_set_rgb8_clipped(unsigned* channel, int r, int g, int b){
- return ncchannel_set_rgb8_clipped(channel, r, g, b);
-}
-
-// Same, but provide an assembled, packed 24 bits of rgb.
-__attribute__ ((deprecated)) static inline int
-channel_set(unsigned* channel, unsigned rgb){
- return ncchannel_set(channel, rgb);
-}
-
-// Extract the 2-bit alpha component from a 32-bit channel.
-__attribute__ ((deprecated)) static inline unsigned
-channel_alpha(unsigned channel){
- return ncchannel_alpha(channel);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channel_palindex(uint32_t channel){
- return ncchannel_palindex(channel);
-}
-
-// Set the 2-bit alpha component of the 32-bit channel.
-__attribute__ ((deprecated)) static inline int
-channel_set_alpha(unsigned* channel, unsigned alpha){
- return ncchannel_set_alpha(channel, alpha);
-}
-
-__attribute__ ((deprecated)) static inline int
-channel_set_palindex(uint32_t* channel, int idx){
- return ncchannel_set_palindex(channel, idx);
-}
-
-__attribute__ ((deprecated)) static inline bool
-channel_default_p(unsigned channel){
- return ncchannel_default_p(channel);
-}
-
-__attribute__ ((deprecated)) static inline bool
-channel_palindex_p(unsigned channel){
- return ncchannel_palindex_p(channel);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channel_set_default(unsigned* channel){
- return ncchannel_set_default(channel);
-}
-
-__attribute__ ((deprecated)) static inline uint32_t
-channels_bchannel(uint64_t channels){
- return ncchannels_bchannel(channels);
-}
-
-__attribute__ ((deprecated)) static inline uint32_t
-channels_fchannel(uint64_t channels){
- return ncchannels_fchannel(channels);
-}
-
-__attribute__ ((deprecated)) static inline uint64_t
-channels_set_bchannel(uint64_t* channels, uint32_t channel){
- return ncchannels_set_bchannel(channels, channel);
-}
-
-__attribute__ ((deprecated)) static inline uint64_t
-channels_set_fchannel(uint64_t* channels, uint32_t channel){
- return ncchannels_set_fchannel(channels, channel);
-}
-
-__attribute__ ((deprecated)) static inline uint64_t
-channels_combine(uint32_t fchan, uint32_t bchan){
- return ncchannels_combine(fchan, bchan);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_fg_palindex(uint64_t channels){
- return ncchannels_fg_palindex(channels);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_bg_palindex(uint64_t channels){
- return ncchannels_bg_palindex(channels);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_fg_rgb(uint64_t channels){
- return ncchannels_fg_rgb(channels);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_bg_rgb(uint64_t channels){
- return ncchannels_bg_rgb(channels);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_fg_alpha(uint64_t channels){
- return ncchannels_fg_alpha(channels);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_bg_alpha(uint64_t channels){
- return ncchannels_bg_alpha(channels);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_fg_rgb8(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){
- return ncchannels_fg_rgb8(channels, r, g, b);
-}
-
-__attribute__ ((deprecated)) static inline unsigned
-channels_bg_rgb8(uint64_t channels, unsigned* r, unsigned* g, unsigned* b){
- return ncchannels_bg_rgb8(channels, r, g, b);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_fg_rgb8(uint64_t* channels, int r, int g, int b){
- return ncchannels_set_fg_rgb8(channels, r, g, b);
-}
-
-__attribute__ ((deprecated)) static inline void
-channels_set_fg_rgb8_clipped(uint64_t* channels, int r, int g, int b){
- ncchannels_set_fg_rgb8_clipped(channels, r, g, b);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_fg_alpha(uint64_t* channels, unsigned alpha){
- return ncchannels_set_fg_alpha(channels, alpha);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_fg_palindex(uint64_t* channels, int idx){
- return ncchannels_set_bg_palindex(channels, idx);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_fg_rgb(uint64_t* channels, unsigned rgb){
- return ncchannels_set_fg_rgb(channels, rgb);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_bg_rgb8(uint64_t* channels, int r, int g, int b){
- return ncchannels_set_bg_rgb8(channels, r, g, b);
-}
-
-__attribute__ ((deprecated)) static inline void
-channels_set_bg_rgb8_clipped(uint64_t* channels, int r, int g, int b){
- ncchannels_set_bg_rgb8_clipped(channels, r, g, b);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_bg_alpha(uint64_t* channels, unsigned alpha){
- return ncchannels_set_bg_alpha(channels, alpha);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_bg_palindex(uint64_t* channels, int idx){
- return ncchannels_set_bg_palindex(channels, idx);
-}
-
-__attribute__ ((deprecated)) static inline int
-channels_set_bg_rgb(uint64_t* channels, unsigned rgb){
- return ncchannels_set_bg_rgb(channels, rgb);
-}
-
-__attribute__ ((deprecated)) static inline bool
-channels_fg_default_p(uint64_t channels){
- return ncchannels_fg_default_p(channels);
-}
-
-__attribute__ ((deprecated)) static inline bool
-channels_fg_palindex_p(uint64_t channels){
- return ncchannels_fg_palindex_p(channels);
-}
-
-__attribute__ ((deprecated)) static inline bool
-channels_bg_default_p(uint64_t channels){
- return ncchannels_bg_default_p(channels);
-}
-
-__attribute__ ((deprecated)) static inline bool
-channels_bg_palindex_p(uint64_t channels){
- return ncchannels_bg_palindex_p(channels);
-}
-
-__attribute__ ((deprecated)) static inline uint64_t
-channels_set_fg_default(uint64_t* channels){
- return ncchannels_set_fg_default(channels);
-}
-
-__attribute__ ((deprecated)) static inline uint64_t
-channels_set_bg_default(uint64_t* channels){
- return ncchannels_set_bg_default(channels);
-}
-
// Inflate each pixel in the image to 'scale'x'scale' pixels. It is an error
// if 'scale' is less than 1. The original color is retained.
// Deprecated; use ncvisual_resize_noninterpolative(), which this now wraps.
@@ -4488,12 +4272,6 @@ typedef nccell cell; // FIXME backwards-compat, remove in ABI3
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)
diff --git a/src/lib/direct.c b/src/lib/direct.c
index 9a6dc5f34..99f5d7102 100644
--- a/src/lib/direct.c
+++ b/src/lib/direct.c
@@ -864,9 +864,7 @@ ncdirect_stop_minimal(void* vnc){
ret |= fbuf_finalize(&f, stdout);
}
if(nc->tcache.ttyfd >= 0){
- if(nc->tcache.kittykbd){
- ret |= tty_emit("\x1b[tcache.ttyfd);
- }
+ ret |= tty_emit("\x1b[tcache.ttyfd);
const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
if(cnorm && tty_emit(cnorm, nc->tcache.ttyfd)){
ret = -1;
@@ -883,7 +881,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));
diff --git a/src/lib/gpm.c b/src/lib/gpm.c
index 3ade79abe..40f3fafc3 100644
--- a/src/lib/gpm.c
+++ b/src/lib/gpm.c
@@ -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;
}
diff --git a/src/lib/in.c b/src/lib/in.c
index 4cbcca682..cb2a221df 100644
--- a/src/lib/in.c
+++ b/src/lib/in.c
@@ -2,6 +2,23 @@
#include "internal.h"
#include "in.h"
+// Notcurses takes over stdin, and if it is not connected to a terminal, also
+// tries to make a connection to the controlling terminal. If such a connection
+// is made, it will read from that source (in addition to stdin). We dump one or
+// both into distinct buffers. We then try to lex structured elements out of
+// the buffer(s). We can extract cursor location reports, mouse events, and
+// UTF-8 characters. Completely extracted ones are placed in their appropriate
+// queues, and removed from the depository buffer. We aim to consume the
+// entirety of the deposit before going back to read more data, but let anyone
+// blocking on data wake up as soon as we've processed any input.
+//
+// The primary goal is to react to terminal messages (mostly cursor location
+// reports) as quickly as possible, and definitely not with unbounded latency,
+// without unbounded allocation, and also without losing data. We'd furthermore
+// like to reliably differentiate escapes and regular input, even when that
+// latter contains escapes. Unbounded input will hopefully only be present when
+// redirected from a file (NCOPTION_TOSS_INPUT)
+
static sig_atomic_t resize_seen;
// called for SIGWINCH and SIGCONT
@@ -24,21 +41,79 @@ typedef struct cursorloc {
int y, x; // 0-indexed cursor location
} cursorloc;
-// local state for the input thread
+typedef enum {
+ STATE_NULL,
+ STATE_ESC, // escape; aborts any active sequence
+ STATE_CSI, // control sequence introducer
+ STATE_DCS, // device control string
+ // XTVERSION replies with DCS > | ... ST
+ STATE_XTVERSION1,
+ STATE_XTVERSION2,
+ // XTGETTCAP replies with DCS 1 + r for a good request, or 0 + r for bad
+ STATE_XTGETTCAP1, // XTGETTCAP, got '0/1' (DCS 0/1 + r Pt ST)
+ STATE_XTGETTCAP2, // XTGETTCAP, got '+' (DCS 0/1 + r Pt ST)
+ STATE_XTGETTCAP3, // XTGETTCAP, got 'r' (DCS 0/1 + r Pt ST)
+ STATE_XTGETTCAP_TERMNAME1, // got property 544E, 'TN' (terminal name) first hex nibble
+ STATE_XTGETTCAP_TERMNAME2, // got property 544E, 'TN' (terminal name) second hex nibble
+ STATE_DCS_DRAIN, // throw away input until we hit escape
+ STATE_APC, // application programming command, starts with \x1b_
+ STATE_APC_DRAIN, // looking for \x1b
+ STATE_APC_ST, // looking for ST
+ STATE_BG1, // got '1'
+ STATE_BG2, // got second '1'
+ STATE_BGSEMI, // got '11;', draining string to ESC ST
+ STATE_TDA1, // tertiary DA, got '!'
+ STATE_TDA2, // tertiary DA, got '|', first hex nibble
+ STATE_TDA3, // tertiary DA, second hex nibble
+ STATE_SDA, // secondary DA (CSI > Pp ; Pv ; Pc c)
+ STATE_SDA_VER, // secondary DA, got semi, reading to next semi
+ STATE_SDA_DRAIN, // drain secondary DA to 'c'
+ STATE_DA, // primary DA (CSI ? ... c) OR XTSMGRAPHICS OR DECRPM or kittykbd
+ STATE_DA_DRAIN, // drain out the primary DA to an alpha
+ STATE_DA_SEMI, // got first semicolon following numeric
+ STATE_DA_SEMI2, // got second semicolon following numeric ; numeric
+ STATE_DA_SEMI3, // got third semicolon following numeric ; numeric ; numeric
+ STATE_APPSYNC_REPORT, // got DECRPT ?2026
+ STATE_APPSYNC_REPORT_DRAIN, // drain out decrpt to 'y'
+ // cursor location report: CSI row ; col R
+ // text area pixel geometry: CSI 4 ; rows ; cols t
+ // text area cell geometry: CSI 8 ; rows ; cols t
+ // so we handle them the same until we hit either a second semicolon or an
+ // 'R' or 't'. at the second ';', we verify that the first variable was
+ // '4' or '8', and continue to 't' via STATE_{PIXELS,CELLS}_WIDTH.
+ STATE_CURSOR_OR_PIXELGEOM, // reading row of cursor location to ';'
+ STATE_CURSOR_COL, // reading col of cursor location to 'R', 't', or ';'
+ STATE_PIXELS_WIDTH, // reading text area width in pixels to ';'
+ STATE_CELLS_WIDTH, // reading text area width in cells to ';'
+} initstates_e;
+
+// local state for the input thread. don't put this large struct on the stack.
typedef struct inputctx {
- int termfd; // terminal fd: -1 with no controlling terminal, or
- // if stdin is a terminal, and on Windows Terminal.
int stdinfd; // bulk in fd. always >= 0 (almost always 0). we do not
// own this descriptor, and must not close() it.
+ int termfd; // terminal fd: -1 with no controlling terminal, or
+ // if stdin is a terminal, or on MSFT Terminal.
#ifdef __MINGW64__
- HANDLE stdinhandle; // handle to input terminal
+ HANDLE stdinhandle; // handle to input terminal for MSFT Terminal
#endif
- unsigned char ibuf[BUFSIZ]; // we dump raw reads into this ringbuffer, and
- // process them all post-read
- int ibufvalid; // we mustn't read() if ibufvalid == sizeof(ibuf)
- int ibufwrite; // we write here next
- int ibufread; // first valid byte here (if any are valid)
+ // these two are not ringbuffers; we always move any leftover materia to the
+ // front of the queue (it ought be a handful of bytes at most).
+ unsigned char ibuf[BUFSIZ]; // might be intermingled bulk/control data
+ unsigned char tbuf[BUFSIZ]; // only used if we have distinct terminal fd
+ int ibufvalid; // we mustn't read() if ibufvalid == sizeof(ibuf)
+ int tbufvalid; // only used if we have distinct terminal connection
+
+ // transient state for processing control sequences
+ // stringstate is the state at which this string was initialized, and can be
+ // one of STATE_XTVERSION1, STATE_XTGETTCAP_TERMNAME1, STATE_TDA1, and STATE_BG1
+ initstates_e state, stringstate;
+ int numeric; // currently-lexed numeric
+ char runstring[BUFSIZ]; // running string (when stringstate != STATE_NULL)
+ int stridx; // length of runstring
+ int p2, p3, p4; // holders for numeric params
+
+ // ringbuffers for processed, structured input
cursorloc* csrs; // cursor reports are dumped here
ncinput* inputs; // processed input is dumped here
int csize, isize; // total number of slots in csrs/inputs
@@ -47,8 +122,15 @@ typedef struct inputctx {
// we cannot write if valid == size
int cread, iread; // slot from which clients read the next csr/input;
// they cannot read if valid == 0
+ pthread_mutex_t ilock; // lock for ncinput ringbuffer, also initial state
+ pthread_cond_t icond; // condvar for ncinput ringbuffer
+ pthread_mutex_t clock; // lock for csrs ringbuffer
+ pthread_cond_t ccond; // condvar for csrs ringbuffer
tinfo* ti; // link back to tinfo
pthread_t tid; // tid for input thread
+
+ struct initial_responses* initdata;
+ struct initial_responses* initdata_complete;
} inputctx;
static inline inputctx*
@@ -59,18 +141,39 @@ create_inputctx(tinfo* ti, FILE* infp){
if( (i->csrs = malloc(sizeof(*i->csrs) * i->csize)) ){
i->isize = BUFSIZ;
if( (i->inputs = malloc(sizeof(*i->inputs) * i->isize)) ){
- 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;
+ if(pthread_mutex_init(&i->ilock, NULL) == 0){
+ if(pthread_cond_init(&i->icond, NULL) == 0){
+ if(pthread_mutex_init(&i->clock, NULL) == 0){
+ if(pthread_cond_init(&i->ccond, NULL) == 0){
+ if((i->stdinfd = fileno(infp)) >= 0){
+ if( (i->initdata = malloc(sizeof(*i->initdata))) ){
+ if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){
+ memset(i->initdata, 0, sizeof(*i->initdata));
+ 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 = 0;
+ i->tbufvalid = 0;
+ i->state = i->stringstate = STATE_NULL;
+ i->numeric = 0;
+ i->stridx = 0;
+ i->initdata_complete = NULL;
+ i->runstring[i->stridx] = '\0';
+ logdebug("input descriptors: %d/%d\n", i->stdinfd, i->termfd);
+ return i;
+ }
+ }
+ free(i->initdata);
+ }
+ pthread_cond_destroy(&i->ccond);
+ }
+ pthread_mutex_destroy(&i->clock);
+ }
+ pthread_cond_destroy(&i->icond);
}
+ pthread_mutex_destroy(&i->ilock);
}
free(i->inputs);
}
@@ -88,79 +191,895 @@ free_inputctx(inputctx* i){
if(i->termfd >= 0){
close(i->termfd);
}
+ pthread_mutex_destroy(&i->ilock);
+ pthread_cond_destroy(&i->icond);
+ pthread_mutex_destroy(&i->clock);
+ pthread_cond_destroy(&i->ccond);
// do not kill the thread here, either.
+ if(i->initdata){
+ free(i->initdata->version);
+ free(i->initdata);
+ }
+ if(i->initdata_complete){
+ free(i->initdata_complete->version);
+ free(i->initdata_complete);
+ }
free(i->inputs);
free(i->csrs);
free(i);
}
}
-// how many bytes can a single read fill in the ibuf? this might be fewer than
-// the actual number of free bytes, due to reading on the right or left side.
-static inline size_t
-space_for_read(inputctx* ictx){
- // if we are valid everywhere, there's no space to read into.
- if(ictx->ibufvalid == sizeof(ictx->ibuf)){
- return 0;
+// populate |buf| with any new data from the specified file descriptor |fd|.
+static void
+read_input_nblock(int fd, unsigned char* buf, size_t buflen, int *bufused){
+ if(fd < 0){
+ return;
}
- // if we are valid nowhere, we can read into the head of the buffer
- if(ictx->ibufvalid == 0){
- ictx->ibufread = 0;
- ictx->ibufwrite = 0;
- return sizeof(ictx->ibuf);
+ size_t space = buflen - *bufused;
+ if(space == 0){
+ return;
}
- // otherwise, we can read either from ibufwrite to the end of the buffer,
- // or from ibufwrite to ibufread.
- if(ictx->ibufwrite < ictx->ibufread){
- return ictx->ibufread - ictx->ibufwrite;
+ ssize_t r = read(fd, buf + *bufused, space);
+ if(r <= 0){
+ if(r < 0){
+ logwarn("couldn't read from %d (%s)\n", fd, strerror(errno));
+ }
+ return;
}
- return sizeof(ictx->ibuf) - ictx->ibufwrite;
+ *bufused += r;
+ space -= r;
+ loginfo("read %lldB from %d (%lluB left)\n", (long long)r, fd, (unsigned long long)space);
+}
+
+// are terminal and stdin distinct for this inputctx?
+static inline bool
+ictx_independent_p(const inputctx* ictx){
+ return ictx->termfd >= 0; // FIXME does this hold on MSFT Terminal?
+}
+
+static int
+ruts_numeric(int* numeric, unsigned char c){
+ if(!isdigit(c)){
+ return -1;
+ }
+ int digit = c - '0';
+ if(INT_MAX / 10 - digit < *numeric){ // would overflow
+ return -1;
+ }
+ *numeric *= 10;
+ *numeric += digit;
+ return 0;
+}
+
+static int
+ruts_hex(int* numeric, unsigned char c){
+ if(!isxdigit(c)){
+ return -1;
+ }
+ int digit;
+ if(isdigit(c)){
+ digit = c - '0';
+ }else if(islower(c)){
+ digit = c - 'a' + 10;
+ }else if(isupper(c)){
+ digit = c - 'A' + 10;
+ }else{
+ return -1; // should be impossible to reach
+ }
+ if(INT_MAX / 10 - digit < *numeric){ // would overflow
+ return -1;
+ }
+ *numeric *= 16;
+ *numeric += digit;
+ return 0;
+}
+
+// add a decoded hex byte to the string
+static int
+ruts_string(inputctx* ictx, initstates_e state){
+ if(ictx->stridx == sizeof(ictx->runstring)){
+ return -1; // overflow, too long
+ }
+ if(ictx->numeric > 255){
+ return -1;
+ }
+ unsigned char c = ictx->numeric;
+ if(!isprint(c)){
+ return -1;
+ }
+ ictx->stringstate = state;
+ ictx->runstring[ictx->stridx] = c;
+ ictx->runstring[++ictx->stridx] = '\0';
+ return 0;
+}
+
+// extract the terminal version from the running string, following 'prefix'
+static int
+extract_version(inputctx* ictx, size_t slen){
+ size_t bytes = strlen(ictx->runstring + slen) + 1;
+ ictx->initdata->version = malloc(bytes);
+ if(ictx->initdata->version == NULL){
+ return -1;
+ }
+ memcpy(ictx->initdata->version, ictx->runstring + slen, bytes);
+ return 0;
+}
+
+static int
+extract_xtversion(inputctx* ictx, size_t slen, char suffix){
+ if(suffix){
+ if(ictx->runstring[ictx->stridx - 1] != suffix){
+ return -1;
+ }
+ ictx->runstring[ictx->stridx - 1] = '\0';
+ }
+ return extract_version(ictx, slen);
+}
+
+static int
+stash_string(inputctx* ictx){
+ struct initial_responses* inits = ictx->initdata;
+//fprintf(stderr, "string terminator after %d [%s]\n", inits->stringstate, inits->runstring);
+ switch(ictx->stringstate){
+ case STATE_XTVERSION1:{
+ static const struct {
+ const char* prefix;
+ char suffix;
+ queried_terminals_e term;
+ } xtvers[] = {
+ { .prefix = "XTerm(", .suffix = ')', .term = TERMINAL_XTERM, },
+ { .prefix = "WezTerm ", .suffix = 0, .term = TERMINAL_WEZTERM, },
+ { .prefix = "contour ", .suffix = 0, .term = TERMINAL_CONTOUR, },
+ { .prefix = "kitty(", .suffix = ')', .term = TERMINAL_KITTY, },
+ { .prefix = "foot(", .suffix = ')', .term = TERMINAL_FOOT, },
+ { .prefix = "mlterm(", .suffix = ')', .term = TERMINAL_MLTERM, },
+ { .prefix = "tmux ", .suffix = 0, .term = TERMINAL_TMUX, },
+ { .prefix = "iTerm2 ", .suffix = 0, .term = TERMINAL_ITERM, },
+ { .prefix = "mintty ", .suffix = 0, .term = TERMINAL_MINTTY, },
+ { .prefix = NULL, .suffix = 0, .term = TERMINAL_UNKNOWN, },
+ }, *xtv;
+ for(xtv = xtvers ; xtv->prefix ; ++xtv){
+ if(strncmp(ictx->runstring, xtv->prefix, strlen(xtv->prefix)) == 0){
+ if(extract_xtversion(ictx, strlen(xtv->prefix), xtv->suffix) == 0){
+ inits->qterm = xtv->term;
+ }
+ break;
+ }
+ }
+ if(xtv->prefix == NULL){
+ logwarn("Unrecognizable XTVERSION [%s]\n", ictx->runstring);
+ }
+ break;
+ }case STATE_XTGETTCAP_TERMNAME1:
+ if(strcmp(ictx->runstring, "xterm-kitty") == 0){
+ inits->qterm = TERMINAL_KITTY;
+ }else if(strcmp(ictx->runstring, "mlterm") == 0){
+ // MLterm prior to late 3.9.1 only reports via XTGETTCAP
+ inits->qterm = TERMINAL_MLTERM;
+ }
+ break;
+ case STATE_TDA1:
+ if(strcmp(ictx->runstring, "~VTE") == 0){
+ inits->qterm = TERMINAL_VTE;
+ }else if(strcmp(ictx->runstring, "~~TY") == 0){
+ inits->qterm = TERMINAL_TERMINOLOGY;
+ }else if(strcmp(ictx->runstring, "FOOT") == 0){
+ inits->qterm = TERMINAL_FOOT;
+ }
+ break;
+ case STATE_BG1:{
+ int r, g, b;
+ if(sscanf(ictx->runstring, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3){
+ // great! =]
+ }else if(sscanf(ictx->runstring, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3){
+ r /= 256;
+ g /= 256;
+ b /= 256;
+ }else{
+ break;
+ }
+ inits->bg = (r << 16u) | (g << 8u) | b;
+ break;
+ }default:
+// don't generally enable this -- XTerm terminates TDA with ST
+//fprintf(stderr, "invalid string [%s] stashed %d\n", inits->runstring, inits->stringstate);
+ break;
+ }
+ ictx->runstring[0] = '\0';
+ ictx->stridx = 0;
+ return 0;
+}
+
+// use the version extracted from Secondary Device Attributes, assuming that
+// it is Alacritty (we ought check the specified terminfo database entry).
+// Alacritty writes its crate version with each more significant portion
+// multiplied by 100^{portion ID}, where major, minor, patch are 2, 1, 0.
+// what happens when a component exceeds 99? who cares. support XTVERSION.
+static char*
+set_sda_version(inputctx* ictx){
+ int maj, min, patch;
+ if(ictx->numeric <= 0){
+ return NULL;
+ }
+ maj = ictx->numeric / 10000;
+ min = (ictx->numeric % 10000) / 100;
+ patch = ictx->numeric % 100;
+ if(maj >= 100 || min >= 100 || patch >= 100){
+ return NULL;
+ }
+ // 3x components (two digits max each), 2x '.', NUL would suggest 9 bytes,
+ // but older gcc __builtin___sprintf_chk insists on 13. fuck it. FIXME.
+ char* buf = malloc(13);
+ if(buf){
+ sprintf(buf, "%d.%d.%d", maj, min, patch);
+ }
+ return buf;
+}
+
+// FIXME ought implement the full Williams automaton
+// FIXME sloppy af in general
+// returns 1 after handling the Device Attributes response, 0 if more input
+// ought be fed to the machine, and -1 on an invalid state transition.
+static int
+pump_control_read(inputctx* ictx, unsigned char c){
+ logdebug("state: %2d char: %1c %3d %02x\n", ictx->state, isprint(c) ? c : ' ', c, c);
+ if(c == NCKEY_ESC){
+ ictx->state = STATE_ESC;
+ return 0;
+ }
+ switch(ictx->state){
+ case STATE_NULL:
+ // not an escape -- throw into user queue
+ break;
+ case STATE_ESC:
+ ictx->numeric = 0;
+ if(c == '['){
+ ictx->state = STATE_CSI;
+ }else if(c == 'P'){
+ ictx->state = STATE_DCS;
+ }else if(c == '\\'){
+ if(stash_string(ictx)){
+ return -1;
+ }
+ ictx->state = STATE_NULL;
+ }else if(c == '1'){
+ ictx->state = STATE_BG1;
+ }else if(c == '_'){
+ ictx->state = STATE_APC;
+ }
+ break;
+ case STATE_APC:
+ if(c == 'G'){
+ ictx->initdata->kitty_graphics = true;
+ }
+ ictx->state = STATE_APC_DRAIN;
+ break;
+ case STATE_APC_DRAIN:
+ if(c == '\x1b'){
+ ictx->state = STATE_APC_ST;
+ }
+ break;
+ case STATE_APC_ST:
+ if(c == '\\'){
+ ictx->state = STATE_NULL;
+ }else{
+ ictx->state = STATE_APC_DRAIN;
+ }
+ break;
+ case STATE_BG1:
+ if(c == '1'){
+ ictx->state = STATE_BG2;
+ }else{
+ // FIXME
+ }
+ break;
+ case STATE_BG2:
+ if(c == ';'){
+ ictx->state = STATE_BGSEMI;
+ ictx->stridx = 0;
+ ictx->runstring[0] = '\0';
+ }else{
+ // FIXME
+ }
+ break;
+ case STATE_BGSEMI: // drain string
+ if(c == '\x07'){ // contour sends this at the end for some unknown reason
+ if(stash_string(ictx)){
+ return -1;
+ }
+ ictx->state = STATE_NULL;
+ break;
+ }
+ ictx->numeric = c;
+ if(ruts_string(ictx, STATE_BG1)){
+ return -1;
+ }
+ break;
+ case STATE_CSI: // terminated by 0x40--0x7E ('@'--'~')
+ if(c == '?'){
+ ictx->state = STATE_DA; // could also be DECRPM/XTSMGRAPHICS/kittykbd
+ }else if(c == '>'){
+ // SDA yields up Alacritty's crate version, but it doesn't unambiguously
+ // identify Alacritty. If we've got any other version information, skip
+ // directly to STATE_SDA_DRAIN, rather than doing STATE_SDA_VER.
+ if(ictx->initdata->qterm || ictx->initdata->version){
+ loginfo("Identified terminal already; ignoring DA2\n");
+ ictx->state = STATE_SDA_DRAIN;
+ }else{
+ ictx->state = STATE_SDA;
+ }
+ }else if(isdigit(c)){
+ ictx->numeric = 0;
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ ictx->state = STATE_CURSOR_OR_PIXELGEOM;
+ }else if(c >= 0x40 && c <= 0x7E){
+ ictx->state = STATE_NULL;
+ }
+ break;
+ case STATE_CURSOR_OR_PIXELGEOM:
+ if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == ';'){
+ ictx->p2 = ictx->numeric;
+ ictx->state = STATE_CURSOR_COL;
+ ictx->numeric = 0;
+ }else{
+ ictx->state = STATE_NULL;
+ }
+ break;
+ case STATE_CURSOR_COL:
+ if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == 'R'){
+//fprintf(stderr, "CURSOR X: %d\n", ictx->numeric);
+ if(ictx->initdata){
+ ictx->initdata->cursorx = ictx->numeric - 1;
+ ictx->initdata->cursory = ictx->p2 - 1;
+ }else{
+ pthread_mutex_lock(&ictx->clock);
+ if(ictx->cvalid == ictx->csize){
+ pthread_mutex_unlock(&ictx->clock);
+ logwarn("dropping cursor location report\n");
+ }else{
+ cursorloc* cloc = &ictx->csrs[ictx->cwrite];
+ cloc->x = ictx->numeric - 1;
+ cloc->y = ictx->p2 - 1;
+ if(++ictx->cwrite == ictx->csize){
+ ictx->cwrite = 0;
+ }
+ ++ictx->cvalid;
+ pthread_mutex_unlock(&ictx->clock);
+ pthread_cond_broadcast(&ictx->ccond);
+ }
+ }
+ ictx->state = STATE_NULL;
+ }else if(c == 't'){
+//fprintf(stderr, "CELLS X: %d\n", ictx->numeric);
+ ictx->initdata->dimx = ictx->numeric;
+ ictx->initdata->dimy = ictx->p2;
+ ictx->state = STATE_NULL;
+ }else if(c == ';'){
+ if(ictx->p2 == 4){
+ ictx->initdata->pixy = ictx->numeric;
+ ictx->state = STATE_PIXELS_WIDTH;
+ ictx->numeric = 0;
+ }else if(ictx->p2 == 8){
+ ictx->initdata->dimy = ictx->numeric;
+ ictx->state = STATE_CELLS_WIDTH;
+ ictx->numeric = 0;
+ }else{
+ logerror("expected 4 to lead pixel report, got %d\n", ictx->p2);
+ return -1;
+ }
+ }else{
+ ictx->state = STATE_NULL;
+ }
+ break;
+ case STATE_PIXELS_WIDTH:
+ if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == 't'){
+ ictx->initdata->pixx = ictx->numeric;
+ loginfo("got pixel geometry: %d/%d\n", ictx->initdata->pixy, ictx->initdata->pixx);
+ ictx->state = STATE_NULL;
+ }else{
+ ictx->state = STATE_NULL;
+ }
+ break;
+ case STATE_CELLS_WIDTH:
+ if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == 't'){
+ ictx->initdata->dimx = ictx->numeric;
+ loginfo("got cell geometry: %d/%d\n", ictx->initdata->dimy, ictx->initdata->dimx);
+ ictx->state = STATE_NULL;
+ }else{
+ ictx->state = STATE_NULL;
+ }
+ break;
+ case STATE_DCS: // terminated by ST
+ if(c == '\\'){
+//fprintf(stderr, "terminated DCS\n");
+ ictx->state = STATE_NULL;
+ }else if(c == '1'){
+ ictx->state = STATE_XTGETTCAP1; // we have tcap
+ }else if(c == '0'){
+ ictx->state = STATE_XTGETTCAP1; // no tcap for us
+ }else if(c == '>'){
+ ictx->state = STATE_XTVERSION1;
+ }else if(c == '!'){
+ ictx->state = STATE_TDA1;
+ }else{
+ ictx->state = STATE_DCS_DRAIN;
+ }
+ break;
+ case STATE_DCS_DRAIN:
+ // we drain to ST, which is an escape, and thus already handled, so...
+ break;
+ case STATE_XTVERSION1:
+ if(c == '|'){
+ ictx->state = STATE_XTVERSION2;
+ ictx->stridx = 0;
+ ictx->runstring[0] = '\0';
+ }else{
+ // FIXME error?
+ }
+ break;
+ case STATE_XTVERSION2:
+ ictx->numeric = c;
+ if(ruts_string(ictx, STATE_XTVERSION1)){
+ return -1;
+ }
+ break;
+ case STATE_XTGETTCAP1:
+ if(c == '+'){
+ ictx->state = STATE_XTGETTCAP2;
+ }else{
+ // FIXME malformed
+ }
+ break;
+ case STATE_XTGETTCAP2:
+ if(c == 'r'){
+ ictx->state = STATE_XTGETTCAP3;
+ }else{
+ // FIXME malformed
+ }
+ break;
+ case STATE_XTGETTCAP3:
+ if(c == '='){
+ if(ictx->numeric == 0x544e){
+ ictx->state = STATE_XTGETTCAP_TERMNAME1;
+ ictx->stridx = 0;
+ ictx->numeric = 0;
+ ictx->runstring[0] = '\0';
+ }else{
+ ictx->state = STATE_DCS_DRAIN;
+ }
+ }else if(ruts_hex(&ictx->numeric, c)){
+ return -1;
+ }
+ break;
+ case STATE_XTGETTCAP_TERMNAME1:
+ if(ruts_hex(&ictx->numeric, c)){
+ return -1;
+ }
+ ictx->state = STATE_XTGETTCAP_TERMNAME2;
+ break;
+ case STATE_XTGETTCAP_TERMNAME2:
+ if(ruts_hex(&ictx->numeric, c)){
+ return -1;
+ }
+ ictx->state = STATE_XTGETTCAP_TERMNAME1;
+ if(ruts_string(ictx, STATE_XTGETTCAP_TERMNAME1)){
+ return -1;
+ }
+ ictx->numeric = 0;
+ break;
+ case STATE_TDA1:
+ if(c == '|'){
+ ictx->state = STATE_TDA2;
+ ictx->stridx = 0;
+ ictx->runstring[0] = '\0';
+ }else{
+ // FIXME
+ }
+ break;
+ case STATE_TDA2:
+ if(ruts_hex(&ictx->numeric, c)){
+ return -1;
+ }
+ ictx->state = STATE_TDA3;
+ break;
+ case STATE_TDA3:
+ if(ruts_hex(&ictx->numeric, c)){
+ return -1;
+ }
+ ictx->state = STATE_TDA2;
+ if(ruts_string(ictx, STATE_TDA1)){
+ ictx->state = STATE_DCS_DRAIN; // FIXME return -1?
+ }
+ ictx->numeric = 0;
+ break;
+ case STATE_SDA:
+ if(c == ';'){
+ ictx->state = STATE_SDA_VER;
+ ictx->numeric = 0;
+ }else if(c == 'c'){
+ ictx->state = STATE_NULL;
+ }
+ break;
+ case STATE_SDA_VER:
+ if(c == ';'){
+ ictx->state = STATE_SDA_DRAIN;
+ loginfo("Got DA2 Pv: %u\n", ictx->numeric);
+ // if a version was set, we couldn't have arrived here. alacritty
+ // writes its crate version here, in an encoded form. nothing else
+ // necessarily does, though, so allow failure. this value will be
+ // interpreted as the version only if TERM indicates alacritty.
+ ictx->initdata->version = set_sda_version(ictx);
+ }else if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ break;
+ case STATE_SDA_DRAIN:
+ if(c == 'c'){
+ ictx->state = STATE_NULL;
+ }
+ break;
+ // primary device attributes and XTSMGRAPHICS replies are generally
+ // indistinguishable until well into the escape. one can get:
+ // XTSMGRAPHICS: CSI ? Pi ; Ps ; Pv S {Pi: 123} {Ps: 0123}
+ // DECRPM: CSI ? Pd ; Ps $ y {Pd: many} {Ps: 01234}
+ // DA: CSI ? 1 ; 2 c ("VT100 with Advanced Video Option")
+ // CSI ? 1 ; 0 c ("VT101 with No Options")
+ // CSI ? 4 ; 6 c ("VT132 with Advanced Video and Graphics")
+ // CSI ? 6 c ("VT102")
+ // CSI ? 7 c ("VT131")
+ // CSI ? 1 2 ; Ps c ("VT125")
+ // CSI ? 6 2 ; Ps c ("VT220")
+ // CSI ? 6 3 ; Ps c ("VT320")
+ // CSI ? 6 4 ; Ps c ("VT420")
+ // KITTYKBD: CSI ? flags u
+ case STATE_DA: // return success on end of DA
+//fprintf(stderr, "DA: %c\n", c);
+ // FIXME several of these numbers could be DECRPM/XTSM/kittykbd. probably
+ // just want to read number, *then* make transition on non-number.
+ if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){ // stash for DECRPM/XTSM/kittykbd
+ return -1;
+ }
+ }else if(c == 'u'){ // kitty keyboard
+ loginfo("keyboard protocol 0x%x\n", ictx->numeric);
+ ictx->state = STATE_NULL;
+ }else if(c == ';'){
+ ictx->p2 = ictx->numeric;
+ ictx->numeric = 0;
+ ictx->state = STATE_DA_SEMI;
+ }else if(c >= 0x40 && c <= 0x7E){
+ ictx->state = STATE_NULL;
+ if(c == 'c'){
+ return 1;
+ }
+ }
+ break;
+ case STATE_DA_SEMI:
+ if(c == ';'){
+ ictx->p3 = ictx->numeric;
+ ictx->numeric = 0;
+ ictx->state = STATE_DA_SEMI2;
+ }else if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == '$'){
+ if(ictx->p2 == 2026){
+ ictx->state = STATE_APPSYNC_REPORT;
+ loginfo("terminal reported SUM support\n");
+ }else{
+ ictx->state = STATE_APPSYNC_REPORT_DRAIN;
+ }
+ }else if(c >= 0x40 && c <= 0x7E){
+ ictx->state = STATE_NULL;
+ if(c == 'c'){
+ return 1;
+ }
+ }
+ break;
+ case STATE_DA_SEMI2:
+ if(c == ';'){
+ ictx->p4 = ictx->numeric;
+ ictx->numeric = 0;
+ ictx->state = STATE_DA_SEMI3;
+ }else if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == 'S'){
+ if(ictx->p2 == 1){
+ ictx->initdata->color_registers = ictx->numeric;
+ loginfo("sixel color registers: %d\n", ictx->initdata->color_registers);
+ ictx->numeric = 0;
+ }
+ ictx->state = STATE_NULL;
+ }else if(c >= 0x40 && c <= 0x7E){
+ ictx->state = STATE_NULL;
+ if(c == 'c'){
+ return 1;
+ }
+ }
+ break;
+ case STATE_DA_DRAIN:
+ if(c >= 0x40 && c <= 0x7E){
+ ictx->state = STATE_NULL;
+ if(c == 'c'){
+ return 1;
+ }
+ }
+ break;
+ case STATE_DA_SEMI3:
+ if(c == ';'){
+ ictx->numeric = 0;
+ ictx->state = STATE_DA_DRAIN;
+ }else if(isdigit(c)){
+ if(ruts_numeric(&ictx->numeric, c)){
+ return -1;
+ }
+ }else if(c == 'S'){
+ ictx->initdata->sixelx = ictx->p4;
+ ictx->initdata->sixely = ictx->numeric;
+ loginfo("max sixel geometry: %dx%d\n", ictx->initdata->sixely, ictx->initdata->sixelx);
+ }else if(c >= 0x40 && c <= 0x7E){
+ ictx->state = STATE_NULL;
+ if(c == 'c'){
+ return 1;
+ }
+ }
+ break;
+ case STATE_APPSYNC_REPORT:
+ if(ictx->numeric == '2'){
+ ictx->initdata->appsync_supported = 1;
+ ictx->state = STATE_APPSYNC_REPORT_DRAIN;
+ }
+ break;
+ case STATE_APPSYNC_REPORT_DRAIN:
+ if(c == 'y'){
+ ictx->state = STATE_NULL;
+ }
+ break;
+ default:
+ fprintf(stderr, "Reached invalid init state %d\n", ictx->state);
+ return -1;
+ }
+ return 0;
}
-// populate the ibuf with any new data from the specified file descriptor.
static void
-read_input_nblock(inputctx* ictx, int fd){
- if(fd >= 0){
- size_t space = space_for_read(ictx);
- if(space == 0){
- return;
+handoff_initial_responses(inputctx* ictx){
+ pthread_mutex_lock(&ictx->ilock);
+ ictx->initdata_complete = ictx->initdata;
+ ictx->initdata = NULL;
+ pthread_mutex_unlock(&ictx->ilock);
+ pthread_cond_broadcast(&ictx->icond);
+}
+
+// try to lex a control sequence off of buf. return the number of bytes
+// consumed if we do so, and -1 otherwise. buf is *not* necessarily
+// NUL-terminated. precondition: buflen >= 1.
+// FIXME we ought play complete failures into the general input buffer?
+static int
+process_escape(inputctx* ictx, const unsigned char* buf, int buflen){
+ int used = 0;
+ while(used < buflen){
+ int r = pump_control_read(ictx, buf[used]);
+ if(r == 1){
+ handoff_initial_responses(ictx);
}
- ssize_t r = read(fd, ictx->ibuf + ictx->ibufwrite, space);
- if(r >= 0){
- ictx->ibufwrite += r;
- if(ictx->ibufwrite == sizeof(ictx->ibuf)){
- ictx->ibufwrite = 0;
- }
- ictx->ibufvalid += r;
- space -= r;
- loginfo("read %lldB from %d (%lluB left)\n", (long long)r, fd, (unsigned long long)space);
- if(space == 0){
- return;
- }
- // might have been falsely limited by space (only reading on the right).
- // this will recurse one time at most.
- if(ictx->ibufwrite == 0){
- read_input_nblock(ictx, fd);
- }
+ ++used;
+ }
+ return used;
+}
+
+// process as many control sequences from |buf|, having |bufused| bytes,
+// as we can. anything not a valid control sequence is dropped. this text
+// needn't be valid UTF-8.
+static void
+process_escapes(inputctx* ictx, unsigned char* buf, int* bufused){
+ int offset = 0;
+ while(*bufused){
+ int consumed = process_escape(ictx, buf + offset, *bufused);
+ if(consumed < 0){
+ break;
}
+ *bufused -= consumed;
+ offset += consumed;
+ }
+ // move any leftovers to the front; only happens if we fill output queue
+ if(*bufused){
+ memmove(buf, buf + offset, *bufused);
+ }
+}
+
+// precondition: buflen >= 1.
+static int
+process_input(const unsigned char* buf, int buflen, ncinput* ni){
+ memset(ni, 0, sizeof(*ni));
+ if(buf[0] < 0x80){ // pure ascii can't show up mid-utf8
+ ni->id = buf[0];
+ return 1;
+ }
+ fprintf(stderr, "wanna parse us up an input! %d\n", buflen);
+ // FIXME extract supraascii UTF8, modifiers, mice
+ return 0;
+}
+
+// precondition: buflen >= 1. gets an ncinput prepared by process_input, and
+// sticks that into the bulk queue.
+static int
+process_ncinput(inputctx* ictx, const unsigned char* buf, int buflen){
+ if(ictx->ivalid == sizeof(ictx->ivalid)){
+ logwarn("blocking on input output queue (%d+%d)\n", ictx->ivalid, buflen);
+ return 0;
+ }
+ ncinput* ni = ictx->inputs + ictx->iwrite;
+ int r = process_input(buf, buflen, ni);
+ if(r > 0){
+ if(++ictx->iwrite == sizeof(ictx->ivalid)){
+ ictx->iwrite = 0;
+ }
+ }
+ return r;
+}
+
+// process as much bulk UTF-8 input as we can, knowing it to be free of control
+// sequences. anything not a valid UTF-8 character is dropped. a control
+// sequence will be chopped up and passed up (assuming it to be valid UTF-8).
+static void
+process_bulk(inputctx* ictx, unsigned char* buf, int* bufused){
+ int offset = 0;
+ while(*bufused){
+ int consumed = process_ncinput(ictx, buf + offset, *bufused);
+ if(consumed <= 0){
+ break;
+ }
+ }
+ // move any leftovers to the front
+ if(*bufused){
+ memmove(buf, buf + offset, *bufused);
+ }
+}
+
+// process as much mixed input as we can. we might find UTF-8 bulk input and
+// control sequences mixed (though each individual character/sequence ought be
+// contiguous). known control sequences are removed for internal processing.
+// everything else will be handed up to the client (assuming it to be valid
+// UTF-8).
+static void
+process_melange(inputctx* ictx, const unsigned char* buf, int* bufused){
+ int offset = 0;
+ while(*bufused){
+ logdebug("input %d/%d [0x%02x]\n", offset, *bufused, buf[offset]);
+ int consumed = 0;
+ if(buf[offset] == '\x1b'){
+ consumed = process_escape(ictx, buf + offset, *bufused);
+ }else{
+ consumed = process_ncinput(ictx, buf + offset, *bufused);
+ }
+ if(consumed < 0){
+ break;
+ }
+ *bufused -= consumed;
+ offset += consumed;
}
}
// walk the matching automaton from wherever we were.
static void
process_ibuf(inputctx* ictx){
- // FIXME
+ if(ictx->tbufvalid){
+ // we could theoretically do this in parallel with process_bulk, but it
+ // hardly seems worthwhile without breaking apart the fetches of input.
+ process_escapes(ictx, ictx->tbuf, &ictx->tbufvalid);
+ }
+ if(ictx->ibufvalid){
+ if(ictx_independent_p(ictx)){
+ process_bulk(ictx, ictx->ibuf, &ictx->ibufvalid);
+ }else{
+ int valid = ictx->ibufvalid;
+ process_melange(ictx, ictx->ibuf, &ictx->ibufvalid);
+ // move any leftovers to the front
+ if(ictx->ibufvalid){
+ memmove(ictx->ibuf, ictx->ibuf + valid - ictx->ibufvalid, ictx->ibufvalid);
+ }
+ }
+ }
+}
+
+int ncinput_shovel(inputctx* ictx, const void* buf, int len){
+ process_melange(ictx, buf, &len);
+ if(len){
+ logwarn("dropping %d byte%s\n", len, len == 1 ? "" : "s");
+ }
+ return 0;
+}
+
+static int
+block_on_input(inputctx* ictx){
+ struct timespec* ts = NULL; // FIXME
+#ifdef __MINGW64__
+ int timeoutms = ts ? ts->tv_sec * 1000 + ts->tv_nsec / 1000000 : -1;
+ DWORD d = WaitForMultipleObjects(1, &ti->inhandle, FALSE, timeoutms);
+ if(d == WAIT_TIMEOUT){
+ return 0;
+ }else if(d == WAIT_FAILED){
+ return -1;
+ }else if(d - WAIT_OBJECT_0 == 0){
+ return 1;
+ }
+ return -1;
+#else
+ int inevents = POLLIN;
+#ifdef POLLRDHUP
+ inevents |= POLLRDHUP;
+#endif
+ struct pollfd pfds[2] = {
+ {
+ .fd = ictx->stdinfd,
+ .events = inevents,
+ .revents = 0,
+ }
+ };
+ int pfdcount = 1;
+ if(ictx->termfd >= 0){
+ pfds[pfdcount].fd = ictx->termfd;
+ pfds[pfdcount].events = inevents;
+ pfds[pfdcount].revents = 0;
+ ++pfdcount;
+ }
+ int events;
+#if defined(__APPLE__) || defined(__MINGW64__)
+ int timeoutms = ts ? ts->tv_sec * 1000 + ts->tv_nsec / 1000000 : -1;
+ while((events = poll(pfds, pfdcount, timeoutms)) < 0){ // FIXME smask?
+#else
+ sigset_t smask;
+ sigfillset(&smask);
+ sigdelset(&smask, SIGCONT);
+ sigdelset(&smask, SIGWINCH);
+ while((events = ppoll(pfds, pfdcount, ts, &smask)) < 0){
+#endif
+ if(errno != EINTR && errno != EAGAIN && errno != EBUSY && errno != EWOULDBLOCK){
+ return -1;
+ }
+ if(resize_seen){
+ return 1;
+ }
+ }
+ return events;
+#endif
}
// populate the ibuf with any new data, up through its size, but do not block.
// don't loop around this call without some kind of readiness notification.
static void
read_inputs_nblock(inputctx* ictx){
+ block_on_input(ictx);
// first we read from the terminal, if that's a distinct source.
- read_input_nblock(ictx, ictx->termfd);
+ read_input_nblock(ictx->termfd, ictx->tbuf, sizeof(ictx->tbuf),
+ &ictx->tbufvalid);
// now read bulk, possibly with term escapes intermingled within (if there
// was not a distinct terminal source).
- read_input_nblock(ictx, ictx->stdinfd);
+ read_input_nblock(ictx->stdinfd, ictx->ibuf, sizeof(ictx->ibuf),
+ &ictx->ibufvalid);
}
static void*
@@ -206,8 +1125,20 @@ 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){
+static inline uint32_t
+internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni,
+ int lmargin, int tmargin){
+ pthread_mutex_lock(&ictx->ilock);
+ while(!ictx->ivalid){
+ pthread_cond_wait(&ictx->icond, &ictx->ilock);
+ }
+ memcpy(ni, &ictx->inputs[ictx->iread], sizeof(*ni));
+ if(++ictx->iread == ictx->isize){
+ ictx->iread = 0;
+ }
+ pthread_mutex_unlock(&ictx->ilock);
+ // FIXME adjust mouse coordinates for margins
+ return ni->id;
/*
uint32_t r = ncinputlayer_prestamp(&nc->tcache, ts, ni,
nc->margin_l, nc->margin_t);
@@ -216,11 +1147,35 @@ uint32_t notcurses_get(notcurses* nc, const struct timespec* ts, ncinput* ni){
if(ni){
ni->seqnum = stamp;
}
- ++nc->stats.s.input_events;
}
return r;
*/
- return -1;
+}
+
+struct initial_responses* inputlayer_get_responses(inputctx* ictx){
+ struct initial_responses* iresp;
+ pthread_mutex_lock(&ictx->ilock);
+ while(!ictx->initdata_complete){
+ pthread_cond_wait(&ictx->icond, &ictx->ilock);
+ }
+ iresp = ictx->initdata_complete;
+ ictx->initdata_complete = NULL;
+ pthread_mutex_unlock(&ictx->ilock);
+ return iresp;
+}
+
+// infp has already been set non-blocking
+uint32_t notcurses_get(notcurses* nc, const struct timespec* ts, ncinput* ni){
+ uint32_t r = internal_get(nc->tcache.ictx, ts, ni,
+ nc->margin_l, nc->margin_t);
+ if(r != (uint32_t)-1){
+ ++nc->stats.s.input_events;
+ }
+ return r;
+}
+
+uint32_t ncdirect_get(ncdirect* n, const struct timespec* ts, ncinput* ni){
+ return internal_get(n->tcache.ictx, ts, ni, 0, 0);
}
uint32_t notcurses_getc(notcurses* nc, const struct timespec* ts,
@@ -229,23 +1184,88 @@ uint32_t notcurses_getc(notcurses* nc, const struct timespec* ts,
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);
}
+int get_cursor_location(struct inputctx* ictx, int* y, int* x){
+ pthread_mutex_lock(&ictx->clock);
+ while(ictx->cvalid == 0){
+ pthread_cond_wait(&ictx->ccond, &ictx->clock);
+ }
+ const cursorloc* cloc = &ictx->csrs[ictx->cread];
+ if(++ictx->cread == ictx->csize){
+ ictx->cread = 0;
+ }
+ --ictx->cvalid;
+ *y = cloc->y;
+ *x = cloc->x;
+ pthread_mutex_unlock(&ictx->clock);
+ return 0;
+}
+
+// Disable signals originating from the terminal's line discipline, i.e.
+// SIGINT (^C), SIGQUIT (^\), and SIGTSTP (^Z). They are enabled by default.
+int notcurses_linesigs_disable(notcurses* n){
+#ifndef __MINGW64__
+ if(n->tcache.ttyfd < 0){
+ return 0;
+ }
+ struct termios tios;
+ if(tcgetattr(n->tcache.ttyfd, &tios)){
+ logerror("Couldn't preserve terminal state for %d (%s)\n", n->tcache.ttyfd, strerror(errno));
+ return -1;
+ }
+ tios.c_lflag &= ~ISIG;
+ if(tcsetattr(n->tcache.ttyfd, TCSANOW, &tios)){
+ logerror("Error disabling signals on %d (%s)\n", n->tcache.ttyfd, strerror(errno));
+ return -1;
+ }
+#else
+ DWORD mode;
+ if(!GetConsoleMode(n->tcache.inhandle, &mode)){
+ logerror("error acquiring input mode\n");
+ return -1;
+ }
+ mode &= ~ENABLE_PROCESSED_INPUT;
+ if(!SetConsoleMode(n->tcache.inhandle, mode)){
+ logerror("error setting input mode\n");
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+// Restore signals originating from the terminal's line discipline, i.e.
+// SIGINT (^C), SIGQUIT (^\), and SIGTSTP (^Z), if disabled.
+int notcurses_linesigs_enable(notcurses* n){
+#ifndef __MINGW64__
+ if(n->tcache.ttyfd < 0){
+ return 0;
+ }
+ struct termios tios;
+ if(tcgetattr(n->tcache.ttyfd, &tios)){
+ logerror("Couldn't preserve terminal state for %d (%s)\n", n->tcache.ttyfd, strerror(errno));
+ return -1;
+ }
+ tios.c_lflag |= ~ISIG;
+ if(tcsetattr(n->tcache.ttyfd, TCSANOW, &tios)){
+ logerror("Error disabling signals on %d (%s)\n", n->tcache.ttyfd, strerror(errno));
+ return -1;
+ }
+#else
+ DWORD mode;
+ if(!GetConsoleMode(n->tcache.inhandle, &mode)){
+ logerror("error acquiring input mode\n");
+ return -1;
+ }
+ mode |= ENABLE_PROCESSED_INPUT;
+ if(!SetConsoleMode(n->tcache.inhandle, mode)){
+ logerror("error setting input mode\n");
+ return -1;
+ }
+#endif
+ return 0;
+}
diff --git a/src/lib/in.h b/src/lib/in.h
new file mode 100644
index 000000000..311061f69
--- /dev/null
+++ b/src/lib/in.h
@@ -0,0 +1,82 @@
+#ifndef NOTCURSES_IN
+#define NOTCURSES_IN
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// internal header, not installed
+
+#include
+
+struct tinfo;
+struct inputctx;
+
+int init_inputlayer(struct tinfo* ti, FILE* infp)
+ __attribute__ ((nonnull (1, 2)));
+
+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, 2, 3)));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/lib/input.h b/src/lib/input.h
deleted file mode 100644
index 24fbbd5f0..000000000
--- a/src/lib/input.h
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef NOTCURSES_INPUT
-#define NOTCURSES_INPUT
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// internal header, not installed
-
-#include
-
-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
diff --git a/src/lib/internal.h b/src/lib/internal.h
index e2d162c90..b02b8babc 100644
--- a/src/lib/internal.h
+++ b/src/lib/internal.h
@@ -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.
diff --git a/src/lib/linux.c b/src/lib/linux.c
index d93d13476..17fecf4e1 100644
--- a/src/lib/linux.c
+++ b/src/lib/linux.c
@@ -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);
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 964d8e1dc..0ccf2f957 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -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[tcache.ttyfd)){
- ret = -1;
- }
+ if(tty_emit("\x1b[tcache.ttyfd)){
+ ret = -1;
}
if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
if(sprite_clear_all(&nc->tcache, f)){ // send this to f
@@ -1001,7 +998,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));
diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c
index aa0034de0..aed57e99d 100644
--- a/src/lib/termdesc.c
+++ b/src/lib/termdesc.c
@@ -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
@@ -780,6 +779,9 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
goto err;
}
}
+ if(init_inputlayer(ti, stdin)){
+ goto err;
+ }
#ifndef __MINGW64__
// windows doesn't really have a concept of terminfo. you might ssh into other
// machines, but they'll use the terminfo installed thereon (putty, etc.).
@@ -916,19 +918,51 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
goto err;
}
}
- unsigned appsync_advertised = 0;
- unsigned kittygraphs = 0;
- if(init_inputlayer(ti, stdin)){
- 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(ncinputlayer_init(ti, stdin, &ti->qterm, &appsync_advertised,
- cursor_y, cursor_x, stats, &kittygraphs)){
- goto err;
- }
- */
if(nocbreak){
if(ti->ttyfd >= 0){
+ // FIXME do this in input later, upon signaling completion?
if(tcsetattr(ti->ttyfd, TCSANOW, ti->tpreserved)){
goto err;
}
@@ -939,11 +973,6 @@ 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)){
@@ -951,8 +980,8 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
}
build_supported_styles(ti);
if(ti->pixel_draw == NULL && ti->pixel_draw_late == NULL){
- if(kittygraphs){
- setup_kitty_bitmaps(ti, ti->ttyfd, KITTY_SELFREF);
+ if(kitty_graphics){
+ setup_kitty_bitmaps(ti, ti->ttyfd, KITTY_ANIMATION);
}
// our current sixel quantization algorithm requires at least 64 color
// registers. we make use of no more than 256. this needs to happen
@@ -1028,18 +1057,13 @@ int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){
if(tty_emit(u7, fd)){
return -1;
}
- // 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;
+ get_cursor_location(ti->ictx, cursor_y, cursor_x);
+ loginfo("got a report from %d %d/%d\n", fd, *cursor_y, *cursor_x);
if(ti->inverted_cursor){
int tmp = *cursor_y;
*cursor_y = *cursor_x;
*cursor_x = tmp;
}
- free(clr);
- */
return 0;
}
diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h
index 144559b71..3c6b99cf6 100644
--- a/src/lib/termdesc.h
+++ b/src/lib/termdesc.h
@@ -13,7 +13,6 @@ extern "C" {
#include
#include
#include
-#include "input.h"
#include "fbuf.h"
#include "in.h"
@@ -89,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
@@ -208,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__
@@ -384,6 +344,8 @@ leave_alternate_screen(FILE* fp, tinfo* ti){
return 0;
}
+int cbreak_mode(tinfo* ti);
+
#ifdef __cplusplus
}
#endif