THANKS: Coverity.com (overdue)
[s-mailx.git] / src / mx / termcap.c
blob9670f38467d33a3bfef863ffd46f112d51a81a7d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Implementation of termcap.h.
3 *@ For encapsulation purposes provide a basic foundation even without
4 *@ HOWTO add a new non-dynamic command or query:
5 *@ - add an entry to enum mx_termcap_{cmd,query}
6 *@ - run make-tcap-map.pl
7 *@ - update the *termcap* member documentation on changes!
8 *@ Bug: in case of clashes of two-letter names terminfo(5) wins.
10 * Copyright (c) 2016 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
11 * SPDX-License-Identifier: ISC
13 * Permission to use, copy, modify, and/or distribute this software for any
14 * purpose with or without fee is hereby granted, provided that the above
15 * copyright notice and this permission notice appear in all copies.
17 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
18 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
20 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
21 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
22 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
23 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 #undef su_FILE
26 #define su_FILE termcap
27 #define mx_SOURCE
29 #ifndef mx_HAVE_AMALGAMATION
30 # include "mx/nail.h"
31 #endif
33 su_EMPTY_FILE()
34 #include "mx/termcap.h"
35 #ifdef mx_HAVE_TCAP
37 /* If available, curses.h must be included before term.h! */
38 #ifdef mx_HAVE_TERMCAP
39 # ifdef mx_HAVE_TERMCAP_CURSES
40 # include <curses.h>
41 # endif
42 # include <term.h>
43 #endif
45 #include <su/cs.h>
46 #include <su/icodec.h>
47 #include <su/mem.h>
49 #include "mx/termios.h"
50 #include "mx/tty.h"
52 /* Already: #include "mx/termcap.h"*/
53 /* TODO fake */
54 #include "su/code-in.h"
57 * xxx We are not really compatible with very old and strange terminals since
58 * we don't care at all for circumstances indicated by terminal flags: if we
59 * find a capability we use it and assume it works. E.g., if "Co" indicates
60 * colours we simply use ISO 6429 also for font attributes etc. That is,
61 * we don't use the ncurses/terminfo interface with all its internal logic.
64 /* Unless mx_HAVE_TERMINFO or mx_HAVE_TGETENT_NULL_BUF are defined we use this
65 * value to space the buffer we pass through to tgetent(3).
66 * Since for (such) elder non-emulated terminals really weird things will
67 * happen if an entry would require more than 1024 bytes, don't really mind.
68 * Use a u16 for storage */
69 #define a_TERMCAP_ENTRYSIZE_MAX ((2668 + 128) & ~127) /* As of ncurses 6.0 */
71 CTA(a_TERMCAP_ENTRYSIZE_MAX < U16_MAX,
72 "Chosen buffer size exceeds datatype capability");
74 /* For simplicity we store commands and queries in single continuous control
75 * and entry structure arrays: to index queries one has to add
76 * mx__TERMCAP_CMD_MAX1 first! And don't confound with ENTRYSIZE_MAX! */
77 enum{
78 a_TERMCAP_ENT_MAX1 = mx__TERMCAP_CMD_MAX1 + mx__TERMCAP_QUERY_MAX1
81 enum a_termcap_flags{
82 a_TERMCAP_F_NONE,
83 /* enum mx_termcap_captype values stored here.
84 * Note presence of a type in an a_termcap_ent signals initialization */
85 a_TERMCAP_F_TYPE_MASK = (1u<<4) - 1,
87 a_TERMCAP_F_QUERY = 1u<<4, /* A query rather than a command */
88 a_TERMCAP_F_DISABLED = 1u<<5, /* User explicitly disabled command/query */
89 a_TERMCAP_F_ALTERN = 1u<<6, /* Not available, but has alternative */
90 a_TERMCAP_F_NOENT = 1u<<7, /* Not available */
92 /* _cmd() argument interpretation (_T_STR) */
93 a_TERMCAP_F_ARG_IDX1 = 1u<<11, /* Argument 1 used, and is an index */
94 a_TERMCAP_F_ARG_IDX2 = 1u<<12,
95 a_TERMCAP_F_ARG_CNT = 1u<<13, /* .., and is a count */
97 a_TERMCAP_F__LAST = a_TERMCAP_F_ARG_CNT
99 CTA(S(u32,mx__TERMCAP_CAPTYPE_MAX1) <= S(u32,a_TERMCAP_F_TYPE_MASK),
100 "enum mx_termcap_captype exceeds bit range of a_termcap_flags");
102 struct a_termcap_control{
103 u16 tc_flags;
104 /* Offset base into a_termcap_namedat[], which stores the two-letter
105 * termcap(5) name directly followed by a NUL terminated terminfo(5) name.
106 * A termcap(5) name may consist of two NULs meaning ERR_NOENT,
107 * a terminfo(5) name may be empty for the same purpose */
108 u16 tc_off;
110 CTA(a_TERMCAP_F__LAST <= U16_MAX,
111 "a_termcap_flags exceed storage datatype in a_termcap_control");
113 struct a_termcap_ent{
114 u16 te_flags;
115 u16 te_off; /* in a_termcap_g->tg_dat / value for T_BOOL and T_NUM */
117 CTA(a_TERMCAP_F__LAST <= U16_MAX,
118 "a_termcap_flags exceed storage datatype in a_termcap_ent");
120 /* Structure for extended queries, which don't have an entry constant in
121 * mx_termcap_query (to allow free query/binding of keycodes) */
122 struct a_termcap_ext_ent{
123 struct a_termcap_ent tee_super;
124 u8 tee__dummy[4];
125 struct a_termcap_ext_ent *tee_next;
126 /* Resolvable termcap(5)/terminfo(5) name as given by user; the actual data
127 * is stored just like for normal queries */
128 char tee_name[VFIELD_SIZE(0)];
131 struct a_termcap_g{
132 struct a_termcap_ext_ent *tg_ext_ents; /* List of extended queries */
133 struct a_termcap_ent tg_ents[a_TERMCAP_ENT_MAX1];
134 struct n_string tg_dat; /* Storage for resolved caps */
135 # if !defined mx_HAVE_TGETENT_NULL_BUF && !defined mx_HAVE_TERMINFO
136 char tg_lib_buf[a_TERMCAP_ENTRYSIZE_MAX];
137 # endif
140 /* Include the constant make-tcap-map.pl output */
141 #include "mx/gen-tcaps.h" /* $(MX_SRCDIR) */
142 CTA(sizeof a_termcap_namedat <= U16_MAX,
143 "Termcap command and query name data exceed storage datatype");
144 CTA(a_TERMCAP_ENT_MAX1 == NELEM(a_termcap_control),
145 "Control array does not match command/query array to be controlled");
147 static struct a_termcap_g *a_termcap_g;
149 /* Query *termcap*, parse it and incorporate into a_termcap_g */
150 static void a_termcap_init_var(struct str const *termvar);
152 /* Expand ^CNTRL, \[Ee] and \OCT. False for parse error and empty results */
153 static boole a_termcap__strexp(struct n_string *store, char const *ibuf);
155 /* Initialize any _ent for which we have _F_ALTERN and which isn't yet set */
156 static void a_termcap_init_altern(void);
158 #ifdef mx_HAVE_TERMCAP
159 /* Setup the library we use to work with term */
160 static boole a_termcap_load(char const *term);
162 /* Query the capability tcp and fill in tep (upon success) */
163 static boole a_termcap_ent_query(struct a_termcap_ent *tep,
164 char const *cname, u16 cflags);
165 su_SINLINE boole a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
166 struct a_termcap_control const *tcp);
168 /* Output PTF for both, termcap(5) and terminfo(5) */
169 static int a_termcap_putc(int c);
170 #endif
172 /* Get mx_termcap_cmd or mx_termcap_query constant belonging to (nlen bytes of)
173 * name, -1 if not found. min and max have to be used to cramp the result */
174 static s32 a_termcap_enum_for_name(char const *name, uz nlen,
175 s32 min, s32 max);
176 #define a_termcap_cmd_for_name(NB,NL) \
177 a_termcap_enum_for_name(NB, NL, 0, mx__TERMCAP_CMD_MAX1)
178 #define a_termcap_query_for_name(NB,NL) \
179 a_termcap_enum_for_name(NB, NL, mx__TERMCAP_CMD_MAX1, a_TERMCAP_ENT_MAX1)
181 static void
182 a_termcap_init_var(struct str const *termvar){
183 char *cbp_base, *cbp;
184 uz i;
185 char const *ccp;
186 NYD2_IN;
188 if(termvar->l >= U16_MAX){
189 n_err(_("*termcap*: length excesses internal limit, skipping\n"));
190 goto j_leave;
193 ASSERT(termvar->s[termvar->l] == '\0');
194 i = termvar->l +1;
195 cbp_base = n_autorec_alloc(i);
196 su_mem_copy(cbp = cbp_base, termvar->s, i);
198 for(; (ccp = su_cs_sep_c(&cbp, ',', TRU1)) != NULL;){
199 struct a_termcap_ent *tep;
200 uz kl;
201 char const *v;
202 u16 f;
204 /* Separate key/value, if any */
205 if(/* no empties ccp[0] == '\0' ||*/ ccp[1] == '\0'){
206 jeinvent:
207 n_err(_("*termcap*: invalid entry: %s\n"), ccp);
208 continue;
210 for(kl = 2, v = &ccp[2];; ++kl, ++v){
211 char c = *v;
213 if(c == '\0'){
214 f = mx_TERMCAP_CAPTYPE_BOOL;
215 break;
216 }else if(c == '#'){
217 f = mx_TERMCAP_CAPTYPE_NUMERIC;
218 ++v;
219 break;
220 }else if(c == '='){
221 f = mx_TERMCAP_CAPTYPE_STRING;
222 ++v;
223 break;
227 /* Do we know about this one? */
228 /* C99 */{
229 struct a_termcap_control const *tcp;
230 s32 tci;
232 tci = a_termcap_enum_for_name(ccp, kl, 0, a_TERMCAP_ENT_MAX1);
233 if(tci < 0){
234 /* For key binding purposes, save any given string */
235 #ifdef mx_HAVE_KEY_BINDINGS
236 if((f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_STRING){
237 struct a_termcap_ext_ent *teep;
239 teep = n_alloc(VSTRUCT_SIZEOF(struct a_termcap_ext_ent,
240 tee_name) + kl +1);
241 teep->tee_next = a_termcap_g->tg_ext_ents;
242 a_termcap_g->tg_ext_ents = teep;
243 su_mem_copy(teep->tee_name, ccp, kl);
244 teep->tee_name[kl] = '\0';
246 tep = &teep->tee_super;
247 tep->te_flags = mx_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY;
248 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
249 if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
250 tep->te_flags |= a_TERMCAP_F_DISABLED;
251 goto jlearned;
252 }else
253 #endif /* mx_HAVE_KEY_BINDINGS */
254 if(n_poption & n_PO_D_V)
255 n_err(_("*termcap*: unknown capability: %s\n"), ccp);
256 continue;
258 i = (uz)tci;
260 tcp = &a_termcap_control[i];
261 if((tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != f){
262 n_err(_("*termcap*: entry type mismatch: %s\n"), ccp);
263 break;
265 tep = &a_termcap_g->tg_ents[i];
266 tep->te_flags = tcp->tc_flags;
267 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
270 if((f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_BOOL)
272 else if(*v == '\0')
273 tep->te_flags |= a_TERMCAP_F_DISABLED;
274 else if((f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_NUMERIC){
275 if((su_idec_u16_cp(&tep->te_off, v, 0, NULL
276 ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
277 ) != su_IDEC_STATE_CONSUMED)
278 goto jeinvent;
279 }else if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
280 tep->te_flags |= a_TERMCAP_F_DISABLED;
281 #ifdef mx_HAVE_KEY_BINDINGS
282 jlearned:
283 #endif
284 if(n_poption & n_PO_D_VV)
285 n_err(_("*termcap*: learned %.*s: %s\n"), (int)kl, ccp,
286 (tep->te_flags & a_TERMCAP_F_DISABLED ? "<disabled>"
287 : (f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_BOOL ? "true"
288 : v));
290 su_DBG( if(n_poption & n_PO_D_VV)
291 n_err("*termcap* parsed: buffer used=%lu\n",
292 (ul)a_termcap_g->tg_dat.s_len) );
294 /* Catch some inter-dependencies the user may have triggered */
295 #ifdef mx_HAVE_TERMCAP
296 if(a_termcap_g->tg_ents[mx_TERMCAP_CMD_te].te_flags & a_TERMCAP_F_DISABLED)
297 a_termcap_g->tg_ents[mx_TERMCAP_CMD_ti].te_flags = a_TERMCAP_F_DISABLED;
298 else if(a_termcap_g->tg_ents[mx_TERMCAP_CMD_ti].te_flags &
299 a_TERMCAP_F_DISABLED)
300 a_termcap_g->tg_ents[mx_TERMCAP_CMD_te].te_flags = a_TERMCAP_F_DISABLED;
301 #endif
303 j_leave:
304 NYD2_OU;
307 static boole
308 a_termcap__strexp(struct n_string *store, char const *ibuf){ /* XXX ASCII */
309 char c;
310 char const *oibuf;
311 uz olen;
312 NYD2_IN;
314 olen = store->s_len;
316 for(oibuf = ibuf; (c = *ibuf) != '\0';){
317 if(c == '\\'){
318 if((c = ibuf[1]) == '\0')
319 goto jebsseq;
321 if(c == 'E'){
322 c = '\033';
323 ibuf += 2;
324 goto jpush;
327 if(su_cs_is_digit(c) && c <= '7'){
328 char c2, c3;
330 if((c2 = ibuf[2]) == '\0' || !su_cs_is_digit(c2) || c2 > '7' ||
331 (c3 = ibuf[3]) == '\0' || !su_cs_is_digit(c3) || c3 > '7'){
332 n_err(_("*termcap*: invalid octal sequence: %s\n"), oibuf);
333 goto jerr;
335 c -= '0', c2 -= '0', c3 -= '0';
336 c <<= 3, c |= c2;
337 if((u8)c > 0x1F){
338 n_err(_("*termcap*: octal number too large: %s\n"), oibuf);
339 goto jerr;
341 c <<= 3, c |= c3;
342 ibuf += 4;
343 goto jpush;
345 jebsseq:
346 n_err(_("*termcap*: invalid reverse solidus \\ sequence: %s\n"),
347 oibuf);
348 goto jerr;
349 }else if(c == '^'){
350 if((c = ibuf[1]) == '\0'){
351 n_err(_("*termcap*: incomplete ^CNTRL sequence: %s\n"), oibuf);
352 goto jerr;
354 c = su_cs_to_upper(c) ^ 0x40;
355 if((u8)c > 0x1F && c != 0x7F){ /* ASCII C0: 0..1F, 7F */
356 n_err(_("*termcap*: invalid ^CNTRL sequence: %s\n"), oibuf);
357 goto jerr;
359 ibuf += 2;
360 }else
361 ++ibuf;
363 jpush:
364 store = n_string_push_c(store, c);
367 c = (store->s_len != olen) ? '\1' : '\0';
368 jleave:
369 n_string_push_c(store, '\0');
370 NYD2_OU;
371 return (c != '\0');
372 jerr:
373 store = n_string_trunc(store, olen);
374 c = '\0';
375 goto jleave;
378 static void
379 a_termcap_init_altern(void){
380 /* We silently ignore user _F_DISABLED requests for those entries for which
381 * we have fallback entries, and which we need to ensure proper functioning.
382 * I.e., this allows users to explicitly disable some termcap(5) capability
383 * and enforce usage of the built-in fallback */
384 /* xxx Use table-based approach for fallback strategies */
385 #define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
386 #define a_OOK(TEP) \
387 ((TEP)->te_flags != 0 && !((TEP)->te_flags & a_TERMCAP_F_NOENT))
388 #define a_SET(TEP,CMD,ALT) \
389 (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
390 ((ALT) ? a_TERMCAP_F_ALTERN : 0)
392 struct a_termcap_ent *tep;
393 NYD2_IN;
394 UNUSED(tep);
396 /* For simplicity in the rest of this file null flags of disabled commands,
397 * as we won't check and try to lazy query any command */
398 /* C99 */{
399 uz i;
401 for(i = mx__TERMCAP_CMD_MAX1;;){
402 if(i-- == 0)
403 break;
404 if((tep = &a_termcap_g->tg_ents[i])->te_flags & a_TERMCAP_F_DISABLED)
405 tep->te_flags = 0;
409 #ifdef mx_HAVE_MLE
410 /* ce == ch + [:SPACE:] (start column specified by argument) */
411 tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_ce];
412 if(!a_OOK(tep))
413 a_SET(tep, mx_TERMCAP_CMD_ce, TRU1);
415 /* ch == cr[\r] + nd[:\033C:] */
416 tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_ch];
417 if(!a_OOK(tep))
418 a_SET(tep, mx_TERMCAP_CMD_ch, TRU1);
420 /* cr == \r */
421 tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_cr];
422 if(!a_OOK(tep)){
423 a_SET(tep, mx_TERMCAP_CMD_cr, FAL0);
424 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
425 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\r'), '\0');
428 /* le == \b */
429 tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_le];
430 if(!a_OOK(tep)){
431 a_SET(tep, mx_TERMCAP_CMD_le, FAL0);
432 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
433 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\b'), '\0');
436 /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
437 tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_nd];
438 if(!a_OOK(tep)){
439 a_SET(tep, mx_TERMCAP_CMD_nd, FAL0);
440 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
441 n_string_push_buf(&a_termcap_g->tg_dat, "\033[C", sizeof("\033[C"));
444 # ifdef mx_HAVE_TERMCAP
445 /* cl == ho+cd */
446 tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_cl];
447 if(!a_OOK(tep)){
448 if(a_OK(mx_TERMCAP_CMD_cd) && a_OK(mx_TERMCAP_CMD_ho))
449 a_SET(tep, mx_TERMCAP_CMD_cl, TRU1);
451 # endif
452 #endif /* mx_HAVE_MLE */
454 NYD2_OU;
455 #undef a_OK
456 #undef a_OOK
457 #undef a_SET
460 #ifdef mx_HAVE_TERMCAP
461 # ifdef mx_HAVE_TERMINFO
462 static boole
463 a_termcap_load(char const *term){
464 boole rv;
465 int err;
466 NYD2_IN;
468 if(!(rv = (setupterm(term, fileno(mx_tty_fp), &err) == OK)))
469 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
470 NYD2_OU;
471 return rv;
474 static boole
475 a_termcap_ent_query(struct a_termcap_ent *tep,
476 char const *cname, u16 cflags){
477 boole rv;
478 NYD2_IN;
479 ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));
481 if(UNLIKELY(*cname == '\0'))
482 rv = FAL0;
483 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
484 case mx_TERMCAP_CAPTYPE_BOOL:
485 if(!(rv = (tigetflag(cname) > 0)))
486 tep->te_flags |= a_TERMCAP_F_NOENT;
487 tep->te_off = rv;
488 break;
489 case mx_TERMCAP_CAPTYPE_NUMERIC:{
490 int r = tigetnum(cname);
492 if((rv = (r >= 0)))
493 tep->te_off = (u16)MIN(U16_MAX, r);
494 else
495 tep->te_flags |= a_TERMCAP_F_NOENT;
496 }break;
497 default:
498 case mx_TERMCAP_CAPTYPE_STRING:{
499 char *cp;
501 cp = tigetstr(cname);
502 if((rv = (cp != NULL && cp != (char*)-1))){
503 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
504 n_string_push_buf(&a_termcap_g->tg_dat, cp, su_cs_len(cp) +1);
505 }else
506 tep->te_flags |= a_TERMCAP_F_NOENT;
507 }break;
509 NYD2_OU;
510 return rv;
513 su_SINLINE boole
514 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
515 struct a_termcap_control const *tcp){
516 ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));
517 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off] + 2,
518 tcp->tc_flags);
521 # else /* mx_HAVE_TERMINFO */
522 static boole
523 a_termcap_load(char const *term){
524 boole rv;
525 NYD2_IN;
527 /* ncurses may return -1 */
528 # ifndef mx_HAVE_TGETENT_NULL_BUF
529 # define a_BUF &a_termcap_g->tg_lib_buf[0]
530 # else
531 # define a_BUF NULL
532 # endif
533 if(!(rv = tgetent(a_BUF, term) > 0))
534 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
535 # undef a_BUF
536 NYD2_OU;
537 return rv;
540 static boole
541 a_termcap_ent_query(struct a_termcap_ent *tep,
542 char const *cname, u16 cflags){
543 boole rv;
544 NYD2_IN;
545 ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));
547 if(UNLIKELY(*cname == '\0'))
548 rv = FAL0;
549 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
550 case mx_TERMCAP_CAPTYPE_BOOL:
551 if(!(rv = (tgetflag(cname) > 0)))
552 tep->te_flags |= a_TERMCAP_F_NOENT;
553 tep->te_off = rv;
554 break;
555 case mx_TERMCAP_CAPTYPE_NUMERIC:{
556 int r = tgetnum(cname);
558 if((rv = (r >= 0)))
559 tep->te_off = (u16)MIN(U16_MAX, r);
560 else
561 tep->te_flags |= a_TERMCAP_F_NOENT;
562 }break;
563 default:
564 case mx_TERMCAP_CAPTYPE_STRING:{
565 # ifndef mx_HAVE_TGETENT_NULL_BUF
566 char buf_base[a_TERMCAP_ENTRYSIZE_MAX], *buf = &buf_base[0];
567 # define a_BUF &buf
568 # else
569 # define a_BUF NULL
570 # endif
571 char *cp;
573 if((rv = ((cp = tgetstr(cname, a_BUF)) != NULL))){
574 tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
575 n_string_push_buf(&a_termcap_g->tg_dat, cp, su_cs_len(cp) +1);
576 # undef a_BUF
577 }else
578 tep->te_flags |= a_TERMCAP_F_NOENT;
579 }break;
581 NYD2_OU;
582 return rv;
585 su_SINLINE boole
586 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
587 struct a_termcap_control const *tcp){
588 ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));
589 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off],
590 tcp->tc_flags);
592 # endif /* !mx_HAVE_TERMINFO */
594 static int
595 a_termcap_putc(int c){
596 return putc(c, mx_tty_fp);
598 #endif /* mx_HAVE_TERMCAP */
600 static s32
601 a_termcap_enum_for_name(char const *name, uz nlen, s32 min, s32 max){
602 struct a_termcap_control const *tcp;
603 char const *cnam;
604 s32 rv;
605 NYD2_IN;
607 /* Prefer terminfo(5) names */
608 for(rv = max;;){
609 if(rv-- == min){
610 rv = -1;
611 break;
614 tcp = &a_termcap_control[(u32)rv];
615 cnam = &a_termcap_namedat[tcp->tc_off];
616 if(cnam[2] != '\0'){
617 char const *xcp = cnam + 2;
619 if(nlen == su_cs_len(xcp) && !su_mem_cmp(xcp, name, nlen))
620 break;
622 if(nlen == 2 && cnam[0] == name[0] && cnam[1] == name[1])
623 break;
625 NYD2_OU;
626 return rv;
629 void
630 mx_termcap_init(void){
631 struct mx_termcap_value tv;
632 struct str termvar;
633 char const *ccp;
634 NYD_IN;
636 ASSERT(n_psonce & n_PSO_TTYANY);
638 a_termcap_g = n_alloc(sizeof *a_termcap_g);
639 a_termcap_g->tg_ext_ents = NULL;
640 su_mem_set(&a_termcap_g->tg_ents[0], 0, sizeof(a_termcap_g->tg_ents));
641 if((ccp = ok_vlook(termcap)) != NULL)
642 termvar.l = su_cs_len(termvar.s = n_UNCONST(ccp));
643 else
644 /*termvar.s = NULL,*/ termvar.l = 0;
645 n_string_reserve(n_string_creat(&a_termcap_g->tg_dat),
646 ((termvar.l + (256 - 64)) & ~127));
648 if(termvar.l > 0)
649 a_termcap_init_var(&termvar);
651 if(ok_blook(termcap_disable))
652 n_psonce |= n_PSO_TERMCAP_DISABLE;
653 #ifdef mx_HAVE_TERMCAP
654 else if((ccp = ok_vlook(TERM)) == NULL){
655 n_err(_("Environment variable $TERM not set, using only *termcap*\n"));
656 n_psonce |= n_PSO_TERMCAP_DISABLE;
657 }else if(!a_termcap_load(ccp))
658 n_psonce |= n_PSO_TERMCAP_DISABLE;
659 else{
660 /* Query termcap(5) for each command slot that is not yet set */
661 struct a_termcap_ent *tep;
662 uz i;
664 for(i = mx__TERMCAP_CMD_MAX1;;){
665 if(i-- == 0)
666 break;
667 if((tep = &a_termcap_g->tg_ents[i])->te_flags == 0)
668 a_termcap_ent_query_tcp(tep, &a_termcap_control[i]);
671 #endif /* mx_HAVE_TERMCAP */
673 a_termcap_init_altern();
675 #ifdef mx_HAVE_TERMCAP
676 if(a_termcap_g->tg_ents[mx_TERMCAP_CMD_te].te_flags != 0 &&
677 ok_blook(termcap_ca_mode))
678 n_psonce |= n_PSO_TERMCAP_CA_MODE;
679 #endif
681 /* TODO We do not handle !mx_TERMCAP_QUERY_sam in this software! */
683 #ifdef mx_HAVE_TERMCAP
684 !mx_termcap_query(mx_TERMCAP_QUERY_am, &tv) ||
685 #endif
686 mx_termcap_query(mx_TERMCAP_QUERY_xenl, &tv)){
687 n_psonce |= n_PSO_TERMCAP_FULLWIDTH;
689 /* Since termcap was not initialized when we did TERMIOS_SETUP_TERMSIZE
690 * we need/should adjust the found setting to reality (without causing
691 * a synthesized SIGWINCH or something even more expensive that is) */
692 if(mx_termios_dimen.tiosd_width > 0)
693 ++mx_termios_dimen.tiosd_width;
696 mx_TERMCAP_RESUME(TRU1);
697 NYD_OU;
700 void
701 mx_termcap_destroy(void){
702 NYD_IN;
703 ASSERT(a_termcap_g != NULL);
705 mx_TERMCAP_SUSPEND(TRU1);
707 #ifdef mx_HAVE_DEBUG
708 /* C99 */{
709 struct a_termcap_ext_ent *tmp;
711 while((tmp = a_termcap_g->tg_ext_ents) != NULL){
712 a_termcap_g->tg_ext_ents = tmp->tee_next;
713 n_free(tmp);
716 n_string_gut(&a_termcap_g->tg_dat);
717 n_free(a_termcap_g);
718 a_termcap_g = NULL;
719 #endif
720 NYD_OU;
723 #ifdef mx_HAVE_TERMCAP
724 void
725 mx_termcap_resume(boole complete){
726 NYD_IN;
727 if(a_termcap_g != NULL && !(n_psonce & n_PSO_TERMCAP_DISABLE)){
728 if(complete && (n_psonce & n_PSO_TERMCAP_CA_MODE))
729 mx_termcap_cmdx(mx_TERMCAP_CMD_ti);
730 mx_termcap_cmdx(mx_TERMCAP_CMD_ks);
731 fflush(mx_tty_fp);
733 NYD_OU;
736 void
737 mx_termcap_suspend(boole complete){
738 NYD_IN;
739 if(a_termcap_g != NULL && !(n_psonce & n_PSO_TERMCAP_DISABLE)){
740 mx_termcap_cmdx(mx_TERMCAP_CMD_ke);
741 if(complete && (n_psonce & n_PSO_TERMCAP_CA_MODE))
742 mx_termcap_cmdx(mx_TERMCAP_CMD_te);
743 fflush(mx_tty_fp);
745 NYD_OU;
747 #endif /* mx_HAVE_TERMCAP */
750 mx_termcap_cmd(enum mx_termcap_cmd cmd, sz a1, sz a2){
751 /* Commands are not lazy queried */
752 struct a_termcap_ent const *tep;
753 enum a_termcap_flags flags;
754 sz rv;
755 NYD2_IN;
756 UNUSED(a1);
757 UNUSED(a2);
759 rv = FAL0;
760 if(a_termcap_g == NULL)
761 goto jleave;
763 flags = cmd & ~mx__TERMCAP_CMD_MASK;
764 cmd &= mx__TERMCAP_CMD_MASK;
765 tep = a_termcap_g->tg_ents;
767 if((flags & mx_TERMCAP_CMD_FLAG_CA_MODE) &&
768 !(n_psonce & n_PSO_TERMCAP_CA_MODE))
769 rv = TRU1;
770 else if((tep += cmd)->te_flags == 0 || (tep->te_flags & a_TERMCAP_F_NOENT))
771 rv = TRUM1;
772 else if(!(tep->te_flags & a_TERMCAP_F_ALTERN)){
773 char const *cp;
775 ASSERT((tep->te_flags & a_TERMCAP_F_TYPE_MASK) ==
776 mx_TERMCAP_CAPTYPE_STRING);
778 cp = &a_termcap_g->tg_dat.s_dat[tep->te_off];
780 #ifdef mx_HAVE_TERMCAP
781 if(tep->te_flags & (a_TERMCAP_F_ARG_IDX1 | a_TERMCAP_F_ARG_IDX2)){
782 if(n_psonce & n_PSO_TERMCAP_DISABLE){
783 if(n_poption & n_PO_D_V){
784 char const *cnam = &a_termcap_namedat[
785 a_termcap_control[cmd].tc_off];
787 if(cnam[2] != '\0')
788 cnam += 2;
789 n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
790 "can't perform CAP: %s\n"), cnam);
792 goto jleave;
795 /* Follow Thomas Dickey's advise on pre-va_arg prototypes, add 0s */
796 # ifdef mx_HAVE_TERMINFO
797 if((cp = tparm(cp, a1, a2, 0,0,0,0,0,0,0)) == NULL)
798 goto jleave;
799 # else
800 /* curs_termcap.3:
801 * The \fBtgoto\fP function swaps the order of parameters.
802 * It does this also for calls requiring only a single parameter.
803 * In that case, the first parameter is merely a placeholder. */
804 if(!(tep->te_flags & a_TERMCAP_F_ARG_IDX2)){
805 a2 = a1;
806 a1 = (u32)-1;
808 if((cp = tgoto(cp, (int)a1, (int)a2)) == NULL)
809 goto jleave;
810 # endif
812 #endif /* mx_HAVE_TERMCAP */
814 for(;;){
815 #ifdef mx_HAVE_TERMCAP
816 if(!(n_psonce & n_PSO_TERMCAP_DISABLE)){
817 if(tputs(cp, 1, &a_termcap_putc) != OK)
818 break;
819 }else
820 #endif
821 if(fputs(cp, mx_tty_fp) == EOF)
822 break;
823 if(!(tep->te_flags & a_TERMCAP_F_ARG_CNT) || --a1 <= 0){
824 rv = TRU1;
825 break;
828 goto jflush;
829 }else{
830 switch(cmd){
831 default:
832 rv = TRUM1;
833 break;
835 #ifdef mx_HAVE_MLE
836 case mx_TERMCAP_CMD_ce: /* ce == ch + [:SPACE:] */
837 if(a1 > 0)
838 --a1;
839 if((rv = mx_termcap_cmd(mx_TERMCAP_CMD_ch, a1, 0)) > 0){
840 for(a2 = mx_termios_dimen.tiosd_width - a1; a2 > 0; --a2)
841 if(putc(' ', mx_tty_fp) == EOF){
842 rv = FAL0;
843 break;
845 if(rv && mx_termcap_cmd(mx_TERMCAP_CMD_ch, a1, -1) != TRU1)
846 rv = FAL0;
848 break;
849 case mx_TERMCAP_CMD_ch: /* ch == cr + nd */
850 rv = mx_termcap_cmdx(mx_TERMCAP_CMD_cr);
851 if(rv > 0 && a1 > 0){
852 rv = mx_termcap_cmd(mx_TERMCAP_CMD_nd, a1, -1);
854 break;
855 # ifdef mx_HAVE_TERMCAP
856 case mx_TERMCAP_CMD_cl: /* cl = ho + cd */
857 rv = mx_termcap_cmdx(mx_TERMCAP_CMD_ho);
858 if(rv > 0)
859 rv = mx_termcap_cmdx(mx_TERMCAP_CMD_cd | flags);
860 break;
861 # endif
862 #endif /* mx_HAVE_MLE */
865 jflush:
866 if(flags & mx_TERMCAP_CMD_FLAG_FLUSH)
867 fflush(mx_tty_fp);
868 if(ferror(mx_tty_fp))
869 rv = FAL0;
872 jleave:
873 NYD2_OU;
874 return rv;
877 boole
878 mx_termcap_query(enum mx_termcap_query query, struct mx_termcap_value *tvp){
879 /* Queries are lazy queried upon request */
880 /* XXX mx_termcap_query(): boole handling suboptimal, tvp used on success */
881 struct a_termcap_ent const *tep;
882 boole rv;
883 NYD2_IN;
885 ASSERT(tvp != NULL);
887 rv = FAL0;
888 if(a_termcap_g == NULL)
889 goto jleave;
891 /* Is it a built-in query? */
892 if(query != mx__TERMCAP_QUERY_MAX1){
893 tep = &a_termcap_g->tg_ents[mx__TERMCAP_CMD_MAX1 + query];
895 if(tep->te_flags == 0
896 #ifdef mx_HAVE_TERMCAP
897 && ((n_psonce & n_PSO_TERMCAP_DISABLE) ||
898 !a_termcap_ent_query_tcp(n_UNCONST(tep),
899 &a_termcap_control[mx__TERMCAP_CMD_MAX1 + query]))
900 #endif
902 goto jleave;
903 }else{
904 #ifdef mx_HAVE_TERMCAP
905 uz nlen;
906 #endif
907 struct a_termcap_ext_ent *teep;
908 char const *ndat = tvp->tv_data.tvd_string;
910 for(teep = a_termcap_g->tg_ext_ents; teep != NULL; teep = teep->tee_next)
911 if(!su_cs_cmp(teep->tee_name, ndat)){
912 tep = &teep->tee_super;
913 goto jextok;
916 #ifdef mx_HAVE_TERMCAP
917 if(n_psonce & n_PSO_TERMCAP_DISABLE)
918 #endif
919 goto jleave;
920 #ifdef mx_HAVE_TERMCAP
921 nlen = su_cs_len(ndat) +1;
922 teep = n_alloc(VSTRUCT_SIZEOF(struct a_termcap_ext_ent, tee_name) +
923 nlen);
924 tep = &teep->tee_super;
925 teep->tee_next = a_termcap_g->tg_ext_ents;
926 a_termcap_g->tg_ext_ents = teep;
927 su_mem_copy(teep->tee_name, ndat, nlen);
929 if(!a_termcap_ent_query(n_UNCONST(tep), ndat,
930 mx_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY))
931 goto jleave;
932 #endif
933 jextok:;
936 if(tep->te_flags & a_TERMCAP_F_NOENT)
937 goto jleave;
939 rv = (tep->te_flags & a_TERMCAP_F_ALTERN) ? TRUM1 : TRU1;
941 switch((tvp->tv_captype = tep->te_flags & a_TERMCAP_F_TYPE_MASK)){
942 case mx_TERMCAP_CAPTYPE_BOOL:
943 tvp->tv_data.tvd_bool = (boole)tep->te_off;
944 break;
945 case mx_TERMCAP_CAPTYPE_NUMERIC:
946 tvp->tv_data.tvd_numeric = (u32)tep->te_off;
947 break;
948 default:
949 case mx_TERMCAP_CAPTYPE_STRING:
950 tvp->tv_data.tvd_string = a_termcap_g->tg_dat.s_dat + tep->te_off;
951 break;
953 jleave:
954 NYD2_OU;
955 return rv;
958 #ifdef mx_HAVE_KEY_BINDINGS
960 mx_termcap_query_for_name(char const *name, enum mx_termcap_captype type){
961 s32 rv;
962 NYD2_IN;
964 if((rv = a_termcap_query_for_name(name, su_cs_len(name))) >= 0){
965 struct a_termcap_control const *tcp = &a_termcap_control[(u32)rv];
967 if(type != mx_TERMCAP_CAPTYPE_NONE &&
968 (tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != type)
969 rv = -2;
970 else
971 rv -= mx__TERMCAP_CMD_MAX1;
973 NYD2_OU;
974 return rv;
977 char const *
978 mx_termcap_name_of_query(enum mx_termcap_query query){
979 char const *rv;
980 NYD2_IN;
982 rv = &a_termcap_namedat[
983 a_termcap_control[mx__TERMCAP_CMD_MAX1 + query].tc_off + 2];
984 NYD2_OU;
985 return rv;
987 #endif /* mx_HAVE_KEY_BINDINGS */
989 #include "su/code-ou.h"
990 #endif /* mx_HAVE_TCAP */
991 /* s-it-mode */