You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
notcurses/src/lib/termdesc.h

357 lines
15 KiB
C

#ifndef NOTCURSES_TERMDESC
#define NOTCURSES_TERMDESC
#ifdef __cplusplus
extern "C" {
#endif
// internal header, not installed
#include "version.h"
#include "builddef.h"
#include <stdint.h>
#include <pthread.h>
#include <stdbool.h>
#include <notcurses/notcurses.h>
#include "sprite.h"
#include "blit.h"
#include "fbuf.h"
#include "in.h"
// kitty keyboard protocol pop, used at end when kitty is verified.
#define KKEYBOARD_POP "\x1b[<u"
// disable key modifier options; this corresponds to a resource value of
// "-1", which cannot be set with the [>m sequence. supposedly, "[>m" by
// itself ought reset all of them, but this doesn't seem to work FIXME.
#define XTMODKEYSUNDO "\x1b[>2m\x1b[>4m"
struct ncpile;
struct sprixel;
struct notcurses;
struct ncsharedstats;
// we store all our escape sequences in a single large block, and use
// 16-bit one-biased byte-granularity indices to get the location in said
// block. we'd otherwise be using 32 or 64-bit pointers to get locations
// scattered all over memory. this way the lookup elements require two or four
// times fewer cachelines total, and the actual escape sequences are packed
// tightly into minimal cachelines. if an escape is not defined, that index
// is 0. the first escape defined has an index of 1, and so on. an escape
// thus cannot actually start at byte 65535.
// indexes into the table of fixed-width (16-bit) indices
typedef enum {
ESCAPE_CUP, // "cup" move cursor to absolute x, y position
ESCAPE_HPA, // "hpa" move cursor to absolute horizontal position
ESCAPE_VPA, // "vpa" move cursor to absolute vertical position
ESCAPE_SETAF, // "setaf" set foreground color
ESCAPE_SETAB, // "setab" set background color
ESCAPE_OP, // "op" set foreground and background color to defaults
ESCAPE_FGOP, // set foreground only to default
ESCAPE_BGOP, // set background only to default
ESCAPE_SGR0, // "sgr0" turn off all styles
ESCAPE_CIVIS, // "civis" make the cursor invisiable
ESCAPE_CNORM, // "cnorm" restore the cursor to normal
ESCAPE_OC, // "oc" restore original colors
ESCAPE_SITM, // "sitm" start italics
ESCAPE_RITM, // "ritm" end italics
ESCAPE_CUU, // "cuu" move n cells up
ESCAPE_CUB, // "cub" move n cells back (left)
ESCAPE_CUF, // "cuf" move n cells forward (right)
ESCAPE_BOLD, // "bold" enter bold mode
ESCAPE_NOBOLD, // disable bold (ANSI but not terminfo, SGR 22)
ESCAPE_CUD, // "cud" move n cells down
ESCAPE_SMKX, // "smkx" keypad_xmit (keypad transmit mode)
ESCAPE_RMKX, // "rmkx" keypad_local
ESCAPE_EL, // "el" clear to end of line, inclusive
ESCAPE_SMCUP, // "smcup" enter alternate screen
ESCAPE_RMCUP, // "rmcup" leave alternate screen
ESCAPE_SMXX, // "smxx" start struckout
ESCAPE_SMUL, // "smul" start underline
ESCAPE_RMUL, // "rmul" end underline
ESCAPE_SMULX, // "Smulx" deparameterized: start extended underline
ESCAPE_SMULNOX, // "Smulx" deparameterized: kill underline
ESCAPE_RMXX, // "rmxx" end struckout
ESCAPE_IND, // "ind" scroll 1 line up
ESCAPE_INDN, // "indn" scroll n lines up
ESCAPE_SC, // "sc" push the cursor onto the stack
ESCAPE_RC, // "rc" pop the cursor off the stack
ESCAPE_CLEAR, // "clear" clear screen and home cursor
ESCAPE_INITC, // "initc" set up palette entry
ESCAPE_U7, // "u7" cursor position report
// Application synchronized updates, not present in terminfo
// (https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec)
ESCAPE_BSUM, // Begin Synchronized Update Mode
ESCAPE_ESUM, // End Synchronized Update Mode
ESCAPE_SAVECOLORS, // XTPUSHCOLORS (push palette/fg/bg)
ESCAPE_RESTORECOLORS, // XTPOPCOLORS (pop palette/fg/bg)
ESCAPE_DECERA, // rectangular erase
ESCAPE_MAX
} escape_e;
// when we read a cursor report, we put it on the queue for internal
// processing. this is necessary since it can be arbitrarily interleaved with
// other input when stdin is connected to our terminal. these are already
// processed to be 0-based.
typedef struct cursorreport {
int x, y;
struct cursorreport* next;
} cursorreport;
// 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
// variable. some are determined via ioctl(2). treat all of them as if they
// can change over the program's life (don't cache them locally).
typedef struct tinfo {
uint16_t escindices[ESCAPE_MAX]; // table of 1-biased indices into esctable
int ttyfd; // connected to true terminal, might be -1
char* esctable; // packed table of escape sequences
nccapabilities caps; // exported to the user, when requested
unsigned pixy; // total pixel geometry, height
unsigned pixx; // total pixel geometry, width
// we use the cell's size in pixels for pixel blitting. this information can
// be acquired on all terminals with pixel support.
unsigned cellpxy; // cell pixel height, might be 0
unsigned cellpxx; // cell pixel width, might be 0
unsigned dimy, dimx; // most recent cell geometry
unsigned supported_styles; // bitmask over NCSTYLE_* driven via sgr/ncv
// kitty interprets an RGB background that matches the default background
// color *as* the default background, meaning it'll be translucent if
// background_opaque is in use. detect this, and avoid the default if so.
// bg_collides_default is either:
// 0xfexxxxxxx (unknown), 0x00RRGGBB (no collide), or 0x01RRGGBB (collides).
uint32_t bg_collides_default;
// 0xffxxxxxxx (unknown), or 0x00RRGGBB (foreground)
uint32_t fg_default;
// bitmap support. if we support bitmaps, pixel_implementation will be a
// value other than NCPIXEL_NONE.
ncpixelimpl_e pixel_implementation;
// wipe out a cell's worth of pixels from within a sprixel. for sixel, this
// means leaving out the pixels (and likely resizes the string). for kitty,
// this means dialing down their alpha to 0 (in equivalent space).
int (*pixel_wipe)(struct sprixel* s, int y, int x);
// perform the inverse of pixel_wipe, restoring an annihilated sprixcell.
int (*pixel_rebuild)(struct sprixel* s, int y, int x, uint8_t* auxvec);
// called in phase 1 when INVALIDATED; this damages cells that have been
// redrawn in a sixel (when old was not transparent, and new is not opaque).
// it leaves the sprixel in INVALIDATED so that it's drawn in phase 2.
void (*pixel_refresh)(const struct ncpile* p, struct sprixel* s);
int (*pixel_remove)(int id, fbuf* f); // kitty only, issue actual delete command
int (*pixel_init)(struct tinfo* ti, int fd); // called when support is detected
int (*pixel_draw)(const struct tinfo*, const struct ncpile* p,
struct sprixel* s, fbuf* f, int y, int x);
int (*pixel_draw_late)(const struct tinfo*, struct sprixel* s, int yoff, int xoff);
// execute move (erase old graphic, place at new location) if non-NULL
int (*pixel_move)(struct sprixel* s, fbuf* f, unsigned noscroll, int yoff, int xoff);
int (*pixel_scrub)(const struct ncpile* p, struct sprixel* s);
int (*pixel_clear_all)(fbuf* f); // called during context startup
// make a loaded graphic visible. only used with kitty.
int (*pixel_commit)(fbuf* f, struct sprixel* s, unsigned noscroll);
// scroll all graphics up. only used with fbcon.
void (*pixel_scroll)(const struct ncpile* p, struct tinfo*, int rows);
void (*pixel_cleanup)(struct tinfo*); // called at shutdown
uint8_t* (*pixel_trans_auxvec)(const struct ncpile* p); // create tranparent auxvec
// sprixel parameters. there are several different sprixel protocols, of
// which we support sixel and kitty. the kitty protocol is used based
// on TERM heuristics. otherwise, we attempt to detect sixel support, and
// query the details of the implementation.
int color_registers; // sixel color registers (post pixel_query_done)
unsigned sixel_maxx; // maximum theoretical sixel width
// in sixel, we can't render to the bottom row, lest we force a one-line
// scroll. we thus clamp sixel_maxy_pristine to the minimum of
// sixel_maxy_pristine (the reported sixel_maxy), and the number of rows
// less one times the cell height. sixel_maxy is thus recomputed whenever
// we get a resize event. it is only defined if we have sixel_maxy_pristine,
// so kitty graphics (which don't force a scroll) never deal with this.
unsigned sixel_maxy; // maximum working sixel height
unsigned sixel_maxy_pristine; // maximum theoretical sixel height, as queried
unsigned sprixel_scale_height;// sprixel must be a multiple of this many rows
void* sixelengine; // opaque threaded engine used by sixel dispatch
const char* termname; // terminal name from environment variables/init
char* termversion; // terminal version (freeform) from query responses
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
struct inputctx* ictx; // new input layer
unsigned stdio_blocking_save; // was stdio blocking at entry? restore on stop.
// ought we issue gratuitous HPAs to work around ambiguous widths?
unsigned gratuitous_hpa;
// 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
// the output region; use margins for that, if necessary.
int default_rows; // LINES environment var / lines terminfo / 24
int default_cols; // COLUMNS environment var / cols terminfo / 80
ncpalette originalpalette; // palette as read from initial queries
int maxpaletteread; // maximum palette entry read
pthread_t gpmthread; // thread handle for GPM watcher
int gpmfd; // connection to GPM daemon
char mouseproto; // DECSET level (100x, '0', '2', '3')
bool pixelmice; // do we support pixel-precision mice?
#ifdef __linux__
int linux_fb_fd; // linux framebuffer device fd
char* linux_fb_dev; // device corresponding to linux_fb_dev
uint8_t* linux_fbuffer; // mmap()ed framebuffer
size_t linux_fb_len; // size of map
#elif defined(__MINGW32__)
HANDLE inhandle;
HANDLE outhandle;
#endif
// kitty keyboard protocol level. we initialize this to UINT_MAX, in case we
// crash while running the initialization automata (in that case, we want to
// pop the keyboard support level, which we normally do only if we detected
// actual support. at that point, we obviously haven't detected anything).
// after getting the initialization package back, if it's still UINT_MAX, we
// set it to 0, and also indicate a lack of support via kittykbdsupport (we
// need distinguish between level 0, used with DRAININPUT, and an absolute
// lack of support, in which case we move to XTMODKEYS, for notcurses-info).
unsigned kbdlevel; // kitty keyboard support level
bool kittykbdsupport; // do we support the kitty keyboard protocol?
bool bce; // is the bce property advertised?
bool in_alt_screen; // are we in the alternate screen?
} tinfo;
// retrieve the terminfo(5)-style escape 'e' from tdesc (NULL if undefined).
static inline __attribute__ ((pure)) const char*
get_escape(const tinfo* tdesc, escape_e e){
unsigned idx = tdesc->escindices[e];
if(idx){
return tdesc->esctable + idx - 1;
}
return NULL;
}
static inline uint16_t
term_supported_styles(const tinfo* ti){
return ti->supported_styles;
}
// prepare |ti| from the terminfo database and other sources. set |utf8| if
// we've verified UTF8 output encoding. set |noaltscreen| to inhibit alternate
// screen detection. |stats| may be NULL; either way, it will be handed to the
// input layer so that its stats can be recorded.
int interrogate_terminfo(tinfo* ti, FILE* out, unsigned utf8,
unsigned noaltscreen, unsigned nocbreak,
unsigned nonewfonts, int* cursor_y, int* cursor_x,
struct ncsharedstats* stats, int lmargin, int tmargin,
int rmargin, int bmargin, unsigned draininput)
__attribute__ ((nonnull (1, 2, 9)));
void free_terminfo_cache(tinfo* ti);
// return a heap-allocated copy of termname + termversion
char* termdesc_longterm(const tinfo* ti);
int locate_cursor(tinfo* ti, unsigned* cursor_y, unsigned* cursor_x);
// tlen -- size of escape table. tused -- used bytes in same.
// returns -1 if the starting location is >= 65535. otherwise,
// copies tstr into the table, and sets up 1-biased index.
static inline int
grow_esc_table(tinfo* ti, const char* tstr, escape_e esc,
size_t* tlen, size_t* tused){
// the actual table can grow past 64KB, but we can't start there, as
// we only have 16-bit indices.
if(*tused >= 65535){
fprintf(stderr, "Can't add escape %d to full table\n", esc);
return -1;
}
if(get_escape(ti, esc)){
fprintf(stderr, "Already defined escape %d (%s)\n",
esc, get_escape(ti, esc));
return -1;
}
size_t slen = strlen(tstr) + 1; // count the nul term
if(*tlen - *tused < slen){
// guaranteed to give us enough space to add tstr (and then some)
size_t newsize = *tlen + 4020 + slen; // don't pull two pages ideally
char* tmp = (char*)realloc(ti->esctable, newsize); // cast for c++
if(tmp == NULL){
return -1;
}
ti->esctable = tmp;
*tlen = newsize;
}
// we now are guaranteed sufficient space to copy tstr
memcpy(ti->esctable + *tused, tstr, slen);
ti->escindices[esc] = *tused + 1; // one-bias
*tused += slen;
return 0;
}
static inline int
ncfputs(const char* ext, FILE* out){
int r;
#ifdef __USE_GNU
r = fputs_unlocked(ext, out);
#else
r = fputs(ext, out);
#endif
return r;
}
static inline int
ncfputc(char c, FILE* out){
#ifdef __USE_GNU
return putc_unlocked(c, out);
#else
return putc(c, out);
#endif
}
// reliably flush a FILE*...except you can't, so far as i can tell. at least
// on glibc, a single fflush() error latches the FILE* error, but ceases to
// perform any work (even following a clearerr()), despite returning 0 from
// that point on. thus, after a fflush() error, even on EAGAIN and friends,
// you can't use the stream any further. doesn't this make fflush() pretty
// much useless? it sure would seem to, which is why we use an fbuf for
// all our important I/O, which we then blit with blocking_write(). if you
// care about your data, you'll do the same.
static inline int
ncflush(FILE* out){
if(ferror(out)){
logerror("Not attempting a flush following error\n");
}
if(fflush(out) == EOF){
logerror("Unrecoverable error flushing io (%s)\n", strerror(errno));
return -1;
}
return 0;
}
static inline int
term_emit(const char* seq, FILE* out, bool flush){
if(!seq){
return -1;
}
if(ncfputs(seq, out) == EOF){
logerror("Error emitting %lub escape (%s)\n",
(unsigned long)strlen(seq), strerror(errno));
return -1;
}
return flush ? ncflush(out) : 0;
}
// |drain| is set iff we're draining input.
int enter_alternate_screen(int ttyfd, FILE* ttyfp, tinfo* ti, unsigned drain);
int leave_alternate_screen(int ttyfd, FILE* ttyfp, tinfo* ti, unsigned drain);
int cbreak_mode(tinfo* ti);
// execute termios's TIOCGWINSZ ioctl(). returns -1 on failure.
int tiocgwinsz(int fd, struct winsize* ws);
#ifdef __cplusplus
}
#endif
#endif