Boing.Boom.Tschak. More fixes in line with [0ef07198,ee4de6e4]
[s-mailx.git] / termcap.c
blob14c3ad306d9b75b80009d413a6919ad1c05259f2
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Terminal capability interaction.
3 *@ For encapsulation purposes provide a basic foundation even without
4 *@ HAVE_TERMCAP, but with nail.h:n_HAVE_TCAP.
5 *@ HOWTO add a new non-dynamic command or query:
6 *@ - add an entry to nail.h:enum n_termcap_{cmd,query}
7 *@ - run mk-tcap-map.pl
8 *@ - update the *termcap* member documentation on changes!
9 *@ Bug: in case of clashes of two-letter names terminfo(5) wins.
11 * Copyright (c) 2016 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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 n_FILE
26 #define n_FILE termcap
28 #ifndef HAVE_AMALGAMATION
29 # include "nail.h"
30 #endif
32 EMPTY_FILE()
33 #ifdef n_HAVE_TCAP
34 /* If available, curses.h must be included before term.h! */
35 #ifdef HAVE_TERMCAP
36 # ifdef HAVE_TERMCAP_CURSES
37 # include <curses.h>
38 # endif
39 # include <term.h>
40 #endif
43 * xxx We are not really compatible with very old and strange terminals since
44 * we don't care at all for circumstances indicated by terminal flags: if we
45 * find a capability we use it and assume it works. E.g., if "Co" indicates
46 * colours we simply use ISO 6429 also for font attributes etc. That is,
47 * we don't use the ncurses/terminfo interface with all its internal logic.
50 /* Unless HAVE_TERMINFO or HAVE_TGETENT_NULL_BUF are defined we will use this
51 * to space the buffer we pass through to tgetent(3).
52 * Since for (such) elder non-emulated terminals really weird things will
53 * happen if an entry would require more than 1024 bytes, don't really mind.
54 * Use a ui16_t for storage */
55 #define a_TERMCAP_ENTRYSIZE_MAX ((2668 + 64) & ~64) /* As of ncurses 6.0 */
57 n_CTA(a_TERMCAP_ENTRYSIZE_MAX < UI16_MAX,
58 "Chosen buffer size exceeds datatype capability");
60 /* For simplicity we store commands and queries in single continuous control
61 * and entry structure arrays: to index queries one has to add
62 * n__TERMCAP_CMD_MAX1 first! And don't confound with ENTRYSIZE_MAX! */
63 enum{
64 a_TERMCAP_ENT_MAX1 = n__TERMCAP_CMD_MAX1 + n__TERMCAP_QUERY_MAX1
67 enum a_termcap_flags{
68 a_TERMCAP_F_NONE,
69 /* enum n_termcap_captype values stored here.
70 * Note that presence of a type in an a_termcap_ent signals initialization */
71 a_TERMCAP_F_TYPE_MASK = (1<<4) - 1,
73 a_TERMCAP_F_QUERY = 1<<4, /* A query rather than a command */
74 a_TERMCAP_F_DISABLED = 1<<5, /* User explicitly disabled command/query */
75 a_TERMCAP_F_ALTERN = 1<<6, /* Not available, but has alternative */
76 a_TERMCAP_F_NOENT = 1<<7, /* Not available */
78 /* _cmd() argument interpretion (_T_STR) */
79 a_TERMCAP_F_ARG_IDX1 = 1<<11, /* Argument 1 used, and is an index */
80 a_TERMCAP_F_ARG_IDX2 = 1<<12,
81 a_TERMCAP_F_ARG_CNT = 1<<13, /* .., and is a count */
83 a_TERMCAP_F__LAST = a_TERMCAP_F_ARG_CNT
85 n_CTA((ui32_t)n__TERMCAP_CAPTYPE_MAX1 <= (ui32_t)a_TERMCAP_F_TYPE_MASK,
86 "enum n_termcap_captype exceeds bit range of a_termcap_flags");
88 struct a_termcap_control{
89 ui16_t tc_flags;
90 /* Offset base into a_termcap_namedat[], which stores the two-letter
91 * termcap(5) name directly followed by a NUL terminated terminfo(5) name.
92 * A termcap(5) name may consist of two NULs meaning ENOENT,
93 * a terminfo(5) name may be empty for the same purpose */
94 ui16_t tc_off;
96 n_CTA(a_TERMCAP_F__LAST <= UI16_MAX,
97 "a_termcap_flags exceed storage datatype in a_termcap_control");
99 struct a_termcap_ent{
100 ui16_t te_flags;
101 ui16_t te_off; /* in a_termcap_g->tg_dat / value for T_BOOL and T_NUM */
103 n_CTA(a_TERMCAP_F__LAST <= UI16_MAX,
104 "a_termcap_flags exceed storage datatype in a_termcap_ent");
106 /* Structure for extended queries, which don't have an entry constant in
107 * n_termcap_query (to allow free query/binding of keycodes) */
108 struct a_termcap_ext_ent{
109 struct a_termcap_ent tee_super;
110 ui8_t tee__dummy[4];
111 struct a_termcap_ext_ent *tee_next;
112 /* Resolvable termcap(5)/terminfo(5) name as given by user; the actual data
113 * is stored just like for normal queries */
114 char tee_name[n_VFIELD_SIZE(0)];
117 struct a_termcap_g{
118 struct a_termcap_ext_ent *tg_ext_ents; /* List of extended queries */
119 struct a_termcap_ent tg_ents[a_TERMCAP_ENT_MAX1];
120 struct n_string tg_dat; /* Storage for resolved caps */
121 # if !defined HAVE_TGETENT_NULL_BUF && !defined HAVE_TERMINFO
122 char tg_lib_buf[a_TERMCAP_ENTRYSIZE_MAX];
123 # endif
126 /* Include the constant mk-tcap-map.pl output */
127 #include "tcaps.h"
128 n_CTA(sizeof a_termcap_namedat <= UI16_MAX,
129 "Termcap command and query name data exceed storage datatype");
130 n_CTA(a_TERMCAP_ENT_MAX1 == n_NELEM(a_termcap_control),
131 "Control array doesn't match command/query array to be controlled");
133 static struct a_termcap_g *a_termcap_g;
135 /* Query *termcap*, parse it and incorporate into a_termcap_g */
136 static void a_termcap_init_var(struct str const *termvar);
138 /* Expand ^CNTRL, \[Ee] and \OCT. False for parse error and empty results */
139 static bool_t a_termcap__strexp(struct n_string *store, char const *ibuf);
141 /* Initialize any _ent for which we have _F_ALTERN and which isn't yet set */
142 static void a_termcap_init_altern(void);
144 #ifdef HAVE_TERMCAP
145 /* Setup the library we use to work with term */
146 static bool_t a_termcap_load(char const *term);
148 /* Query the capability tcp and fill in tep (upon success) */
149 static bool_t a_termcap_ent_query(struct a_termcap_ent *tep,
150 char const *cname, ui16_t cflags);
151 SINLINE bool_t a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
152 struct a_termcap_control const *tcp);
154 /* Output PTF for both, termcap(5) and terminfo(5) */
155 static int a_termcap_putc(int c);
156 #endif
158 /* Get n_termcap_cmd or n_termcap_query constant belonging to (nlen bytes of)
159 * name, or -1 if not found. min and max have to be used to cramp the result */
160 static si32_t a_termcap_enum_for_name(char const *name, size_t nlen,
161 si32_t min, si32_t max);
162 #define a_termcap_cmd_for_name(NB,NL) \
163 a_termcap_enum_for_name(NB, NL, 0, n__TERMCAP_CMD_MAX1)
164 #define a_termcap_query_for_name(NB,NL) \
165 a_termcap_enum_for_name(NB, NL, n__TERMCAP_CMD_MAX1, a_TERMCAP_ENT_MAX1)
167 static void
168 a_termcap_init_var(struct str const *termvar){
169 char *cbp_base, *cbp;
170 size_t i;
171 char const *ccp;
172 NYD2_ENTER;
174 if(termvar->l >= UI16_MAX){
175 n_err(_("*termcap*: length excesses internal limit, skipping\n"));
176 goto j_leave;
179 assert(termvar->s[termvar->l] == '\0');
180 i = termvar->l +1;
181 cbp_base = salloc(i);
182 memcpy(cbp = cbp_base, termvar->s, i);
184 for(; (ccp = n_strsep(&cbp, ',', TRU1)) != NULL;){
185 struct a_termcap_ent *tep;
186 size_t kl;
187 char const *v;
188 ui16_t f;
190 /* Separate key/value, if any */
191 if(/* no empties ccp[0] == '\0' ||*/ ccp[1] == '\0'){
192 jeinvent:
193 n_err(_("*termcap*: invalid entry: %s\n"), ccp);
194 continue;
196 for(kl = 2, v = &ccp[2];; ++kl, ++v){
197 char c = *v;
199 if(c == '\0'){
200 f = n_TERMCAP_CAPTYPE_BOOL;
201 break;
202 }else if(c == '#'){
203 f = n_TERMCAP_CAPTYPE_NUMERIC;
204 ++v;
205 break;
206 }else if(c == '='){
207 f = n_TERMCAP_CAPTYPE_STRING;
208 ++v;
209 break;
213 /* Do we know about this one? */
214 /* C99 */{
215 struct a_termcap_control const *tcp;
216 si32_t tci;
218 tci = a_termcap_enum_for_name(ccp, kl, 0, a_TERMCAP_ENT_MAX1);
219 if(tci < 0){
220 /* For key binding purposes, save any given string */
221 #ifdef HAVE_KEY_BINDINGS
222 if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_STRING){
223 struct a_termcap_ext_ent *teep;
225 teep = smalloc(n_VSTRUCT_SIZEOF(struct a_termcap_ext_ent,
226 tee_name) + kl +1);
227 teep->tee_next = a_termcap_g->tg_ext_ents;
228 a_termcap_g->tg_ext_ents = teep;
229 memcpy(teep->tee_name, ccp, kl);
230 teep->tee_name[kl] = '\0';
232 tep = &teep->tee_super;
233 tep->te_flags = n_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY;
234 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
235 if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
236 tep->te_flags |= a_TERMCAP_F_DISABLED;
237 goto jlearned;
238 }else
239 #endif /* HAVE_KEY_BINDINGS */
240 if(options & OPT_D_V)
241 n_err(_("*termcap*: unknown capability: %s\n"), ccp);
242 continue;
244 i = (size_t)tci;
246 tcp = &a_termcap_control[i];
247 if((tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != f){
248 n_err(_("*termcap*: entry type mismatch: %s\n"), ccp);
249 break;
251 tep = &a_termcap_g->tg_ents[i];
252 tep->te_flags = tcp->tc_flags;
253 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
256 if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_BOOL)
258 else if(*v == '\0')
259 tep->te_flags |= a_TERMCAP_F_DISABLED;
260 else if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_NUMERIC){
261 char *eptr;
262 long l = strtol(v, &eptr, 10);
264 if(*eptr != '\0' || l < 0 || UICMP(32, l, >=, UI16_MAX))
265 goto jeinvent;
266 tep->te_off = (ui16_t)l;
267 }else if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
268 tep->te_flags |= a_TERMCAP_F_DISABLED;
269 #ifdef HAVE_KEY_BINDINGS
270 jlearned:
271 #endif
272 if(options & OPT_D_VV)
273 n_err(_("*termcap*: learned %.*s: %s\n"), (int)kl, ccp,
274 (tep->te_flags & a_TERMCAP_F_DISABLED ? "<disabled>"
275 : (f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_BOOL ? "true"
276 : v));
278 DBG( if(options & OPT_D_V) n_err("*termcap* parsed: buffer used=%lu\n",
279 (ul_i)a_termcap_g->tg_dat.s_len) );
281 /* Catch some inter-dependencies the user may have triggered */
282 #ifdef HAVE_TERMCAP
283 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags & a_TERMCAP_F_DISABLED)
284 a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags = a_TERMCAP_F_DISABLED;
285 else if(a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags &
286 a_TERMCAP_F_DISABLED)
287 a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags = a_TERMCAP_F_DISABLED;
288 #endif
290 j_leave:
291 NYD2_LEAVE;
294 static bool_t
295 a_termcap__strexp(struct n_string *store, char const *ibuf){ /* XXX ASCII */
296 char c;
297 char const *oibuf;
298 size_t olen;
299 NYD2_ENTER;
301 olen = store->s_len;
303 for(oibuf = ibuf; (c = *ibuf) != '\0';){
304 if(c == '\\'){
305 if((c = ibuf[1]) == '\0')
306 goto jebsseq;
308 if(c == 'E'){
309 c = '\033';
310 ibuf += 2;
311 goto jpush;
314 if(octalchar(c)){
315 char c2, c3;
317 if((c2 = ibuf[2]) == '\0' || !octalchar(c2) ||
318 (c3 = ibuf[3]) == '\0' || !octalchar(c3)){
319 n_err(_("*termcap*: invalid octal sequence: %s\n"), oibuf);
320 goto jerr;
322 c -= '0', c2 -= '0', c3 -= '0';
323 c <<= 3, c |= c2;
324 if((ui8_t)c > 0x1F){
325 n_err(_("*termcap*: octal number too large: %s\n"), oibuf);
326 goto jerr;
328 c <<= 3, c |= c3;
329 ibuf += 4;
330 goto jpush;
332 jebsseq:
333 n_err(_("*termcap*: invalid reverse solidus \\ sequence: %s\n"),oibuf);
334 goto jerr;
335 }else if(c == '^'){
336 if((c = ibuf[1]) == '\0'){
337 n_err(_("*termcap*: incomplete ^CNTRL sequence: %s\n"), oibuf);
338 goto jerr;
340 c = upperconv(c) ^ 0x40;
341 if((ui8_t)c > 0x1F && c != 0x7F){ /* ASCII C0: 0..1F, 7F */
342 n_err(_("*termcap*: invalid ^CNTRL sequence: %s\n"), oibuf);
343 goto jerr;
345 ibuf += 2;
346 }else
347 ++ibuf;
349 jpush:
350 store = n_string_push_c(store, c);
353 c = (store->s_len != olen) ? '\1' : '\0';
354 jleave:
355 n_string_push_c(store, '\0');
356 NYD2_LEAVE;
357 return (c != '\0');
358 jerr:
359 store = n_string_trunc(store, olen);
360 c = '\0';
361 goto jleave;
364 static void
365 a_termcap_init_altern(void){
366 /* We silently ignore user _F_DISABLED requests for those entries for which
367 * we have fallback entries, and which we need to ensure proper functioning.
368 * I.e., this allows users to explicitly disable some termcap(5) capability
369 * and enforce usage of the builtin fallback */
370 /* xxx Use table-based approach for fallback strategies */
371 #define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
372 #define a_OOK(TEP) \
373 ((TEP)->te_flags != 0 && !((TEP)->te_flags & a_TERMCAP_F_NOENT))
374 #define a_SET(TEP,CMD,ALT) \
375 (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
376 ((ALT) ? a_TERMCAP_F_ALTERN : 0)
378 struct a_termcap_ent *tep;
379 NYD2_ENTER;
380 n_UNUSED(tep);
382 /* For simplicity in the rest of this file null flags of disabled commands,
383 * as we won't check and try to lazy query any command */
384 /* C99 */{
385 size_t i;
387 for(i = n__TERMCAP_CMD_MAX1;;){
388 if(i-- == 0)
389 break;
390 if((tep = &a_termcap_g->tg_ents[i])->te_flags & a_TERMCAP_F_DISABLED)
391 tep->te_flags = 0;
395 #ifdef HAVE_TERMCAP
396 /* cl == ho+cd */
397 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cl];
398 if(!a_OOK(tep)){
399 if(a_OK(n_TERMCAP_CMD_cd) && a_OK(n_TERMCAP_CMD_ho))
400 a_SET(tep, n_TERMCAP_CMD_cl, TRU1);
402 #endif
404 #ifdef HAVE_MLE
405 /* ce == ch + [:SPC:] (start column specified by argument) */
406 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ce];
407 if(!a_OOK(tep))
408 a_SET(tep, n_TERMCAP_CMD_ce, TRU1);
410 /* ch == cr[\r] + nd[:\033C:] */
411 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ch];
412 if(!a_OOK(tep))
413 a_SET(tep, n_TERMCAP_CMD_ch, TRU1);
415 /* cr == \r */
416 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cr];
417 if(!a_OOK(tep)){
418 a_SET(tep, n_TERMCAP_CMD_cr, FAL0);
419 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
420 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\r'), '\0');
423 /* le == \b */
424 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_le];
425 if(!a_OOK(tep)){
426 a_SET(tep, n_TERMCAP_CMD_le, FAL0);
427 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
428 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\b'), '\0');
431 /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
432 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_nd];
433 if(!a_OOK(tep)){
434 a_SET(tep, n_TERMCAP_CMD_nd, FAL0);
435 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
436 n_string_push_buf(&a_termcap_g->tg_dat, "\033[C", sizeof("\033[C"));
438 #endif /* HAVE_MLE */
440 NYD2_LEAVE;
441 #undef a_OK
442 #undef a_OOK
443 #undef a_SET
446 #ifdef HAVE_TERMCAP
447 # ifdef HAVE_TERMINFO
448 static bool_t
449 a_termcap_load(char const *term){
450 bool_t rv;
451 int err;
452 NYD2_ENTER;
454 if(!(rv = (setupterm(term, fileno(n_tty_fp), &err) == OK)))
455 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
456 NYD2_LEAVE;
457 return rv;
460 static bool_t
461 a_termcap_ent_query(struct a_termcap_ent *tep,
462 char const *cname, ui16_t cflags){
463 bool_t rv;
464 NYD2_ENTER;
465 assert(!(pstate & PS_TERMCAP_DISABLE));
467 if(n_UNLIKELY(*cname == '\0'))
468 rv = FAL0;
469 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
470 case n_TERMCAP_CAPTYPE_BOOL:
471 tep->te_off = (tigetflag(cname) > 0);
472 rv = TRU1;
473 break;
474 case n_TERMCAP_CAPTYPE_NUMERIC:{
475 int r = tigetnum(cname);
477 if((rv = (r >= 0)))
478 tep->te_off = (ui16_t)n_MIN(UI16_MAX, r);
479 else
480 tep->te_flags |= a_TERMCAP_F_NOENT;
481 } break;
482 default:
483 case n_TERMCAP_CAPTYPE_STRING:{
484 char *cp;
486 cp = tigetstr(cname);
487 if((rv = (cp != NULL && cp != (char*)-1))){
488 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
489 n_string_push_buf(&a_termcap_g->tg_dat, cp, strlen(cp) +1);
490 }else
491 tep->te_flags |= a_TERMCAP_F_NOENT;
492 } break;
494 NYD2_LEAVE;
495 return rv;
498 SINLINE bool_t
499 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
500 struct a_termcap_control const *tcp){
501 assert(!(pstate & PS_TERMCAP_DISABLE));
502 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off] + 2,
503 tcp->tc_flags);
506 # else /* HAVE_TERMINFO */
507 static bool_t
508 a_termcap_load(char const *term){
509 bool_t rv;
510 NYD2_ENTER;
512 /* ncurses may return -1 */
513 # ifndef HAVE_TGETENT_NULL_BUF
514 # define a_BUF &a_termcap_g->tg_lib_buf[0]
515 # else
516 # define a_BUF NULL
517 # endif
518 if(!(rv = tgetent(a_BUF, term) > 0))
519 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
520 # undef a_BUF
521 NYD2_LEAVE;
522 return rv;
525 static bool_t
526 a_termcap_ent_query(struct a_termcap_ent *tep,
527 char const *cname, ui16_t cflags){
528 bool_t rv;
529 NYD2_ENTER;
530 assert(!(pstate & PS_TERMCAP_DISABLE));
532 if(n_UNLIKELY(*cname == '\0'))
533 rv = FAL0;
534 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
535 case n_TERMCAP_CAPTYPE_BOOL:
536 tep->te_off = (tgetflag(cname) > 0);
537 rv = TRU1;
538 break;
539 case n_TERMCAP_CAPTYPE_NUMERIC:{
540 int r = tgetnum(cname);
542 if((rv = (r >= 0)))
543 tep->te_off = (ui16_t)n_MIN(UI16_MAX, r);
544 else
545 tep->te_flags |= a_TERMCAP_F_NOENT;
546 } break;
547 default:
548 case n_TERMCAP_CAPTYPE_STRING:{
549 # ifndef HAVE_TGETENT_NULL_BUF
550 char buf_base[a_TERMCAP_ENTRYSIZE_MAX], *buf = &buf_base[0];
551 # define a_BUF &buf
552 # else
553 # define a_BUF NULL
554 # endif
555 char *cp;
557 if((rv = ((cp = tgetstr(cname, a_BUF)) != NULL))){
558 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
559 n_string_push_buf(&a_termcap_g->tg_dat, cp, strlen(cp) +1);
560 # undef a_BUF
561 }else
562 tep->te_flags |= a_TERMCAP_F_NOENT;
563 } break;
565 NYD2_LEAVE;
566 return rv;
569 SINLINE bool_t
570 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
571 struct a_termcap_control const *tcp){
572 assert(!(pstate & PS_TERMCAP_DISABLE));
573 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off],
574 tcp->tc_flags);
576 # endif /* !HAVE_TERMINFO */
578 static int
579 a_termcap_putc(int c){
580 return putc(c, n_tty_fp);
582 #endif /* HAVE_TERMCAP */
584 static si32_t
585 a_termcap_enum_for_name(char const *name, size_t nlen, si32_t min, si32_t max){
586 struct a_termcap_control const *tcp;
587 char const *cnam;
588 si32_t rv;
589 NYD2_ENTER;
591 /* Prefer terminfo(5) names */
592 for(rv = max;;){
593 if(rv-- == min){
594 rv = -1;
595 break;
598 tcp = &a_termcap_control[(ui32_t)rv];
599 cnam = &a_termcap_namedat[tcp->tc_off];
600 if(cnam[2] != '\0'){
601 char const *xcp = cnam + 2;
603 if(nlen == strlen(xcp) && !memcmp(xcp, name, nlen))
604 break;
606 if(nlen == 2 && cnam[0] == name[0] && cnam[1] == name[1])
607 break;
609 NYD2_LEAVE;
610 return rv;
613 FL void
614 n_termcap_init(void){
615 struct str termvar;
616 char const *ccp;
617 NYD_ENTER;
619 assert((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE);
621 a_termcap_g = smalloc(sizeof *a_termcap_g);
622 a_termcap_g->tg_ext_ents = NULL;
623 memset(&a_termcap_g->tg_ents[0], 0, sizeof(a_termcap_g->tg_ents));
624 if((ccp = ok_vlook(termcap)) != NULL)
625 termvar.l = strlen(termvar.s = n_UNCONST(ccp));
626 else
627 /*termvar.s = NULL,*/ termvar.l = 0;
628 n_string_reserve(n_string_creat(&a_termcap_g->tg_dat),
629 ((termvar.l + (256 - 64)) & ~127));
631 if(termvar.l > 0)
632 a_termcap_init_var(&termvar);
634 if(ok_blook(termcap_disable))
635 pstate |= PS_TERMCAP_DISABLE;
636 #ifdef HAVE_TERMCAP
637 else if((ccp = ok_vlook(TERM)) == NULL){
638 n_err(_("Environment variable $TERM is not set, using only *termcap*\n"));
639 pstate |= PS_TERMCAP_DISABLE;
640 }else if(!a_termcap_load(ccp))
641 pstate |= PS_TERMCAP_DISABLE;
642 else{
643 /* Query termcap(5) for each command slot that is not yet set */
644 struct a_termcap_ent *tep;
645 size_t i;
647 for(i = n__TERMCAP_CMD_MAX1;;){
648 if(i-- == 0)
649 break;
650 if((tep = &a_termcap_g->tg_ents[i])->te_flags == 0)
651 a_termcap_ent_query_tcp(tep, &a_termcap_control[i]);
654 #endif
656 a_termcap_init_altern();
658 #ifdef HAVE_TERMCAP
659 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags != 0)
660 pstate |= PS_TERMCAP_CA_MODE;
661 #endif
662 n_TERMCAP_RESUME(TRU1);
663 NYD_LEAVE;
666 FL void
667 n_termcap_destroy(void){
668 NYD_ENTER;
669 assert((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE);
671 n_TERMCAP_SUSPEND(TRU1);
673 #ifdef HAVE_DEBUG
674 /* C99 */{
675 struct a_termcap_ext_ent *tmp;
677 while((tmp = a_termcap_g->tg_ext_ents) != NULL){
678 a_termcap_g->tg_ext_ents = tmp->tee_next;
679 free(tmp);
682 n_string_gut(&a_termcap_g->tg_dat);
683 free(a_termcap_g);
684 a_termcap_g = NULL;
685 #endif
686 NYD_LEAVE;
689 #ifdef HAVE_TERMCAP
690 FL void
691 n_termcap_resume(bool_t complete){
692 NYD_ENTER;
693 if(!(pstate & PS_TERMCAP_DISABLE) &&
694 (options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE){
695 if(complete && (pstate & PS_TERMCAP_CA_MODE))
696 n_termcap_cmdx(n_TERMCAP_CMD_ti);
697 n_termcap_cmdx(n_TERMCAP_CMD_ks);
698 fflush(n_tty_fp);
700 NYD_LEAVE;
703 FL void
704 n_termcap_suspend(bool_t complete){
705 NYD_ENTER;
706 if(!(pstate & PS_TERMCAP_DISABLE) &&
707 (options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE){
708 n_termcap_cmdx(n_TERMCAP_CMD_ke);
709 if(complete && (pstate & PS_TERMCAP_CA_MODE))
710 n_termcap_cmdx(n_TERMCAP_CMD_te);
711 fflush(n_tty_fp);
713 NYD_LEAVE;
715 #endif /* HAVE_TERMCAP */
717 FL ssize_t
718 n_termcap_cmd(enum n_termcap_cmd cmd, ssize_t a1, ssize_t a2){
719 /* Commands are not lazy queried */
720 struct a_termcap_ent const *tep;
721 enum a_termcap_flags flags;
722 ssize_t rv;
723 NYD2_ENTER;
724 n_UNUSED(a1);
725 n_UNUSED(a2);
727 rv = FAL0;
728 if((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) != OPT_INTERACTIVE)
729 goto jleave;
730 assert(a_termcap_g != NULL);
732 flags = cmd & ~n__TERMCAP_CMD_MASK;
733 cmd &= n__TERMCAP_CMD_MASK;
734 tep = a_termcap_g->tg_ents;
736 if((flags & n_TERMCAP_CMD_FLAG_CA_MODE) && !(pstate & PS_TERMCAP_CA_MODE))
737 rv = TRU1;
738 else if((tep += cmd)->te_flags == 0 || (tep->te_flags & a_TERMCAP_F_NOENT))
739 rv = TRUM1;
740 else if(!(tep->te_flags & a_TERMCAP_F_ALTERN)){
741 char const *cp = a_termcap_g->tg_dat.s_dat + tep->te_off;
743 assert((tep->te_flags & a_TERMCAP_F_TYPE_MASK) ==
744 n_TERMCAP_CAPTYPE_STRING);
746 #ifdef HAVE_TERMCAP
747 if(tep->te_flags & (a_TERMCAP_F_ARG_IDX1 | a_TERMCAP_F_ARG_IDX2)){
748 if(pstate & PS_TERMCAP_DISABLE){
749 if(options & OPT_D_V){
750 char const *cnam = &a_termcap_namedat[
751 a_termcap_control[cmd].tc_off];
753 if(cnam[2] != '\0')
754 cnam += 2;
755 n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
756 "can't perform CAP: %s\n"), cnam);
758 goto jleave;
761 /* Follow Thomas Dickey's advise on pre-va_arg prototypes, add 0s */
762 # ifdef HAVE_TERMINFO
763 if((cp = tparm(cp, a1, a2, 0,0,0,0,0,0,0)) == NULL)
764 goto jleave;
765 # else
766 /* curs_termcap.3:
767 * The \fBtgoto\fP function swaps the order of parameters.
768 * It does this also for calls requiring only a single parameter.
769 * In that case, the first parameter is merely a placeholder. */
770 if(!(tep->te_flags & a_TERMCAP_F_ARG_IDX2)){
771 a2 = a1;
772 a1 = (ui32_t)-1;
774 if((cp = tgoto(cp, (int)a1, (int)a2)) == NULL)
775 goto jleave;
776 # endif
778 #endif
780 for(;;){
781 #ifdef HAVE_TERMCAP
782 if(!(pstate & PS_TERMCAP_DISABLE)){
783 if(tputs(cp, 1, &a_termcap_putc) != OK)
784 break;
785 }else
786 #endif
787 if(fputs(cp, n_tty_fp) == EOF)
788 break;
789 if(!(tep->te_flags & a_TERMCAP_F_ARG_CNT) || --a1 <= 0){
790 rv = TRU1;
791 break;
794 goto jflush;
795 }else{
796 switch(cmd){
797 default:
798 rv = TRUM1;
799 break;
801 #ifdef HAVE_TERMCAP
802 case n_TERMCAP_CMD_cl: /* cl = ho + cd */
803 rv = n_termcap_cmdx(n_TERMCAP_CMD_ho);
804 if(rv > 0)
805 rv = n_termcap_cmdx(n_TERMCAP_CMD_cd | flags);
806 break;
807 #endif
809 #ifdef HAVE_MLE
810 case n_TERMCAP_CMD_ce: /* ce == ch + [:SPC:] */
811 if(a1 > 0)
812 --a1;
813 if((rv = n_termcap_cmd(n_TERMCAP_CMD_ch, a1, 0)) > 0){
814 for(a2 = scrnwidth - a1 - 1; a2 > 0; --a2)
815 if(putc(' ', n_tty_fp) == EOF){
816 rv = FAL0;
817 break;
819 if(rv && n_termcap_cmd(n_TERMCAP_CMD_ch, a1, -1) != TRU1)
820 rv = FAL0;
822 break;
823 case n_TERMCAP_CMD_ch: /* ch == cr + nd */
824 rv = n_termcap_cmdx(n_TERMCAP_CMD_cr);
825 if(rv > 0 && a1 > 0){
826 rv = n_termcap_cmd(n_TERMCAP_CMD_nd, a1, -1);
828 break;
829 #endif /* HAVE_MLE */
832 jflush:
833 if(flags & n_TERMCAP_CMD_FLAG_FLUSH)
834 fflush(n_tty_fp);
835 if(ferror(n_tty_fp))
836 rv = FAL0;
839 jleave:
840 NYD2_LEAVE;
841 return rv;
844 FL bool_t
845 n_termcap_query(enum n_termcap_query query, struct n_termcap_value *tvp){
846 /* Queries are lazy queried upon request */
847 struct a_termcap_ent const *tep;
848 bool_t rv;
849 NYD2_ENTER;
851 assert(tvp != NULL);
852 rv = FAL0;
854 if((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) != OPT_INTERACTIVE)
855 goto jleave;
856 assert(a_termcap_g != NULL);
858 /* Is it a builtin query? */
859 if(query != n__TERMCAP_QUERY_MAX1){
860 tep = &a_termcap_g->tg_ents[n__TERMCAP_CMD_MAX1 + query];
862 if(tep->te_flags == 0
863 #ifdef HAVE_TERMCAP
864 && ((pstate & PS_TERMCAP_DISABLE) ||
865 !a_termcap_ent_query_tcp(n_UNCONST(tep),
866 &a_termcap_control[n__TERMCAP_CMD_MAX1 + query]))
867 #endif
869 goto jleave;
870 }else{
871 #ifdef HAVE_TERMCAP
872 size_t nlen;
873 #endif
874 struct a_termcap_ext_ent *teep;
875 char const *ndat = tvp->tv_data.tvd_string;
877 for(teep = a_termcap_g->tg_ext_ents; teep != NULL; teep = teep->tee_next)
878 if(!strcmp(teep->tee_name, ndat)){
879 tep = &teep->tee_super;
880 goto jextok;
883 #ifdef HAVE_TERMCAP
884 if(pstate & PS_TERMCAP_DISABLE)
885 #endif
886 goto jleave;
887 #ifdef HAVE_TERMCAP
888 nlen = strlen(ndat) +1;
889 teep = smalloc(n_VSTRUCT_SIZEOF(struct a_termcap_ext_ent, tee_name) +
890 nlen);
891 tep = &teep->tee_super;
892 teep->tee_next = a_termcap_g->tg_ext_ents;
893 a_termcap_g->tg_ext_ents = teep;
894 memcpy(teep->tee_name, ndat, nlen);
896 if(!a_termcap_ent_query(n_UNCONST(tep), ndat,
897 n_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY))
898 goto jleave;
899 #endif
900 jextok:;
903 if(tep->te_flags & a_TERMCAP_F_NOENT)
904 goto jleave;
906 rv = (tep->te_flags & a_TERMCAP_F_ALTERN) ? TRUM1 : TRU1;
908 switch((tvp->tv_captype = tep->te_flags & a_TERMCAP_F_TYPE_MASK)){
909 case n_TERMCAP_CAPTYPE_BOOL:
910 tvp->tv_data.tvd_bool = (bool_t)tep->te_off;
911 break;
912 case n_TERMCAP_CAPTYPE_NUMERIC:
913 tvp->tv_data.tvd_numeric = (ui32_t)tep->te_off;
914 break;
915 default:
916 case n_TERMCAP_CAPTYPE_STRING:
917 tvp->tv_data.tvd_string = a_termcap_g->tg_dat.s_dat + tep->te_off;
918 break;
920 jleave:
921 NYD2_LEAVE;
922 return rv;
925 #ifdef HAVE_KEY_BINDINGS
926 FL si32_t
927 n_termcap_query_for_name(char const *name, enum n_termcap_captype type){
928 si32_t rv;
929 NYD2_ENTER;
931 if((rv = a_termcap_query_for_name(name, strlen(name))) >= 0){
932 struct a_termcap_control const *tcp = &a_termcap_control[(ui32_t)rv];
934 if(type != n_TERMCAP_CAPTYPE_NONE &&
935 (tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != type)
936 rv = -2;
937 else
938 rv -= n__TERMCAP_CMD_MAX1;
940 NYD2_LEAVE;
941 return rv;
944 FL char const *
945 n_termcap_name_of_query(enum n_termcap_query query){
946 char const *rv;
947 NYD2_ENTER;
949 rv = &a_termcap_namedat[
950 a_termcap_control[n__TERMCAP_CMD_MAX1 + query].tc_off + 2];
951 NYD2_LEAVE;
952 return rv;
954 #endif /* HAVE_KEY_BINDINGS */
955 #endif /* n_HAVE_TCAP */
957 /* s-it-mode */