Support queries of ANY (string) capability
[s-mailx.git] / termcap.c
blobf30469b9d5652638dd2c44212b56eca4f75deedb
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) 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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.
48 * TODO After I/O layer rewrite, "output to STDIN_FILENO".
51 /* As long as struct a_termcap_g.tg_buf is fixed size we need an indicator for
52 * spacing it, so that it is large enough to store the biggest terminal entry.
53 * Unless HAVE_TERMINFO or HAVE_TGETENT_NULL_BUF are defined we will also use
54 * this to space the buffer we pass through to tgetent(3).
55 * Since for (such) elder non-emulated terminals really weird things will
56 * happen if an entry would require more than 1024 bytes, don't really mind.
57 * Use a ui16_t for storage */
58 #define a_TERMCAP_ENTRYSIZE_MAX ((2668 + 64) & ~64) /* As of ncurses 6.0 */
60 n_CTA(a_TERMCAP_ENTRYSIZE_MAX < UI16_MAX,
61 "Chosen buffer size exceeds datatype capability");
63 /* For simplicity we store commands and queries in single continuous control
64 * and entry structure arrays: to index queries one has to add
65 * n__TERMCAP_CMD_MAX first! And don't confound with ENTRYSIZE_MAX! */
66 enum{
67 a_TERMCAP_ENT_MAX = n__TERMCAP_CMD_MAX + n__TERMCAP_QUERY_MAX
70 enum a_termcap_flags{
71 a_TERMCAP_F_NONE,
72 /* enum n_termcap_captype values stored here.
73 * Note that presence of a type in an a_termcap_ent signals initialization */
74 a_TERMCAP_F_TYPE_MASK = (1<<4) - 1,
76 a_TERMCAP_F_QUERY = 1<<4, /* A query rather than a command */
77 a_TERMCAP_F_DISABLED = 1<<5, /* User explicitly disabled command/query */
78 a_TERMCAP_F_ALTERN = 1<<6, /* Not available, but has alternative */
79 a_TERMCAP_F_NOENT = 1<<7, /* Not available */
81 /* _cmd() argument interpretion (_T_STR) */
82 a_TERMCAP_F_ARG_IDX1 = 1<<11, /* Argument 1 used, and is an index */
83 a_TERMCAP_F_ARG_IDX2 = 1<<12,
84 a_TERMCAP_F_ARG_CNT = 1<<13, /* .., and is a count */
86 a_TERMCAP_F__LAST = a_TERMCAP_F_ARG_CNT
88 n_CTA((ui32_t)n__TERMCAP_CAPTYPE_MAX <= (ui32_t)a_TERMCAP_F_TYPE_MASK,
89 "enum n_termcap_captype exceeds bit range of a_termcap_flags");
91 struct a_termcap_control{
92 ui16_t tc_flags;
93 /* Offset base into a_termcap_namedat[], which stores the two-letter
94 * termcap(5) name directly followed by a NUL terminated terminfo(5) name.
95 * A termcap(5) name may consist of two NULs meaning ENOENT,
96 * a terminfo(5) name may be empty for the same purpose */
97 ui16_t tc_off;
99 n_CTA(a_TERMCAP_F__LAST <= UI16_MAX,
100 "a_termcap_flags exceed storage datatype in a_termcap_control");
102 struct a_termcap_ent{
103 ui16_t te_flags;
104 ui16_t te_off; /* in a_termcap_g->tg_buf / value for T_BOOL and T_NUM */
106 n_CTA(a_TERMCAP_F__LAST <= UI16_MAX,
107 "a_termcap_flags exceed storage datatype in a_termcap_ent");
109 /* Structure for extended queries, which don't have an entry constant in
110 * n_termcap_query (to allow free query/binding of keycodes) */
111 struct a_termcap_ext_ent{
112 struct a_termcap_ent tee_super;
113 ui8_t tee__dummy[4];
114 struct a_termcap_ext_ent *tee_next;
115 /* Resolvable termcap(5)/terminfo(5) name as given by user; the actual data
116 * is stored just like for normal queries */
117 char tee_name[VFIELD_SIZE(0)];
120 struct a_termcap_g{
121 struct a_termcap_ext_ent *tg_ext_ents; /* List of extended queries */
122 struct a_termcap_ent tg_ents[a_TERMCAP_ENT_MAX];
123 char *tg_buf_tail; /* Current cast-off in data storage */
124 char *tg_lib_buf; /* Shall tgetent(3) need buffer, that it is */
125 char tg_buf[VFIELD_SIZE(0)]; /* Storage for just about anything */
126 /* TODO replace .tg_buf with a string object and resize as necessary; then
127 * TODO add a variable member to hold .tg_lib_buf IF we really need that */
130 /* Include the constant mk-tcap-map.pl output */
131 #include "tcaps.h"
132 n_CTA(sizeof a_termcap_namedat <= UI16_MAX,
133 "Termcap command and query name data exceed storage datatype");
134 n_CTA(a_TERMCAP_ENT_MAX == NELEM(a_termcap_control),
135 "Control array doesn't match command/query array to be controlled");
137 static struct a_termcap_g *a_termcap_g;
139 /* Query *termcap*, parse it and incorporate into a_termcap_g */
140 static void a_termcap_init_var(struct str const *termvar);
142 /* Initialize any _ent for which we have _F_ALTERN and which isn't yet set */
143 static void a_termcap_init_altern(void);
145 #ifdef HAVE_TERMCAP
146 /* Setup the library we use to work with term */
147 static bool_t a_termcap_load(char const *term);
149 /* Query the capability tcp and fill in tep (upon success) */
150 static bool_t a_termcap_ent_query(struct a_termcap_ent *tep,
151 char const *cname, ui16_t cflags);
152 SINLINE bool_t a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
153 struct a_termcap_control const *tcp);
155 /* Output PTF for both, termcap(5) and terminfo(5) */
156 static int a_termcap_putc(int c);
157 #endif
159 /* Get n_termcap_cmd or n_termcap_query constant belonging to (nlen bytes of)
160 * name, or -1 if not found. min and max have to be used to cramp the result */
161 static si32_t a_termcap_enum_for_name(char const *name, size_t nlen,
162 si32_t min, si32_t max);
163 #define a_termcap_cmd_for_name(NB,NL) \
164 a_termcap_enum_for_name(NB, NL, 0, n__TERMCAP_CMD_MAX)
165 #define a_termcap_query_for_name(NB,NL) \
166 a_termcap_enum_for_name(NB, NL, n__TERMCAP_CMD_MAX, a_TERMCAP_ENT_MAX)
168 static void
169 a_termcap_init_var(struct str const *termvar){
170 char *cbp_base, *cbp, *ebp;
171 size_t i;
172 char const *ccp;
173 NYD2_ENTER;
175 if(termvar->l >= UI16_MAX){
176 n_err(_("*termcap*: length excesses internal limit, skipping\n"));
177 goto j_leave;
180 assert(termvar->s[termvar->l] == '\0');
181 i = termvar->l +1;
182 cbp_base = ac_alloc(i);
183 memcpy(cbp = cbp_base, termvar->s, i);
185 ebp = a_termcap_g->tg_buf_tail;
186 for(; (ccp = n_strsep(&cbp, ',', TRU1)) != NULL;){
187 struct a_termcap_ent *tep;
188 size_t kl;
189 char const *v;
190 ui16_t f;
192 /* Separate key/value, if any */
193 if(/* no empties ccp[0] == '\0' ||*/ ccp[1] == '\0'){
194 jeinvent:
195 n_err(_("*termcap*: invalid entry: \"%s\"\n"), ccp);
196 continue;
198 for(kl = 2, v = &ccp[2];; ++kl, ++v){
199 char c = *v;
201 if(c == '\0'){
202 f = n_TERMCAP_CAPTYPE_BOOL;
203 break;
204 }else if(c == '#'){
205 f = n_TERMCAP_CAPTYPE_NUMERIC;
206 ++v;
207 break;
208 }else if(c == '='){
209 f = n_TERMCAP_CAPTYPE_STRING;
210 ++v;
211 break;
215 /* Do we know about this one? */
216 /* C99 */{
217 struct a_termcap_control const *tcp;
218 si32_t tci = a_termcap_enum_for_name(ccp, kl, 0, a_TERMCAP_ENT_MAX);
220 if(tci < 0){
221 if(options & OPT_D_V)
222 n_err(_("*termcap*: unknown capability: \"%s\"\n"), ccp);
223 continue;
225 i = (size_t)tci;
227 tcp = &a_termcap_control[i];
228 if((tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != f){
229 n_err(_("*termcap*: entry type mismatch: \"%s\"\n"), ccp);
230 break;
232 tep = &a_termcap_g->tg_ents[i];
233 tep->te_flags = tcp->tc_flags;
234 tep->te_off = (ui16_t)PTR2SIZE(ebp - a_termcap_g->tg_buf);
237 if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_BOOL)
239 else if(*v == '\0')
240 tep->te_flags |= a_TERMCAP_F_DISABLED;
241 else if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_NUMERIC){
242 char *eptr;
243 long l = strtol(v, &eptr, 10);
245 if(*eptr != '\0' || l < 0 || UICMP(32, l, >=, UI16_MAX))
246 goto jeinvent;
247 tep->te_off = (ui16_t)l;
248 }else for(;;){
249 int c = n_shell_expand_escape(&v, FAL0);
251 *ebp++ = (char)c;
252 if(c == '\0')
253 break;
256 a_termcap_g->tg_buf_tail = ebp;
257 DBG( if(options & OPT_D_V) n_err("*termcap* parsed: buffer used=%lu\n",
258 (ul_i)PTR2SIZE(ebp - a_termcap_g->tg_buf)) );
260 ac_free(cbp_base);
262 /* Catch some inter-dependencies the user may have triggered */
263 #ifdef HAVE_TERMCAP
264 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags & a_TERMCAP_F_DISABLED)
265 a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags = a_TERMCAP_F_DISABLED;
266 else if(a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags &
267 a_TERMCAP_F_DISABLED)
268 a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags = a_TERMCAP_F_DISABLED;
269 #endif
271 j_leave:
272 NYD2_LEAVE;
275 static void
276 a_termcap_init_altern(void){
277 /* We silently ignore user _F_DISABLED requests for those entries for which
278 * we have fallback entries, and which we need to ensure proper functioning.
279 * I.e., this allows users to explicitly disable some termcap(5) capability
280 * and enforce usage of the builtin fallback */
281 /* xxx Use table-based approach for fallback strategies */
282 #define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
283 #define a_OOK(TEP) ((TEP)->te_flags != 0)
284 #define a_SET(TEP,CMD,ALT) \
285 (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
286 ((ALT) ? a_TERMCAP_F_ALTERN : 0)
288 struct a_termcap_ent *tep;
289 NYD2_ENTER;
290 UNUSED(tep);
292 /* For simplicity in the rest of this file null flags of disabled commands,
293 * as we won't check and try to lazy query any command */
294 /* C99 */{
295 size_t i;
297 for(i = n__TERMCAP_CMD_MAX;;){
298 if(i-- == 0)
299 break;
300 if((tep = &a_termcap_g->tg_ents[i])->te_flags & a_TERMCAP_F_DISABLED)
301 tep->te_flags = 0;
305 #ifdef HAVE_TERMCAP
306 /* cl == ho+cd */
307 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cl];
308 if(!a_OOK(tep)){
309 if(a_OK(n_TERMCAP_CMD_cd) && a_OK(n_TERMCAP_CMD_ho))
310 a_SET(tep, n_TERMCAP_CMD_cl, TRU1);
312 #endif
314 #ifdef HAVE_MLE
315 /* ce == ch + [:SPC:] (start column specified by argument) */
316 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ce];
317 if(!a_OOK(tep))
318 a_SET(tep, n_TERMCAP_CMD_ce, TRU1);
320 /* ch == cr[\r] + nd[:\033C:] */
321 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ch];
322 if(!a_OOK(tep))
323 a_SET(tep, n_TERMCAP_CMD_ch, TRU1);
325 /* cr == \r */
326 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cr];
327 if(!a_OOK(tep)){
328 char *bp = a_termcap_g->tg_buf_tail;
330 a_SET(tep, n_TERMCAP_CMD_cr, FAL0);
331 tep->te_off = (ui16_t)PTR2SIZE(bp - a_termcap_g->tg_buf);
332 bp[0] = '\r';
333 bp[1] = '\0';
334 a_termcap_g->tg_buf_tail = (bp += 2);
337 /* le == \b */
338 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_le];
339 if(!a_OOK(tep)){
340 char *bp = a_termcap_g->tg_buf_tail;
342 a_SET(tep, n_TERMCAP_CMD_le, FAL0);
343 tep->te_off = (ui16_t)PTR2SIZE(bp - a_termcap_g->tg_buf);
344 bp[0] = '\b';
345 bp[1] = '\0';
346 a_termcap_g->tg_buf_tail = (bp += 2);
349 /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
350 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_nd];
351 if(!a_OOK(tep)){
352 char *bp = a_termcap_g->tg_buf_tail;
354 a_SET(tep, n_TERMCAP_CMD_nd, FAL0);
355 tep->te_off = (ui16_t)PTR2SIZE(bp - a_termcap_g->tg_buf);
356 memcpy(bp, "\033[C", sizeof("\033[C"));
357 a_termcap_g->tg_buf_tail = (bp += sizeof("\033[C"));
359 #endif /* HAVE_MLE */
361 NYD2_LEAVE;
362 #undef a_OK
363 #undef a_OOK
364 #undef a_SET
367 #ifdef HAVE_TERMCAP
368 # ifdef HAVE_TERMINFO
369 static bool_t
370 a_termcap_load(char const *term){
371 bool_t rv;
372 int err;
373 NYD2_ENTER;
375 if(!(rv = (setupterm(term, STDOUT_FILENO, &err) == OK)))
376 n_err(_("Unknown ${TERM}inal \"%s\", using only *termcap*\n"), term);
377 NYD2_LEAVE;
378 return rv;
381 static bool_t
382 a_termcap_ent_query(struct a_termcap_ent *tep,
383 char const *cname, ui16_t cflags){
384 bool_t rv;
385 NYD2_ENTER;
387 if(UNLIKELY(*cname == '\0'))
388 rv = FAL0;
389 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
390 case n_TERMCAP_CAPTYPE_BOOL:
391 tep->te_off = (tigetflag(cname) > 0);
392 rv = TRU1;
393 break;
394 case n_TERMCAP_CAPTYPE_NUMERIC:{
395 int r = tigetnum(cname);
397 if((rv = (r >= 0)))
398 tep->te_off = (ui16_t)MIN(UI16_MAX, r);
399 else
400 tep->te_flags |= a_TERMCAP_F_NOENT;
401 } break;
402 default:
403 case n_TERMCAP_CAPTYPE_STRING:{
404 char *cp;
406 cp = tigetstr(cname);
407 if((rv = (cp != NULL && cp != (char*)-1))){
408 char *ebp_orig = a_termcap_g->tg_buf_tail;
409 size_t l = strlen(cp) +1;
411 tep->te_off = (ui16_t)PTR2SIZE(ebp_orig - a_termcap_g->tg_buf);
412 assert((size_t)tep->te_off ==
413 PTR2SIZE(ebp_orig - a_termcap_g->tg_buf));
414 memcpy(ebp_orig, cp, l);
415 a_termcap_g->tg_buf_tail = (ebp_orig += l);
416 }else
417 tep->te_flags |= a_TERMCAP_F_NOENT;
418 } break;
420 NYD2_LEAVE;
421 return rv;
424 SINLINE bool_t
425 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
426 struct a_termcap_control const *tcp){
427 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off] + 2,
428 tcp->tc_flags);
431 # else /* HAVE_TERMINFO */
432 static bool_t
433 a_termcap_load(char const *term){
434 bool_t rv;
435 NYD2_ENTER;
437 /* ncurses may return -1 */
438 if(!(rv = tgetent(a_termcap_g->tg_lib_buf, term) > 0))
439 n_err(_("Unknown ${TERM}inal \"%s\", using only *termcap*\n"), term);
440 NYD2_LEAVE;
441 return rv;
444 static bool_t
445 a_termcap_ent_query(struct a_termcap_ent *tep,
446 char const *cname, ui16_t cflags){
447 bool_t rv;
448 NYD2_ENTER;
450 if(UNLIKELY(*cname == '\0'))
451 rv = FAL0;
452 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
453 case n_TERMCAP_CAPTYPE_BOOL:
454 tep->te_off = (tgetflag(cname) > 0);
455 rv = TRU1;
456 break;
457 case n_TERMCAP_CAPTYPE_NUMERIC:{
458 int r = tgetnum(cname);
460 if((rv = (r >= 0)))
461 tep->te_off = (ui16_t)MIN(UI16_MAX, r);
462 else
463 tep->te_flags |= a_TERMCAP_F_NOENT;
464 } break;
465 default:
466 case n_TERMCAP_CAPTYPE_STRING:{
467 char *ebp_orig = a_termcap_g->tg_buf_tail;
469 if((rv = (tgetstr(cname, &a_termcap_g->tg_buf_tail) != NULL))){
470 tep->te_off = (ui16_t)PTR2SIZE(ebp_orig - a_termcap_g->tg_buf);
471 assert((size_t)tep->te_off ==
472 PTR2SIZE(ebp_orig - a_termcap_g->tg_buf));
473 }else
474 tep->te_flags |= a_TERMCAP_F_NOENT;
475 } break;
477 NYD2_LEAVE;
478 return rv;
481 SINLINE bool_t
482 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
483 struct a_termcap_control const *tcp){
484 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off],
485 tcp->tc_flags);
487 # endif /* !HAVE_TERMINFO */
489 static int
490 a_termcap_putc(int c){
491 return putchar(c);
493 #endif /* HAVE_TERMCAP */
495 static si32_t
496 a_termcap_enum_for_name(char const *name, size_t nlen, si32_t min, si32_t max){
497 struct a_termcap_control const *tcp;
498 char const *cnam;
499 si32_t rv;
500 NYD2_ENTER;
502 /* Prefer terminfo(5) names */
503 for(rv = max;;){
504 if(rv-- == min){
505 rv = -1;
506 break;
509 tcp = &a_termcap_control[(ui32_t)rv];
510 cnam = &a_termcap_namedat[tcp->tc_off];
511 if(cnam[2] != '\0'){
512 char const *xcp = cnam + 2;
514 if(nlen == strlen(xcp) && !memcmp(xcp, name, nlen))
515 break;
517 if(nlen == 2 && cnam[0] == name[0] && cnam[1] == name[1])
518 break;
520 NYD2_LEAVE;
521 return rv;
524 FL void
525 n_termcap_init(void){
526 struct str termvar;
527 char const *ccp;
528 NYD_ENTER;
530 assert((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE);
532 if((ccp = ok_vlook(termcap)) != NULL)
533 termvar.l = strlen(termvar.s = UNCONST(ccp));
534 else
535 /*termvar.s = NULL,*/ termvar.l = 0;
537 /* C99 */{
538 size_t bl = sizeof(struct a_termcap_g) -
539 VFIELD_SIZEOF(struct a_termcap_g, tg_buf) +
540 termvar.l + a_TERMCAP_ENTRYSIZE_MAX
541 # if !defined HAVE_TGETENT_NULL_BUF && !defined HAVE_TERMINFO
542 + a_TERMCAP_ENTRYSIZE_MAX
543 # endif
545 a_termcap_g = smalloc(bl);
547 a_termcap_g->tg_ext_ents = NULL;
548 a_termcap_g->tg_buf_tail = a_termcap_g->tg_buf;
549 a_termcap_g->tg_lib_buf =
550 # if defined HAVE_TGETENT_NULL_BUF || defined HAVE_TERMINFO
551 NULL
552 # else
553 &a_termcap_g->tg_buf[termvar.l + a_TERMCAP_ENTRYSIZE_MAX]
554 # endif
556 memset(&a_termcap_g->tg_ents[0], 0, sizeof(a_termcap_g->tg_ents));
558 if(termvar.l > 0)
559 a_termcap_init_var(&termvar);
561 if(ok_blook(termcap_disable))
562 pstate |= PS_TERMCAP_DISABLE;
563 #ifdef HAVE_TERMCAP
564 else if((ccp = env_vlook("TERM", FAL0)) == NULL){
565 n_err(_("Environment variable $TERM is not set, using only *termcap*\n"));
566 pstate |= PS_TERMCAP_DISABLE;
567 }else if(!a_termcap_load(ccp))
568 pstate |= PS_TERMCAP_DISABLE;
569 else{
570 /* Query termcap(5) for each command slot that is not yet set */
571 struct a_termcap_ent *tep;
572 size_t i;
574 for(i = n__TERMCAP_CMD_MAX;;){
575 if(i-- == 0)
576 break;
577 if((tep = &a_termcap_g->tg_ents[i])->te_flags == 0)
578 a_termcap_ent_query_tcp(tep, &a_termcap_control[i]);
581 #endif
583 a_termcap_init_altern();
585 #ifdef HAVE_TERMCAP
586 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags != 0)
587 pstate |= PS_TERMCAP_CA_MODE;
588 #endif
589 n_TERMCAP_RESUME(TRU1);
590 NYD_LEAVE;
593 FL void
594 n_termcap_destroy(void){
595 NYD_ENTER;
596 assert((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE);
598 n_TERMCAP_SUSPEND(TRU1);
600 #ifdef HAVE_DEBUG
601 /* C99 */{
602 struct a_termcap_ext_ent *tmp;
604 while((tmp = a_termcap_g->tg_ext_ents) != NULL){
605 a_termcap_g->tg_ext_ents = tmp->tee_next;
606 free(tmp);
609 free(a_termcap_g);
610 a_termcap_g = NULL;
611 #endif
612 NYD_LEAVE;
615 #ifdef HAVE_TERMCAP
616 FL void
617 n_termcap_resume(bool_t complete){
618 NYD_ENTER;
619 if(!(pstate & PS_TERMCAP_DISABLE) &&
620 (options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE){
621 if(complete && (pstate & PS_TERMCAP_CA_MODE))
622 n_termcap_cmdx(n_TERMCAP_CMD_ti);
623 n_termcap_cmdx(n_TERMCAP_CMD_ks);
624 fflush(stdout);
626 NYD_LEAVE;
629 FL void
630 n_termcap_suspend(bool_t complete){
631 NYD_ENTER;
632 if(!(pstate & PS_TERMCAP_DISABLE) &&
633 (options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE){
634 if(complete && (pstate & PS_TERMCAP_CA_MODE))
635 n_termcap_cmdx(n_TERMCAP_CMD_ke);
636 n_termcap_cmdx(n_TERMCAP_CMD_te);
637 fflush(stdout);
639 NYD_LEAVE;
641 #endif /* HAVE_TERMCAP */
643 FL ssize_t
644 n_termcap_cmd(enum n_termcap_cmd cmd, ssize_t a1, ssize_t a2){
645 /* Commands are not lazy queried */
646 struct a_termcap_ent const *tep;
647 enum a_termcap_flags flags;
648 ssize_t rv;
649 NYD2_ENTER;
650 UNUSED(a1);
651 UNUSED(a2);
653 rv = FAL0;
654 if((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) != OPT_INTERACTIVE)
655 goto jleave;
656 assert(a_termcap_g != NULL);
658 flags = cmd & ~n__TERMCAP_CMD_MASK;
659 cmd &= n__TERMCAP_CMD_MASK;
660 tep = a_termcap_g->tg_ents;
662 if((flags & n_TERMCAP_CMD_FLAG_CA_MODE) && !(pstate & PS_TERMCAP_CA_MODE))
663 rv = TRU1;
664 else if((tep += cmd)->te_flags == 0 || (tep->te_flags & a_TERMCAP_F_NOENT))
665 rv = TRUM1;
666 else if(!(tep->te_flags & a_TERMCAP_F_ALTERN)){
667 char const *cp = a_termcap_g->tg_buf + tep->te_off;
669 assert((tep->te_flags & a_TERMCAP_F_TYPE_MASK) ==
670 n_TERMCAP_CAPTYPE_STRING);
672 #ifdef HAVE_TERMCAP
673 if(tep->te_flags & (a_TERMCAP_F_ARG_IDX1 | a_TERMCAP_F_ARG_IDX2)){
674 if(pstate & PS_TERMCAP_DISABLE){
675 if(options & OPT_D_V){
676 char const *cnam = &a_termcap_namedat[
677 a_termcap_control[cmd].tc_off];
679 if(cnam[2] != '\0')
680 cnam += 2;
681 n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
682 "can't perform CAP \"%s\"\n"), cnam);
684 goto jleave;
687 # ifdef HAVE_TERMINFO
688 if((cp = tparm(cp, a1, a2, 0,0,0,0,0,0,0)) == NULL)
689 goto jleave;
690 # else
691 /* curs_termcap.3:
692 * The \fBtgoto\fP function swaps the order of parameters.
693 * It does this also for calls requiring only a single parameter.
694 * In that case, the first parameter is merely a placeholder. */
695 if(!(tep->te_flags & a_TERMCAP_F_ARG_IDX2)){
696 a2 = a1;
697 a1 = (ui32_t)-1;
699 if((cp = tgoto(cp, (int)a1, (int)a2)) == NULL)
700 goto jleave;
701 # endif
703 #endif
705 for(;;){
706 #ifdef HAVE_TERMCAP
707 if(!(pstate & PS_TERMCAP_DISABLE)){
708 if(tputs(cp, 1, &a_termcap_putc) != OK)
709 break;
710 }else
711 #endif
712 if(fputs(cp, stdout) == EOF)
713 break;
714 if(!(tep->te_flags & a_TERMCAP_F_ARG_CNT) || --a1 <= 0){
715 rv = TRU1;
716 break;
719 goto jflush;
720 }else{
721 switch(cmd){
722 default:
723 rv = TRUM1;
724 break;
726 #ifdef HAVE_TERMCAP
727 case n_TERMCAP_CMD_cl: /* cl = ho + cd */
728 rv = n_termcap_cmdx(n_TERMCAP_CMD_ho);
729 if(rv > 0)
730 rv = n_termcap_cmdx(n_TERMCAP_CMD_cd | flags);
731 break;
732 #endif
734 #ifdef HAVE_MLE
735 case n_TERMCAP_CMD_ce: /* ce == ch + [:SPC:] */
736 if(a1 > 0)
737 --a1;
738 if((rv = n_termcap_cmd(n_TERMCAP_CMD_ch, a1, 0)) > 0){
739 for(a2 = scrnwidth - a1 - 1; a2 > 0; --a2)
740 if(putchar(' ') == EOF){
741 rv = FAL0;
742 break;
744 if(rv && n_termcap_cmd(n_TERMCAP_CMD_ch, a1, -1) != TRU1)
745 rv = FAL0;
747 break;
748 case n_TERMCAP_CMD_ch: /* ch == cr + nd */
749 rv = n_termcap_cmdx(n_TERMCAP_CMD_cr);
750 if(rv > 0 && a1 > 0){
751 rv = n_termcap_cmd(n_TERMCAP_CMD_nd, a1, -1);
753 break;
754 #endif /* HAVE_MLE */
757 jflush:
758 if(flags & n_TERMCAP_CMD_FLAG_FLUSH)
759 fflush(stdout);
760 if(ferror(stdout))
761 rv = FAL0;
764 jleave:
765 NYD2_LEAVE;
766 return rv;
769 FL bool_t
770 n_termcap_query(enum n_termcap_query query, struct n_termcap_value *tvp){
771 /* Queries are lazy queried upon request */
772 struct a_termcap_ent const *tep;
773 bool_t rv;
774 NYD2_ENTER;
776 assert(tvp != NULL);
777 rv = FAL0;
779 if((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) != OPT_INTERACTIVE)
780 goto jleave;
781 assert(a_termcap_g != NULL);
783 /* Is it a builtin query? */
784 if(query != n__TERMCAP_QUERY_MAX){
785 tep = &a_termcap_g->tg_ents[n__TERMCAP_CMD_MAX + query];
787 if(tep->te_flags == 0
788 #ifdef HAVE_TERMCAP
789 && !a_termcap_ent_query_tcp(UNCONST(tep),
790 &a_termcap_control[n__TERMCAP_CMD_MAX + query])
791 #endif
793 goto jleave;
794 }else{
795 #ifndef HAVE_TERMCAP
796 goto jleave;
797 #else
798 size_t nlen;
799 struct a_termcap_ext_ent *teep;
800 char const *ndat = tvp->tv_data.tvd_string;
802 for(teep = a_termcap_g->tg_ext_ents; teep != NULL; teep = teep->tee_next)
803 if(!strcmp(teep->tee_name, ndat)){
804 tep = &teep->tee_super;
805 goto jextok;
808 nlen = strlen(ndat) +1;
809 teep = smalloc(sizeof(*teep) -
810 VFIELD_SIZEOF(struct a_termcap_ext_ent, tee_name) + nlen);
811 tep = &teep->tee_super;
812 teep->tee_next = a_termcap_g->tg_ext_ents;
813 a_termcap_g->tg_ext_ents = teep;
814 memcpy(teep->tee_name, ndat, nlen);
816 if(!a_termcap_ent_query(UNCONST(tep), ndat,
817 n_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY))
818 goto jleave;
819 jextok:;
820 #endif
823 if(tep->te_flags & a_TERMCAP_F_NOENT)
824 goto jleave;
826 rv = (tep->te_flags & a_TERMCAP_F_ALTERN) ? TRUM1 : TRU1;
828 switch((tvp->tv_captype = tep->te_flags & a_TERMCAP_F_TYPE_MASK)){
829 case n_TERMCAP_CAPTYPE_BOOL:
830 tvp->tv_data.tvd_bool = (bool_t)tep->te_off;
831 break;
832 case n_TERMCAP_CAPTYPE_NUMERIC:
833 tvp->tv_data.tvd_numeric = (ui32_t)tep->te_off;
834 break;
835 default:
836 case n_TERMCAP_CAPTYPE_STRING:
837 tvp->tv_data.tvd_string = a_termcap_g->tg_buf + tep->te_off;
838 break;
840 jleave:
841 NYD2_LEAVE;
842 return rv;
844 #endif /* n_HAVE_TCAP */
846 /* s-it-mode */