Notcurses 3.0.16
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, NCKEY_ESC);
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 = "ghostty ", .suffix = 0, .term = TERMINAL_GHOSTTY, },
1560 { .prefix = "mlterm(", .suffix = ')', .term = TERMINAL_MLTERM, },
1561 { .prefix = "tmux ", .suffix = 0, .term = TERMINAL_TMUX, },
1562 { .prefix = "iTerm2 ", .suffix = 0, .term = TERMINAL_ITERM, },
1563 { .prefix = "mintty ", .suffix = 0, .term = TERMINAL_MINTTY, },
1564 { .prefix = "terminology ", .suffix = 0, .term = TERMINAL_TERMINOLOGY, },
1565 { .prefix = NULL, .suffix = 0, .term = TERMINAL_UNKNOWN, },
1566 }, *xtv;
1567 for(xtv = xtvers ; xtv->prefix ; ++xtv){
1568 if(strncmp(xtversion, xtv->prefix, strlen(xtv->prefix)) == 0){
1569 if(extract_xtversion(ictx, xtversion + strlen(xtv->prefix), xtv->suffix) == 0){
1570 loginfo("found terminal type %d version %s", xtv->term, ictx->initdata->version);
1571 ictx->initdata->qterm = xtv->term;
1572 }else{
1573 free(xtversion);
1574 return 2;
1575 }
1576 break;
1577 }
1578 }
1579 if(xtv->prefix == NULL){
1580 logwarn("unknown xtversion [%s]", xtversion);
1581 }
1582 free(xtversion);
1583 return 2;
1584}
1585
1586// precondition: s starts with two hex digits, the first of which is not
1587// greater than 7.
1588static inline char
1589toxdigit(const char* s){
1590 char c = isalpha(*s) ? tolower(*s) - 'a' + 10 : *s - '0';
1591 c *= 16;
1592 ++s;
1593 c += isalpha(*s) ? tolower(*s) - 'a' + 10 : *s - '0';
1594 return c;
1595}
1596
1597// on success, the subsequent character is returned, and |key| and |val| have
1598// heap-allocated, decoded, nul-terminated copies of the appropriate input.
1599static const char*
1600gettcap(const char* s, char** key, char** val){
1601 const char* equals = s;
1602 // we don't want anything bigger than 7 in the first nibble
1603 unsigned firstnibble = true;
1604 while(*equals != '='){
1605 if(!isxdigit(*equals)){ // rejects a NUL byte
1606 logerror("bad key in %s", s);
1607 return NULL;
1608 }
1609 if(firstnibble && (!isdigit(*equals) || *equals - '0' >= 8)){
1610 logerror("bad key in %s", s);
1611 return NULL;
1612 }
1613 firstnibble = !firstnibble;
1614 ++equals;
1615 }
1616 if(equals - s == 0 || !firstnibble){
1617 logerror("bad key in %s", s);
1618 return NULL;
1619 }
1620 if((*key = malloc((equals - s) / 2 + 1)) == NULL){
1621 return NULL;
1622 }
1623 char* keytarg = *key;
1624 do{
1625 *keytarg = toxdigit(s);
1626 s += 2;
1627 ++keytarg;
1628 }while(*s != '=');
1629 *keytarg = '\0';
1630 ++equals; // now one past the equal sign
1631 const char *end = equals;
1632 firstnibble = true;
1633 while(*end != ';' && *end){
1634 if(!isxdigit(*end)){
1635 logerror("bad value in %s", s);
1636 goto valerr;
1637 }
1638 if(firstnibble && (!isdigit(*end) || *end - '0' >= 8)){
1639 logerror("bad value in %s", s);
1640 goto valerr;
1641 }
1642 firstnibble = !firstnibble;
1643 ++end;
1644 }
1645 if(end - equals == 0 || !firstnibble){
1646 logerror("bad value in %s", s);
1647 goto valerr;
1648 }
1649 if((*val = malloc((end - equals) / 2 + 1)) == NULL){
1650 goto valerr;
1651 }
1652 char* valtarg = *val;
1653 ++s;
1654 do{
1655 *valtarg = toxdigit(s);
1656 s += 2;
1657 ++valtarg;
1658 }while(s != end);
1659 *valtarg = '\0';
1660 loginfo("key: %s val: %s", *key, *val);
1661 return end;
1662
1663valerr:
1664 free(*key);
1665 *key = NULL;
1666 return NULL;
1667}
1668
1669// replace \E with actual 0x1b for use as a terminfo-like format string,
1670// writing in-place (and updating the nul terminator, if necessary).
1671// returns its input.
1672static inline char*
1673determinfo(char* old){
1674 bool escaped = false;
1675 char* targo = old;
1676 for(char* o = old ; *o ; ++o){
1677 if(escaped){
1678 if(*o == 'E'){
1679 *targo = 0x1b;
1680 ++targo;
1681 }else{
1682 *targo = '\\';
1683 ++targo;
1684 *targo = *o;
1685 ++targo;
1686 }
1687 escaped = false;
1688 }else if(*o == '\\'){
1689 escaped = true;
1690 }else{
1691 *targo = *o;
1692 ++targo;
1693 }
1694 }
1695 *targo = '\0';
1696 return old;
1697}
1698
1699// XTGETTCAP responses are delimited by semicolons
1700static int
1701tcap_cb(inputctx* ictx){
1702 char* str = amata_next_string(&ictx->amata, "\x1bP1+r");
1703 if(str == NULL){
1704 return 2;
1705 }
1706 loginfo("xtgettcap [%s]", str);
1707 if(ictx->initdata == NULL){
1708 free(str);
1709 return 2;
1710 }
1711 const char* s = str;
1712 char* key;
1713 char* val;
1714 // answers are delimited with semicolons, hex-encoded, key=value
1715 while(*s && (s = gettcap(s, &val, &key)) ){
1716 if(strcmp(val, "TN") == 0){
1717 if(ictx->initdata->qterm == TERMINAL_UNKNOWN){
1718 if(strcmp(key, "xterm") == 0){
1719 ictx->initdata->qterm = TERMINAL_XTERM;
1720 }else if(strcmp(key, "mlterm") == 0){
1722 }else if(strcmp(key, "xterm-kitty") == 0){
1723 ictx->initdata->qterm = TERMINAL_KITTY;
1724 }else if(strcmp(key, "xterm-ghostty") == 0){
1726 }else if(strcmp(key, "xterm-256color") == 0){
1727 ictx->initdata->qterm = TERMINAL_XTERM;
1728 }else{
1729 logwarn("unknown terminal name %s", key);
1730 }
1731 }
1732 }else if(strcmp(val, "RGB") == 0){
1733 loginfo("got rgb (%s)", key);
1734 ictx->initdata->rgb = true;
1735 }else if(strcmp(val, "hpa") == 0){
1736 loginfo("got hpa (%s)", key);
1737 ictx->initdata->hpa = determinfo(key);
1738 key = NULL;
1739 }else{
1740 logwarn("unknown capability: %s", str);
1741 }
1742 free(val);
1743 free(key);
1744 if(*s == ';'){
1745 ++s;
1746 }
1747 }
1748 if(!s || *s){
1749 free(str);
1750 return -1;
1751 }
1752 free(str);
1753 return 2;
1754}
1755
1756static int
1757tda_cb(inputctx* ictx){
1758 char* str = amata_next_string(&ictx->amata, "\x1bP!|");
1759 if(str == NULL){
1760 logwarn("empty ternary device attribute");
1761 return 2; // don't replay
1762 }
1763 if(ictx->initdata && ictx->initdata->qterm == TERMINAL_UNKNOWN){
1764 if(strcmp(str, "7E565445") == 0){ // "~VTE"
1765 ictx->initdata->qterm = TERMINAL_VTE;
1766 }else if(strcmp(str, "7E7E5459") == 0){ // "~~TY"
1768 }else if(strcmp(str, "464F4F54") == 0){ // "FOOT"
1769 ictx->initdata->qterm = TERMINAL_FOOT;
1770 }else if(strcmp(str, "7E4B4445") == 0){
1772 }
1773 loginfo("got TDA: %s, terminal type %d", str, ictx->initdata->qterm);
1774 }
1775 free(str);
1776 return 2;
1777}
1778
1779static int
1780build_cflow_automaton(inputctx* ictx){
1781 // syntax: literals are matched. \N is a numeric. \D is a drain (Kleene
1782 // closure). \S is a ST-terminated string. this working is very dependent on
1783 // order, and very delicate! hands off!
1784 const struct {
1785 const char* cflow;
1786 triefunc fxn;
1787 } csis[] = {
1788 // CSI (\e[)
1789 { "[E", simple_cb_begin, },
1790 { "[<\\N;\\N;\\NM", mouse_press_cb, },
1791 { "[<\\N;\\N;\\Nm", mouse_release_cb, },
1792 // technically these must begin with "4" or "8"; enforce in callbacks
1793 { "[\\N;\\N;\\Nt", geom_cb, },
1794 { "[\\Nu", kitty_cb_simple, },
1795 { "[13~", kitty_cb_f3_alternate, },
1796 { "[\\N;\\N~", wezterm_cb, },
1797 { "[\\N;\\Nu", kitty_cb, },
1798 { "[\\N;\\N;\\Nu", kitty_cb_atxt1, },
1799 { "[\\N;\\N;\\N;\\Nu", kitty_cb_atxt2, },
1800 { "[\\N;\\N;\\N;\\N;\\Nu", kitty_cb_atxt3, },
1801 { "[\\N;\\N;\\N;\\N;\\N;\\Nu", kitty_cb_atxt4, },
1802 { "[\\N;\\N:\\Nu", kitty_cb_complex, },
1803 { "[\\N;\\N:\\N;\\Nu", kitty_cb_complex_atxt1, },
1804 { "[\\N;\\N:\\N;\\N;\\Nu", kitty_cb_complex_atxt2, },
1805 { "[\\N;\\N:\\N;\\N;\\N;\\Nu", kitty_cb_complex_atxt3, },
1806 { "[\\N;\\N:\\N;\\N;\\N;\\N;\\Nu", kitty_cb_complex_atxt4, },
1807 { "[\\N;\\N;\\N~", xtmodkey_cb, },
1808 { "[\\N;\\N:\\N~", kitty_cb_functional, },
1809 { "[1;\\NP", legacy_cb_f1, },
1810 { "[1;\\NQ", legacy_cb_f2, },
1811 { "[1;\\NS", legacy_cb_f4, },
1812 { "[1;\\ND", legacy_cb_left, },
1813 { "[1;\\NC", legacy_cb_right, },
1814 { "[1;\\NB", legacy_cb_down, },
1815 { "[1;\\NA", legacy_cb_up, },
1816 { "[1;\\NE", legacy_cb_begin, },
1817 { "[1;\\NF", legacy_cb_end, },
1818 { "[1;\\NH", legacy_cb_home, },
1819 { "[1;\\N:\\NP", kitty_cb_f1, },
1820 { "[1;\\N:\\NQ", kitty_cb_f2, },
1821 { "[1;\\N:\\NR", kitty_cb_f3, },
1822 { "[1;\\N:\\NS", kitty_cb_f4, },
1823 { "[1;\\N:\\ND", kitty_cb_left, },
1824 { "[1;\\N:\\NC", kitty_cb_right, },
1825 { "[1;\\N:\\NB", kitty_cb_down, },
1826 { "[1;\\N:\\NA", kitty_cb_up, },
1827 { "[1;\\N:\\NE", kitty_cb_begin, },
1828 { "[1;\\N:\\NF", kitty_cb_end, },
1829 { "[1;\\N:\\NH", kitty_cb_home, },
1830 { "[?\\Nu", kitty_keyboard_cb, },
1831 { "[?1016;\\N$y", decrpm_pixelmice, },
1832 { "[?2026;\\N$y", decrpm_asu_cb, },
1833 { "[\\N;\\NR", cursor_location_cb, },
1834 { "[?1;1S", NULL, }, // negative cregs XTSMGRAPHICS
1835 { "[?1;2S", NULL, }, // negative cregs XTSMGRAPHICS
1836 { "[?1;3S", NULL, }, // negative cregs XTSMGRAPHICS
1837 { "[?1;3;S", NULL, }, // iterm2 negative cregs XTSMGRAPHICS
1838 { "[?1;3;0S", NULL, }, // negative cregs XTSMGRAPHICS
1839 { "[?2;1S", NULL, }, // negative pixels XTSMGRAPHICS
1840 { "[?2;2S", NULL, }, // negative pixels XTSMGRAPHICS
1841 { "[?2;3S", NULL, }, // negative pixels XTSMGRAPHICS
1842 { "[?2;3;S", NULL, }, // iterm2 negative pixels XTSMGRAPHICS
1843 { "[?2;3;0S", NULL, }, // negative pixels XTSMGRAPHICS
1844 { "[?6c", da1_vt102_cb, }, // CSI ? 6 c ("VT102")
1845 { "[?7c", da1_cb, }, // CSI ? 7 c ("VT131")
1846 { "[?1;0c", da1_cb, }, // CSI ? 1 ; 0 c ("VT101 with No Options")
1847 { "[?1;2c", da1_cb, }, // CSI ? 1 ; 2 c ("VT100 with Advanced Video Option")
1848 { "[?4;6c", da1_cb, }, // CSI ? 4 ; 6 c ("VT132 with Advanced Video and Graphics")
1849 // CSI ? 1 2 ; Ps c ("VT125")
1850 // CSI ? 6 0 ; Ps c (kmscon)
1851 // CSI ? 6 2 ; Ps c ("VT220")
1852 // CSI ? 6 3 ; Ps c ("VT320")
1853 // CSI ? 6 4 ; Ps c ("VT420")
1854 // CSI ? 6 5 ; Ps c (WezTerm, VT5xx?)
1855 { "[?\\N;\\Dc", da1_attrs_cb, },
1856 { "[?1;0;\\NS", xtsmgraphics_cregs_cb, },
1857 { "[?2;0;\\N;\\NS", xtsmgraphics_sixel_cb, },
1858 { "[>83;\\N;0c", da2_screen_cb, },
1859 { "[>\\N;\\N;\\Nc", da2_cb, },
1860 { "[=67;84;101;114;109;\\Dc", da1_syncterm_cb, }, // CSI da1 form as issued by SyncTERM
1861 // DCS (\eP...ST)
1862 { "P0+\\S", NULL, }, // negative XTGETTCAP
1863 { "P1+r\\S", tcap_cb, }, // positive XTGETTCAP
1864 { "P!|\\S", tda_cb, }, // DCS da3 form used by XTerm
1865 { "P>|\\S", xtversion_cb, },
1866 // OSC (\e_...ST)
1867 { "_G\\S", kittygraph_cb, },
1868 // a mystery to everyone!
1869 { "]10;rgb:\\S", fgdef_cb, },
1870 { "]11;rgb:\\S", bgdef_cb, },
1871 { NULL, NULL, },
1872 }, *csi;
1873 for(csi = csis ; csi->cflow ; ++csi){
1874 if(inputctx_add_cflow(&ictx->amata, csi->cflow, csi->fxn)){
1875 logerror("failed adding %p via %s", csi->fxn, csi->cflow);
1876 return -1;
1877 }
1878 loginfo("added %p via %s", csi->fxn, csi->cflow);
1879 }
1880 if(ictx->ti->qterm == TERMINAL_RXVT){
1881 if(inputctx_add_cflow(&ictx->amata, "]4;\\N;rgb:\\R", palette_cb)){
1882 logerror("failed adding palette_cb");
1883 return -1;
1884 }
1885 }else{
1886 if(inputctx_add_cflow(&ictx->amata, "]4;\\N;rgb:\\S", palette_cb)){
1887 logerror("failed adding palette_cb");
1888 return -1;
1889 }
1890 // handle old-style contour responses, though we can't make use of them
1891 if(inputctx_add_cflow(&ictx->amata, "]4;rgb:\\S", palette_cb)){
1892 logerror("failed adding palette_cb");
1893 return -1;
1894 }
1895 }
1896 return 0;
1897}
1898
1899static void
1900closepipe(ipipe p){
1901#ifndef __MINGW32__
1902 if(p >= 0){
1903 close(p);
1904 }
1905#else
1906 if(p){
1907 CloseHandle(p);
1908 }
1909#endif
1910}
1911
1912static void
1913endpipes(ipipe pipes[static 2]){
1914 closepipe(pipes[0]);
1915 closepipe(pipes[1]);
1916}
1917
1918// only linux and freebsd13+ have eventfd(), so we'll fall back to pipes sigh.
1919static int
1920getpipes(ipipe pipes[static 2]){
1921#ifndef __MINGW32__
1922#ifndef __APPLE__
1923 if(pipe2(pipes, O_CLOEXEC | O_NONBLOCK)){
1924 logerror("couldn't get pipes (%s)", strerror(errno));
1925 return -1;
1926 }
1927#else
1928 if(pipe(pipes)){
1929 logerror("couldn't get pipes (%s)", strerror(errno));
1930 return -1;
1931 }
1932 if(set_fd_cloexec(pipes[0], 1, NULL) || set_fd_nonblocking(pipes[0], 1, NULL)){
1933 logerror("couldn't prep pipe[0] (%s)", strerror(errno));
1934 endpipes(pipes);
1935 return -1;
1936 }
1937 if(set_fd_cloexec(pipes[1], 1, NULL) || set_fd_nonblocking(pipes[1], 1, NULL)){
1938 logerror("couldn't prep pipe[1] (%s)", strerror(errno));
1939 endpipes(pipes);
1940 return -1;
1941 }
1942#endif
1943#else // windows
1944 if(!CreatePipe(&pipes[0], &pipes[1], NULL, BUFSIZ)){
1945 logerror("couldn't get pipes");
1946 return -1;
1947 }
1948#endif
1949 return 0;
1950}
1951
1952static inline inputctx*
1953create_inputctx(tinfo* ti, FILE* infp, int lmargin, int tmargin, int rmargin,
1954 int bmargin, ncsharedstats* stats, unsigned drain,
1955 int linesigs_enabled){
1956 bool sent_queries = (ti->ttyfd >= 0) ? true : false;
1957 inputctx* i = malloc(sizeof(*i));
1958 if(i){
1959 i->csize = 64;
1960 if( (i->csrs = malloc(sizeof(*i->csrs) * i->csize)) ){
1961 i->isize = BUFSIZ;
1962 if( (i->inputs = malloc(sizeof(*i->inputs) * i->isize)) ){
1963 if(pthread_mutex_init(&i->ilock, NULL) == 0){
1964 if(pthread_condmonotonic_init(&i->icond) == 0){
1965 if(pthread_mutex_init(&i->clock, NULL) == 0){
1966 if(pthread_condmonotonic_init(&i->ccond) == 0){
1967 if((i->stdinfd = fileno(infp)) >= 0){
1968 if( (i->initdata = malloc(sizeof(*i->initdata))) ){
1969 if(getpipes(i->readypipes) == 0){
1970 if(getpipes(i->ipipes) == 0){
1971 memset(&i->amata, 0, sizeof(i->amata));
1972 if(prep_special_keys(i) == 0){
1973 if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){
1974 i->termfd = tty_check(i->stdinfd) ? -1 : get_tty_fd(infp);
1975 memset(i->initdata, 0, sizeof(*i->initdata));
1976 if(sent_queries){
1977 i->coutstanding = 1; // one in initial request set
1978 i->initdata->qterm = ti->qterm;
1979 i->initdata->cursory = -1;
1980 i->initdata->cursorx = -1;
1981 i->initdata->maxpaletteread = -1;
1982 i->initdata->kbdlevel = UINT_MAX;
1983 }else{
1984 free(i->initdata);
1985 i->initdata = NULL;
1986 i->coutstanding = 0;
1987 }
1988 i->kittykbd = 0;
1989 i->iread = i->iwrite = i->ivalid = 0;
1990 i->cread = i->cwrite = i->cvalid = 0;
1992 i->stats = stats;
1993 i->ti = ti;
1994 i->stdineof = 0;
1995#ifdef __MINGW32__
1996 i->stdinhandle = ti->inhandle;
1997#endif
1998 i->ibufvalid = 0;
1999 i->linesigs = linesigs_enabled;
2000 i->tbufvalid = 0;
2001 i->midescape = 0;
2002 i->lmargin = lmargin;
2003 i->tmargin = tmargin;
2004 i->rmargin = rmargin;
2005 i->bmargin = bmargin;
2006 i->drain = drain;
2007 i->failed = false;
2008 logdebug("input descriptors: %d/%d", i->stdinfd, i->termfd);
2009 return i;
2010 }
2011 }
2013 }
2014 endpipes(i->ipipes);
2015 }
2016 endpipes(i->readypipes);
2017 }
2018 free(i->initdata);
2019 }
2020 pthread_cond_destroy(&i->ccond);
2021 }
2022 pthread_mutex_destroy(&i->clock);
2023 }
2024 pthread_cond_destroy(&i->icond);
2025 }
2026 pthread_mutex_destroy(&i->ilock);
2027 }
2028 free(i->inputs);
2029 }
2030 free(i->csrs);
2031 }
2032 free(i);
2033 }
2034 return NULL;
2035}
2036
2037static inline void
2038free_inputctx(inputctx* i){
2039 if(i){
2040 // we *do not* own stdinfd; don't close() it! we do own termfd.
2041 if(i->termfd >= 0){
2042 close(i->termfd);
2043 }
2044 pthread_mutex_destroy(&i->ilock);
2045 pthread_cond_destroy(&i->icond);
2046 pthread_mutex_destroy(&i->clock);
2047 pthread_cond_destroy(&i->ccond);
2049 // do not kill the thread here, either.
2050 if(i->initdata){
2051 free(i->initdata->version);
2052 free(i->initdata);
2053 }else if(i->initdata_complete){
2054 free(i->initdata_complete->version);
2055 free(i->initdata_complete);
2056 }
2057 endpipes(i->readypipes);
2058 endpipes(i->ipipes);
2059 free(i->inputs);
2060 free(i->csrs);
2061 free(i);
2062 }
2063}
2064
2065// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#functional-key-definitions
2066static int
2067prep_kitty_special_keys(inputctx* ictx){
2068 // we do not list here those already handled by prep_windows_special_keys()
2069 static const struct {
2070 const char* esc;
2071 uint32_t key;
2072 unsigned modifiers;
2073 } keys[] = {
2074 { .esc = "\x1b[P", .key = NCKEY_F01, },
2075 { .esc = "\x1b[Q", .key = NCKEY_F02, },
2076 { .esc = "\x1b[R", .key = NCKEY_F03, },
2077 { .esc = "\x1b[13~", .key = NCKEY_F03, },
2078 { .esc = "\x1b[S", .key = NCKEY_F04, },
2079 { .esc = "\x1b[127;2u", .key = NCKEY_BACKSPACE,
2080 .modifiers = NCKEY_MOD_SHIFT, },
2081 { .esc = "\x1b[127;3u", .key = NCKEY_BACKSPACE,
2082 .modifiers = NCKEY_MOD_ALT, },
2083 { .esc = "\x1b[127;5u", .key = NCKEY_BACKSPACE,
2084 .modifiers = NCKEY_MOD_CTRL, },
2085 { .esc = NULL, .key = 0, },
2086 }, *k;
2087 for(k = keys ; k->esc ; ++k){
2088 if(inputctx_add_input_escape(&ictx->amata, k->esc, k->key, k->modifiers)){
2089 return -1;
2090 }
2091 }
2092 loginfo("added all kitty special keys");
2093 return 0;
2094}
2095
2096// add the hardcoded windows input sequences to ti->input. should only
2097// be called after verifying that this is TERMINAL_MSTERMINAL.
2098static int
2099prep_windows_special_keys(inputctx* ictx){
2100 // here, lacking terminfo, we hardcode the sequences. they can be found at
2101 // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
2102 // under the "Input Sequences" heading.
2103 static const struct {
2104 const char* esc;
2105 uint32_t key;
2106 unsigned modifiers;
2107 } keys[] = {
2108 { .esc = "\x1b[A", .key = NCKEY_UP, },
2109 { .esc = "\x1b[B", .key = NCKEY_DOWN, },
2110 { .esc = "\x1b[C", .key = NCKEY_RIGHT, },
2111 { .esc = "\x1b[D", .key = NCKEY_LEFT, },
2112 { .esc = "\x1b[1;5A", .key = NCKEY_UP,
2113 .modifiers = NCKEY_MOD_CTRL, },
2114 { .esc = "\x1b[1;5B", .key = NCKEY_DOWN,
2115 .modifiers = NCKEY_MOD_CTRL, },
2116 { .esc = "\x1b[1;5C", .key = NCKEY_RIGHT,
2117 .modifiers = NCKEY_MOD_CTRL, },
2118 { .esc = "\x1b[1;5D", .key = NCKEY_LEFT,
2119 .modifiers = NCKEY_MOD_CTRL, },
2120 { .esc = "\x1b[H", .key = NCKEY_HOME, },
2121 { .esc = "\x1b[F", .key = NCKEY_END, },
2122 { .esc = "\x1b[2~", .key = NCKEY_INS, },
2123 { .esc = "\x1b[3~", .key = NCKEY_DEL, },
2124 { .esc = "\x1b[5~", .key = NCKEY_PGUP, },
2125 { .esc = "\x1b[6~", .key = NCKEY_PGDOWN, },
2126 { .esc = "\x1bOP", .key = NCKEY_F01, },
2127 { .esc = "\x1bOQ", .key = NCKEY_F02, },
2128 { .esc = "\x1bOR", .key = NCKEY_F03, },
2129 { .esc = "\x1bOS", .key = NCKEY_F04, },
2130 { .esc = "\x1b[15~", .key = NCKEY_F05, },
2131 { .esc = "\x1b[17~", .key = NCKEY_F06, },
2132 { .esc = "\x1b[18~", .key = NCKEY_F07, },
2133 { .esc = "\x1b[19~", .key = NCKEY_F08, },
2134 { .esc = "\x1b[20~", .key = NCKEY_F09, },
2135 { .esc = "\x1b[21~", .key = NCKEY_F10, },
2136 { .esc = "\x1b[23~", .key = NCKEY_F11, },
2137 { .esc = "\x1b[24~", .key = NCKEY_F12, },
2138 { .esc = NULL, .key = 0, },
2139 }, *k;
2140 for(k = keys ; k->esc ; ++k){
2141 if(inputctx_add_input_escape(&ictx->amata, k->esc, k->key, k->modifiers)){
2142 return -1;
2143 }
2144 logdebug("added %s %u", k->esc, k->key);
2145 }
2146 loginfo("added all windows special keys");
2147 return 0;
2148}
2149
2150static int
2151prep_all_keys(inputctx* ictx){
2152 if(prep_windows_special_keys(ictx)){
2153 return -1;
2154 }
2155 if(prep_kitty_special_keys(ictx)){
2156 return -1;
2157 }
2158 if(prep_xtmodkeys(ictx)){
2159 return -1;
2160 }
2161 return 0;
2162}
2163
2164// populate |buf| with any new data from the specified file descriptor |fd|.
2165// bufused indicates the offset into buf at which to begin reading, and is
2166// updated to reflect data read. buflen is the length of the total buffer
2167// (including any skipped material). the read is nonblocking, and only one is
2168// performed. this is used by the input thread.
2169static void
2170read_input_nblock(int fd, unsigned char* buf, size_t buflen, int *bufused,
2171 unsigned* goteof){
2172 if(fd < 0){
2173 return;
2174 }
2175 size_t space = buflen - *bufused;
2176 if(space == 0){
2177 return;
2178 }
2179 ssize_t r = read(fd, buf + *bufused, space);
2180 if(r <= 0){
2181 if(r < 0 && (errno != EAGAIN && errno != EBUSY && errno == EWOULDBLOCK)){
2182 logwarn("couldn't read from %d (%s)", fd, strerror(errno));
2183 }else{
2184 if(r < 0){
2185 logerror("error reading from %d (%s)", fd, strerror(errno));
2186 }else{
2187 logwarn("got EOF on %d for %p", fd, goteof);
2188 }
2189 if(goteof){
2190 *goteof = 1;
2191 }
2192 }
2193 return;
2194 }
2195 *bufused += r;
2196 space -= r;
2197 loginfo("read %" PRIdPTR "B from %d (%" PRIuPTR "B left)", r, fd, space);
2198}
2199
2200// are terminal and stdin distinct for this inputctx?
2201static inline bool
2202ictx_independent_p(const inputctx* ictx){
2203 return ictx->termfd >= 0;
2204}
2205
2206static inline void
2207walk_escape_automaton(inputctx* ictx, const unsigned char* start){
2208 ictx->amata.matchstart = start;
2209 ictx->amata.state = ictx->amata.escapes;
2210 ictx->amata.used = 1;
2211 logtrace("initialized automaton to %u", ictx->amata.state);
2212}
2213
2214// try to lex a single control sequence off of buf. return the number of bytes
2215// consumed if we do so. otherwise, return the negative number of bytes
2216// examined. set ictx->midescape if we're uncertain. we preserve a->used,
2217// a->state, etc. across runs to avoid reprocessing. buf is almost certainly
2218// *not* NUL-terminated.
2219//
2220// our rule is: an escape must arrive as a single unit to be interpreted as
2221// an escape. this is most relevant for Alt+keypress (Esc followed by the
2222// character), which is ambiguous with regards to pressing 'Escape' followed
2223// by some other character. if they arrive together, we consider it to be
2224// the escape. we might need to allow more than one process_escape call,
2225// however, in case the escape ended the previous read buffer.
2226// precondition: buflen >= 1. precondition: buf[0] == 0x1b.
2227static int
2228process_escape(inputctx* ictx, const unsigned char* buf, int buflen){
2229 assert(ictx->amata.used <= buflen);
2230 while(ictx->amata.used < buflen){
2231 unsigned char candidate = buf[ictx->amata.used++];
2232 unsigned used = ictx->amata.used;
2233 if(candidate >= 0x80){
2234 ictx->amata.used = 0;
2235 return -(used - 1);
2236 }
2237 // an escape always resets the trie (unless we're in the middle of an
2238 // ST-terminated string), as does a NULL transition.
2239 if(candidate == NCKEY_ESC && !ictx->amata.instring){
2240 walk_escape_automaton(ictx, buf + ictx->amata.used - 1);
2241 if(used > 1){ // we got reset; replay as input
2242 return -(used - 1);
2243 }
2244 // validated first byte as escape! keep going. otherwise, check trie.
2245 // we can safely check trie[candidate] above because we are either coming
2246 // off the initial node, which definitely has a valid ->trie, or we're
2247 // coming from a transition, where ictx->triepos->trie is checked below.
2248 }else{
2249 ncinput ni = {0};
2250 int w = walk_automaton(&ictx->amata, ictx, candidate, &ni);
2251 logdebug("walk result on 0x%02x (%c): %d %u", candidate,
2252 isprint(candidate) ? candidate : ' ', w, ictx->amata.state);
2253 if(w > 0){
2254 if(ni.id){
2255 load_ncinput(ictx, &ni);
2256 }
2257 ictx->amata.used = 0;
2258 ictx->amata.state = 0;
2259 return used;
2260 }else if(w < 0){
2261 // all inspected characters are invalid; return full negative "used"
2262 ictx->amata.used = 0;
2263 return -used;
2264 }
2265 }
2266 }
2267 // we exhausted input without knowing whether or not this is a valid control
2268 // sequence; we're still on-trie, and need more (immediate) input.
2269 ictx->midescape = 1;
2270 return -ictx->amata.used;
2271}
2272
2273// process as many control sequences from |buf|, having |bufused| bytes,
2274// as we can. this text needn't be valid UTF-8. this is always called on
2275// tbuf; if we find bulk data here, we need replay it into ibuf (assuming
2276// that there's room).
2277static void
2278process_escapes(inputctx* ictx, unsigned char* buf, int* bufused){
2279 int offset = 0;
2280 while(*bufused > 0){
2281 int consumed = process_escape(ictx, buf + offset, *bufused);
2282 // negative |consumed| means either that we're not sure whether it's an
2283 // escape, or it definitely is not.
2284 if(consumed < 0){
2285 // if midescape is not set, the negative return means invalid escape.
2286 // replay it to the bulk input buffer; our automaton will have been reset.
2287 if(!ictx->midescape){
2288 consumed = -consumed;
2289 int available = sizeof(ictx->ibuf) - ictx->ibufvalid;
2290 if(available){
2291 if(available > consumed){
2292 available = consumed;
2293 }
2294 logwarn("replaying %dB of %dB to ibuf", available, consumed);
2295 memcpy(ictx->ibuf + ictx->ibufvalid, buf + offset, available);
2296 ictx->ibufvalid += available;
2297 }
2298 offset += consumed;
2299 ictx->midescape = 0;
2300 *bufused -= consumed;
2301 assert(0 <= *bufused);
2302 }else{
2303 break;
2304 }
2305 }
2306 *bufused -= consumed;
2307 offset += consumed;
2308 assert(0 <= *bufused);
2309 }
2310 // move any leftovers to the front; only happens if we fill output queue,
2311 // or ran out of input data mid-escape
2312 if(*bufused > 0){
2313 ictx->amata.matchstart = buf;
2314 memmove(buf, buf + offset, *bufused);
2315 }
2316}
2317
2318// precondition: buflen >= 1. attempts to consume UTF8 input from buf. the
2319// expected length of a UTF8 character can be determined from its first byte.
2320// if we don't have that much data, return 0 and read more. if we determine
2321// an error, return -1 to consume 1 byte, restarting the UTF8 lex on the next
2322// byte. on a valid UTF8 character, set up the ncinput and return its length.
2323static int
2324process_input(const unsigned char* buf, int buflen, ncinput* ni){
2325 assert(1 <= buflen);
2326 memset(ni, 0, sizeof(*ni));
2327 const int cpointlen = utf8_codepoint_length(*buf);
2328 if(cpointlen <= 0){
2329 logwarn("invalid UTF8 initiator on input (0x%02x)", *buf);
2330 return -1;
2331 }else if(cpointlen == 1){ // pure ascii can't show up mid-utf8-character
2332 ni->id = buf[0];
2333 return 1;
2334 }
2335 if(cpointlen > buflen){
2336 logwarn("utf8 character (%dB) broken across read", cpointlen);
2337 return 0; // need read more data; we don't have the complete character
2338 }
2339 wchar_t w;
2340 mbstate_t mbstate = {0};
2341//fprintf(stderr, "CANDIDATE: %d cpointlen: %zu cpoint: %d\n", candidate, cpointlen, cpoint[cpointlen]);
2342 // FIXME how the hell does this work with 16-bit wchar_t?
2343 size_t r = mbrtowc(&w, (const char*)buf, cpointlen, &mbstate);
2344 if(r == (size_t)-1 || r == (size_t)-2){
2345 logerror("invalid utf8 prefix (%dB) on input", cpointlen);
2346 return -1;
2347 }
2348 ni->id = w;
2349 return cpointlen;
2350}
2351
2352// precondition: buflen >= 1. gets an ncinput prepared by process_input, and
2353// sticks that into the bulk queue.
2354static int
2355process_ncinput(inputctx* ictx, const unsigned char* buf, int buflen){
2356 ncinput ni;
2357 int r = process_input(buf, buflen, &ni);
2358 if(r > 0){
2359 load_ncinput(ictx, &ni);
2360 }else if(r < 0){
2361 inc_input_errors(ictx);
2362 r = 1; // we want to consume a single byte, upstairs
2363 }
2364 return r;
2365}
2366
2367// handle redirected input (i.e. not from our connected terminal). process as
2368// much bulk UTF-8 input as we can, knowing it to be free of control sequences.
2369// anything not a valid UTF-8 character is dropped.
2370static void
2371process_bulk(inputctx* ictx, unsigned char* buf, int* bufused){
2372 int offset = 0;
2373 while(*bufused){
2374 bool noroom = false;
2375 pthread_mutex_lock(&ictx->ilock);
2376 if(ictx->ivalid == ictx->isize){
2377 noroom = true;
2378 }
2379 pthread_mutex_unlock(&ictx->ilock);
2380 if(noroom){
2381 break;
2382 }
2383 int consumed = process_ncinput(ictx, buf + offset, *bufused);
2384 if(consumed <= 0){
2385 break;
2386 }
2387 *bufused -= consumed;
2388 offset += consumed;
2389 }
2390 // move any leftovers to the front
2391 if(*bufused > 0){
2392 memmove(buf, buf + offset, *bufused);
2393 }
2394}
2395
2396// process as much mixed input as we can. we might find UTF-8 bulk input and
2397// control sequences mixed (though each individual character/sequence ought be
2398// contiguous). known control sequences are removed for internal processing.
2399// everything else will be handed up to the client (assuming it to be valid
2400// UTF-8).
2401static void
2402process_melange(inputctx* ictx, const unsigned char* buf, int* bufused){
2403 int offset = 0;
2404 int origlen = *bufused;
2405 while(*bufused){
2406 logdebug("input %d (%u)/%d [0x%02x] (%c)", offset, ictx->amata.used,
2407 *bufused, buf[offset], isprint(buf[offset]) ? buf[offset] : ' ');
2408 int consumed = 0;
2409 if(buf[offset] == NCKEY_ESC){
2410 consumed = process_escape(ictx, buf + offset, *bufused);
2411 if(consumed < 0){
2412 if(ictx->midescape){
2413 if(*bufused != -consumed || *bufused == origlen){
2414 // not at the end; treat it as input. no need to move between
2415 // buffers; simply ensure we process it as input, and don't mark
2416 // anything as consumed.
2417 ictx->midescape = 0;
2418 }
2419 }
2420 }
2421 }
2422 // don't process as input only if we just read a valid control character,
2423 // or if we need to read more to determine what it is.
2424 if(consumed <= 0 && !ictx->midescape){
2425 consumed = process_ncinput(ictx, buf + offset, *bufused);
2426 }
2427 if(consumed < 0){
2428 break;
2429 }
2430 *bufused -= consumed;
2431 offset += consumed;
2432 }
2433 handoff_initial_responses_late(ictx);
2434}
2435
2436// walk the matching automaton from wherever we were.
2437static void
2438process_ibuf(inputctx* ictx){
2439 if(resize_seen){
2440 ncinput tni = {
2441 .id = NCKEY_RESIZE,
2442 };
2443 load_ncinput(ictx, &tni);
2444 resize_seen = 0;
2445 }
2446 if(cont_seen){
2447 ncinput tni = {
2448 .id = NCKEY_SIGNAL,
2449 };
2450 load_ncinput(ictx, &tni);
2451 cont_seen = 0;
2452 }
2453 if(ictx->tbufvalid){
2454 // we could theoretically do this in parallel with process_bulk, but it
2455 // hardly seems worthwhile without breaking apart the fetches of input.
2456 process_escapes(ictx, ictx->tbuf, &ictx->tbufvalid);
2457 handoff_initial_responses_late(ictx);
2458 }
2459 if(ictx->ibufvalid){
2460 if(ictx_independent_p(ictx)){
2461 process_bulk(ictx, ictx->ibuf, &ictx->ibufvalid);
2462 }else{
2463 int valid = ictx->ibufvalid;
2464 process_melange(ictx, ictx->ibuf, &ictx->ibufvalid);
2465 // move any leftovers to the front
2466 if(ictx->ibufvalid){
2467 memmove(ictx->ibuf, ictx->ibuf + valid - ictx->ibufvalid, ictx->ibufvalid);
2468 if(ictx->amata.matchstart){
2469 ictx->amata.matchstart = ictx->ibuf;
2470 }
2471 }
2472 }
2473 }
2474}
2475
2476int ncinput_shovel(inputctx* ictx, const void* buf, int len){
2477 process_melange(ictx, buf, &len);
2478 if(len){
2479 logwarn("dropping %d byte%s", len, len == 1 ? "" : "s");
2480 inc_input_errors(ictx);
2481 }
2482 return 0;
2483}
2484
2485// here, we always block for an arbitrarily long time, or not at all,
2486// doing the latter only when ictx->midescape is set. |rtfd| and/or |rifd|
2487// are set high iff they are ready for reading, and otherwise cleared.
2488static int
2489block_on_input(inputctx* ictx, unsigned* rtfd, unsigned* rifd){
2490 logtrace("blocking on input availability");
2491 *rtfd = *rifd = 0;
2492 unsigned nonblock = ictx->midescape;
2493 if(nonblock){
2494 loginfo("nonblocking read to check for completion");
2495 ictx->midescape = 0;
2496 }
2497#ifdef __MINGW32__
2498 int timeoutms = nonblock ? 0 : -1;
2499 DWORD ncount = 0;
2500 HANDLE handles[2];
2501 if(!ictx->stdineof){
2502 if(ictx->ibufvalid != sizeof(ictx->ibuf)){
2503 handles[ncount++] = ictx->stdinhandle;
2504 }
2505 }
2506 if(ncount == 0){
2507 handles[ncount++] = ictx->ipipes[0];
2508 }
2509 DWORD d = WaitForMultipleObjects(ncount, handles, false, timeoutms);
2510 if(d == WAIT_TIMEOUT){
2511 return 0;
2512 }else if(d == WAIT_FAILED){
2513 return -1;
2514 }else if(d - WAIT_OBJECT_0 == 0){
2515 *rifd = 1;
2516 return 1;
2517 }
2518 return -1;
2519#else
2520 // do *not* use POLLRDHUP; it is not necessary, and causes trouble on BSD
2521 const int inevents = POLLIN;
2522 struct pollfd pfds[2];
2523 int pfdcount = 0;
2524 if(!ictx->stdineof){
2525 if(ictx->ibufvalid != sizeof(ictx->ibuf)){
2526 pfds[pfdcount].fd = ictx->stdinfd;
2527 pfds[pfdcount].events = inevents;
2528 pfds[pfdcount].revents = 0;
2529 ++pfdcount;
2530 }
2531 }
2532 if(pfdcount == 0){
2533 loginfo("output queues full; blocking on ipipes");
2534 pfds[pfdcount].fd = ictx->ipipes[0];
2535 pfds[pfdcount].events = inevents;
2536 pfds[pfdcount].revents = 0;
2537 ++pfdcount;
2538 }
2539 if(ictx->termfd >= 0){
2540 pfds[pfdcount].fd = ictx->termfd;
2541 pfds[pfdcount].events = inevents;
2542 pfds[pfdcount].revents = 0;
2543 ++pfdcount;
2544 }
2545 logtrace("waiting on %d fds (ibuf: %u/%"PRIuPTR")", pfdcount, ictx->ibufvalid, sizeof(ictx->ibuf));
2546 sigset_t smask;
2547 sigfillset(&smask);
2548 sigdelset(&smask, SIGCONT);
2549 sigdelset(&smask, SIGWINCH);
2550#ifdef SIGTHR
2551 // freebsd uses SIGTHR for thread cancellation; need this to ensure wakeup
2552 // on exit (in cancel_and_join()).
2553 sigdelset(&smask, SIGTHR);
2554#endif
2555 int events;
2556#if defined(__APPLE__) || defined(__MINGW32__)
2557 int timeoutms = nonblock ? 0 : -1;
2558 while((events = poll(pfds, pfdcount, timeoutms)) < 0){ // FIXME smask?
2559#else
2560 struct timespec ts = { .tv_sec = 0, .tv_nsec = 0, };
2561 struct timespec* pts = nonblock ? &ts : NULL;
2562 while((events = ppoll(pfds, pfdcount, pts, &smask)) < 0){
2563#endif
2564 if(errno == EINTR){
2565 loginfo("interrupted by signal");
2566 return resize_seen;
2567 }else if(errno != EAGAIN && errno != EBUSY && errno != EWOULDBLOCK){
2568 logerror("error polling (%s)", strerror(errno));
2569 return -1;
2570 }
2571 }
2572 loginfo("poll returned %d", events);
2573 pfdcount = 0;
2574 while(events){
2575 if(pfds[pfdcount].revents){
2576 if(pfds[pfdcount].fd == ictx->stdinfd){
2577 *rifd = 1;
2578 }else if(pfds[pfdcount].fd == ictx->termfd){
2579 *rtfd = 1;
2580 }else if(pfds[pfdcount].fd == ictx->ipipes[0]){
2581 char c;
2582 while(read(ictx->ipipes[0], &c, sizeof(c)) == 1){
2583 // FIXME accelerate?
2584 }
2585 }
2586 --events;
2587 }
2588 ++pfdcount;
2589 }
2590 loginfo("got events: %c%c", *rtfd ? 'T' : 't', *rifd ? 'I' : 'i');
2591 return events;
2592#endif
2593}
2594
2595// populate the ibuf with any new data, up through its size, but do not block.
2596// don't loop around this call without some kind of readiness notification.
2597static void
2598read_inputs_nblock(inputctx* ictx){
2599 unsigned rtfd, rifd;
2600 block_on_input(ictx, &rtfd, &rifd);
2601 // first we read from the terminal, if that's a distinct source.
2602 if(rtfd){
2603 read_input_nblock(ictx->termfd, ictx->tbuf, sizeof(ictx->tbuf),
2604 &ictx->tbufvalid, NULL);
2605 }
2606 // now read bulk, possibly with term escapes intermingled within (if there
2607 // was not a distinct terminal source).
2608 if(rifd){
2609 unsigned eof = ictx->stdineof;
2610 read_input_nblock(ictx->stdinfd, ictx->ibuf, sizeof(ictx->ibuf),
2611 &ictx->ibufvalid, &ictx->stdineof);
2612 // did we switch from non-EOF state to EOF? if so, mark us ready
2613 if(!eof && ictx->stdineof){
2614 // we hit EOF; write an event to the readiness fd
2615 mark_pipe_ready(ictx->readypipes);
2616 pthread_cond_broadcast(&ictx->icond);
2617 }
2618 }
2619}
2620
2621static void*
2622input_thread(void* vmarshall){
2624 inputctx* ictx = vmarshall;
2625 if(prep_all_keys(ictx) || build_cflow_automaton(ictx)){
2626 ictx->failed = true;
2627 handoff_initial_responses_early(ictx);
2628 handoff_initial_responses_late(ictx);
2629 }
2630 for(;;){
2631 read_inputs_nblock(ictx);
2632 // process anything we've read
2633 process_ibuf(ictx);
2634 }
2635 return NULL;
2636}
2637
2638int init_inputlayer(tinfo* ti, FILE* infp, int lmargin, int tmargin,
2639 int rmargin, int bmargin, ncsharedstats* stats,
2640 unsigned drain, int linesigs_enabled){
2641 inputctx* ictx = create_inputctx(ti, infp, lmargin, tmargin, rmargin,
2642 bmargin, stats, drain, linesigs_enabled);
2643 if(ictx == NULL){
2644 return -1;
2645 }
2646 if(pthread_create(&ictx->tid, NULL, input_thread, ictx)){
2647 free_inputctx(ictx);
2648 return -1;
2649 }
2650 ti->ictx = ictx;
2651 loginfo("spun up input thread");
2652 return 0;
2653}
2654
2656 int ret = 0;
2657 if(ti){
2658 // FIXME cancellation on shutdown does not yet work on windows #2192
2659#ifndef __MINGW32__
2660 if(ti->ictx){
2661 loginfo("tearing down input thread");
2662 ret |= cancel_and_join("input", ti->ictx->tid, NULL);
2664 free_inputctx(ti->ictx);
2665 ti->ictx = NULL;
2666 }
2667#endif
2668 }
2669 return ret;
2670}
2671
2672int inputready_fd(const inputctx* ictx){
2673#ifndef __MINGW32__
2674 return ictx->readypipes[0];
2675#else
2676 (void)ictx;
2677 logerror("readiness descriptor unavailable on windows");
2678 return -1;
2679#endif
2680}
2681
2682static void
2683cleanup_mutex(void* v){
2684 pthread_mutex_unlock(v);
2685}
2686
2687static inline uint32_t
2688internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni){
2689 uint32_t id;
2690 if(ictx->drain){
2691 logerror("input is being drained");
2692 if(ni){
2693 memset(ni, 0, sizeof(*ni));
2694 ni->id = (uint32_t)-1;
2695 }
2696 return (uint32_t)-1;
2697 }
2698 pthread_mutex_lock(&ictx->ilock);
2699 while(!ictx->ivalid){
2700 if(ictx->stdineof){
2701 pthread_mutex_unlock(&ictx->ilock);
2702 logwarn("read eof on stdin");
2703 if(ni){
2704 memset(ni, 0, sizeof(*ni));
2705 ni->id = NCKEY_EOF;
2706 }
2707 return NCKEY_EOF;
2708 }
2709 int r;
2710 pthread_cleanup_push(cleanup_mutex, &ictx->ilock);
2711 if(ts == NULL){
2712 r = pthread_cond_wait(&ictx->icond, &ictx->ilock);
2713 }else{
2714 r = pthread_cond_timedwait(&ictx->icond, &ictx->ilock, ts);
2715 }
2716 pthread_cleanup_pop(0);
2717 if(r){
2718 pthread_mutex_unlock(&ictx->ilock);
2719 if(r == ETIMEDOUT){
2720 if(ni){
2721 memset(ni, 0, sizeof(*ni));
2722 }
2723 return 0;
2724 }else{
2725 inc_input_errors(ictx);
2726 if(ni){
2727 memset(ni, 0, sizeof(*ni));
2728 ni->id = (uint32_t)-1;
2729 }
2730 return (uint32_t)-1;
2731 }
2732 }
2733 }
2734 id = ictx->inputs[ictx->iread].id;
2735 if(ni){
2736 memcpy(ni, &ictx->inputs[ictx->iread], sizeof(*ni));
2737 if(notcurses_ucs32_to_utf8(&ni->id, 1, (unsigned char*)ni->utf8, sizeof(ni->utf8)) < 0){
2738 ni->utf8[0] = 0;
2739 }
2740 if(ni->eff_text[0]==0){
2741 ni->eff_text[0]=ni->id;
2742 }
2743 }
2744 if(++ictx->iread == ictx->isize){
2745 ictx->iread = 0;
2746 }
2747 bool sendsignal = false;
2748 if(ictx->ivalid-- == ictx->isize){
2749 sendsignal = true;
2750 }else{
2751 logtrace("draining event readiness pipe %d", ictx->ivalid);
2752#ifndef __MINGW32__
2753 char c;
2754 pthread_cleanup_push(cleanup_mutex, &ictx->ilock);
2755 while(read(ictx->readypipes[0], &c, sizeof(c)) == 1){
2756 // FIXME accelerate?
2757 }
2758 pthread_cleanup_pop(0);
2759#else
2760 // we ought be draining this, but it breaks everything, as we can't easily
2761 // do nonblocking input from a pipe in windows, augh...
2762 // Ne pleure pas, Alfred! J'ai besoin de tout mon courage pour mourir a vingt ans!
2763 /*while(ReadFile(ictx->readypipes[0], &c, sizeof(c), NULL, NULL)){
2764 // FIXME accelerate?
2765 }*/
2766#endif
2767 }
2768 pthread_mutex_unlock(&ictx->ilock);
2769 if(sendsignal){
2770 mark_pipe_ready(ictx->ipipes);
2771 }
2772 return id;
2773}
2774
2775// infp has already been set non-blocking
2776uint32_t notcurses_get(notcurses* nc, const struct timespec* absdl, ncinput* ni){
2777 uint32_t ret = internal_get(nc->tcache.ictx, absdl, ni);
2778 return ret;
2779}
2780
2781// FIXME better performance if we move this within the locked area
2782int notcurses_getvec(notcurses* n, const struct timespec* absdl,
2783 ncinput* ni, int vcount){
2784 for(int v = 0 ; v < vcount ; ++v){
2785 uint32_t u = notcurses_get(n, absdl, &ni[v]);
2786 if(u == (uint32_t)-1){
2787 if(v == 0){
2788 return -1;
2789 }
2790 return v;
2791 }else if(u == 0){
2792 return v;
2793 }
2794 }
2795 return vcount;
2796}
2797
2798uint32_t ncdirect_get(ncdirect* n, const struct timespec* absdl, ncinput* ni){
2799 if(n->eof){
2800 logerror("already got EOF");
2801 return -1;
2802 }
2803 uint32_t r = internal_get(n->tcache.ictx, absdl, ni);
2804 if(r == NCKEY_EOF){
2805 n->eof = 1;
2806 }
2807 return r;
2808}
2809
2810int get_cursor_location(inputctx* ictx, const char* u7, unsigned* y, unsigned* x){
2811 pthread_mutex_lock(&ictx->clock);
2812 while(ictx->cvalid == 0){
2813 if(ictx->coutstanding == 0){
2814 if(tty_emit(u7, ictx->ti->ttyfd)){
2815 pthread_mutex_unlock(&ictx->clock);
2816 return -1;
2817 }
2818 ++ictx->coutstanding;
2819 }
2820 pthread_cond_wait(&ictx->ccond, &ictx->clock);
2821 }
2822 const cursorloc* cloc = &ictx->csrs[ictx->cread];
2823 if(++ictx->cread == ictx->csize){
2824 ictx->cread = 0;
2825 }
2826 --ictx->cvalid;
2827 if(y){
2828 *y = cloc->y;
2829 }
2830 if(x){
2831 *x = cloc->x;
2832 }
2833 pthread_mutex_unlock(&ictx->clock);
2834 return 0;
2835}
2836
2837// Disable signals originating from the terminal's line discipline, i.e.
2838// SIGINT (^C), SIGQUIT (^\‍), and SIGTSTP (^Z). They are enabled by default.
2839static int
2840linesigs_disable(tinfo* ti){
2841 if(!ti->ictx->linesigs){
2842 logwarn("linedisc signals already disabled");
2843 }
2844#ifndef __MINGW32__
2845 if(ti->ttyfd < 0){
2846 return 0;
2847 }
2848 struct termios tios;
2849 if(tcgetattr(ti->ttyfd, &tios)){
2850 logerror("Couldn't preserve terminal state for %d (%s)", ti->ttyfd, strerror(errno));
2851 return -1;
2852 }
2853 tios.c_lflag &= ~ISIG;
2854 if(tcsetattr(ti->ttyfd, TCSANOW, &tios)){
2855 logerror("Error disabling signals on %d (%s)", ti->ttyfd, strerror(errno));
2856 return -1;
2857 }
2858#else
2859 DWORD mode;
2860 if(!GetConsoleMode(ti->inhandle, &mode)){
2861 logerror("error acquiring input mode");
2862 return -1;
2863 }
2864 mode &= ~ENABLE_PROCESSED_INPUT;
2865 if(!SetConsoleMode(ti->inhandle, mode)){
2866 logerror("error setting input mode");
2867 return -1;
2868 }
2869#endif
2870 ti->ictx->linesigs = 0;
2871 loginfo("disabled line discipline signals");
2872 return 0;
2873}
2874
2876 return linesigs_disable(&nc->tcache);
2877}
2878
2879static int
2880linesigs_enable(tinfo* ti){
2881 if(ti->ictx->linesigs){
2882 logwarn("linedisc signals already enabled");
2883 }
2884#ifndef __MINGW32__
2885 if(ti->ttyfd < 0){
2886 return 0;
2887 }
2888 struct termios tios;
2889 if(tcgetattr(ti->ttyfd, &tios)){
2890 logerror("couldn't preserve terminal state for %d (%s)", ti->ttyfd, strerror(errno));
2891 return -1;
2892 }
2893 tios.c_lflag |= ISIG;
2894 if(tcsetattr(ti->ttyfd, TCSANOW, &tios)){
2895 logerror("error disabling signals on %d (%s)", ti->ttyfd, strerror(errno));
2896 return -1;
2897 }
2898#else
2899 DWORD mode;
2900 if(!GetConsoleMode(ti->inhandle, &mode)){
2901 logerror("error acquiring input mode");
2902 return -1;
2903 }
2904 mode |= ENABLE_PROCESSED_INPUT;
2905 if(!SetConsoleMode(ti->inhandle, mode)){
2906 logerror("error setting input mode");
2907 return -1;
2908 }
2909#endif
2910 ti->ictx->linesigs = 1;
2911 loginfo("enabled line discipline signals");
2912 return 0;
2913}
2914
2915// Restore signals originating from the terminal's line discipline, i.e.
2916// SIGINT (^C), SIGQUIT (^\‍), and SIGTSTP (^Z), if disabled.
2918 return linesigs_enable(&n->tcache);
2919}
2920
2922 struct initial_responses* iresp;
2923 pthread_mutex_lock(&ictx->ilock);
2924 while(ictx->initdata || !ictx->initdata_complete){
2925 pthread_cond_wait(&ictx->icond, &ictx->ilock);
2926 }
2927 iresp = ictx->initdata_complete;
2928 ictx->initdata_complete = NULL;
2929 pthread_mutex_unlock(&ictx->ilock);
2930 if(ictx->failed){
2931 logpanic("aborting after automaton construction failure");
2932 free(iresp);
2933 return NULL;
2934 }
2935 return iresp;
2936}
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
__attribute__((nonnull(1)))
Definition egcpool.c:4
const nccell * c
Definition egcpool.h:207
uint32_t idx
Definition egcpool.h:209
f used
Definition fbuf.h:232
int r
Definition fbuf.h:226
assert(r >=0)
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:2638
int ncinput_shovel(inputctx *ictx, const void *buf, int len)
Definition in.c:2476
uint32_t notcurses_get(notcurses *nc, const struct timespec *absdl, ncinput *ni)
Definition in.c:2776
struct initial_responses * inputlayer_get_responses(inputctx *ictx)
Definition in.c:2921
int notcurses_linesigs_enable(notcurses *n)
Definition in.c:2917
int ipipe
Definition in.c:46
int notcurses_linesigs_disable(notcurses *nc)
Definition in.c:2875
int inputready_fd(const inputctx *ictx)
Definition in.c:2672
int notcurses_getvec(notcurses *n, const struct timespec *absdl, ncinput *ni, int vcount)
Definition in.c:2782
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:2810
int stop_inputlayer(tinfo *ti)
Definition in.c:2655
uint32_t ncdirect_get(ncdirect *n, const struct timespec *absdl, ncinput *ni)
Definition in.c:2798
queried_terminals_e
Definition in.h:31
@ TERMINAL_XTERM
Definition in.h:38
@ TERMINAL_GHOSTTY
Definition in.h:55
@ 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:3303
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:3506
int int x
Definition notcurses.h:1905
struct ncvisual_options v
Definition notcurses.h:3501
API int API int const nccell unsigned len
Definition notcurses.h:2592
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:64
int maxpaletteread
Definition in.h:85
unsigned appsync_supported
Definition in.h:63
ncpalette palette
Definition in.h:84
bool pixelmice
Definition in.h:86
unsigned kbdlevel
Definition in.h:83
bool rectangular_edits
Definition in.h:71
char * version
Definition in.h:82
unsigned kitty_graphics
Definition in.h:65
int cursorx
Definition in.h:62
int cursory
Definition in.h:61
uint32_t fg
Definition in.h:67
bool got_fg
Definition in.h:69
bool got_bg
Definition in.h:68
uint32_t bg
Definition in.h:66
char * hpa
Definition in.h:87
int color_registers
Definition in.h:79
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:182