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