1 #ifndef NOTCURSES_TERMDESC
2 #define NOTCURSES_TERMDESC
3 
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7 
8 // internal header, not installed
9 
10 #include "version.h"
11 #include "builddef.h"
12 #include <stdint.h>
13 #include <pthread.h>
14 #include <stdbool.h>
15 #include <notcurses/notcurses.h>
16 #include "fbuf.h"
17 #include "in.h"
18 
19 // kitty keyboard protocol pop, used at end when kitty is verified.
20 #define KKEYBOARD_POP  "\x1b[<u"
21 
22 // disable key modifier options; this corresponds to a resource value of
23 // "-1", which cannot be set with the [>m sequence. supposedly, "[>m" by
24 // itself ought reset all of them, but this doesn't seem to work FIXME.
25 #define XTMODKEYSUNDO "\x1b[>2n\x1b[>4n"
26 
27 struct ncpile;
28 struct sprixel;
29 struct notcurses;
30 struct ncsharedstats;
31 
32 // we store all our escape sequences in a single large block, and use
33 // 16-bit one-biased byte-granularity indices to get the location in said
34 // block. we'd otherwise be using 32 or 64-bit pointers to get locations
35 // scattered all over memory. this way the lookup elements require two or four
36 // times fewer cachelines total, and the actual escape sequences are packed
37 // tightly into minimal cachelines. if an escape is not defined, that index
38 // is 0. the first escape defined has an index of 1, and so on. an escape
39 // thus cannot actually start at byte 65535.
40 
41 // indexes into the table of fixed-width (16-bit) indices
42 typedef enum {
43   ESCAPE_CUP,     // "cup" move cursor to absolute x, y position
44   ESCAPE_HPA,     // "hpa" move cursor to absolute horizontal position
45   ESCAPE_VPA,     // "vpa" move cursor to absolute vertical position
46   ESCAPE_SETAF,   // "setaf" set foreground color
47   ESCAPE_SETAB,   // "setab" set background color
48   ESCAPE_OP,      // "op" set foreground and background color to defaults
49   ESCAPE_FGOP,    // set foreground only to default
50   ESCAPE_BGOP,    // set background only to default
51   ESCAPE_SGR0,    // "sgr0" turn off all styles
52   ESCAPE_CIVIS,   // "civis" make the cursor invisiable
53   ESCAPE_CNORM,   // "cnorm" restore the cursor to normal
54   ESCAPE_OC,      // "oc" restore original colors
55   ESCAPE_SITM,    // "sitm" start italics
56   ESCAPE_RITM,    // "ritm" end italics
57   ESCAPE_CUU,     // "cuu" move n cells up
58   ESCAPE_CUB,     // "cub" move n cells back (left)
59   ESCAPE_CUF,     // "cuf" move n cells forward (right)
60   ESCAPE_BOLD,    // "bold" enter bold mode
61   ESCAPE_NOBOLD,  // disable bold (ANSI but not terminfo, SGR 22)
62   ESCAPE_CUD,     // "cud" move n cells down
63   ESCAPE_SMKX,    // "smkx" keypad_xmit (keypad transmit mode)
64   ESCAPE_RMKX,    // "rmkx" keypad_local
65   ESCAPE_EL,      // "el" clear to end of line, inclusive
66   ESCAPE_SMCUP,   // "smcup" enter alternate screen
67   ESCAPE_RMCUP,   // "rmcup" leave alternate screen
68   ESCAPE_SMXX,    // "smxx" start struckout
69   ESCAPE_SMUL,    // "smul" start underline
70   ESCAPE_RMUL,    // "rmul" end underline
71   ESCAPE_SMULX,   // "Smulx" deparameterized: start extended underline
72   ESCAPE_SMULNOX, // "Smulx" deparameterized: kill underline
73   ESCAPE_RMXX,    // "rmxx" end struckout
74   ESCAPE_IND,     // "ind" scroll 1 line up
75   ESCAPE_INDN,    // "indn" scroll n lines up
76   ESCAPE_SC,      // "sc" push the cursor onto the stack
77   ESCAPE_RC,      // "rc" pop the cursor off the stack
78   ESCAPE_CLEAR,   // "clear" clear screen and home cursor
79   ESCAPE_INITC,   // "initc" set up palette entry
80   ESCAPE_U7,      // "u7" cursor position report
81   // Application synchronized updates, not present in terminfo
82   // (https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec)
83   ESCAPE_BSUM,     // Begin Synchronized Update Mode
84   ESCAPE_ESUM,     // End Synchronized Update Mode
85   ESCAPE_SAVECOLORS,    // XTPUSHCOLORS (push palette/fg/bg)
86   ESCAPE_RESTORECOLORS, // XTPOPCOLORS  (pop palette/fg/bg)
87   ESCAPE_MAX
88 } escape_e;
89 
90 // when we read a cursor report, we put it on the queue for internal
91 // processing. this is necessary since it can be arbitrarily interleaved with
92 // other input when stdin is connected to our terminal. these are already
93 // processed to be 0-based.
94 typedef struct cursorreport {
95   int x, y;
96   struct cursorreport* next;
97 } cursorreport;
98 
99 // terminal interface description. most of these are acquired from terminfo(5)
100 // (using a database entry specified by TERM). some are determined via
101 // heuristics based off terminal interrogation or the TERM environment
102 // variable. some are determined via ioctl(2). treat all of them as if they
103 // can change over the program's life (don't cache them locally).
104 typedef struct tinfo {
105   uint16_t escindices[ESCAPE_MAX]; // table of 1-biased indices into esctable
106   int ttyfd;                       // connected to true terminal, might be -1
107   char* esctable;                  // packed table of escape sequences
108   nccapabilities caps;             // exported to the user, when requested
109   unsigned pixy;                   // total pixel geometry, height
110   unsigned pixx;                   // total pixel geometry, width
111   // we use the cell's size in pixels for pixel blitting. this information can
112   // be acquired on all terminals with pixel support.
113   unsigned cellpxy;                // cell pixel height, might be 0
114   unsigned cellpxx;                // cell pixel width, might be 0
115   unsigned dimy, dimx;             // most recent cell geometry
116 
117   unsigned supported_styles; // bitmask over NCSTYLE_* driven via sgr/ncv
118 
119   // kitty interprets an RGB background that matches the default background
120   // color *as* the default background, meaning it'll be translucent if
121   // background_opaque is in use. detect this, and avoid the default if so.
122   // bg_collides_default is either:
123   // 0xfexxxxxxx (unknown), 0x00RRGGBB (no collide), or 0x01RRGGBB (collides).
124   uint32_t bg_collides_default;
125 
126   // 0xffxxxxxxx (unknown), or 0x00RRGGBB (foreground)
127   uint32_t fg_default;
128 
129   // bitmap support. if we support bitmaps, pixel_implementation will be a
130   // value other than NCPIXEL_NONE.
131   ncpixelimpl_e pixel_implementation;
132   // wipe out a cell's worth of pixels from within a sprixel. for sixel, this
133   // means leaving out the pixels (and likely resizes the string). for kitty,
134   // this means dialing down their alpha to 0 (in equivalent space).
135   int (*pixel_wipe)(struct sprixel* s, int y, int x);
136   // perform the inverse of pixel_wipe, restoring an annihilated sprixcell.
137   int (*pixel_rebuild)(struct sprixel* s, int y, int x, uint8_t* auxvec);
138   // called in phase 1 when INVALIDATED; this damages cells that have been
139   // redrawn in a sixel (when old was not transparent, and new is not opaque).
140   // it leaves the sprixel in INVALIDATED so that it's drawn in phase 2.
141   void (*pixel_refresh)(const struct ncpile* p, struct sprixel* s);
142   int (*pixel_remove)(int id, fbuf* f); // kitty only, issue actual delete command
143   int (*pixel_init)(int fd); // called when support is detected
144   int (*pixel_draw)(const struct tinfo*, const struct ncpile* p,
145                     struct sprixel* s, fbuf* f, int y, int x);
146   int (*pixel_draw_late)(const struct tinfo*, struct sprixel* s, int yoff, int xoff);
147   // execute move (erase old graphic, place at new location) if non-NULL
148   int (*pixel_move)(struct sprixel* s, fbuf* f, unsigned noscroll, int yoff, int xoff);
149   int (*pixel_scrub)(const struct ncpile* p, struct sprixel* s);
150   int (*pixel_clear_all)(fbuf* f);  // called during context startup
151   // make a loaded graphic visible. only used with kitty.
152   int (*pixel_commit)(fbuf* f, struct sprixel* s, unsigned noscroll);
153   // scroll all graphics up. only used with fbcon.
154   void (*pixel_scroll)(const struct ncpile* p, struct tinfo*, int rows);
155   uint8_t* (*pixel_trans_auxvec)(const struct ncpile* p); // create tranparent auxvec
156   // sprixel parameters. there are several different sprixel protocols, of
157   // which we support sixel and kitty. the kitty protocol is used based
158   // on TERM heuristics. otherwise, we attempt to detect sixel support, and
159   // query the details of the implementation.
160   int color_registers; // sixel color registers (post pixel_query_done)
161   unsigned sixel_maxx; // maximum theoretical sixel width
162   // in sixel, we can't render to the bottom row, lest we force a one-line
163   // scroll. we thus clamp sixel_maxy_pristine to the minimum of
164   // sixel_maxy_pristine (the reported sixel_maxy), and the number of rows
165   // less one times the cell height. sixel_maxy is thus recomputed whenever
166   // we get a resize event. it is only defined if we have sixel_maxy_pristine,
167   // so kitty graphics (which don't force a scroll) never deal with this.
168   unsigned sixel_maxy;          // maximum working sixel height
169   unsigned sixel_maxy_pristine; // maximum theoretical sixel height, as queried
170   unsigned sprixel_scale_height;// sprixel must be a multiple of this many rows
171   const char* termname;      // terminal name from environment variables/init
172   char* termversion;         // terminal version (freeform) from query responses
173   queried_terminals_e qterm; // detected terminal class
174   // we heap-allocate this one (if we use it), as it's not fully defined on Windows
175   struct termios *tpreserved;// terminal state upon entry
176   struct inputctx* ictx;     // new input layer
177   unsigned stdio_blocking_save; // was stdio blocking at entry? restore on stop.
178   // ought we issue gratuitous HPAs to work around ambiguous widths?
179   unsigned gratuitous_hpa;
180 
181   // if we get a reply to our initial \e[18t cell geometry query, it will
182   // replace these values. note that LINES/COLUMNS cannot be used to limit
183   // the output region; use margins for that, if necessary.
184   int default_rows;          // LINES environment var / lines terminfo / 24
185   int default_cols;          // COLUMNS environment var / cols terminfo / 80
186 
187   ncpalette originalpalette; // palette as read from initial queries
188   int maxpaletteread;        // maximum palette entry read
189   pthread_t gpmthread;       // thread handle for GPM watcher
190   int gpmfd;                 // connection to GPM daemon
191   char mouseproto;           // DECSET level (100x, '0', '2', '3')
192 #ifdef __linux__
193   int linux_fb_fd;           // linux framebuffer device fd
194   char* linux_fb_dev;        // device corresponding to linux_fb_dev
195   uint8_t* linux_fbuffer;    // mmap()ed framebuffer
196   size_t linux_fb_len;       // size of map
197 #elif defined(__MINGW64__)
198   HANDLE inhandle;
199   HANDLE outhandle;
200 #endif
201 
202   // kitty keyboard protocol level. we initialize this to UINT_MAX, in case we
203   // crash while running the initialization automata (in that case, we want to
204   // pop the keyboard support level, which we normally do only if we detected
205   // actual support. at that point, we obviously haven't detected anything).
206   // after getting the initialization package back, if it's still UINT_MAX, we
207   // set it to 0, and also indicate a lack of support via kittykbdsupport (we
208   // need distinguish between level 0, used with DRAININPUT, and an absolute
209   // lack of support, in which case we move to XTMODKEYS, for notcurses-info).
210   unsigned kbdlevel;         // kitty keyboard support level
211   bool kittykbdsupport;      // do we support the kitty keyboard protocol?
212   bool bce;                  // is the bce property advertised?
213   bool in_alt_screen;        // are we in the alternate screen?
214 } tinfo;
215 
216 // retrieve the terminfo(5)-style escape 'e' from tdesc (NULL if undefined).
217 static inline __attribute__ ((pure)) const char*
get_escape(const tinfo * tdesc,escape_e e)218 get_escape(const tinfo* tdesc, escape_e e){
219   unsigned idx = tdesc->escindices[e];
220   if(idx){
221     return tdesc->esctable + idx - 1;
222   }
223   return NULL;
224 }
225 
226 static inline uint16_t
term_supported_styles(const tinfo * ti)227 term_supported_styles(const tinfo* ti){
228   return ti->supported_styles;
229 }
230 
231 // prepare |ti| from the terminfo database and other sources. set |utf8| if
232 // we've verified UTF8 output encoding. set |noaltscreen| to inhibit alternate
233 // screen detection. |stats| may be NULL; either way, it will be handed to the
234 // input layer so that its stats can be recorded.
235 int interrogate_terminfo(tinfo* ti, FILE* out, unsigned utf8,
236                          unsigned noaltscreen, unsigned nocbreak,
237                          unsigned nonewfonts, int* cursor_y, int* cursor_x,
238                          struct ncsharedstats* stats, int lmargin, int tmargin,
239                          int rmargin, int bmargin, unsigned draininput)
240   __attribute__ ((nonnull (1, 2, 9)));
241 
242 void free_terminfo_cache(tinfo* ti);
243 
244 // return a heap-allocated copy of termname + termversion
245 char* termdesc_longterm(const tinfo* ti);
246 
247 int locate_cursor(tinfo* ti, unsigned* cursor_y, unsigned* cursor_x);
248 
249 // tlen -- size of escape table. tused -- used bytes in same.
250 // returns -1 if the starting location is >= 65535. otherwise,
251 // copies tstr into the table, and sets up 1-biased index.
252 static inline int
grow_esc_table(tinfo * ti,const char * tstr,escape_e esc,size_t * tlen,size_t * tused)253 grow_esc_table(tinfo* ti, const char* tstr, escape_e esc,
254                size_t* tlen, size_t* tused){
255   // the actual table can grow past 64KB, but we can't start there, as
256   // we only have 16-bit indices.
257   if(*tused >= 65535){
258     fprintf(stderr, "Can't add escape %d to full table\n", esc);
259     return -1;
260   }
261   if(get_escape(ti, esc)){
262     fprintf(stderr, "Already defined escape %d (%s)\n",
263             esc, get_escape(ti, esc));
264     return -1;
265   }
266   size_t slen = strlen(tstr) + 1; // count the nul term
267   if(*tlen - *tused < slen){
268     // guaranteed to give us enough space to add tstr (and then some)
269     size_t newsize = *tlen + 4020 + slen; // don't pull two pages ideally
270     char* tmp = (char*)realloc(ti->esctable, newsize); // cast for c++
271     if(tmp == NULL){
272       return -1;
273     }
274     ti->esctable = tmp;
275     *tlen = newsize;
276   }
277   // we now are guaranteed sufficient space to copy tstr
278   memcpy(ti->esctable + *tused, tstr, slen);
279   ti->escindices[esc] = *tused + 1; // one-bias
280   *tused += slen;
281   return 0;
282 }
283 
284 static inline int
ncfputs(const char * ext,FILE * out)285 ncfputs(const char* ext, FILE* out){
286   int r;
287 #ifdef __USE_GNU
288   r = fputs_unlocked(ext, out);
289 #else
290   r = fputs(ext, out);
291 #endif
292   return r;
293 }
294 
295 static inline int
ncfputc(char c,FILE * out)296 ncfputc(char c, FILE* out){
297 #ifdef __USE_GNU
298   return putc_unlocked(c, out);
299 #else
300   return putc(c, out);
301 #endif
302 }
303 
304 // reliably flush a FILE*...except you can't, so far as i can tell. at least
305 // on glibc, a single fflush() error latches the FILE* error, but ceases to
306 // perform any work (even following a clearerr()), despite returning 0 from
307 // that point on. thus, after a fflush() error, even on EAGAIN and friends,
308 // you can't use the stream any further. doesn't this make fflush() pretty
309 // much useless? it sure would seem to, which is why we use an fbuf for
310 // all our important I/O, which we then blit with blocking_write(). if you
311 // care about your data, you'll do the same.
312 static inline int
ncflush(FILE * out)313 ncflush(FILE* out){
314   if(ferror(out)){
315     logerror("Not attempting a flush following error\n");
316   }
317   if(fflush(out) == EOF){
318     logerror("Unrecoverable error flushing io (%s)\n", strerror(errno));
319     return -1;
320   }
321   return 0;
322 }
323 
324 static inline int
term_emit(const char * seq,FILE * out,bool flush)325 term_emit(const char* seq, FILE* out, bool flush){
326   if(!seq){
327     return -1;
328   }
329   if(ncfputs(seq, out) == EOF){
330     logerror("Error emitting %lub escape (%s)\n",
331              (unsigned long)strlen(seq), strerror(errno));
332     return -1;
333   }
334   return flush ? ncflush(out) : 0;
335 }
336 
337 // |drain| is set iff we're draining input.
338 int enter_alternate_screen(int ttyfd, FILE* ttyfp, tinfo* ti, unsigned drain);
339 int leave_alternate_screen(int ttyfd, FILE* ttyfp, tinfo* ti, unsigned drain);
340 
341 int cbreak_mode(tinfo* ti);
342 
343 // execute termios's TIOCGWINSZ ioctl(). returns -1 on failure.
344 int tiocgwinsz(int fd, struct winsize* ws);
345 
346 #ifdef __cplusplus
347 }
348 #endif
349 
350 #endif
351