Notcurses 3.0.13
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
in.c
Go to the documentation of this file.
1#include <stdio.h>
2#include <fcntl.h>
3#include <unistd.h>
4#include "automaton.h"
5#include "internal.h"
6#include "unixsig.h"
7#include "render.h"
8#include "in.h"
9
10// Notcurses takes over stdin, and if it is not connected to a terminal, also
11// tries to make a connection to the controlling terminal. If such a connection
12// is made, it will read from that source (in addition to stdin). We dump one or
13// both into distinct buffers. We then try to lex structured elements out of
14// the buffer(s). We can extract cursor location reports, mouse events, and
15// UTF-8 characters. Completely extracted ones are placed in their appropriate
16// queues, and removed from the depository buffer. We aim to consume the
17// entirety of the deposit before going back to read more data, but let anyone
18// blocking on data wake up as soon as we've processed any input.
19//
20// The primary goal is to react to terminal messages (mostly cursor location
21// reports) as quickly as possible, and definitely not with unbounded latency,
22// without unbounded allocation, and also without losing data. We'd furthermore
23// like to reliably differentiate escapes and regular input, even when that
24// latter contains escapes. Unbounded input will hopefully only be present when
25// redirected from a file.
26
27static sig_atomic_t cont_seen;
28static sig_atomic_t resize_seen;
29
30// called for SIGWINCH and SIGCONT, and causes block_on_input to return
31void sigwinch_handler(int signo){
32 if(signo == SIGWINCH){
33 resize_seen = signo;
35 }else if(signo == SIGCONT){
36 cont_seen = signo;
38 }
39}
40
41typedef struct cursorloc {
42 int y, x; // 0-indexed cursor location
44
45#ifndef __MINGW32__
46typedef int ipipe;
47#else
48typedef HANDLE ipipe;
49#endif
50
51// local state for the input thread. don't put this large struct on the stack.
52typedef struct inputctx {
53 // these two are not ringbuffers; we always move any leftover materia to the
54 // front of the queue (it ought be a handful of bytes at most).
55 unsigned char tbuf[BUFSIZ]; // only used if we have distinct terminal fd
56 unsigned char ibuf[BUFSIZ]; // might be intermingled bulk/control data
57
58 int stdinfd; // bulk in fd. always >= 0 (almost always 0). we do not
59 // own this descriptor, and must not close() it.
60 int termfd; // terminal fd: -1 with no controlling terminal, or
61 // if stdin is a terminal, or on MSFT Terminal.
62#ifdef __MINGW32__
63 HANDLE stdinhandle; // handle to input terminal for MSFT Terminal
64#endif
65
66 int lmargin, tmargin; // margins in use at left and top
67 int rmargin, bmargin; // margins in use at right and bottom
68
70 int ibufvalid; // we mustn't read() if ibufvalid == sizeof(ibuf)
71 int tbufvalid; // only used if we have distinct terminal connection
72
73 uint8_t backspace; // backspace is usually not an escape sequence, but
74 // instead ^H or ^? or something. only escape sequences
75 // go into our automaton, so we handle this one
76 // out-of-band. set to non-zero; match with ctrl.
77 // ringbuffers for processed, structured input
78 cursorloc* csrs; // cursor reports are dumped here
79 ncinput* inputs; // processed input is dumped here
80 int coutstanding; // outstanding cursor location requests
81 int csize, isize; // total number of slots in csrs/inputs
82 int cvalid, ivalid; // population count of csrs/inputs
83 int cwrite, iwrite; // slot where we'll write the next csr/input;
84 // we cannot write if valid == size
85 int cread, iread; // slot from which clients read the next csr/input;
86 // they cannot read if valid == 0
87 pthread_mutex_t ilock; // lock for ncinput ringbuffer, also initial state
88 pthread_cond_t icond; // condvar for ncinput ringbuffer
89 pthread_mutex_t clock; // lock for csrs ringbuffer
90 pthread_cond_t ccond; // condvar for csrs ringbuffer
91 tinfo* ti; // link back to tinfo
92 pthread_t tid; // tid for input thread
93
94 unsigned midescape; // we're in the middle of a potential escape. we need
95 // to do a nonblocking read and try to complete it.
96 unsigned stdineof; // have we seen an EOF on stdin?
97
98 unsigned linesigs; // are line discipline signals active?
99 unsigned drain; // drain away bulk input?
100 ncsharedstats *stats; // stats shared with notcurses context
101
103 ipipe readypipes[2]; // pipes[0]: poll()able fd indicating the presence of user input
104 // initially, initdata is non-NULL and initdata_complete is NULL. once we
105 // get DA1, initdata_complete is non-NULL (it is the same value as
106 // initdata). once we complete reading the input payload that the DA1 arrived
107 // in, initdata is set to NULL, and we broadcast availability. once it has
108 // been taken, both become NULL.
111 int kittykbd; // kitty keyboard protocol support level
112 bool failed; // error initializing input automaton, abort
114
115static inline void
116inc_input_events(inputctx* ictx){
117 pthread_mutex_lock(&ictx->stats->lock);
118 ++ictx->stats->s.input_events;
119 pthread_mutex_unlock(&ictx->stats->lock);
120}
121
122static inline void
123inc_input_errors(inputctx* ictx){
124 pthread_mutex_lock(&ictx->stats->lock);
125 ++ictx->stats->s.input_errors;
126 pthread_mutex_unlock(&ictx->stats->lock);
127}
128
129// load representations used by XTMODKEYS
130static int
131prep_xtmodkeys(inputctx* ictx){
132 // XTMODKEYS enables unambiguous representations of certain inputs. We
133 // enable XTMODKEYS where supported.
134 static const struct {
135 const char* esc;
136 uint32_t key;
137 unsigned modifiers;
138 } keys[] = {
139 { .esc = "\x1b\x8", .key = NCKEY_BACKSPACE,
140 .modifiers = NCKEY_MOD_ALT, },
141 { .esc = "\x1b[2P", .key = NCKEY_F01,
142 .modifiers = NCKEY_MOD_SHIFT, },
143 { .esc = "\x1b[5P", .key = NCKEY_F01,
144 .modifiers = NCKEY_MOD_CTRL, },
145 { .esc = "\x1b[6P", .key = NCKEY_F01,
146 .modifiers = NCKEY_MOD_CTRL | NCKEY_MOD_SHIFT, },
147 { .esc = "\x1b[2Q", .key = NCKEY_F02,
148 .modifiers = NCKEY_MOD_SHIFT, },
149 { .esc = "\x1b[5Q", .key = NCKEY_F02,
150 .modifiers = NCKEY_MOD_CTRL, },
151 { .esc = "\x1b[6Q", .key = NCKEY_F02,
152 .modifiers = NCKEY_MOD_CTRL | NCKEY_MOD_SHIFT, },
153 { .esc = "\x1b[2R", .key = NCKEY_F03,
154 .modifiers = NCKEY_MOD_SHIFT, },
155 { .esc = "\x1b[5R", .key = NCKEY_F03,
156 .modifiers = NCKEY_MOD_CTRL, },
157 { .esc = "\x1b[6R", .key = NCKEY_F03,
158 .modifiers = NCKEY_MOD_CTRL | NCKEY_MOD_SHIFT, },
159 { .esc = "\x1b[2S", .key = NCKEY_F04,
160 .modifiers = NCKEY_MOD_SHIFT, },
161 { .esc = "\x1b[5S", .key = NCKEY_F04,
162 .modifiers = NCKEY_MOD_CTRL, },
163 { .esc = "\x1b[6S", .key = NCKEY_F04,
164 .modifiers = NCKEY_MOD_CTRL | NCKEY_MOD_SHIFT, },
165 { .esc = NULL, .key = 0, },
166 }, *k;
167 for(k = keys ; k->esc ; ++k){
168 if(inputctx_add_input_escape(&ictx->amata, k->esc, k->key,
169 k->modifiers)){
170 return -1;
171 }
172 logdebug("added %u", k->key);
173 }
174 loginfo("added all xtmodkeys");
175 return 0;
176}
177
178// load all known special keys from terminfo, and build the input sequence trie
179static int
180prep_special_keys(inputctx* ictx){
181#ifndef __MINGW32__
182 static const struct {
183 const char* tinfo;
184 uint32_t key;
185 bool shift, ctrl, alt;
186 } keys[] = {
187 // backspace (kbs) is handled seperately at the end
188 { .tinfo = "kbeg", .key = NCKEY_BEGIN, },
189 { .tinfo = "kcbt", .key = '\t', .shift = true, }, // "back-tab"
190 { .tinfo = "kcub1", .key = NCKEY_LEFT, },
191 { .tinfo = "kcuf1", .key = NCKEY_RIGHT, },
192 { .tinfo = "kcuu1", .key = NCKEY_UP, },
193 { .tinfo = "kcud1", .key = NCKEY_DOWN, },
194 { .tinfo = "kdch1", .key = NCKEY_DEL, },
195 { .tinfo = "kbs", .key = NCKEY_BACKSPACE, },
196 { .tinfo = "kich1", .key = NCKEY_INS, },
197 { .tinfo = "kend", .key = NCKEY_END, },
198 { .tinfo = "khome", .key = NCKEY_HOME, },
199 { .tinfo = "knp", .key = NCKEY_PGDOWN, },
200 { .tinfo = "kpp", .key = NCKEY_PGUP, },
201 { .tinfo = "kf0", .key = NCKEY_F01, },
202 { .tinfo = "kf1", .key = NCKEY_F01, },
203 { .tinfo = "kf2", .key = NCKEY_F02, },
204 { .tinfo = "kf3", .key = NCKEY_F03, },
205 { .tinfo = "kf4", .key = NCKEY_F04, },
206 { .tinfo = "kf5", .key = NCKEY_F05, },
207 { .tinfo = "kf6", .key = NCKEY_F06, },
208 { .tinfo = "kf7", .key = NCKEY_F07, },
209 { .tinfo = "kf8", .key = NCKEY_F08, },
210 { .tinfo = "kf9", .key = NCKEY_F09, },
211 { .tinfo = "kf10", .key = NCKEY_F10, },
212 { .tinfo = "kf11", .key = NCKEY_F11, },
213 { .tinfo = "kf12", .key = NCKEY_F12, },
214 { .tinfo = "kf13", .key = NCKEY_F13, },
215 { .tinfo = "kf14", .key = NCKEY_F14, },
216 { .tinfo = "kf15", .key = NCKEY_F15, },
217 { .tinfo = "kf16", .key = NCKEY_F16, },
218 { .tinfo = "kf17", .key = NCKEY_F17, },
219 { .tinfo = "kf18", .key = NCKEY_F18, },
220 { .tinfo = "kf19", .key = NCKEY_F19, },
221 { .tinfo = "kf20", .key = NCKEY_F20, },
222 { .tinfo = "kf21", .key = NCKEY_F21, },
223 { .tinfo = "kf22", .key = NCKEY_F22, },
224 { .tinfo = "kf23", .key = NCKEY_F23, },
225 { .tinfo = "kf24", .key = NCKEY_F24, },
226 { .tinfo = "kf25", .key = NCKEY_F25, },
227 { .tinfo = "kf26", .key = NCKEY_F26, },
228 { .tinfo = "kf27", .key = NCKEY_F27, },
229 { .tinfo = "kf28", .key = NCKEY_F28, },
230 { .tinfo = "kf29", .key = NCKEY_F29, },
231 { .tinfo = "kf30", .key = NCKEY_F30, },
232 { .tinfo = "kf31", .key = NCKEY_F31, },
233 { .tinfo = "kf32", .key = NCKEY_F32, },
234 { .tinfo = "kf33", .key = NCKEY_F33, },
235 { .tinfo = "kf34", .key = NCKEY_F34, },
236 { .tinfo = "kf35", .key = NCKEY_F35, },
237 { .tinfo = "kf36", .key = NCKEY_F36, },
238 { .tinfo = "kf37", .key = NCKEY_F37, },
239 { .tinfo = "kf38", .key = NCKEY_F38, },
240 { .tinfo = "kf39", .key = NCKEY_F39, },
241 { .tinfo = "kf40", .key = NCKEY_F40, },
242 { .tinfo = "kf41", .key = NCKEY_F41, },
243 { .tinfo = "kf42", .key = NCKEY_F42, },
244 { .tinfo = "kf43", .key = NCKEY_F43, },
245 { .tinfo = "kf44", .key = NCKEY_F44, },
246 { .tinfo = "kf45", .key = NCKEY_F45, },
247 { .tinfo = "kf46", .key = NCKEY_F46, },
248 { .tinfo = "kf47", .key = NCKEY_F47, },
249 { .tinfo = "kf48", .key = NCKEY_F48, },
250 { .tinfo = "kf49", .key = NCKEY_F49, },
251 { .tinfo = "kf50", .key = NCKEY_F50, },
252 { .tinfo = "kf51", .key = NCKEY_F51, },
253 { .tinfo = "kf52", .key = NCKEY_F52, },
254 { .tinfo = "kf53", .key = NCKEY_F53, },
255 { .tinfo = "kf54", .key = NCKEY_F54, },
256 { .tinfo = "kf55", .key = NCKEY_F55, },
257 { .tinfo = "kf56", .key = NCKEY_F56, },
258 { .tinfo = "kf57", .key = NCKEY_F57, },
259 { .tinfo = "kf58", .key = NCKEY_F58, },
260 { .tinfo = "kf59", .key = NCKEY_F59, },
261 { .tinfo = "kent", .key = NCKEY_ENTER, },
262 { .tinfo = "kclr", .key = NCKEY_CLS, },
263 { .tinfo = "kc1", .key = NCKEY_DLEFT, },
264 { .tinfo = "kc3", .key = NCKEY_DRIGHT, },
265 { .tinfo = "ka1", .key = NCKEY_ULEFT, },
266 { .tinfo = "ka3", .key = NCKEY_URIGHT, },
267 { .tinfo = "kb2", .key = NCKEY_CENTER, },
268 { .tinfo = "kbeg", .key = NCKEY_BEGIN, },
269 { .tinfo = "kcan", .key = NCKEY_CANCEL, },
270 { .tinfo = "kclo", .key = NCKEY_CLOSE, },
271 { .tinfo = "kcmd", .key = NCKEY_COMMAND, },
272 { .tinfo = "kcpy", .key = NCKEY_COPY, },
273 { .tinfo = "kext", .key = NCKEY_EXIT, },
274 { .tinfo = "kprt", .key = NCKEY_PRINT, },
275 { .tinfo = "krfr", .key = NCKEY_REFRESH, },
276 { .tinfo = "kBEG", .key = NCKEY_BEGIN, .shift = 1, },
277 { .tinfo = "kBEG3", .key = NCKEY_BEGIN, .alt = 1, },
278 { .tinfo = "kBEG4", .key = NCKEY_BEGIN, .alt = 1, .shift = 1, },
279 { .tinfo = "kBEG5", .key = NCKEY_BEGIN, .ctrl = 1, },
280 { .tinfo = "kBEG6", .key = NCKEY_BEGIN, .ctrl = 1, .shift = 1, },
281 { .tinfo = "kBEG7", .key = NCKEY_BEGIN, .alt = 1, .ctrl = 1, },
282 { .tinfo = "kDC", .key = NCKEY_DEL, .shift = 1, },
283 { .tinfo = "kDC3", .key = NCKEY_DEL, .alt = 1, },
284 { .tinfo = "kDC4", .key = NCKEY_DEL, .alt = 1, .shift = 1, },
285 { .tinfo = "kDC5", .key = NCKEY_DEL, .ctrl = 1, },
286 { .tinfo = "kDC6", .key = NCKEY_DEL, .ctrl = 1, .shift = 1, },
287 { .tinfo = "kDC7", .key = NCKEY_DEL, .alt = 1, .ctrl = 1, },
288 { .tinfo = "kDN", .key = NCKEY_DOWN, .shift = 1, },
289 { .tinfo = "kDN3", .key = NCKEY_DOWN, .alt = 1, },
290 { .tinfo = "kDN4", .key = NCKEY_DOWN, .alt = 1, .shift = 1, },
291 { .tinfo = "kDN5", .key = NCKEY_DOWN, .ctrl = 1, },
292 { .tinfo = "kDN6", .key = NCKEY_DOWN, .ctrl = 1, .shift = 1, },
293 { .tinfo = "kDN7", .key = NCKEY_DOWN, .alt = 1, .ctrl = 1, },
294 { .tinfo = "kEND", .key = NCKEY_END, .shift = 1, },
295 { .tinfo = "kEND3", .key = NCKEY_END, .alt = 1, },
296 { .tinfo = "kEND4", .key = NCKEY_END, .alt = 1, .shift = 1, },
297 { .tinfo = "kEND5", .key = NCKEY_END, .ctrl = 1, },
298 { .tinfo = "kEND6", .key = NCKEY_END, .ctrl = 1, .shift = 1, },
299 { .tinfo = "kEND7", .key = NCKEY_END, .alt = 1, .ctrl = 1, },
300 { .tinfo = "kHOM", .key = NCKEY_HOME, .shift = 1, },
301 { .tinfo = "kHOM3", .key = NCKEY_HOME, .alt = 1, },
302 { .tinfo = "kHOM4", .key = NCKEY_HOME, .alt = 1, .shift = 1, },
303 { .tinfo = "kHOM5", .key = NCKEY_HOME, .ctrl = 1, },
304 { .tinfo = "kHOM6", .key = NCKEY_HOME, .ctrl = 1, .shift = 1, },
305 { .tinfo = "kHOM7", .key = NCKEY_HOME, .alt = 1, .ctrl = 1, },
306 { .tinfo = "kIC", .key = NCKEY_INS, .shift = 1, },
307 { .tinfo = "kIC3", .key = NCKEY_INS, .alt = 1, },
308 { .tinfo = "kIC4", .key = NCKEY_INS, .alt = 1, .shift = 1, },
309 { .tinfo = "kIC5", .key = NCKEY_INS, .ctrl = 1, },
310 { .tinfo = "kIC6", .key = NCKEY_INS, .ctrl = 1, .shift = 1, },
311 { .tinfo = "kIC7", .key = NCKEY_INS, .alt = 1, .ctrl = 1, },
312 { .tinfo = "kLFT", .key = NCKEY_LEFT, .shift = 1, },
313 { .tinfo = "kLFT3", .key = NCKEY_LEFT, .alt = 1, },
314 { .tinfo = "kLFT4", .key = NCKEY_LEFT, .alt = 1, .shift = 1, },
315 { .tinfo = "kLFT5", .key = NCKEY_LEFT, .ctrl = 1, },
316 { .tinfo = "kLFT6", .key = NCKEY_LEFT, .ctrl = 1, .shift = 1, },
317 { .tinfo = "kLFT7", .key = NCKEY_LEFT, .alt = 1, .ctrl = 1, },
318 { .tinfo = "kNXT", .key = NCKEY_PGDOWN, .shift = 1, },
319 { .tinfo = "kNXT3", .key = NCKEY_PGDOWN, .alt = 1, },
320 { .tinfo = "kNXT4", .key = NCKEY_PGDOWN, .alt = 1, .shift = 1, },
321 { .tinfo = "kNXT5", .key = NCKEY_PGDOWN, .ctrl = 1, },
322 { .tinfo = "kNXT6", .key = NCKEY_PGDOWN, .ctrl = 1, .shift = 1, },
323 { .tinfo = "kNXT7", .key = NCKEY_PGDOWN, .alt = 1, .ctrl = 1, },
324 { .tinfo = "kPRV", .key = NCKEY_PGUP, .shift = 1, },
325 { .tinfo = "kPRV3", .key = NCKEY_PGUP, .alt = 1, },
326 { .tinfo = "kPRV4", .key = NCKEY_PGUP, .alt = 1, .shift = 1, },
327 { .tinfo = "kPRV5", .key = NCKEY_PGUP, .ctrl = 1, },
328 { .tinfo = "kPRV6", .key = NCKEY_PGUP, .ctrl = 1, .shift = 1, },
329 { .tinfo = "kPRV7", .key = NCKEY_PGUP, .alt = 1, .ctrl = 1, },
330 { .tinfo = "kRIT", .key = NCKEY_RIGHT, .shift = 1, },
331 { .tinfo = "kRIT3", .key = NCKEY_RIGHT, .alt = 1, },
332 { .tinfo = "kRIT4", .key = NCKEY_RIGHT, .alt = 1, .shift = 1, },
333 { .tinfo = "kRIT5", .key = NCKEY_RIGHT, .ctrl = 1, },
334 { .tinfo = "kRIT6", .key = NCKEY_RIGHT, .ctrl = 1, .shift = 1, },
335 { .tinfo = "kRIT7", .key = NCKEY_RIGHT, .alt = 1, .ctrl = 1, },
336 { .tinfo = "kUP", .key = NCKEY_UP, .shift = 1, },
337 { .tinfo = "kUP3", .key = NCKEY_UP, .alt = 1, },
338 { .tinfo = "kUP4", .key = NCKEY_UP, .alt = 1, .shift = 1, },
339 { .tinfo = "kUP5", .key = NCKEY_UP, .ctrl = 1, },
340 { .tinfo = "kUP6", .key = NCKEY_UP, .ctrl = 1, .shift = 1, },
341 { .tinfo = "kUP7", .key = NCKEY_UP, .alt = 1, .ctrl = 1, },
342 { .tinfo = NULL, .key = 0, }
343 }, *k;
344 for(k = keys ; k->tinfo ; ++k){
345 char* seq = tigetstr(k->tinfo);
346 if(seq == NULL || seq == (char*)-1){
347 loginfo("no terminfo declaration for %s", k->tinfo);
348 continue;
349 }
350 if(seq[0] != NCKEY_ESC || strlen(seq) < 2){ // assume ESC prefix + content
351 logwarn("invalid escape: %s (0x%x)", k->tinfo, k->key);
352 continue;
353 }
354 unsigned modifiers = (k->shift ? NCKEY_MOD_SHIFT : 0)
355 | (k->alt ? NCKEY_MOD_ALT : 0)
356 | (k->ctrl ? NCKEY_MOD_CTRL : 0);
357 if(inputctx_add_input_escape(&ictx->amata, seq, k->key, modifiers)){
358 return -1;
359 }
360 logdebug("support for terminfo's %s: %s", k->tinfo, seq);
361 }
362 const char* bs = tigetstr("kbs");
363 if(bs == NULL){
364 logwarn("no backspace key was defined");
365 }else{
366 if(bs[0] == NCKEY_ESC){
368 return -1;
369 }
370 }else{
371 ictx->backspace = bs[0];
372 }
373 }
374#else
375 (void)ictx;
376#endif
377 return 0;
378}
379
380// starting from the current amata match point, match any necessary prefix, then
381// extract the (possibly empty) content, then match the follow. as we are only
382// called from a callback context, and know we've been properly matched, there
383// is no error-checking per se (we do require prefix/follow matches, but if
384// missed, we just return NULL). indicate empty prefix with "", not NULL.
385// updates ictx->amata.matchstart to be pointing past the follow. follow ought
386// not be NUL.
387static char*
388amata_next_kleene(automaton* amata, const char* prefix, char follow){
389 char c;
390 while( (c = *prefix++) ){
391 if(*amata->matchstart != c){
392 logerror("matchstart didn't match prefix (%c vs %c)", c, *amata->matchstart);
393 return NULL;
394 }
395 ++amata->matchstart;
396 }
397 // prefix has been matched. mark start of string and find follow.
398 const unsigned char* start = amata->matchstart;
399 while(*amata->matchstart != follow){
400 ++amata->matchstart;
401 }
402 char* ret = malloc(amata->matchstart - start + 1);
403 if(ret){
404 memcpy(ret, start, amata->matchstart - start);
405 ret[amata->matchstart - start] = '\0';
406 }
407 return ret;
408}
409
410// starting from the current amata match point, match any necessary prefix, then
411// extract the numeric (possibly empty), then match the follow. as we are only
412// called from a callback context, and know we've been properly matched, there
413// is no error-checking per se (we do require prefix/follow matches, but if
414// missed, we just return 0). indicate empty prefix with "", not NULL.
415// updates ictx->amata.matchstart to be pointing past the follow. follow ought
416// not be a digit nor NUL.
417static unsigned
418amata_next_numeric(automaton* amata, const char* prefix, char follow){
419 char c;
420 while( (c = *prefix++) ){
421 if(*amata->matchstart != c){
422 logerror("matchstart didn't match prefix (%c vs %c)", c, *amata->matchstart);
423 return 0;
424 }
425 ++amata->matchstart;
426 }
427 // prefix has been matched
428 unsigned ret = 0;
429 while(isdigit(*amata->matchstart)){
430 int addend = *amata->matchstart - '0';
431 if((UINT_MAX - addend) / 10 < ret){
432 logerror("overflow: %u * 10 + %u > %u", ret, addend, UINT_MAX);
433 }
434 ret *= 10;
435 ret += addend;
436 ++amata->matchstart;
437 }
438 char candidate = *amata->matchstart++;
439 if(candidate != follow){
440 logerror("didn't see follow (%c vs %c)", candidate, follow);
441 return 0;
442 }
443 return ret;
444}
445
446// same deal as amata_next_numeric, but returns a heap-allocated string.
447// strings always end with ST ("x1b\\"). this one *does* return NULL on
448// either a match failure or an alloc failure.
449static char*
450amata_next_string(automaton* amata, const char* prefix){
451 return amata_next_kleene(amata, prefix, '\x1b');
452}
453
454static inline void
455send_synth_signal(int sig){
456 if(sig){
457#ifndef __MINGW32__
458 raise(sig);
459#endif
460 }
461}
462
463static void
464mark_pipe_ready(ipipe pipes[static 2]){
465 char sig = 1;
466#ifndef __MINGW32__
467 if(write(pipes[1], &sig, sizeof(sig)) != 1){
468 logwarn("error writing to pipe (%d) (%s)", pipes[1], strerror(errno));
469#else
470 DWORD wrote;
471 if(!WriteFile(pipes[1], &sig, sizeof(sig), &wrote, NULL) || wrote != sizeof(sig)){
472 logwarn("error writing to pipe");
473#endif
474 }else{
475 loginfo("wrote to readiness pipe");
476 }
477}
478
479// shove the assembled input |tni| into the input queue (if there's room, and
480// we're not draining, and we haven't hit EOF). send any synthesized signal as
481// the last thing we do. if Ctrl or Shift are among the modifiers, we replace
482// any lowercase letter with its uppercase form, to maintain compatibility with
483// other input methods.
484//
485// note that this w orks entirely off 'modifiers', not the obsolete
486// shift/alt/ctrl booleans, which it neither sets nor tests!
487static void
488load_ncinput(inputctx* ictx, ncinput *tni){
489 int synth = 0;
491 // when ctrl/shift are used with an ASCII (0..127) lowercase letter, always
492 // supply the capitalized form, to maintain compatibility among solutions.
493 if(tni->id < 0x7f){
494 if(islower(tni->id)){
495 tni->id = toupper(tni->id);
496 }
497 }
498 }
499 // if the kitty keyboard protocol is in use, any input without an explicit
500 // evtype can be safely considered a PRESS.
501 if(ictx->kittykbd){
502 if(tni->evtype == NCTYPE_UNKNOWN){
503 tni->evtype = NCTYPE_PRESS;
504 }
505 }
506 if(tni->modifiers == NCKEY_MOD_CTRL){ // exclude all other modifiers
507 if(ictx->linesigs){
508 if(tni->id == 'C'){
509 synth = SIGINT;
510 }else if(tni->id == 'Z'){
511 synth = SIGSTOP;
512 }else if(tni->id == '\\'){
513 synth = SIGQUIT;
514 }
515 }
516 }
517 inc_input_events(ictx);
518 if(ictx->drain || ictx->stdineof){
519 send_synth_signal(synth);
520 return;
521 }
522 pthread_mutex_lock(&ictx->ilock);
523 if(ictx->ivalid == ictx->isize){
524 pthread_mutex_unlock(&ictx->ilock);
525 logwarn("dropping input 0x%08x", tni->id);
526 inc_input_errors(ictx);
527 send_synth_signal(synth);
528 return;
529 }
530 ncinput* ni = ictx->inputs + ictx->iwrite;
531 memcpy(ni, tni, sizeof(*tni));
532 // perform final normalizations
533 if(ni->id == 0x7f || ni->id == 0x8){
534 ni->id = NCKEY_BACKSPACE;
535 }else if(ni->id == '\n' || ni->id == '\r'){
536 ni->id = NCKEY_ENTER;
537 }else if(ni->id == ictx->backspace){
538 ni->id = NCKEY_BACKSPACE;
539 }else if(ni->id > 0 && ni->id <= 26 && ni->id != '\t'){
540 ni->id = ni->id + 'A' - 1;
542 }
543 if(++ictx->iwrite == ictx->isize){
544 ictx->iwrite = 0;
545 }
546 ++ictx->ivalid;
547 // FIXME we don't always need to write here; write if ictx->ivalid was 0, and
548 // also write *from the client context* if we empty the input buffer there..?
549 mark_pipe_ready(ictx->readypipes);
550 pthread_mutex_unlock(&ictx->ilock);
551 pthread_cond_broadcast(&ictx->icond);
552 send_synth_signal(synth);
553}
554
555static void
556pixelmouse_click(inputctx* ictx, ncinput* ni, long y, long x){
557 --x;
558 --y;
559 if(ictx->ti->cellpxy == 0 || ictx->ti->cellpxx == 0){
560 logerror("pixelmouse event without pixel info (%ld/%ld)", y, x);
561 inc_input_errors(ictx);
562 return;
563 }
564 ni->ypx = y % ictx->ti->cellpxy;
565 ni->xpx = x % ictx->ti->cellpxx;
566 y /= ictx->ti->cellpxy;
567 x /= ictx->ti->cellpxx;
568 x -= ictx->lmargin;
569 y -= ictx->tmargin;
570 // convert from 1- to 0-indexing, and account for margins
571 if(x < 0 || y < 0){ // click was in margins, drop it
572 logwarn("dropping click in margins %ld/%ld", y, x);
573 return;
574 }
575 if((unsigned)x >= ictx->ti->dimx - (ictx->rmargin + ictx->lmargin)){
576 logwarn("dropping click in margins %ld/%ld", y, x);
577 return;
578 }
579 if((unsigned)y >= ictx->ti->dimy - (ictx->bmargin + ictx->tmargin)){
580 logwarn("dropping click in margins %ld/%ld", y, x);
581 return;
582 }
583 ni->y = y;
584 ni->x = x;
585 load_ncinput(ictx, ni);
586}
587
588// ictx->numeric, ictx->p3, and ictx->p2 have the two parameters. we're using
589// SGR (1006) mouse encoding, so use the final character to determine release
590// ('M' for click, 'm' for release).
591static void
592mouse_click(inputctx* ictx, unsigned release, char follow){
593 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[<", ';');
594 long x = amata_next_numeric(&ictx->amata, "", ';');
595 long y = amata_next_numeric(&ictx->amata, "", follow);
596 ncinput tni = {
597 .ctrl = mods & 0x10,
598 .alt = mods & 0x08,
599 .shift = mods & 0x04,
600 };
601 tni.modifiers = (tni.shift ? NCKEY_MOD_SHIFT : 0)
602 | (tni.ctrl ? NCKEY_MOD_CTRL : 0)
603 | (tni.alt ? NCKEY_MOD_ALT : 0);
604 // SGR mouse reporting: lower two bits signify base button + {0, 1, 2} press
605 // and no button pressed/release/{3}. bit 5 indicates motion. bits 6 and 7
606 // select device groups: 64 is buttons 4--7, 128 is 8--11. a pure motion
607 // report (no button) is 35 (32 + 3 (no button pressed)) with (oddly enough)
608 // 'M' (i.e. release == true).
609 if(release){
611 }else{
612 tni.evtype = NCTYPE_PRESS;
613 }
614 if(mods % 4 == 3){
615 tni.id = NCKEY_MOTION;
617 }else{
618 if(mods < 64){
619 tni.id = NCKEY_BUTTON1 + (mods % 4);
620 }else if(mods >= 64 && mods < 128){
621 tni.id = NCKEY_BUTTON4 + (mods % 4);
622 }else if(mods >= 128 && mods < 192){
623 tni.id = NCKEY_BUTTON8 + (mods % 4);
624 }
625 }
626 if(ictx->ti->pixelmice){
627 if(ictx->ti->cellpxx == 0){
628 logerror("pixelmouse but no pixel info");
629 }
630 return pixelmouse_click(ictx, &tni, y, x);
631 }
632 x -= (1 + ictx->lmargin);
633 y -= (1 + ictx->tmargin);
634 // convert from 1- to 0-indexing, and account for margins
635 if(x < 0 || y < 0){ // click was in margins, drop it
636 logwarn("dropping click in margins %ld/%ld", y, x);
637 return;
638 }
639 if((unsigned)x >= ictx->ti->dimx - (ictx->rmargin + ictx->lmargin)){
640 logwarn("dropping click in margins %ld/%ld", y, x);
641 return;
642 }
643 if((unsigned)y >= ictx->ti->dimy - (ictx->bmargin + ictx->tmargin)){
644 logwarn("dropping click in margins %ld/%ld", y, x);
645 return;
646 }
647 tni.x = x;
648 tni.y = y;
649 tni.ypx = -1;
650 tni.xpx = -1;
651 load_ncinput(ictx, &tni);
652}
653
654static int
655mouse_press_cb(inputctx* ictx){
656 mouse_click(ictx, 0, 'M');
657 return 2;
658}
659
660static int
661mouse_release_cb(inputctx* ictx){
662 mouse_click(ictx, 1, 'm');
663 return 2;
664}
665
666static int
667cursor_location_cb(inputctx* ictx){
668 unsigned y = amata_next_numeric(&ictx->amata, "\x1b[", ';') - 1;
669 unsigned x = amata_next_numeric(&ictx->amata, "", 'R') - 1;
670 // the first one doesn't go onto the queue; consume it here
671 pthread_mutex_lock(&ictx->clock);
672 --ictx->coutstanding;
673 if(ictx->initdata){
674 pthread_mutex_unlock(&ictx->clock);
675 ictx->initdata->cursory = y;
676 ictx->initdata->cursorx = x;
677 return 2;
678 }
679 if(ictx->cvalid == ictx->csize){
680 pthread_mutex_unlock(&ictx->clock);
681 logwarn("dropping cursor location report %u/%u", y, x);
682 inc_input_errors(ictx);
683 }else{
684 cursorloc* cloc = &ictx->csrs[ictx->cwrite];
685 if(++ictx->cwrite == ictx->csize){
686 ictx->cwrite = 0;
687 }
688 cloc->y = y;
689 cloc->x = x;
690 ++ictx->cvalid;
691 pthread_mutex_unlock(&ictx->clock);
692 pthread_cond_broadcast(&ictx->ccond);
693 loginfo("cursor location: %u/%u", y, x);
694 }
695 return 2;
696}
697
698static int
699geom_cb(inputctx* ictx){
700 unsigned kind = amata_next_numeric(&ictx->amata, "\x1b[", ';');
701 unsigned y = amata_next_numeric(&ictx->amata, "", ';');
702 unsigned x = amata_next_numeric(&ictx->amata, "", 't');
703 if(kind == 4){ // pixel geometry
704 if(ictx->initdata){
705 ictx->initdata->pixy = y;
706 ictx->initdata->pixx = x;
707 }
708 loginfo("pixel geom report %d/%d", y, x);
709 }else if(kind == 8){ // cell geometry
710 if(ictx->initdata){
711 ictx->initdata->dimy = y;
712 ictx->initdata->dimx = x;
713 }
714 loginfo("cell geom report %d/%d", y, x);
715 }else{
716 logerror("invalid geom report type: %d", kind);
717 return -1;
718 }
719 return 2;
720}
721
722static void
723xtmodkey(inputctx* ictx, int val, int mods){
724 assert(mods >= 0);
725 assert(val > 0);
726 logdebug("v/m %d %d", val, mods);
727 ncinput tni = {
728 .id = val,
729 .evtype = NCTYPE_UNKNOWN,
730 };
731 if(mods == 2 || mods == 4 || mods == 6 || mods == 8 || mods == 10
732 || mods == 12 || mods == 14 || mods == 16){
733 tni.shift = 1;
735 }
736 if(mods == 5 || mods == 6 || mods == 7 || mods == 8 ||
737 (mods >= 13 && mods <= 16)){
738 tni.ctrl = 1;
740 }
741 if(mods == 3 || mods == 4 || mods == 7 || mods == 8 || mods == 11
742 || mods == 12 || mods == 15 || mods == 16){
743 tni.alt = 1;
745 }
746 if(mods >= 9 && mods <= 16){
748 }
749 load_ncinput(ictx, &tni);
750}
751
752static uint32_t
753kitty_functional(uint32_t val){
754 if(val >= 57344 && val <= 63743){
755 if(val >= 57376 && val <= 57398){
756 val = NCKEY_F13 + val - 57376;
757 }else if(val >= 57428 && val <= 57440){
758 val = NCKEY_MEDIA_PLAY + val - 57428;
759 }else if(val >= 57399 && val <= 57408){
760 val = '0' + val - 57399;
761 }else if(val >= 57441 && val <= 57454){ // up through NCKEY_L5SHIFT
762 val = NCKEY_LSHIFT + val - 57441;
763 }else switch(val){
764 case 57358: val = NCKEY_CAPS_LOCK; break;
765 case 57400: val = '1'; break;
766 case 57359: val = NCKEY_SCROLL_LOCK; break;
767 case 57360: val = NCKEY_NUM_LOCK; break;
768 case 57361: val = NCKEY_PRINT_SCREEN; break;
769 case 57362: val = NCKEY_PAUSE; break;
770 case 57363: val = NCKEY_MENU; break;
771 case 57409: val = '.'; break;
772 case 57410: val = '/'; break;
773 case 57411: val = '*'; break;
774 case 57412: val = '-'; break;
775 case 57413: val = '+'; break;
776 case 57414: val = NCKEY_ENTER; break;
777 case 57415: val = '='; break;
778 case 57416: val = NCKEY_SEPARATOR; break;
779 case 57417: val = NCKEY_LEFT; break;
780 case 57418: val = NCKEY_RIGHT; break;
781 case 57419: val = NCKEY_UP; break;
782 case 57420: val = NCKEY_DOWN; break;
783 case 57421: val = NCKEY_PGUP; break;
784 case 57422: val = NCKEY_PGDOWN; break;
785 case 57423: val = NCKEY_HOME; break;
786 case 57424: val = NCKEY_END; break;
787 case 57425: val = NCKEY_INS; break;
788 case 57426: val = NCKEY_DEL; break;
789 case 57427: val = NCKEY_BEGIN; break;
790 }
791 }else{
792 switch(val){
793 case 0xd: val = NCKEY_ENTER; break;
794 }
795 }
796 return val;
797}
798
799static void
800kitty_kbd_txt(inputctx* ictx, int val, int mods, uint32_t *txt, int evtype){
801 assert(evtype >= 0);
802 assert(mods >= 0);
803 assert(val > 0);
804 logdebug("v/m/e %d %d %d", val, mods, evtype);
805 // "If the modifier field is not present in the escape code, its default value
806 // is 1 which means no modifiers."
807 if(mods == 0){
808 mods = 1;
809 }
810 ncinput tni = {
811 .id = kitty_functional(val),
812 .shift = mods && !!((mods - 1) & 0x1),
813 .alt = mods && !!((mods - 1) & 0x2),
814 .ctrl = mods && !!((mods - 1) & 0x4),
815 .modifiers = mods - 1,
816 };
817 switch(evtype){
818 case 0:
819 __attribute__ ((fallthrough));
820 case 1:
821 tni.evtype = NCTYPE_PRESS;
822 break;
823 case 2:
824 tni.evtype = NCTYPE_REPEAT;
825 break;
826 case 3:
828 break;
829 default:
831 break;
832 }
833 //note: if we don't set eff_text here, it will be set to .id later.
834 if(txt && txt[0]!=0){
835 for(int i=0 ; i<NCINPUT_MAX_EFF_TEXT_CODEPOINTS ; i++){
836 tni.eff_text[i] = txt[i];
837 }
838 }
839 load_ncinput(ictx, &tni);
840}
841
842static void
843kitty_kbd(inputctx* ictx, int val, int mods, int evtype){
844 kitty_kbd_txt(ictx, val, mods, NULL, evtype);
845}
846
847static int
848kitty_cb_simple(inputctx* ictx){
849 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[", 'u');
850 val = kitty_functional(val);
851 kitty_kbd(ictx, val, 0, 0);
852 return 2;
853}
854
855static int
856kitty_cb(inputctx* ictx){
857 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[", ';');
858 unsigned mods = amata_next_numeric(&ictx->amata, "", 'u');
859 kitty_kbd(ictx, val, mods, 0);
860 return 2;
861}
862
863static int
864kitty_cb_atxtn(inputctx* ictx, int n, int with_event){
865 uint32_t txt[5]={0};
866 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[", ';');
867 unsigned ev = 0;
868 unsigned mods = 0;
869 if (with_event) {
870 mods = amata_next_numeric(&ictx->amata, "", ':');
871 ev = amata_next_numeric(&ictx->amata, "", ';');
872 } else {
873 mods = amata_next_numeric(&ictx->amata, "", ';');
874 }
875 for (int i = 0; i<n; i++) {
876 txt[i] = amata_next_numeric(&ictx->amata, "", (i==n-1)?'u':';');
877 }
878 kitty_kbd_txt(ictx, val, mods, txt, ev);
879 return 2;
880}
881
882static int
883kitty_cb_atxt1(inputctx* ictx){
884 return kitty_cb_atxtn(ictx, 1, 0);
885}
886
887static int
888kitty_cb_atxt2(inputctx* ictx){
889 return kitty_cb_atxtn(ictx, 2, 0);
890}
891
892static int
893kitty_cb_atxt3(inputctx* ictx){
894 return kitty_cb_atxtn(ictx, 3, 0);
895}
896
897static int
898kitty_cb_atxt4(inputctx* ictx){
899 return kitty_cb_atxtn(ictx, 4, 0);
900}
901
902
903static int
904kitty_cb_complex_atxt1(inputctx* ictx){
905 return kitty_cb_atxtn(ictx, 1, 1);
906}
907
908static int
909kitty_cb_complex_atxt2(inputctx* ictx){
910 return kitty_cb_atxtn(ictx, 2, 1);
911}
912
913static int
914kitty_cb_complex_atxt3(inputctx* ictx){
915 return kitty_cb_atxtn(ictx, 3, 1);
916}
917
918static int
919kitty_cb_complex_atxt4(inputctx* ictx){
920 return kitty_cb_atxtn(ictx, 4, 1);
921}
922
923static uint32_t
924legacy_functional(uint32_t id){
925 switch(id){
926 case 2: id = NCKEY_INS; break;
927 case 3: id = NCKEY_DEL; break;
928 case 5: id = NCKEY_PGUP; break;
929 case 6: id = NCKEY_PGDOWN; break;
930 case 7: id = NCKEY_HOME; break;
931 case 8: id = NCKEY_END; break;
932 case 11: id = NCKEY_F01; break;
933 case 12: id = NCKEY_F02; break;
934 case 13: id = NCKEY_F03; break;
935 case 14: id = NCKEY_F04; break;
936 case 15: id = NCKEY_F05; break;
937 case 17: id = NCKEY_F06; break;
938 case 18: id = NCKEY_F07; break;
939 case 19: id = NCKEY_F08; break;
940 case 20: id = NCKEY_F09; break;
941 case 21: id = NCKEY_F10; break;
942 case 23: id = NCKEY_F11; break;
943 case 24: id = NCKEY_F12; break;
944 }
945 return id;
946}
947
948static int
949simple_cb_begin(inputctx* ictx){
950 kitty_kbd(ictx, NCKEY_BEGIN, 0, 0);
951 return 2;
952}
953
954static int
955kitty_cb_functional(inputctx* ictx){
956 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[", ';');
957 unsigned mods = amata_next_numeric(&ictx->amata, "", ':');
958 unsigned ev = amata_next_numeric(&ictx->amata, "", '~');
959 if(val == 13) {
960 kitty_kbd(ictx, NCKEY_F03, mods, ev);
961 return 23;
962 }
963 uint32_t kval = kitty_functional(val);
964 if(kval == val){
965 kval = legacy_functional(val);
966 }
967 kitty_kbd(ictx, kval, mods, ev);
968 return 2;
969}
970
971static int
972wezterm_cb(inputctx* ictx){
973 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[", ';');
974 unsigned mods = amata_next_numeric(&ictx->amata, "", '~');
975 uint32_t kval = legacy_functional(val);
976 kitty_kbd(ictx, kval, mods, 0);
977 return 2;
978}
979
980static int
981legacy_cb_f1(inputctx* ictx){
982 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'P');
983 kitty_kbd(ictx, NCKEY_F01, mods, 0);
984 return 2;
985}
986
987static int
988legacy_cb_f2(inputctx* ictx){
989 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'Q');
990 kitty_kbd(ictx, NCKEY_F02, mods, 0);
991 return 2;
992}
993
994static int
995legacy_cb_f4(inputctx* ictx){
996 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'S');
997 kitty_kbd(ictx, NCKEY_F04, mods, 0);
998 return 2;
999}
1000
1001static int
1002kitty_cb_f1(inputctx* ictx){
1003 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1004 unsigned ev = amata_next_numeric(&ictx->amata, "", 'P');
1005 kitty_kbd(ictx, NCKEY_F01, mods, ev);
1006 return 2;
1007}
1008
1009static int
1010kitty_cb_f2(inputctx* ictx){
1011 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1012 unsigned ev = amata_next_numeric(&ictx->amata, "", 'Q');
1013 kitty_kbd(ictx, NCKEY_F02, mods, ev);
1014 return 2;
1015}
1016
1017static int
1018kitty_cb_f3(inputctx* ictx){
1019 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1020 unsigned ev = amata_next_numeric(&ictx->amata, "", 'R');
1021 kitty_kbd(ictx, NCKEY_F03, mods, ev);
1022 return 2;
1023}
1024
1025static int
1026kitty_cb_f3_alternate(inputctx* ictx){
1027 amata_next_numeric(&ictx->amata, "\x1b[", '~');
1028 kitty_kbd(ictx, NCKEY_F03, 1, 0);
1029 return 2;
1030}
1031
1032static int
1033kitty_cb_f4(inputctx* ictx){
1034 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1035 unsigned ev = amata_next_numeric(&ictx->amata, "", 'S');
1036 kitty_kbd(ictx, NCKEY_F04, mods, ev);
1037 return 2;
1038}
1039
1040static int
1041legacy_cb_right(inputctx* ictx){
1042 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'C');
1043 kitty_kbd(ictx, NCKEY_RIGHT, mods, 0);
1044 return 2;
1045}
1046
1047static int
1048legacy_cb_left(inputctx* ictx){
1049 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'D');
1050 kitty_kbd(ictx, NCKEY_LEFT, mods, 0);
1051 return 2;
1052}
1053
1054static int
1055legacy_cb_down(inputctx* ictx){
1056 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'B');
1057 kitty_kbd(ictx, NCKEY_DOWN, mods, 0);
1058 return 2;
1059}
1060
1061static int
1062legacy_cb_up(inputctx* ictx){
1063 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'A');
1064 kitty_kbd(ictx, NCKEY_UP, mods, 0);
1065 return 2;
1066}
1067
1068static int
1069kitty_cb_right(inputctx* ictx){
1070 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1071 unsigned ev = amata_next_numeric(&ictx->amata, "", 'C');
1072 kitty_kbd(ictx, NCKEY_RIGHT, mods, ev);
1073 return 2;
1074}
1075
1076static int
1077kitty_cb_left(inputctx* ictx){
1078 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1079 unsigned ev = amata_next_numeric(&ictx->amata, "", 'D');
1080 kitty_kbd(ictx, NCKEY_LEFT, mods, ev);
1081 return 2;
1082}
1083
1084static int
1085kitty_cb_down(inputctx* ictx){
1086 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1087 unsigned ev = amata_next_numeric(&ictx->amata, "", 'B');
1088 kitty_kbd(ictx, NCKEY_DOWN, mods, ev);
1089 return 2;
1090}
1091
1092static int
1093kitty_cb_up(inputctx* ictx){
1094 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1095 unsigned ev = amata_next_numeric(&ictx->amata, "", 'A');
1096 kitty_kbd(ictx, NCKEY_UP, mods, ev);
1097 return 2;
1098}
1099
1100static int
1101legacy_cb_begin(inputctx* ictx){
1102 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'E');
1103 kitty_kbd(ictx, NCKEY_BEGIN, mods, 0);
1104 return 2;
1105}
1106
1107static int
1108legacy_cb_end(inputctx* ictx){
1109 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'F');
1110 kitty_kbd(ictx, NCKEY_END, mods, 0);
1111 return 2;
1112}
1113
1114static int
1115legacy_cb_home(inputctx* ictx){
1116 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", 'H');
1117 kitty_kbd(ictx, NCKEY_HOME, mods, 0);
1118 return 2;
1119}
1120
1121static int
1122kitty_cb_begin(inputctx* ictx){
1123 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1124 unsigned ev = amata_next_numeric(&ictx->amata, "", 'E');
1125 kitty_kbd(ictx, NCKEY_BEGIN, mods, ev);
1126 return 2;
1127}
1128
1129static int
1130kitty_cb_end(inputctx* ictx){
1131 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1132 unsigned ev = amata_next_numeric(&ictx->amata, "", 'F');
1133 kitty_kbd(ictx, NCKEY_END, mods, ev);
1134 return 2;
1135}
1136
1137static int
1138kitty_cb_home(inputctx* ictx){
1139 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[1;", ':');
1140 unsigned ev = amata_next_numeric(&ictx->amata, "", 'H');
1141 kitty_kbd(ictx, NCKEY_HOME, mods, ev);
1142 return 2;
1143}
1144
1145static int
1146kitty_cb_complex(inputctx* ictx){
1147 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[", ';');
1148 unsigned mods = amata_next_numeric(&ictx->amata, "", ':');
1149 unsigned ev = amata_next_numeric(&ictx->amata, "", 'u');
1150 val = kitty_functional(val);
1151 kitty_kbd(ictx, val, mods, ev);
1152 return 2;
1153}
1154
1155static int
1156kitty_keyboard_cb(inputctx* ictx){
1157 unsigned level = amata_next_numeric(&ictx->amata, "\x1b[?", 'u');
1158 if(ictx->initdata){
1159 ictx->initdata->kbdlevel = level;
1160 }
1161 loginfo("kitty keyboard level %u (was %u)", level, ictx->kittykbd);
1162 ictx->kittykbd = level;
1163 return 2;
1164}
1165
1166static int
1167xtmodkey_cb(inputctx* ictx){
1168 unsigned mods = amata_next_numeric(&ictx->amata, "\x1b[27;", ';');
1169 unsigned val = amata_next_numeric(&ictx->amata, "", '~');
1170 xtmodkey(ictx, val, mods);
1171 return 2;
1172}
1173
1174// the only xtsmgraphics reply with a single Pv arg is color registers
1175static int
1176xtsmgraphics_cregs_cb(inputctx* ictx){
1177 unsigned pv = amata_next_numeric(&ictx->amata, "\x1b[?1;0;", 'S');
1178 if(ictx->initdata){
1179 ictx->initdata->color_registers = pv;
1180 }
1181 loginfo("sixel color registers: %d", pv);
1182 return 2;
1183}
1184
1185// the only xtsmgraphics reply with a dual Pv arg we want is sixel geometry
1186static int
1187xtsmgraphics_sixel_cb(inputctx* ictx){
1188 unsigned width = amata_next_numeric(&ictx->amata, "\x1b[?2;0;", ';');
1189 unsigned height = amata_next_numeric(&ictx->amata, "", 'S');
1190 if(ictx->initdata){
1191 ictx->initdata->sixelx = width;
1192 ictx->initdata->sixely = height;
1193 }
1194 loginfo("max sixel geometry: %dx%d", height, width);
1195 return 2;
1196}
1197
1198static void
1199handoff_initial_responses_late(inputctx* ictx){
1200 bool sig = false;
1201 pthread_mutex_lock(&ictx->ilock);
1202 if(ictx->initdata_complete && ictx->initdata){
1203 ictx->initdata = NULL;
1204 sig = true;
1205 }
1206 pthread_mutex_unlock(&ictx->ilock);
1207 if(sig){
1208 pthread_cond_broadcast(&ictx->icond);
1209 loginfo("handing off initial responses");
1210 }
1211}
1212
1213
1214// mark the initdata as complete, but don't yet broadcast it off.
1215static void
1216handoff_initial_responses_early(inputctx* ictx){
1217 pthread_mutex_lock(&ictx->ilock);
1218 // set initdata_complete, but don't clear initdata
1219 ictx->initdata_complete = ictx->initdata;
1220 pthread_mutex_unlock(&ictx->ilock);
1221}
1222
1223// if XTSMGRAPHICS responses were provided, but DA1 didn't advertise sixel
1224// support, we need scrub those responses, lest we try to use Sixel.
1225static inline void
1226scrub_sixel_responses(struct initial_responses* idata){
1227 if(idata->color_registers || idata->sixelx || idata->sixely){
1228 logwarn("answered XTSMGRAPHICS, but no sixel in DA1");
1229 idata->color_registers = 0;
1230 idata->sixelx = 0;
1231 idata->sixely = 0;
1232 }
1233}
1234
1235// Most annoyingly, SyncTERM sends a different DA response, which includes
1236// the revision of the parser, not a list of features.
1237// TODO: Be useful...
1238static int
1239da1_syncterm_cb(inputctx* ictx){
1240 loginfo("read primary device attributes");
1241 if(ictx->initdata){
1242 // TODO: SyncTERM supports sixel in some modes
1243 // but not others... discovered via CSI < Ps c
1244 // See: https://gitlab.synchro.net/main/sbbs/-/raw/master/src/conio/cterm.txt
1245 scrub_sixel_responses(ictx->initdata);
1246 handoff_initial_responses_early(ictx);
1247 }
1248 return 1;
1249}
1250
1251// annoyingly, alacritty (well, branches of alacritty) supports Sixel, but
1252// does not indicate this in their Primary Device Attributes response (there
1253// is no room for attributes in a VT102-style DA1, which alacritty uses).
1254// so, iff we've determined we're alacritty, don't scrub out Sixel details.
1255static int
1256da1_vt102_cb(inputctx* ictx){
1257 loginfo("read primary device attributes");
1258 if(ictx->initdata){
1259 if(ictx->initdata->qterm != TERMINAL_ALACRITTY){
1260 scrub_sixel_responses(ictx->initdata);
1261 }
1262 handoff_initial_responses_early(ictx);
1263 }
1264 return 1;
1265}
1266
1267static int
1268da1_cb(inputctx* ictx){
1269 loginfo("read primary device attributes");
1270 if(ictx->initdata){
1271 scrub_sixel_responses(ictx->initdata);
1272 handoff_initial_responses_early(ictx);
1273 }
1274 return 1;
1275}
1276
1277static int
1278da1_attrs_cb(inputctx* ictx){
1279 loginfo("read primary device attributes");
1280 unsigned val = amata_next_numeric(&ictx->amata, "\x1b[?", ';');
1281 char* attrlist = amata_next_kleene(&ictx->amata, "", 'c');
1282 logdebug("DA1: %u [%s]", val, attrlist);
1283 if(ictx->initdata){
1284 int foundsixel = 0;
1285 unsigned curattr = 0;
1286 for(const char* a = attrlist ; *a ; ++a){
1287 if(isdigit(*a)){
1288 curattr *= 10;
1289 curattr += *a - '0';
1290 }else if(*a == ';'){
1291 if(curattr == 4){
1292 foundsixel = 1;
1293 if(ictx->initdata->color_registers <= 0){
1294 ictx->initdata->color_registers = 256;
1295 }
1296 }else if(curattr == 28){
1297 ictx->initdata->rectangular_edits = true;
1298 }
1299 curattr = 0;
1300 }
1301 }
1302 if(curattr == 4){
1303 foundsixel = 1;
1304 if(ictx->initdata->color_registers <= 0){
1305 ictx->initdata->color_registers = 256;
1306 }
1307 }else if(curattr == 28){
1308 ictx->initdata->rectangular_edits = true;
1309 }
1310 if(!foundsixel){
1311 scrub_sixel_responses(ictx->initdata);
1312 }
1313 handoff_initial_responses_early(ictx);
1314 }
1315 free(attrlist);
1316 return 1;
1317}
1318
1319// GNU screen primarily identifies itself via an "83" as the first parameter
1320// of its DA2 reply. the version is the second parameter.
1321static int
1322da2_screen_cb(inputctx* ictx){
1323 if(ictx->initdata == NULL){
1324 return 2;
1325 }
1326 if(ictx->initdata->qterm != TERMINAL_UNKNOWN){
1327 logwarn("already identified term (%d)", ictx->initdata->qterm);
1328 return 2;
1329 }
1330 unsigned ver = amata_next_numeric(&ictx->amata, "\x1b[>83;", ';');
1331 if(ver < 10000){
1332 logwarn("version %u doesn't look like GNU screen", ver);
1333 return 2;
1334 }
1335 char verstr[9]; // three two-digit components plus two delims
1336 int s = snprintf(verstr, sizeof(verstr), "%u.%02u.%02u",
1337 ver / 10000, ver / 100 % 100, ver % 100);
1338 if(s < 0 || (unsigned)s >= sizeof(verstr)){
1339 logwarn("bad screen version %u", ver);
1340 return 2;
1341 }
1342 ictx->initdata->version = strdup(verstr);
1344 return 2;
1345}
1346
1347// we use secondary device attributes to recognize the alacritty crate
1348// version, and to ascertain the version of old, pre-XTVERSION XTerm.
1349static int
1350da2_cb(inputctx* ictx){
1351 loginfo("read secondary device attributes");
1352 if(ictx->initdata == NULL){
1353 return 2;
1354 }
1355 amata_next_numeric(&ictx->amata, "\x1b[>", ';');
1356 unsigned pv = amata_next_numeric(&ictx->amata, "", ';');
1357 int maj, min, patch;
1358 if(pv == 0){
1359 return 2;
1360 }
1361 // modern XTerm replies to XTVERSION, but older versions require extracting
1362 // the version from secondary DA
1363 if(ictx->initdata->qterm == TERMINAL_XTERM){
1364 if(ictx->initdata->version == NULL){
1365 char ver[8];
1366 int s = snprintf(ver, sizeof(ver), "%u", pv);
1367 if(s < 0 || (unsigned)s >= sizeof(ver)){
1368 logerror("bad version: %u", pv);
1369 }else{
1370 ictx->initdata->version = strdup(ver);
1371 }
1372 return 2;
1373 }
1374 }
1375 // SDA yields up Alacritty's crate version, but it doesn't unambiguously
1376 // identify Alacritty. If we've got any other version information, don't
1377 // use this. use the second parameter (Pv).
1378 if(ictx->initdata->qterm != TERMINAL_UNKNOWN || ictx->initdata->version){
1379 loginfo("termtype was %d %s, not alacritty", ictx->initdata->qterm,
1380 ictx->initdata->version);
1381 return 2;
1382 }
1383 // if a termname was manually supplied in setup, it was written to the env
1384 const char* termname = getenv("TERM");
1385 if(termname == NULL || strstr(termname, "alacritty") == NULL){
1386 loginfo("termname was [%s], probably not alacritty",
1387 termname ? termname : "unset");
1388 return 2;
1389 }
1390 maj = pv / 10000;
1391 min = (pv % 10000) / 100;
1392 patch = pv % 100;
1393 if(maj >= 100 || min >= 100 || patch >= 100){
1394 return 2;
1395 }
1396 // 3x components (two digits max each), 2x '.', NUL would suggest 9 bytes,
1397 // but older gcc __builtin___sprintf_chk insists on 13. fuck it. FIXME.
1398 char* buf = malloc(13);
1399 if(buf){
1400 sprintf(buf, "%d.%d.%d", maj, min, patch);
1401 loginfo("might be alacritty %s", buf);
1402 ictx->initdata->version = buf;
1404 }
1405 return 2;
1406}
1407
1408static int
1409kittygraph_cb(inputctx* ictx){
1410 loginfo("kitty graphics message");
1411 if(ictx->initdata){
1412 ictx->initdata->kitty_graphics = 1;
1413 }
1414 return 2;
1415}
1416
1417static int
1418decrpm_pixelmice(inputctx* ictx){
1419 unsigned ps = amata_next_numeric(&ictx->amata, "\x1b[?1016;", '$');
1420 loginfo("received decrpm 1016 %u", ps);
1421 if(ps == 2){
1422 if(ictx->initdata){
1423 ictx->initdata->pixelmice = 1;
1424 }
1425 }
1426 return 2;
1427}
1428
1429static int
1430decrpm_asu_cb(inputctx* ictx){
1431 unsigned ps = amata_next_numeric(&ictx->amata, "\x1b[?2026;", '$');
1432 loginfo("received decrpm 2026 %u", ps);
1433 if(ps == 2){
1434 if(ictx->initdata){
1435 ictx->initdata->appsync_supported = 1;
1436 }
1437 }
1438 return 2;
1439}
1440
1441static int
1442get_default_color(const char* str, uint32_t* color){
1443 int r, g, b;
1444 if(sscanf(str, "%02x/%02x/%02x", &r, &g, &b) == 3){
1445 // great! =]
1446 }else if(sscanf(str, "%04x/%04x/%04x", &r, &g, &b) == 3){
1447 r /= 256;
1448 g /= 256;
1449 b /= 256;
1450 }else{
1451 logerror("couldn't extract rgb from %s", str);
1452 return -1;
1453 }
1454 if(r < 0 || g < 0 || b < 0){
1455 logerror("invalid colors %d %d %d", r, g, b);
1456 return -1;
1457 }
1458 *color = (r << 16u) | (g << 8u) | b;
1459 return 0;
1460}
1461
1462static int
1463bgdef_cb(inputctx* ictx){
1464 if(ictx->initdata){
1465 char* str = amata_next_string(&ictx->amata, "\x1b]11;rgb:");
1466 if(str == NULL){
1467 logerror("empty bg string");
1468 }else{
1469 if(get_default_color(str, &ictx->initdata->bg) == 0){
1470 ictx->initdata->got_bg = true;
1471 loginfo("default background 0x%06x", ictx->initdata->bg);
1472 }
1473 free(str);
1474 }
1475 }
1476 return 2;
1477}
1478
1479static int
1480fgdef_cb(inputctx* ictx){
1481 if(ictx->initdata){
1482 char* str = amata_next_string(&ictx->amata, "\x1b]10;rgb:");
1483 if(str == NULL){
1484 logerror("empty fg string");
1485 }else{
1486 if(get_default_color(str, &ictx->initdata->fg) == 0){
1487 ictx->initdata->got_fg = true;
1488 loginfo("default foreground 0x%06x", ictx->initdata->fg);
1489 }
1490 free(str);
1491 }
1492 }
1493 return 2;
1494}
1495
1496static int
1497palette_cb(inputctx* ictx){
1498 if(ictx->initdata){
1499 unsigned idx = amata_next_numeric(&ictx->amata, "\x1b]4;", ';');
1500 char* str = amata_next_string(&ictx->amata, "rgb:");
1501 if(idx > sizeof(ictx->initdata->palette.chans) / sizeof(*ictx->initdata->palette.chans)){
1502 logerror("invalid index %u", idx);
1503 }else if(str == NULL){
1504 logerror("empty palette string");
1505 }else{
1506 if(get_default_color(str, &ictx->initdata->palette.chans[idx]) == 0){
1507 if((int)idx > ictx->initdata->maxpaletteread){
1508 ictx->initdata->maxpaletteread = idx;
1509 }
1510 logverbose("index %u 0x%06x", idx, ictx->initdata->palette.chans[idx]);
1511 }
1512 free(str);
1513 }
1514 }
1515 return 2;
1516}
1517
1518static int
1519extract_xtversion(inputctx* ictx, const char* str, char suffix){
1520 size_t slen = strlen(str);
1521 if(slen == 0){
1522 logwarn("empty version in xtversion");
1523 return -1;
1524 }
1525 if(suffix){
1526 if(str[slen - 1] != suffix){
1527 return -1;
1528 }
1529 --slen;
1530 }
1531 if(slen == 0){
1532 logwarn("empty version in xtversion");
1533 return -1;
1534 }
1535 ictx->initdata->version = strndup(str, slen);
1536 return 0;
1537}
1538
1539static int
1540xtversion_cb(inputctx* ictx){
1541 if(ictx->initdata == NULL){
1542 return 2;
1543 }
1544 char* xtversion = amata_next_string(&ictx->amata, "\x1bP>|");
1545 if(xtversion == NULL){
1546 logwarn("empty xtversion");
1547 return 2; // don't replay as input
1548 }
1549 static const struct {
1550 const char* prefix;
1551 char suffix;
1553 } xtvers[] = {
1554 { .prefix = "XTerm(", .suffix = ')', .term = TERMINAL_XTERM, },
1555 { .prefix = "WezTerm ", .suffix = 0, .term = TERMINAL_WEZTERM, },
1556 { .prefix = "contour ", .suffix = 0, .term = TERMINAL_CONTOUR, },
1557 { .prefix = "kitty(", .suffix = ')', .term = TERMINAL_KITTY, },
1558 { .prefix = "foot(", .suffix = ')', .term = TERMINAL_FOOT, },
1559 { .prefix = "mlterm(", .suffix = ')', .term = TERMINAL_MLTERM, },
1560 { .prefix = "tmux ", .suffix = 0, .term = TERMINAL_TMUX, },
1561 { .prefix = "iTerm2 ", .suffix = 0, .term = TERMINAL_ITERM, },
1562 { .prefix = "mintty ", .suffix = 0, .term = TERMINAL_MINTTY, },
1563 { .prefix = "terminology ", .suffix = 0, .term = TERMINAL_TERMINOLOGY, },
1564 { .prefix = NULL, .suffix = 0, .term = TERMINAL_UNKNOWN, },
1565 }, *xtv;
1566 for(xtv = xtvers ; xtv->prefix ; ++xtv){
1567 if(strncmp(xtversion, xtv->prefix, strlen(xtv->prefix)) == 0){
1568 if(extract_xtversion(ictx, xtversion + strlen(xtv->prefix), xtv->suffix) == 0){
1569 loginfo("found terminal type %d version %s", xtv->term, ictx->initdata->version);
1570 ictx->initdata->qterm = xtv->term;
1571 }else{
1572 free(xtversion);
1573 return 2;
1574 }
1575 break;
1576 }
1577 }
1578 if(xtv->prefix == NULL){
1579 logwarn("unknown xtversion [%s]", xtversion);
1580 }
1581 free(xtversion);
1582 return 2;
1583}
1584
1585// precondition: s starts with two hex digits, the first of which is not
1586// greater than 7.
1587static inline char
1588toxdigit(const char* s){
1589 char c = isalpha(*s) ? tolower(*s) - 'a' + 10 : *s - '0';
1590 c *= 16;
1591 ++s;
1592 c += isalpha(*s) ? tolower(*s) - 'a' + 10 : *s - '0';
1593 return c;
1594}
1595
1596// on success, the subsequent character is returned, and |key| and |val| have
1597// heap-allocated, decoded, nul-terminated copies of the appropriate input.
1598static const char*
1599gettcap(const char* s, char** key, char** val){
1600 const char* equals = s;
1601 // we don't want anything bigger than 7 in the first nibble
1602 unsigned firstnibble = true;
1603 while(*equals != '='){
1604 if(!isxdigit(*equals)){ // rejects a NUL byte
1605 logerror("bad key in %s", s);
1606 return NULL;
1607 }
1608 if(firstnibble && (!isdigit(*equals) || *equals - '0' >= 8)){
1609 logerror("bad key in %s", s);
1610 return NULL;
1611 }
1612 firstnibble = !firstnibble;
1613 ++equals;
1614 }
1615 if(equals - s == 0 || !firstnibble){
1616 logerror("bad key in %s", s);
1617 return NULL;
1618 }
1619 if((*key = malloc((equals - s) / 2 + 1)) == NULL){
1620 return NULL;
1621 }
1622 char* keytarg = *key;
1623 do{
1624 *keytarg = toxdigit(s);
1625 s += 2;
1626 ++keytarg;
1627 }while(*s != '=');
1628 *keytarg = '\0';
1629 ++equals; // now one past the equal sign
1630 const char *end = equals;
1631 firstnibble = true;
1632 while(*end != ';' && *end){
1633 if(!isxdigit(*end)){
1634 logerror("bad value in %s", s);
1635 goto valerr;
1636 }
1637 if(firstnibble && (!isdigit(*end) || *end - '0' >= 8)){
1638 logerror("bad value in %s", s);
1639 goto valerr;
1640 }
1641 firstnibble = !firstnibble;
1642 ++end;
1643 }
1644 if(end - equals == 0 || !firstnibble){
1645 logerror("bad value in %s", s);
1646 goto valerr;
1647 }
1648 if((*val = malloc((end - equals) / 2 + 1)) == NULL){
1649 goto valerr;
1650 }
1651 char* valtarg = *val;
1652 ++s;
1653 do{
1654 *valtarg = toxdigit(s);
1655 s += 2;
1656 ++valtarg;
1657 }while(s != end);
1658 *valtarg = '\0';
1659 loginfo("key: %s val: %s", *key, *val);
1660 return end;
1661
1662valerr:
1663 free(*key);
1664 *key = NULL;
1665 return NULL;
1666}
1667
1668// replace \E with actual 0x1b for use as a terminfo-like format string,
1669// writing in-place (and updating the nul terminator, if necessary).
1670// returns its input.
1671static inline char*
1672determinfo(char* old){
1673 bool escaped = false;
1674 char* targo = old;
1675 for(char* o = old ; *o ; ++o){
1676 if(escaped){
1677 if(*o == 'E'){
1678 *targo = 0x1b;
1679 ++targo;
1680 }else{
1681 *targo = '\\';
1682 ++targo;
1683 *targo = *o;
1684 ++targo;
1685 }
1686 escaped = false;
1687 }else if(*o == '\\'){
1688 escaped = true;
1689 }else{
1690 *targo = *o;
1691 ++targo;
1692 }
1693 }
1694 *targo = '\0';
1695 return old;
1696}
1697
1698// XTGETTCAP responses are delimited by semicolons
1699static int
1700tcap_cb(inputctx* ictx){
1701 char* str = amata_next_string(&ictx->amata, "\x1bP1+r");
1702 if(str == NULL){
1703 return 2;
1704 }
1705 loginfo("xtgettcap [%s]", str);
1706 if(ictx->initdata == NULL){
1707 free(str);
1708 return 2;
1709 }
1710 const char* s = str;
1711 char* key;
1712 char* val;
1713 // answers are delimited with semicolons, hex-encoded, key=value
1714 while(*s && (s = gettcap(s, &val, &key)) ){
1715 if(strcmp(val, "TN") == 0){
1716 if(ictx->initdata->qterm == TERMINAL_UNKNOWN){
1717 if(strcmp(key, "xterm") == 0){
1718 ictx->initdata->qterm = TERMINAL_XTERM;
1719 }else if(strcmp(key, "mlterm") == 0){
1721 }else if(strcmp(key, "xterm-kitty") == 0){
1722 ictx->initdata->qterm = TERMINAL_KITTY;
1723 }else if(strcmp(key, "xterm-256color") == 0){
1724 ictx->initdata->qterm = TERMINAL_XTERM;
1725 }else{
1726 logdebug("unknown terminal name %s", key);
1727 }
1728 }
1729 }else if(strcmp(val, "RGB") == 0){
1730 loginfo("got rgb (%s)", s);
1731 ictx->initdata->rgb = true;
1732 }else if(strcmp(val, "hpa") == 0){
1733 loginfo("got hpa (%s)", key);
1734 ictx->initdata->hpa = determinfo(key);
1735 key = NULL;
1736 }else{
1737 logwarn("unknown capability: %s", str);
1738 }
1739 free(val);
1740 free(key);
1741 if(*s == ';'){
1742 ++s;
1743 }
1744 }
1745 if(!s || *s){
1746 free(str);
1747 return -1;
1748 }
1749 free(str);
1750 return 2;
1751}
1752
1753static int
1754tda_cb(inputctx* ictx){
1755 char* str = amata_next_string(&ictx->amata, "\x1bP!|");
1756 if(str == NULL){
1757 logwarn("empty ternary device attribute");
1758 return 2; // don't replay
1759 }
1760 if(ictx->initdata && ictx->initdata->qterm == TERMINAL_UNKNOWN){
1761 if(strcmp(str, "7E565445") == 0){ // "~VTE"
1762 ictx->initdata->qterm = TERMINAL_VTE;
1763 }else if(strcmp(str, "7E7E5459") == 0){ // "~~TY"
1765 }else if(strcmp(str, "464F4F54") == 0){ // "FOOT"
1766 ictx->initdata->qterm = TERMINAL_FOOT;
1767 }else if(strcmp(str, "7E4B4445") == 0){
1769 }
1770 loginfo("got TDA: %s, terminal type %d", str, ictx->initdata->qterm);
1771 }
1772 free(str);
1773 return 2;
1774}
1775
1776static int
1777build_cflow_automaton(inputctx* ictx){
1778 // syntax: literals are matched. \N is a numeric. \D is a drain (Kleene
1779 // closure). \S is a ST-terminated string. this working is very dependent on
1780 // order, and very delicate! hands off!
1781 const struct {
1782 const char* cflow;
1783 triefunc fxn;
1784 } csis[] = {
1785 // CSI (\e[)
1786 { "[E", simple_cb_begin, },
1787 { "[<\\N;\\N;\\NM", mouse_press_cb, },
1788 { "[<\\N;\\N;\\Nm", mouse_release_cb, },
1789 // technically these must begin with "4" or "8"; enforce in callbacks
1790 { "[\\N;\\N;\\Nt", geom_cb, },
1791 { "[\\Nu", kitty_cb_simple, },
1792 { "[13~", kitty_cb_f3_alternate, },
1793 { "[\\N;\\N~", wezterm_cb, },
1794 { "[\\N;\\Nu", kitty_cb, },
1795 { "[\\N;\\N;\\Nu", kitty_cb_atxt1, },
1796 { "[\\N;\\N;\\N;\\Nu", kitty_cb_atxt2, },
1797 { "[\\N;\\N;\\N;\\N;\\Nu", kitty_cb_atxt3, },
1798 { "[\\N;\\N;\\N;\\N;\\N;\\Nu", kitty_cb_atxt4, },
1799 { "[\\N;\\N:\\Nu", kitty_cb_complex, },
1800 { "[\\N;\\N:\\N;\\Nu", kitty_cb_complex_atxt1, },
1801 { "[\\N;\\N:\\N;\\N;\\Nu", kitty_cb_complex_atxt2, },
1802 { "[\\N;\\N:\\N;\\N;\\N;\\Nu", kitty_cb_complex_atxt3, },
1803 { "[\\N;\\N:\\N;\\N;\\N;\\N;\\Nu", kitty_cb_complex_atxt4, },
1804 { "[\\N;\\N;\\N~", xtmodkey_cb, },
1805 { "[\\N;\\N:\\N~", kitty_cb_functional, },
1806 { "[1;\\NP", legacy_cb_f1, },
1807 { "[1;\\NQ", legacy_cb_f2, },
1808 { "[1;\\NS", legacy_cb_f4, },
1809 { "[1;\\ND", legacy_cb_left, },
1810 { "[1;\\NC", legacy_cb_right, },
1811 { "[1;\\NB", legacy_cb_down, },
1812 { "[1;\\NA", legacy_cb_up, },
1813 { "[1;\\NE", legacy_cb_begin, },
1814 { "[1;\\NF", legacy_cb_end, },
1815 { "[1;\\NH", legacy_cb_home, },
1816 { "[1;\\N:\\NP", kitty_cb_f1, },
1817 { "[1;\\N:\\NQ", kitty_cb_f2, },
1818 { "[1;\\N:\\NR", kitty_cb_f3, },
1819 { "[1;\\N:\\NS", kitty_cb_f4, },
1820 { "[1;\\N:\\ND", kitty_cb_left, },
1821 { "[1;\\N:\\NC", kitty_cb_right, },
1822 { "[1;\\N:\\NB", kitty_cb_down, },
1823 { "[1;\\N:\\NA", kitty_cb_up, },
1824 { "[1;\\N:\\NE", kitty_cb_begin, },
1825 { "[1;\\N:\\NF", kitty_cb_end, },
1826 { "[1;\\N:\\NH", kitty_cb_home, },
1827 { "[?\\Nu", kitty_keyboard_cb, },
1828 { "[?1016;\\N$y", decrpm_pixelmice, },
1829 { "[?2026;\\N$y", decrpm_asu_cb, },
1830 { "[\\N;\\NR", cursor_location_cb, },
1831 { "[?1;1S", NULL, }, // negative cregs XTSMGRAPHICS
1832 { "[?1;2S", NULL, }, // negative cregs XTSMGRAPHICS
1833 { "[?1;3S", NULL, }, // negative cregs XTSMGRAPHICS
1834 { "[?1;3;S", NULL, }, // iterm2 negative cregs XTSMGRAPHICS
1835 { "[?1;3;0S", NULL, }, // negative cregs XTSMGRAPHICS
1836 { "[?2;1S", NULL, }, // negative pixels XTSMGRAPHICS
1837 { "[?2;2S", NULL, }, // negative pixels XTSMGRAPHICS
1838 { "[?2;3S", NULL, }, // negative pixels XTSMGRAPHICS
1839 { "[?2;3;S", NULL, }, // iterm2 negative pixels XTSMGRAPHICS
1840 { "[?2;3;0S", NULL, }, // negative pixels XTSMGRAPHICS
1841 { "[?6c", da1_vt102_cb, }, // CSI ? 6 c ("VT102")
1842 { "[?7c", da1_cb, }, // CSI ? 7 c ("VT131")
1843 { "[?1;0c", da1_cb, }, // CSI ? 1 ; 0 c ("VT101 with No Options")
1844 { "[?1;2c", da1_cb, }, // CSI ? 1 ; 2 c ("VT100 with Advanced Video Option")
1845 { "[?4;6c", da1_cb, }, // CSI ? 4 ; 6 c ("VT132 with Advanced Video and Graphics")
1846 // CSI ? 1 2 ; Ps c ("VT125")
1847 // CSI ? 6 0 ; Ps c (kmscon)
1848 // CSI ? 6 2 ; Ps c ("VT220")
1849 // CSI ? 6 3 ; Ps c ("VT320")
1850 // CSI ? 6 4 ; Ps c ("VT420")
1851 // CSI ? 6 5 ; Ps c (WezTerm, VT5xx?)
1852 { "[?\\N;\\Dc", da1_attrs_cb, },
1853 { "[?1;0;\\NS", xtsmgraphics_cregs_cb, },
1854 { "[?2;0;\\N;\\NS", xtsmgraphics_sixel_cb, },
1855 { "[>83;\\N;0c", da2_screen_cb, },
1856 { "[>\\N;\\N;\\Nc", da2_cb, },
1857 { "[=67;84;101;114;109;\\Dc", da1_syncterm_cb, }, // CSI da1 form as issued by SyncTERM
1858 // DCS (\eP...ST)
1859 { "P0+\\S", NULL, }, // negative XTGETTCAP
1860 { "P1+r\\S", tcap_cb, }, // positive XTGETTCAP
1861 { "P!|\\S", tda_cb, }, // DCS da3 form used by XTerm
1862 { "P>|\\S", xtversion_cb, },
1863 // OSC (\e_...ST)
1864 { "_G\\S", kittygraph_cb, },
1865 // a mystery to everyone!
1866 { "]10;rgb:\\S", fgdef_cb, },
1867 { "]11;rgb:\\S", bgdef_cb, },
1868 { NULL, NULL, },
1869 }, *csi;
1870 for(csi = csis ; csi->cflow ; ++csi){
1871 if(inputctx_add_cflow(&ictx->amata, csi->cflow, csi->fxn)){
1872 logerror("failed adding %p via %s", csi->fxn, csi->cflow);
1873 return -1;
1874 }
1875 loginfo("added %p via %s", csi->fxn, csi->cflow);
1876 }
1877 if(ictx->ti->qterm == TERMINAL_RXVT){
1878 if(inputctx_add_cflow(&ictx->amata, "]4;\\N;rgb:\\R", palette_cb)){
1879 logerror("failed adding palette_cb");
1880 return -1;
1881 }
1882 }else{
1883 if(inputctx_add_cflow(&ictx->amata, "]4;\\N;rgb:\\S", palette_cb)){
1884 logerror("failed adding palette_cb");
1885 return -1;
1886 }
1887 // handle old-style contour responses, though we can't make use of them
1888 if(inputctx_add_cflow(&ictx->amata, "]4;rgb:\\S", palette_cb)){
1889 logerror("failed adding palette_cb");
1890 return -1;
1891 }
1892 }
1893 return 0;
1894}
1895
1896static void
1897closepipe(ipipe p){
1898#ifndef __MINGW32__
1899 if(p >= 0){
1900 close(p);
1901 }
1902#else
1903 if(p){
1904 CloseHandle(p);
1905 }
1906#endif
1907}
1908
1909static void
1910endpipes(ipipe pipes[static 2]){
1911 closepipe(pipes[0]);
1912 closepipe(pipes[1]);
1913}
1914
1915// only linux and freebsd13+ have eventfd(), so we'll fall back to pipes sigh.
1916static int
1917getpipes(ipipe pipes[static 2]){
1918#ifndef __MINGW32__
1919#ifndef __APPLE__
1920 if(pipe2(pipes, O_CLOEXEC | O_NONBLOCK)){
1921 logerror("couldn't get pipes (%s)", strerror(errno));
1922 return -1;
1923 }
1924#else
1925 if(pipe(pipes)){
1926 logerror("couldn't get pipes (%s)", strerror(errno));
1927 return -1;
1928 }
1929 if(set_fd_cloexec(pipes[0], 1, NULL) || set_fd_nonblocking(pipes[0], 1, NULL)){
1930 logerror("couldn't prep pipe[0] (%s)", strerror(errno));
1931 endpipes(pipes);
1932 return -1;
1933 }
1934 if(set_fd_cloexec(pipes[1], 1, NULL) || set_fd_nonblocking(pipes[1], 1, NULL)){
1935 logerror("couldn't prep pipe[1] (%s)", strerror(errno));
1936 endpipes(pipes);
1937 return -1;
1938 }
1939#endif
1940#else // windows
1941 if(!CreatePipe(&pipes[0], &pipes[1], NULL, BUFSIZ)){
1942 logerror("couldn't get pipes");
1943 return -1;
1944 }
1945#endif
1946 return 0;
1947}
1948
1949static inline inputctx*
1950create_inputctx(tinfo* ti, FILE* infp, int lmargin, int tmargin, int rmargin,
1951 int bmargin, ncsharedstats* stats, unsigned drain,
1952 int linesigs_enabled){
1953 bool sent_queries = (ti->ttyfd >= 0) ? true : false;
1954 inputctx* i = malloc(sizeof(*i));
1955 if(i){
1956 i->csize = 64;
1957 if( (i->csrs = malloc(sizeof(*i->csrs) * i->csize)) ){
1958 i->isize = BUFSIZ;
1959 if( (i->inputs = malloc(sizeof(*i->inputs) * i->isize)) ){
1960 if(pthread_mutex_init(&i->ilock, NULL) == 0){
1961 if(pthread_condmonotonic_init(&i->icond) == 0){
1962 if(pthread_mutex_init(&i->clock, NULL) == 0){
1963 if(pthread_condmonotonic_init(&i->ccond) == 0){
1964 if((i->stdinfd = fileno(infp)) >= 0){
1965 if( (i->initdata = malloc(sizeof(*i->initdata))) ){
1966 if(getpipes(i->readypipes) == 0){
1967 if(getpipes(i->ipipes) == 0){
1968 memset(&i->amata, 0, sizeof(i->amata));
1969 if(prep_special_keys(i) == 0){
1970 if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){
1971 i->termfd = tty_check(i->stdinfd) ? -1 : get_tty_fd(infp);
1972 memset(i->initdata, 0, sizeof(*i->initdata));
1973 if(sent_queries){
1974 i->coutstanding = 1; // one in initial request set
1975 i->initdata->qterm = ti->qterm;
1976 i->initdata->cursory = -1;
1977 i->initdata->cursorx = -1;
1978 i->initdata->maxpaletteread = -1;
1979 i->initdata->kbdlevel = UINT_MAX;
1980 }else{
1981 free(i->initdata);
1982 i->initdata = NULL;
1983 i->coutstanding = 0;
1984 }
1985 i->kittykbd = 0;
1986 i->iread = i->iwrite = i->ivalid = 0;
1987 i->cread = i->cwrite = i->cvalid = 0;
1989 i->stats = stats;
1990 i->ti = ti;
1991 i->stdineof = 0;
1992#ifdef __MINGW32__
1993 i->stdinhandle = ti->inhandle;
1994#endif
1995 i->ibufvalid = 0;
1996 i->linesigs = linesigs_enabled;
1997 i->tbufvalid = 0;
1998 i->midescape = 0;
1999 i->lmargin = lmargin;
2000 i->tmargin = tmargin;
2001 i->rmargin = rmargin;
2002 i->bmargin = bmargin;
2003 i->drain = drain;
2004 i->failed = false;
2005 logdebug("input descriptors: %d/%d", i->stdinfd, i->termfd);
2006 return i;
2007 }
2008 }
2010 }
2011 endpipes(i->ipipes);
2012 }
2013 endpipes(i->readypipes);
2014 }
2015 free(i->initdata);
2016 }
2017 pthread_cond_destroy(&i->ccond);
2018 }
2019 pthread_mutex_destroy(&i->clock);
2020 }
2021 pthread_cond_destroy(&i->icond);
2022 }
2023 pthread_mutex_destroy(&i->ilock);
2024 }
2025 free(i->inputs);
2026 }
2027 free(i->csrs);
2028 }
2029 free(i);
2030 }
2031 return NULL;
2032}
2033
2034static inline void
2035free_inputctx(inputctx* i){
2036 if(i){
2037 // we *do not* own stdinfd; don't close() it! we do own termfd.
2038 if(i->termfd >= 0){
2039 close(i->termfd);
2040 }
2041 pthread_mutex_destroy(&i->ilock);
2042 pthread_cond_destroy(&i->icond);
2043 pthread_mutex_destroy(&i->clock);
2044 pthread_cond_destroy(&i->ccond);
2046 // do not kill the thread here, either.
2047 if(i->initdata){
2048 free(i->initdata->version);
2049 free(i->initdata);
2050 }else if(i->initdata_complete){
2053 }
2054 endpipes(i->readypipes);
2055 endpipes(i->ipipes);
2056 free(i->inputs);
2057 free(i->csrs);
2058 free(i);
2059 }
2060}
2061
2062// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions
2063static int
2064prep_kitty_special_keys(inputctx* ictx){
2065 // we do not list here those already handled by prep_windows_special_keys()
2066 static const struct {
2067 const char* esc;
2068 uint32_t key;
2069 unsigned modifiers;
2070 } keys[] = {
2071 { .esc = "\x1b[P", .key = NCKEY_F01, },
2072 { .esc = "\x1b[Q", .key = NCKEY_F02, },
2073 { .esc = "\x1b[R", .key = NCKEY_F03, },
2074 { .esc = "\x1b[13~", .key = NCKEY_F03, },
2075 { .esc = "\x1b[S", .key = NCKEY_F04, },
2076 { .esc = "\x1b[127;2u", .key = NCKEY_BACKSPACE,
2077 .modifiers = NCKEY_MOD_SHIFT, },
2078 { .esc = "\x1b[127;3u", .key = NCKEY_BACKSPACE,
2079 .modifiers = NCKEY_MOD_ALT, },
2080 { .esc = "\x1b[127;5u", .key = NCKEY_BACKSPACE,
2081 .modifiers = NCKEY_MOD_CTRL, },
2082 { .esc = NULL, .key = 0, },
2083 }, *k;
2084 for(k = keys ; k->esc ; ++k){
2085 if(inputctx_add_input_escape(&ictx->amata, k->esc, k->key, k->modifiers)){
2086 return -1;
2087 }
2088 }
2089 loginfo("added all kitty special keys");
2090 return 0;
2091}
2092
2093// add the hardcoded windows input sequences to ti->input. should only
2094// be called after verifying that this is TERMINAL_MSTERMINAL.
2095static int
2096prep_windows_special_keys(inputctx* ictx){
2097 // here, lacking terminfo, we hardcode the sequences. they can be found at
2098 // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
2099 // under the "Input Sequences" heading.
2100 static const struct {
2101 const char* esc;
2102 uint32_t key;
2103 unsigned modifiers;
2104 } keys[] = {
2105 { .esc = "\x1b[A", .key = NCKEY_UP, },
2106 { .esc = "\x1b[B", .key = NCKEY_DOWN, },
2107 { .esc = "\x1b[C", .key = NCKEY_RIGHT, },
2108 { .esc = "\x1b[D", .key = NCKEY_LEFT, },
2109 { .esc = "\x1b[1;5A", .key = NCKEY_UP,
2110 .modifiers = NCKEY_MOD_CTRL, },
2111 { .esc = "\x1b[1;5B", .key = NCKEY_DOWN,
2112 .modifiers = NCKEY_MOD_CTRL, },
2113 { .esc = "\x1b[1;5C", .key = NCKEY_RIGHT,
2114 .modifiers = NCKEY_MOD_CTRL, },
2115 { .esc = "\x1b[1;5D", .key = NCKEY_LEFT,
2116 .modifiers = NCKEY_MOD_CTRL, },
2117 { .esc = "\x1b[H", .key = NCKEY_HOME, },
2118 { .esc = "\x1b[F", .key = NCKEY_END, },
2119 { .esc = "\x1b[2~", .key = NCKEY_INS, },
2120 { .esc = "\x1b[3~", .key = NCKEY_DEL, },
2121 { .esc = "\x1b[5~", .key = NCKEY_PGUP, },
2122 { .esc = "\x1b[6~", .key = NCKEY_PGDOWN, },
2123 { .esc = "\x1bOP", .key = NCKEY_F01, },
2124 { .esc = "\x1bOQ", .key = NCKEY_F02, },
2125 { .esc = "\x1bOR", .key = NCKEY_F03, },
2126 { .esc = "\x1bOS", .key = NCKEY_F04, },
2127 { .esc = "\x1b[15~", .key = NCKEY_F05, },
2128 { .esc = "\x1b[17~", .key = NCKEY_F06, },
2129 { .esc = "\x1b[18~", .key = NCKEY_F07, },
2130 { .esc = "\x1b[19~", .key = NCKEY_F08, },
2131 { .esc = "\x1b[20~", .key = NCKEY_F09, },
2132 { .esc = "\x1b[21~", .key = NCKEY_F10, },
2133 { .esc = "\x1b[23~", .key = NCKEY_F11, },
2134 { .esc = "\x1b[24~", .key = NCKEY_F12, },
2135 { .esc = NULL, .key = 0, },
2136 }, *k;
2137 for(k = keys ; k->esc ; ++k){
2138 if(inputctx_add_input_escape(&ictx->amata, k->esc, k->key, k->modifiers)){
2139 return -1;
2140 }
2141 logdebug("added %s %u", k->esc, k->key);
2142 }
2143 loginfo("added all windows special keys");
2144 return 0;
2145}
2146
2147static int
2148prep_all_keys(inputctx* ictx){
2149 if(prep_windows_special_keys(ictx)){
2150 return -1;
2151 }
2152 if(prep_kitty_special_keys(ictx)){
2153 return -1;
2154 }
2155 if(prep_xtmodkeys(ictx)){
2156 return -1;
2157 }
2158 return 0;
2159}
2160
2161// populate |buf| with any new data from the specified file descriptor |fd|.
2162static void
2163read_input_nblock(int fd, unsigned char* buf, size_t buflen, int *bufused,
2164 unsigned* goteof){
2165 if(fd < 0){
2166 return;
2167 }
2168 size_t space = buflen - *bufused;
2169 if(space == 0){
2170 return;
2171 }
2172 ssize_t r = read(fd, buf + *bufused, space);
2173 if(r <= 0){
2174 if(r < 0 && (errno != EAGAIN && errno != EBUSY && errno == EWOULDBLOCK)){
2175 logwarn("couldn't read from %d (%s)", fd, strerror(errno));
2176 }else{
2177 if(r < 0){
2178 logerror("error reading from %d (%s)", fd, strerror(errno));
2179 }else{
2180 logwarn("got EOF on %d", fd);
2181 }
2182 if(goteof){
2183 *goteof = 1;
2184 }
2185 }
2186 return;
2187 }
2188 *bufused += r;
2189 space -= r;
2190 loginfo("read %" PRIdPTR "B from %d (%" PRIuPTR "B left)", r, fd, space);
2191}
2192
2193// are terminal and stdin distinct for this inputctx?
2194static inline bool
2195ictx_independent_p(const inputctx* ictx){
2196 return ictx->termfd >= 0;
2197}
2198
2199// try to lex a single control sequence off of buf. return the number of bytes
2200// consumed if we do so. otherwise, return the negative number of bytes
2201// examined. set ictx->midescape if we're uncertain. we preserve a->used,
2202// a->state, etc. across runs to avoid reprocessing. buf is almost certainly
2203// *not* NUL-terminated.
2204//
2205// our rule is: an escape must arrive as a single unit to be interpreted as
2206// an escape. this is most relevant for Alt+keypress (Esc followed by the
2207// character), which is ambiguous with regards to pressing 'Escape' followed
2208// by some other character. if they arrive together, we consider it to be
2209// the escape. we might need to allow more than one process_escape call,
2210// however, in case the escape ended the previous read buffer.
2211// precondition: buflen >= 1. precondition: buf[0] == 0x1b.
2212static int
2213process_escape(inputctx* ictx, const unsigned char* buf, int buflen){
2214 assert(ictx->amata.used <= buflen);
2215 while(ictx->amata.used < buflen){
2216 unsigned char candidate = buf[ictx->amata.used++];
2217 unsigned used = ictx->amata.used;
2218 if(candidate >= 0x80){
2219 ictx->amata.used = 0;
2220 return -(used - 1);
2221 }
2222 // an escape always resets the trie (unless we're in the middle of an
2223 // ST-terminated string), as does a NULL transition.
2224 if(candidate == NCKEY_ESC && !ictx->amata.instring){
2225 ictx->amata.matchstart = buf + ictx->amata.used - 1;
2226 ictx->amata.state = ictx->amata.escapes;
2227 logtrace("initialized automaton to %u", ictx->amata.state);
2228 ictx->amata.used = 1;
2229 if(used > 1){ // we got reset; replay as input
2230 return -(used - 1);
2231 }
2232 // validated first byte as escape! keep going. otherwise, check trie.
2233 // we can safely check trie[candidate] above because we are either coming
2234 // off the initial node, which definitely has a valid ->trie, or we're
2235 // coming from a transition, where ictx->triepos->trie is checked below.
2236 }else{
2237 ncinput ni = {0};
2238 int w = walk_automaton(&ictx->amata, ictx, candidate, &ni);
2239 logdebug("walk result on %u (%c): %d %u", candidate,
2240 isprint(candidate) ? candidate : ' ', w, ictx->amata.state);
2241 if(w > 0){
2242 if(ni.id){
2243 load_ncinput(ictx, &ni);
2244 }
2245 ictx->amata.used = 0;
2246 return used;
2247 }else if(w < 0){
2248 // all inspected characters are invalid; return full negative "used"
2249 ictx->amata.used = 0;
2250 return -used;
2251 }
2252 }
2253 }
2254 // we exhausted input without knowing whether or not this is a valid control
2255 // sequence; we're still on-trie, and need more (immediate) input.
2256 ictx->midescape = 1;
2257 return -ictx->amata.used;
2258}
2259
2260// process as many control sequences from |buf|, having |bufused| bytes,
2261// as we can. this text needn't be valid UTF-8. this is always called on
2262// tbuf; if we find bulk data here, we need replay it into ibuf (assuming
2263// that there's room).
2264static void
2265process_escapes(inputctx* ictx, unsigned char* buf, int* bufused){
2266 int offset = 0;
2267 while(*bufused){
2268 int consumed = process_escape(ictx, buf + offset, *bufused);
2269 // negative |consumed| means either that we're not sure whether it's an
2270 // escape, or it definitely is not.
2271 if(consumed < 0){
2272 // if midescape is not set, the negative return means invalid escape.
2273 // replay it to the bulk input buffer; our automaton will have been reset.
2274 if(!ictx->midescape){
2275 consumed = -consumed;
2276 int available = sizeof(ictx->ibuf) - ictx->ibufvalid;
2277 if(available){
2278 if(available > consumed){
2279 available = consumed;
2280 }
2281 logwarn("replaying %dB of %dB to ibuf", available, consumed);
2282 memcpy(ictx->ibuf + ictx->ibufvalid, buf + offset, available);
2283 ictx->ibufvalid += available;
2284 }
2285 offset += consumed;
2286 ictx->midescape = 0;
2287 *bufused -= consumed;
2288 assert(0 <= *bufused);
2289 }else{
2290 break;
2291 }
2292 }
2293 *bufused -= consumed;
2294 offset += consumed;
2295 assert(0 <= *bufused);
2296 }
2297 // move any leftovers to the front; only happens if we fill output queue,
2298 // or ran out of input data mid-escape
2299 if(*bufused){
2300 ictx->amata.matchstart = buf;
2301 memmove(buf, buf + offset, *bufused);
2302 }
2303}
2304
2305// precondition: buflen >= 1. attempts to consume UTF8 input from buf. the
2306// expected length of a UTF8 character can be determined from its first byte.
2307// if we don't have that much data, return 0 and read more. if we determine
2308// an error, return -1 to consume 1 byte, restarting the UTF8 lex on the next
2309// byte. on a valid UTF8 character, set up the ncinput and return its length.
2310static int
2311process_input(const unsigned char* buf, int buflen, ncinput* ni){
2312 assert(1 <= buflen);
2313 memset(ni, 0, sizeof(*ni));
2314 const int cpointlen = utf8_codepoint_length(*buf);
2315 if(cpointlen <= 0){
2316 logwarn("invalid UTF8 initiator on input (0x%02x)", *buf);
2317 return -1;
2318 }else if(cpointlen == 1){ // pure ascii can't show up mid-utf8-character
2319 ni->id = buf[0];
2320 return 1;
2321 }
2322 if(cpointlen > buflen){
2323 logwarn("utf8 character (%dB) broken across read", cpointlen);
2324 return 0; // need read more data; we don't have the complete character
2325 }
2326 wchar_t w;
2327 mbstate_t mbstate = {0};
2328//fprintf(stderr, "CANDIDATE: %d cpointlen: %zu cpoint: %d\n", candidate, cpointlen, cpoint[cpointlen]);
2329 // FIXME how the hell does this work with 16-bit wchar_t?
2330 size_t r = mbrtowc(&w, (const char*)buf, cpointlen, &mbstate);
2331 if(r == (size_t)-1 || r == (size_t)-2){
2332 logerror("invalid utf8 prefix (%dB) on input", cpointlen);
2333 return -1;
2334 }
2335 ni->id = w;
2336 return cpointlen;
2337}
2338
2339// precondition: buflen >= 1. gets an ncinput prepared by process_input, and
2340// sticks that into the bulk queue.
2341static int
2342process_ncinput(inputctx* ictx, const unsigned char* buf, int buflen){
2343 ncinput ni;
2344 int r = process_input(buf, buflen, &ni);
2345 if(r > 0){
2346 load_ncinput(ictx, &ni);
2347 }else if(r < 0){
2348 inc_input_errors(ictx);
2349 r = 1; // we want to consume a single byte, upstairs
2350 }
2351 return r;
2352}
2353
2354// handle redirected input (i.e. not from our connected terminal). process as
2355// much bulk UTF-8 input as we can, knowing it to be free of control sequences.
2356// anything not a valid UTF-8 character is dropped.
2357static void
2358process_bulk(inputctx* ictx, unsigned char* buf, int* bufused){
2359 int offset = 0;
2360 while(*bufused){
2361 bool noroom = false;
2362 pthread_mutex_lock(&ictx->ilock);
2363 if(ictx->ivalid == ictx->isize){
2364 noroom = true;
2365 }
2366 pthread_mutex_unlock(&ictx->ilock);
2367 if(noroom){
2368 break;
2369 }
2370 int consumed = process_ncinput(ictx, buf + offset, *bufused);
2371 if(consumed <= 0){
2372 break;
2373 }
2374 *bufused -= consumed;
2375 offset += consumed;
2376 }
2377 // move any leftovers to the front
2378 if(*bufused){
2379 memmove(buf, buf + offset, *bufused);
2380 }
2381}
2382
2383// process as much mixed input as we can. we might find UTF-8 bulk input and
2384// control sequences mixed (though each individual character/sequence ought be
2385// contiguous). known control sequences are removed for internal processing.
2386// everything else will be handed up to the client (assuming it to be valid
2387// UTF-8).
2388static void
2389process_melange(inputctx* ictx, const unsigned char* buf, int* bufused){
2390 int offset = 0;
2391 int origlen = *bufused;
2392 while(*bufused){
2393 logdebug("input %d (%u)/%d [0x%02x] (%c)", offset, ictx->amata.used,
2394 *bufused, buf[offset], isprint(buf[offset]) ? buf[offset] : ' ');
2395 int consumed = 0;
2396 if(buf[offset] == '\x1b'){
2397 consumed = process_escape(ictx, buf + offset, *bufused);
2398 if(consumed < 0){
2399 if(ictx->midescape){
2400 if(*bufused != -consumed || *bufused == origlen){
2401 // not at the end; treat it as input. no need to move between
2402 // buffers; simply ensure we process it as input, and don't mark
2403 // anything as consumed.
2404 ictx->midescape = 0;
2405 }
2406 }
2407 }
2408 }
2409 // don't process as input only if we just read a valid control character,
2410 // or if we need to read more to determine what it is.
2411 if(consumed <= 0 && !ictx->midescape){
2412 consumed = process_ncinput(ictx, buf + offset, *bufused);
2413 }
2414 if(consumed < 0){
2415 break;
2416 }
2417 *bufused -= consumed;
2418 offset += consumed;
2419 }
2420 handoff_initial_responses_late(ictx);
2421}
2422
2423// walk the matching automaton from wherever we were.
2424static void
2425process_ibuf(inputctx* ictx){
2426 if(resize_seen){
2427 ncinput tni = {
2428 .id = NCKEY_RESIZE,
2429 };
2430 load_ncinput(ictx, &tni);
2431 resize_seen = 0;
2432 }
2433 if(cont_seen){
2434 ncinput tni = {
2435 .id = NCKEY_SIGNAL,
2436 };
2437 load_ncinput(ictx, &tni);
2438 cont_seen = 0;
2439 }
2440 if(ictx->tbufvalid){
2441 // we could theoretically do this in parallel with process_bulk, but it
2442 // hardly seems worthwhile without breaking apart the fetches of input.
2443 process_escapes(ictx, ictx->tbuf, &ictx->tbufvalid);
2444 handoff_initial_responses_late(ictx);
2445 }
2446 if(ictx->ibufvalid){
2447 if(ictx_independent_p(ictx)){
2448 process_bulk(ictx, ictx->ibuf, &ictx->ibufvalid);
2449 }else{
2450 int valid = ictx->ibufvalid;
2451 process_melange(ictx, ictx->ibuf, &ictx->ibufvalid);
2452 // move any leftovers to the front
2453 if(ictx->ibufvalid){
2454 memmove(ictx->ibuf, ictx->ibuf + valid - ictx->ibufvalid, ictx->ibufvalid);
2455 if(ictx->amata.matchstart){
2456 ictx->amata.matchstart = ictx->ibuf;
2457 }
2458 }
2459 }
2460 }
2461}
2462
2463int ncinput_shovel(inputctx* ictx, const void* buf, int len){
2464 process_melange(ictx, buf, &len);
2465 if(len){
2466 logwarn("dropping %d byte%s", len, len == 1 ? "" : "s");
2467 inc_input_errors(ictx);
2468 }
2469 return 0;
2470}
2471
2472// here, we always block for an arbitrarily long time, or not at all,
2473// doing the latter only when ictx->midescape is set. |rtfd| and/or |rifd|
2474// are set high iff they are ready for reading, and otherwise cleared.
2475static int
2476block_on_input(inputctx* ictx, unsigned* rtfd, unsigned* rifd){
2477 logtrace("blocking on input availability");
2478 *rtfd = *rifd = 0;
2479 unsigned nonblock = ictx->midescape;
2480 if(nonblock){
2481 loginfo("nonblocking read to check for completion");
2482 ictx->midescape = 0;
2483 }
2484#ifdef __MINGW32__
2485 int timeoutms = nonblock ? 0 : -1;
2486 DWORD ncount = 0;
2487 HANDLE handles[2];
2488 if(!ictx->stdineof){
2489 if(ictx->ibufvalid != sizeof(ictx->ibuf)){
2490 handles[ncount++] = ictx->stdinhandle;
2491 }
2492 }
2493 if(ncount == 0){
2494 handles[ncount++] = ictx->ipipes[0];
2495 }
2496 DWORD d = WaitForMultipleObjects(ncount, handles, false, timeoutms);
2497 if(d == WAIT_TIMEOUT){
2498 return 0;
2499 }else if(d == WAIT_FAILED){
2500 return -1;
2501 }else if(d - WAIT_OBJECT_0 == 0){
2502 *rifd = 1;
2503 return 1;
2504 }
2505 return -1;
2506#else
2507 int inevents = POLLIN;
2508#ifdef POLLRDHUP
2509 inevents |= POLLRDHUP;
2510#endif
2511 struct pollfd pfds[2];
2512 int pfdcount = 0;
2513 if(!ictx->stdineof){
2514 if(ictx->ibufvalid != sizeof(ictx->ibuf)){
2515 pfds[pfdcount].fd = ictx->stdinfd;
2516 pfds[pfdcount].events = inevents;
2517 pfds[pfdcount].revents = 0;
2518 ++pfdcount;
2519 }
2520 }
2521 if(pfdcount == 0){
2522 loginfo("output queues full; blocking on ipipes");
2523 pfds[pfdcount].fd = ictx->ipipes[0];
2524 pfds[pfdcount].events = inevents;
2525 pfds[pfdcount].revents = 0;
2526 ++pfdcount;
2527 }
2528 if(ictx->termfd >= 0){
2529 pfds[pfdcount].fd = ictx->termfd;
2530 pfds[pfdcount].events = inevents;
2531 pfds[pfdcount].revents = 0;
2532 ++pfdcount;
2533 }
2534 logtrace("waiting on %d fds (ibuf: %u/%"PRIuPTR")", pfdcount, ictx->ibufvalid, sizeof(ictx->ibuf));
2535 sigset_t smask;
2536 sigfillset(&smask);
2537 sigdelset(&smask, SIGCONT);
2538 sigdelset(&smask, SIGWINCH);
2539#ifdef SIGTHR
2540 // freebsd uses SIGTHR for thread cancellation; need this to ensure wakeup
2541 // on exit (in cancel_and_join()).
2542 sigdelset(&smask, SIGTHR);
2543#endif
2544 int events;
2545#if defined(__APPLE__) || defined(__MINGW32__)
2546 int timeoutms = nonblock ? 0 : -1;
2547 while((events = poll(pfds, pfdcount, timeoutms)) < 0){ // FIXME smask?
2548#else
2549 struct timespec ts = { .tv_sec = 0, .tv_nsec = 0, };
2550 struct timespec* pts = nonblock ? &ts : NULL;
2551 while((events = ppoll(pfds, pfdcount, pts, &smask)) < 0){
2552#endif
2553 if(errno == EINTR){
2554 loginfo("interrupted by signal");
2555 return resize_seen;
2556 }else if(errno != EAGAIN && errno != EBUSY && errno != EWOULDBLOCK){
2557 logerror("error polling (%s)", strerror(errno));
2558 return -1;
2559 }
2560 }
2561 loginfo("poll returned %d", events);
2562 pfdcount = 0;
2563 while(events){
2564 if(pfds[pfdcount].revents){
2565 if(pfds[pfdcount].fd == ictx->stdinfd){
2566 *rifd = 1;
2567 }else if(pfds[pfdcount].fd == ictx->termfd){
2568 *rtfd = 1;
2569 }else if(pfds[pfdcount].fd == ictx->ipipes[0]){
2570 char c;
2571 while(read(ictx->ipipes[0], &c, sizeof(c)) == 1){
2572 // FIXME accelerate?
2573 }
2574 }
2575 --events;
2576 }
2577 ++pfdcount;
2578 }
2579 loginfo("got events: %c%c", *rtfd ? 'T' : 't', *rifd ? 'I' : 'i');
2580 return events;
2581#endif
2582}
2583
2584// populate the ibuf with any new data, up through its size, but do not block.
2585// don't loop around this call without some kind of readiness notification.
2586static void
2587read_inputs_nblock(inputctx* ictx){
2588 unsigned rtfd, rifd;
2589 block_on_input(ictx, &rtfd, &rifd);
2590 // first we read from the terminal, if that's a distinct source.
2591 if(rtfd){
2592 read_input_nblock(ictx->termfd, ictx->tbuf, sizeof(ictx->tbuf),
2593 &ictx->tbufvalid, NULL);
2594 }
2595 // now read bulk, possibly with term escapes intermingled within (if there
2596 // was not a distinct terminal source).
2597 if(rifd){
2598 unsigned eof = ictx->stdineof;
2599 read_input_nblock(ictx->stdinfd, ictx->ibuf, sizeof(ictx->ibuf),
2600 &ictx->ibufvalid, &ictx->stdineof);
2601 // did we switch from non-EOF state to EOF? if so, mark us ready
2602 if(!eof && ictx->stdineof){
2603 // we hit EOF; write an event to the readiness fd
2604 mark_pipe_ready(ictx->readypipes);
2605 pthread_cond_broadcast(&ictx->icond);
2606 }
2607 }
2608}
2609
2610static void*
2611input_thread(void* vmarshall){
2613 inputctx* ictx = vmarshall;
2614 if(prep_all_keys(ictx) || build_cflow_automaton(ictx)){
2615 ictx->failed = true;
2616 handoff_initial_responses_early(ictx);
2617 handoff_initial_responses_late(ictx);
2618 }
2619 for(;;){
2620 read_inputs_nblock(ictx);
2621 // process anything we've read
2622 process_ibuf(ictx);
2623 }
2624 return NULL;
2625}
2626
2627int init_inputlayer(tinfo* ti, FILE* infp, int lmargin, int tmargin,
2628 int rmargin, int bmargin, ncsharedstats* stats,
2629 unsigned drain, int linesigs_enabled){
2630 inputctx* ictx = create_inputctx(ti, infp, lmargin, tmargin, rmargin,
2631 bmargin, stats, drain, linesigs_enabled);
2632 if(ictx == NULL){
2633 return -1;
2634 }
2635 if(pthread_create(&ictx->tid, NULL, input_thread, ictx)){
2636 free_inputctx(ictx);
2637 return -1;
2638 }
2639 ti->ictx = ictx;
2640 loginfo("spun up input thread");
2641 return 0;
2642}
2643
2645 int ret = 0;
2646 if(ti){
2647 // FIXME cancellation on shutdown does not yet work on windows #2192
2648#ifndef __MINGW32__
2649 if(ti->ictx){
2650 loginfo("tearing down input thread");
2651 ret |= cancel_and_join("input", ti->ictx->tid, NULL);
2653 free_inputctx(ti->ictx);
2654 ti->ictx = NULL;
2655 }
2656#endif
2657 }
2658 return ret;
2659}
2660
2661int inputready_fd(const inputctx* ictx){
2662#ifndef __MINGW32__
2663 return ictx->readypipes[0];
2664#else
2665 (void)ictx;
2666 logerror("readiness descriptor unavailable on windows");
2667 return -1;
2668#endif
2669}
2670
2671static inline uint32_t
2672internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni){
2673 uint32_t id;
2674 if(ictx->drain){
2675 logerror("input is being drained");
2676 if(ni){
2677 memset(ni, 0, sizeof(*ni));
2678 ni->id = (uint32_t)-1;
2679 }
2680 return (uint32_t)-1;
2681 }
2682 pthread_mutex_lock(&ictx->ilock);
2683 while(!ictx->ivalid){
2684 if(ictx->stdineof){
2685 pthread_mutex_unlock(&ictx->ilock);
2686 logwarn("read eof on stdin");
2687 if(ni){
2688 memset(ni, 0, sizeof(*ni));
2689 ni->id = NCKEY_EOF;
2690 }
2691 return NCKEY_EOF;
2692 }
2693 if(ts == NULL){
2694 pthread_cond_wait(&ictx->icond, &ictx->ilock);
2695 }else{
2696 int r = pthread_cond_timedwait(&ictx->icond, &ictx->ilock, ts);
2697 if(r == ETIMEDOUT){
2698 pthread_mutex_unlock(&ictx->ilock);
2699 if(ni){
2700 memset(ni, 0, sizeof(*ni));
2701 }
2702 return 0;
2703 }else if(r < 0){
2704 inc_input_errors(ictx);
2705 if(ni){
2706 memset(ni, 0, sizeof(*ni));
2707 ni->id = (uint32_t)-1;
2708 }
2709 return (uint32_t)-1;
2710 }
2711 }
2712 }
2713 id = ictx->inputs[ictx->iread].id;
2714 if(ni){
2715 memcpy(ni, &ictx->inputs[ictx->iread], sizeof(*ni));
2716 if(notcurses_ucs32_to_utf8(&ni->id, 1, (unsigned char*)ni->utf8, sizeof(ni->utf8)) < 0){
2717 ni->utf8[0] = 0;
2718 }
2719 if (ni->eff_text[0]==0) ni->eff_text[0]=ni->id;
2720 }
2721 if(++ictx->iread == ictx->isize){
2722 ictx->iread = 0;
2723 }
2724 bool sendsignal = false;
2725 if(ictx->ivalid-- == ictx->isize){
2726 sendsignal = true;
2727 }else{
2728 logtrace("draining event readiness pipe %d", ictx->ivalid);
2729#ifndef __MINGW32__
2730 char c;
2731 while(read(ictx->readypipes[0], &c, sizeof(c)) == 1){
2732 // FIXME accelerate?
2733 }
2734#else
2735 // we ought be draining this, but it breaks everything, as we can't easily
2736 // do nonblocking input from a pipe in windows, augh...
2737 // Ne pleure pas, Alfred! J'ai besoin de tout mon courage pour mourir a vingt ans!
2738 /*while(ReadFile(ictx->readypipes[0], &c, sizeof(c), NULL, NULL)){
2739 // FIXME accelerate?
2740 }*/
2741#endif
2742 }
2743 pthread_mutex_unlock(&ictx->ilock);
2744 if(sendsignal){
2745 mark_pipe_ready(ictx->ipipes);
2746 }
2747 return id;
2748}
2749
2750// infp has already been set non-blocking
2751uint32_t notcurses_get(notcurses* nc, const struct timespec* absdl, ncinput* ni){
2752 uint32_t ret = internal_get(nc->tcache.ictx, absdl, ni);
2753 return ret;
2754}
2755
2756// FIXME better performance if we move this within the locked area
2757int notcurses_getvec(notcurses* n, const struct timespec* absdl,
2758 ncinput* ni, int vcount){
2759 for(int v = 0 ; v < vcount ; ++v){
2760 uint32_t u = notcurses_get(n, absdl, &ni[v]);
2761 if(u == (uint32_t)-1){
2762 if(v == 0){
2763 return -1;
2764 }
2765 return v;
2766 }else if(u == 0){
2767 return v;
2768 }
2769 }
2770 return vcount;
2771}
2772
2773uint32_t ncdirect_get(ncdirect* n, const struct timespec* absdl, ncinput* ni){
2774 if(n->eof){
2775 logerror("already got EOF");
2776 return -1;
2777 }
2778 uint32_t r = internal_get(n->tcache.ictx, absdl, ni);
2779 if(r == NCKEY_EOF){
2780 n->eof = 1;
2781 }
2782 return r;
2783}
2784
2785int get_cursor_location(inputctx* ictx, const char* u7, unsigned* y, unsigned* x){
2786 pthread_mutex_lock(&ictx->clock);
2787 while(ictx->cvalid == 0){
2788 if(ictx->coutstanding == 0){
2789 if(tty_emit(u7, ictx->ti->ttyfd)){
2790 pthread_mutex_unlock(&ictx->clock);
2791 return -1;
2792 }
2793 ++ictx->coutstanding;
2794 }
2795 pthread_cond_wait(&ictx->ccond, &ictx->clock);
2796 }
2797 const cursorloc* cloc = &ictx->csrs[ictx->cread];
2798 if(++ictx->cread == ictx->csize){
2799 ictx->cread = 0;
2800 }
2801 --ictx->cvalid;
2802 if(y){
2803 *y = cloc->y;
2804 }
2805 if(x){
2806 *x = cloc->x;
2807 }
2808 pthread_mutex_unlock(&ictx->clock);
2809 return 0;
2810}
2811
2812// Disable signals originating from the terminal's line discipline, i.e.
2813// SIGINT (^C), SIGQUIT (^\‍), and SIGTSTP (^Z). They are enabled by default.
2814static int
2815linesigs_disable(tinfo* ti){
2816 if(!ti->ictx->linesigs){
2817 logwarn("linedisc signals already disabled");
2818 }
2819#ifndef __MINGW32__
2820 if(ti->ttyfd < 0){
2821 return 0;
2822 }
2823 struct termios tios;
2824 if(tcgetattr(ti->ttyfd, &tios)){
2825 logerror("Couldn't preserve terminal state for %d (%s)", ti->ttyfd, strerror(errno));
2826 return -1;
2827 }
2828 tios.c_lflag &= ~ISIG;
2829 if(tcsetattr(ti->ttyfd, TCSANOW, &tios)){
2830 logerror("Error disabling signals on %d (%s)", ti->ttyfd, strerror(errno));
2831 return -1;
2832 }
2833#else
2834 DWORD mode;
2835 if(!GetConsoleMode(ti->inhandle, &mode)){
2836 logerror("error acquiring input mode");
2837 return -1;
2838 }
2839 mode &= ~ENABLE_PROCESSED_INPUT;
2840 if(!SetConsoleMode(ti->inhandle, mode)){
2841 logerror("error setting input mode");
2842 return -1;
2843 }
2844#endif
2845 ti->ictx->linesigs = 0;
2846 loginfo("disabled line discipline signals");
2847 return 0;
2848}
2849
2851 return linesigs_disable(&nc->tcache);
2852}
2853
2854static int
2855linesigs_enable(tinfo* ti){
2856 if(ti->ictx->linesigs){
2857 logwarn("linedisc signals already enabled");
2858 }
2859#ifndef __MINGW32__
2860 if(ti->ttyfd < 0){
2861 return 0;
2862 }
2863 struct termios tios;
2864 if(tcgetattr(ti->ttyfd, &tios)){
2865 logerror("couldn't preserve terminal state for %d (%s)", ti->ttyfd, strerror(errno));
2866 return -1;
2867 }
2868 tios.c_lflag |= ISIG;
2869 if(tcsetattr(ti->ttyfd, TCSANOW, &tios)){
2870 logerror("error disabling signals on %d (%s)", ti->ttyfd, strerror(errno));
2871 return -1;
2872 }
2873#else
2874 DWORD mode;
2875 if(!GetConsoleMode(ti->inhandle, &mode)){
2876 logerror("error acquiring input mode");
2877 return -1;
2878 }
2879 mode |= ENABLE_PROCESSED_INPUT;
2880 if(!SetConsoleMode(ti->inhandle, mode)){
2881 logerror("error setting input mode");
2882 return -1;
2883 }
2884#endif
2885 ti->ictx->linesigs = 1;
2886 loginfo("enabled line discipline signals");
2887 return 0;
2888}
2889
2890// Restore signals originating from the terminal's line discipline, i.e.
2891// SIGINT (^C), SIGQUIT (^\‍), and SIGTSTP (^Z), if disabled.
2893 return linesigs_enable(&n->tcache);
2894}
2895
2897 struct initial_responses* iresp;
2898 pthread_mutex_lock(&ictx->ilock);
2899 while(ictx->initdata || !ictx->initdata_complete){
2900 pthread_cond_wait(&ictx->icond, &ictx->ilock);
2901 }
2902 iresp = ictx->initdata_complete;
2903 ictx->initdata_complete = NULL;
2904 pthread_mutex_unlock(&ictx->ilock);
2905 if(ictx->failed){
2906 logpanic("aborting after automaton construction failure");
2907 free(iresp);
2908 return NULL;
2909 }
2910 return iresp;
2911}
int walk_automaton(automaton *a, struct inputctx *ictx, unsigned candidate, ncinput *ni)
Definition automaton.c:546
int inputctx_add_input_escape(automaton *a, const char *esc, uint32_t special, unsigned modifiers)
Definition automaton.c:513
void input_free_esctrie(automaton *a)
Definition automaton.c:81
int inputctx_add_cflow(automaton *a, const char *seq, triefunc fxn)
Definition automaton.c:502
int(* triefunc)(struct inputctx *)
Definition automaton.h:14
assert(false)
const nccell * c
Definition egcpool.h:296
uint32_t idx
Definition egcpool.h:298
free(duplicated)
__attribute__((nonnull(1, 2))) static inline int egcpool_stash(egcpool *pool
f used
Definition fbuf.h:232
int r
Definition fbuf.h:226
int get_tty_fd(FILE *ttyfp)
Definition fd.c:455
int init_inputlayer(tinfo *ti, FILE *infp, int lmargin, int tmargin, int rmargin, int bmargin, ncsharedstats *stats, unsigned drain, int linesigs_enabled)
Definition in.c:2627
int ncinput_shovel(inputctx *ictx, const void *buf, int len)
Definition in.c:2463
uint32_t notcurses_get(notcurses *nc, const struct timespec *absdl, ncinput *ni)
Definition in.c:2751
struct initial_responses * inputlayer_get_responses(inputctx *ictx)
Definition in.c:2896
int notcurses_linesigs_enable(notcurses *n)
Definition in.c:2892
int ipipe
Definition in.c:46
int notcurses_linesigs_disable(notcurses *nc)
Definition in.c:2850
int inputready_fd(const inputctx *ictx)
Definition in.c:2661
int notcurses_getvec(notcurses *n, const struct timespec *absdl, ncinput *ni, int vcount)
Definition in.c:2757
void sigwinch_handler(int signo)
Definition in.c:31
int get_cursor_location(inputctx *ictx, const char *u7, unsigned *y, unsigned *x)
Definition in.c:2785
int stop_inputlayer(tinfo *ti)
Definition in.c:2644
uint32_t ncdirect_get(ncdirect *n, const struct timespec *absdl, ncinput *ni)
Definition in.c:2773
queried_terminals_e
Definition in.h:31
@ TERMINAL_XTERM
Definition in.h:38
@ TERMINAL_CONTOUR
Definition in.h:47
@ TERMINAL_UNKNOWN
Definition in.h:32
@ TERMINAL_ALACRITTY
Definition in.h:46
@ TERMINAL_GNUSCREEN
Definition in.h:44
@ TERMINAL_TERMINOLOGY
Definition in.h:49
@ TERMINAL_RXVT
Definition in.h:51
@ TERMINAL_KONSOLE
Definition in.h:54
@ TERMINAL_MLTERM
Definition in.h:42
@ TERMINAL_KITTY
Definition in.h:40
@ TERMINAL_MINTTY
Definition in.h:53
@ TERMINAL_ITERM
Definition in.h:48
@ TERMINAL_WEZTERM
Definition in.h:45
@ TERMINAL_VTE
Definition in.h:39
@ TERMINAL_FOOT
Definition in.h:41
@ TERMINAL_TMUX
Definition in.h:43
int set_fd_nonblocking(int fd, unsigned state, unsigned *oldstate)
#define logerror(fmt,...)
Definition logging.h:32
#define logtrace(fmt,...)
Definition logging.h:57
#define loginfo(fmt,...)
Definition logging.h:42
#define logdebug(fmt,...)
Definition logging.h:52
#define logwarn(fmt,...)
Definition logging.h:37
#define logverbose(fmt,...)
Definition logging.h:47
#define logpanic(fmt,...)
Definition logging.h:22
#define NCKEY_F52
Definition nckeys.h:100
#define NCKEY_CLOSE
Definition nckeys.h:119
#define NCKEY_CENTER
Definition nckeys.h:116
#define NCKEY_MOD_META
Definition nckeys.h:224
#define NCKEY_COPY
Definition nckeys.h:121
#define NCKEY_CANCEL
Definition nckeys.h:118
#define NCKEY_F45
Definition nckeys.h:93
#define NCKEY_MEDIA_PLAY
Definition nckeys.h:134
#define NCKEY_F40
Definition nckeys.h:88
#define NCKEY_PRINT
Definition nckeys.h:123
#define NCKEY_LSHIFT
Definition nckeys.h:149
#define NCKEY_F01
Definition nckeys.h:49
#define NCKEY_PAUSE
Definition nckeys.h:131
#define NCKEY_MOD_SHIFT
Definition nckeys.h:219
#define NCKEY_F53
Definition nckeys.h:101
#define NCKEY_END
Definition nckeys.h:47
#define NCKEY_MENU
Definition nckeys.h:132
#define NCKEY_F15
Definition nckeys.h:63
#define NCKEY_F24
Definition nckeys.h:72
#define NCKEY_F03
Definition nckeys.h:51
#define NCKEY_F44
Definition nckeys.h:92
#define NCKEY_F49
Definition nckeys.h:97
#define NCKEY_BUTTON8
Definition nckeys.h:173
#define NCKEY_F34
Definition nckeys.h:82
#define NCKEY_MOD_CTRL
Definition nckeys.h:221
#define NCKEY_F32
Definition nckeys.h:80
#define NCKEY_F55
Definition nckeys.h:103
#define NCKEY_SCROLL_LOCK
Definition nckeys.h:128
#define NCKEY_F26
Definition nckeys.h:74
#define NCKEY_SIGNAL
Definition nckeys.h:179
#define NCKEY_DEL
Definition nckeys.h:42
#define NCKEY_F35
Definition nckeys.h:83
#define NCKEY_DLEFT
Definition nckeys.h:112
#define NCKEY_F47
Definition nckeys.h:95
#define NCKEY_F19
Definition nckeys.h:67
#define NCKEY_UP
Definition nckeys.h:37
#define NCKEY_ULEFT
Definition nckeys.h:114
#define NCKEY_F29
Definition nckeys.h:77
#define NCKEY_F20
Definition nckeys.h:68
#define NCKEY_F50
Definition nckeys.h:98
#define NCKEY_BACKSPACE
Definition nckeys.h:43
#define NCKEY_F28
Definition nckeys.h:76
#define NCKEY_F57
Definition nckeys.h:105
#define NCKEY_F43
Definition nckeys.h:91
#define NCKEY_PRINT_SCREEN
Definition nckeys.h:130
#define NCKEY_F56
Definition nckeys.h:104
#define NCKEY_F54
Definition nckeys.h:102
#define NCKEY_URIGHT
Definition nckeys.h:115
#define NCKEY_EOF
Definition nckeys.h:183
#define NCKEY_F48
Definition nckeys.h:96
#define NCKEY_F08
Definition nckeys.h:56
#define NCKEY_F38
Definition nckeys.h:86
#define NCKEY_BUTTON4
Definition nckeys.h:169
#define NCKEY_DRIGHT
Definition nckeys.h:113
#define NCKEY_F22
Definition nckeys.h:70
#define NCKEY_F31
Definition nckeys.h:79
#define NCKEY_F13
Definition nckeys.h:61
#define NCKEY_F16
Definition nckeys.h:64
#define NCKEY_EXIT
Definition nckeys.h:122
#define NCKEY_MOD_CAPSLOCK
Definition nckeys.h:225
#define NCKEY_BEGIN
Definition nckeys.h:117
#define NCKEY_F10
Definition nckeys.h:58
#define NCKEY_DOWN
Definition nckeys.h:39
#define NCKEY_F07
Definition nckeys.h:55
#define NCKEY_F25
Definition nckeys.h:73
#define NCKEY_F04
Definition nckeys.h:52
#define NCKEY_NUM_LOCK
Definition nckeys.h:129
#define NCKEY_F02
Definition nckeys.h:50
#define NCKEY_F42
Definition nckeys.h:90
#define NCKEY_RESIZE
Definition nckeys.h:36
#define NCKEY_COMMAND
Definition nckeys.h:120
#define NCKEY_F39
Definition nckeys.h:87
#define NCKEY_PGUP
Definition nckeys.h:45
#define NCKEY_SEPARATOR
Definition nckeys.h:125
#define NCKEY_RIGHT
Definition nckeys.h:38
#define NCKEY_F18
Definition nckeys.h:66
#define NCKEY_ESC
Definition nckeys.h:196
#define NCKEY_CAPS_LOCK
Definition nckeys.h:127
#define NCKEY_F51
Definition nckeys.h:99
#define NCKEY_F23
Definition nckeys.h:71
#define NCKEY_F21
Definition nckeys.h:69
#define NCKEY_REFRESH
Definition nckeys.h:124
#define NCKEY_F09
Definition nckeys.h:57
#define NCKEY_F33
Definition nckeys.h:81
#define NCKEY_BUTTON1
Definition nckeys.h:166
#define NCKEY_F12
Definition nckeys.h:60
#define NCKEY_F27
Definition nckeys.h:75
#define NCKEY_ENTER
Definition nckeys.h:110
#define NCKEY_CLS
Definition nckeys.h:111
#define NCKEY_MOD_ALT
Definition nckeys.h:220
#define NCKEY_HOME
Definition nckeys.h:46
#define NCKEY_PGDOWN
Definition nckeys.h:44
#define NCKEY_F05
Definition nckeys.h:53
#define NCKEY_F59
Definition nckeys.h:107
#define NCKEY_F11
Definition nckeys.h:59
#define NCKEY_F46
Definition nckeys.h:94
#define NCKEY_F41
Definition nckeys.h:89
#define NCKEY_MOTION
Definition nckeys.h:165
#define NCKEY_F58
Definition nckeys.h:106
#define NCKEY_F30
Definition nckeys.h:78
#define NCKEY_F06
Definition nckeys.h:54
#define NCKEY_INS
Definition nckeys.h:41
#define NCKEY_F17
Definition nckeys.h:65
#define NCKEY_LEFT
Definition nckeys.h:40
#define NCKEY_F36
Definition nckeys.h:84
#define NCKEY_F37
Definition nckeys.h:85
#define NCKEY_F14
Definition nckeys.h:62
int notcurses_ucs32_to_utf8(const uint32_t *ucs32, unsigned ucs32count, unsigned char *resultbuf, size_t buflen)
Definition notcurses.c:3301
int y
Definition notcurses.h:1905
#define NCINPUT_MAX_EFF_TEXT_CODEPOINTS
Definition notcurses.h:1203
@ NCTYPE_REPEAT
Definition notcurses.h:1197
@ NCTYPE_RELEASE
Definition notcurses.h:1198
@ NCTYPE_PRESS
Definition notcurses.h:1196
@ NCTYPE_UNKNOWN
Definition notcurses.h:1195
vopts n
Definition notcurses.h:3502
int int x
Definition notcurses.h:1905
struct ncvisual_options v
Definition notcurses.h:3497
API int API int const nccell unsigned len
Definition notcurses.h:2588
sig_atomic_t sigcont_seen_for_render
Definition render.c:7
unsigned state
Definition automaton.h:23
unsigned escapes
Definition automaton.h:20
const unsigned char * matchstart
Definition automaton.h:24
int instring
Definition automaton.h:22
Definition in.c:41
int y
Definition in.c:42
int x
Definition in.c:42
queried_terminals_e qterm
Definition in.h:63
int maxpaletteread
Definition in.h:84
unsigned appsync_supported
Definition in.h:62
ncpalette palette
Definition in.h:83
bool pixelmice
Definition in.h:85
unsigned kbdlevel
Definition in.h:82
bool rectangular_edits
Definition in.h:70
char * version
Definition in.h:81
unsigned kitty_graphics
Definition in.h:64
int cursorx
Definition in.h:61
int cursory
Definition in.h:60
uint32_t fg
Definition in.h:66
bool got_fg
Definition in.h:68
bool got_bg
Definition in.h:67
uint32_t bg
Definition in.h:65
char * hpa
Definition in.h:86
int color_registers
Definition in.h:78
Definition in.c:52
ncinput * inputs
Definition in.c:79
int stdinfd
Definition in.c:58
unsigned linesigs
Definition in.c:98
unsigned drain
Definition in.c:99
pthread_cond_t ccond
Definition in.c:90
cursorloc * csrs
Definition in.c:78
unsigned char ibuf[BUFSIZ]
Definition in.c:56
int ibufvalid
Definition in.c:70
uint8_t backspace
Definition in.c:73
pthread_t tid
Definition in.c:92
pthread_mutex_t clock
Definition in.c:89
int termfd
Definition in.c:60
ncsharedstats * stats
Definition in.c:100
int cvalid
Definition in.c:82
bool failed
Definition in.c:112
int lmargin
Definition in.c:66
int cwrite
Definition in.c:83
int tbufvalid
Definition in.c:71
int kittykbd
Definition in.c:111
int cread
Definition in.c:85
int tmargin
Definition in.c:66
pthread_mutex_t ilock
Definition in.c:87
int coutstanding
Definition in.c:80
ipipe readypipes[2]
Definition in.c:103
int iwrite
Definition in.c:83
int bmargin
Definition in.c:67
pthread_cond_t icond
Definition in.c:88
int iread
Definition in.c:85
unsigned midescape
Definition in.c:94
int ivalid
Definition in.c:82
unsigned stdineof
Definition in.c:96
unsigned char tbuf[BUFSIZ]
Definition in.c:55
ipipe ipipes[2]
Definition in.c:102
tinfo * ti
Definition in.c:91
struct initial_responses * initdata_complete
Definition in.c:110
automaton amata
Definition in.c:69
int isize
Definition in.c:81
int csize
Definition in.c:81
struct initial_responses * initdata
Definition in.c:109
int rmargin
Definition in.c:67
ncintype_e evtype
Definition notcurses.h:1218
uint32_t eff_text[NCINPUT_MAX_EFF_TEXT_CODEPOINTS]
Definition notcurses.h:1221
bool alt
Definition notcurses.h:1214
bool ctrl
Definition notcurses.h:1216
uint32_t id
Definition notcurses.h:1210
bool shift
Definition notcurses.h:1215
char utf8[5]
Definition notcurses.h:1212
unsigned modifiers
Definition notcurses.h:1219
uint32_t chans[NCPALETTESIZE]
Definition notcurses.h:1585
ncstats s
Definition internal.h:247
pthread_mutex_t lock
Definition internal.h:246
uint64_t input_errors
Definition notcurses.h:1812
uint64_t input_events
Definition notcurses.h:1813
tinfo tcache
Definition internal.h:360
queried_terminals_e qterm
Definition termdesc.h:178
unsigned cellpxx
Definition termdesc.h:117
bool pixelmice
Definition termdesc.h:197
unsigned dimx
Definition termdesc.h:118
unsigned dimy
Definition termdesc.h:118
unsigned stdio_blocking_save
Definition termdesc.h:182
struct inputctx * ictx
Definition termdesc.h:181
unsigned cellpxy
Definition termdesc.h:116
int ttyfd
Definition termdesc.h:109
return NULL
Definition termdesc.h:229
void setup_alt_sig_stack(void)
Definition unixsig.c:175