Compare *newfolders* case-insensitively (Justin Ellingwood)..
[s-mailx.git] / termcap.c
blob3a4f517391be01656d6962d4ac9d7f72cb1893cf
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 /* Unless HAVE_TERMINFO or HAVE_TGETENT_NULL_BUF are defined we will use this
52 * to space the buffer we pass through to tgetent(3).
53 * Since for (such) elder non-emulated terminals really weird things will
54 * happen if an entry would require more than 1024 bytes, don't really mind.
55 * Use a ui16_t for storage */
56 #define a_TERMCAP_ENTRYSIZE_MAX ((2668 + 64) & ~64) /* As of ncurses 6.0 */
58 n_CTA(a_TERMCAP_ENTRYSIZE_MAX < UI16_MAX,
59 "Chosen buffer size exceeds datatype capability");
61 /* For simplicity we store commands and queries in single continuous control
62 * and entry structure arrays: to index queries one has to add
63 * n__TERMCAP_CMD_MAX first! And don't confound with ENTRYSIZE_MAX! */
64 enum{
65 a_TERMCAP_ENT_MAX = n__TERMCAP_CMD_MAX + n__TERMCAP_QUERY_MAX
68 enum a_termcap_flags{
69 a_TERMCAP_F_NONE,
70 /* enum n_termcap_captype values stored here.
71 * Note that presence of a type in an a_termcap_ent signals initialization */
72 a_TERMCAP_F_TYPE_MASK = (1<<4) - 1,
74 a_TERMCAP_F_QUERY = 1<<4, /* A query rather than a command */
75 a_TERMCAP_F_DISABLED = 1<<5, /* User explicitly disabled command/query */
76 a_TERMCAP_F_ALTERN = 1<<6, /* Not available, but has alternative */
77 a_TERMCAP_F_NOENT = 1<<7, /* Not available */
79 /* _cmd() argument interpretion (_T_STR) */
80 a_TERMCAP_F_ARG_IDX1 = 1<<11, /* Argument 1 used, and is an index */
81 a_TERMCAP_F_ARG_IDX2 = 1<<12,
82 a_TERMCAP_F_ARG_CNT = 1<<13, /* .., and is a count */
84 a_TERMCAP_F__LAST = a_TERMCAP_F_ARG_CNT
86 n_CTA((ui32_t)n__TERMCAP_CAPTYPE_MAX <= (ui32_t)a_TERMCAP_F_TYPE_MASK,
87 "enum n_termcap_captype exceeds bit range of a_termcap_flags");
89 struct a_termcap_control{
90 ui16_t tc_flags;
91 /* Offset base into a_termcap_namedat[], which stores the two-letter
92 * termcap(5) name directly followed by a NUL terminated terminfo(5) name.
93 * A termcap(5) name may consist of two NULs meaning ENOENT,
94 * a terminfo(5) name may be empty for the same purpose */
95 ui16_t tc_off;
97 n_CTA(a_TERMCAP_F__LAST <= UI16_MAX,
98 "a_termcap_flags exceed storage datatype in a_termcap_control");
100 struct a_termcap_ent{
101 ui16_t te_flags;
102 ui16_t te_off; /* in a_termcap_g->tg_dat / value for T_BOOL and T_NUM */
104 n_CTA(a_TERMCAP_F__LAST <= UI16_MAX,
105 "a_termcap_flags exceed storage datatype in a_termcap_ent");
107 /* Structure for extended queries, which don't have an entry constant in
108 * n_termcap_query (to allow free query/binding of keycodes) */
109 struct a_termcap_ext_ent{
110 struct a_termcap_ent tee_super;
111 ui8_t tee__dummy[4];
112 struct a_termcap_ext_ent *tee_next;
113 /* Resolvable termcap(5)/terminfo(5) name as given by user; the actual data
114 * is stored just like for normal queries */
115 char tee_name[VFIELD_SIZE(0)];
118 struct a_termcap_g{
119 struct a_termcap_ext_ent *tg_ext_ents; /* List of extended queries */
120 struct a_termcap_ent tg_ents[a_TERMCAP_ENT_MAX];
121 struct n_string tg_dat; /* Storage for resolved caps */
122 # if !defined HAVE_TGETENT_NULL_BUF && !defined HAVE_TERMINFO
123 char tg_lib_buf[a_TERMCAP_ENTRYSIZE_MAX];
124 # endif
127 /* Include the constant mk-tcap-map.pl output */
128 #include "tcaps.h"
129 n_CTA(sizeof a_termcap_namedat <= UI16_MAX,
130 "Termcap command and query name data exceed storage datatype");
131 n_CTA(a_TERMCAP_ENT_MAX == NELEM(a_termcap_control),
132 "Control array doesn't match command/query array to be controlled");
134 static struct a_termcap_g *a_termcap_g;
136 /* Query *termcap*, parse it and incorporate into a_termcap_g */
137 static void a_termcap_init_var(struct str const *termvar);
139 /* Expand ^CNTRL, \[Ee] and \OCT */
140 static void a_termcap__strexp(struct n_string *store, char const *ibuf);
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;
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 = salloc(i);
183 memcpy(cbp = cbp_base, termvar->s, i);
185 for(; (ccp = n_strsep(&cbp, ',', TRU1)) != NULL;){
186 struct a_termcap_ent *tep;
187 size_t kl;
188 char const *v;
189 ui16_t f;
191 /* Separate key/value, if any */
192 if(/* no empties ccp[0] == '\0' ||*/ ccp[1] == '\0'){
193 jeinvent:
194 n_err(_("*termcap*: invalid entry: \"%s\"\n"), ccp);
195 continue;
197 for(kl = 2, v = &ccp[2];; ++kl, ++v){
198 char c = *v;
200 if(c == '\0'){
201 f = n_TERMCAP_CAPTYPE_BOOL;
202 break;
203 }else if(c == '#'){
204 f = n_TERMCAP_CAPTYPE_NUMERIC;
205 ++v;
206 break;
207 }else if(c == '='){
208 f = n_TERMCAP_CAPTYPE_STRING;
209 ++v;
210 break;
214 /* Do we know about this one? */
215 /* C99 */{
216 struct a_termcap_control const *tcp;
217 si32_t tci = a_termcap_enum_for_name(ccp, kl, 0, a_TERMCAP_ENT_MAX);
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(sizeof(*teep) -
226 VFIELD_SIZEOF(struct a_termcap_ext_ent, 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 a_termcap__strexp(&a_termcap_g->tg_dat, v);
236 }else
237 #endif /* HAVE_KEY_BINDINGS */
238 if(options & OPT_D_V)
239 n_err(_("*termcap*: unknown capability: \"%s\"\n"), ccp);
240 continue;
242 i = (size_t)tci;
244 tcp = &a_termcap_control[i];
245 if((tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != f){
246 n_err(_("*termcap*: entry type mismatch: \"%s\"\n"), ccp);
247 break;
249 tep = &a_termcap_g->tg_ents[i];
250 tep->te_flags = tcp->tc_flags;
251 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
254 if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_BOOL)
256 else if(*v == '\0')
257 tep->te_flags |= a_TERMCAP_F_DISABLED;
258 else if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_NUMERIC){
259 char *eptr;
260 long l = strtol(v, &eptr, 10);
262 if(*eptr != '\0' || l < 0 || UICMP(32, l, >=, UI16_MAX))
263 goto jeinvent;
264 tep->te_off = (ui16_t)l;
265 }else
266 a_termcap__strexp(&a_termcap_g->tg_dat, v);
268 DBG( if(options & OPT_D_V) n_err("*termcap* parsed: buffer used=%lu\n",
269 (ul_i)a_termcap_g->tg_dat.s_len) );
271 /* Catch some inter-dependencies the user may have triggered */
272 #ifdef HAVE_TERMCAP
273 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags & a_TERMCAP_F_DISABLED)
274 a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags = a_TERMCAP_F_DISABLED;
275 else if(a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags &
276 a_TERMCAP_F_DISABLED)
277 a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags = a_TERMCAP_F_DISABLED;
278 #endif
280 j_leave:
281 NYD2_LEAVE;
284 static void
285 a_termcap__strexp(struct n_string *store, char const *ibuf){ /* XXX ASCII */
286 char c;
287 char const *oibuf;
288 size_t olen;
289 NYD2_ENTER;
291 olen = store->s_len;
293 for(oibuf = ibuf; (c = *ibuf) != '\0';){
294 if(c == '\\'){
295 if((c = ibuf[1]) == '\0')
296 goto jebsseq;
298 if(c == 'E'){
299 c = '\033';
300 ibuf += 2;
301 goto jpush;
304 if(octalchar(c)){
305 char c2, c3;
307 if((c2 = ibuf[2]) == '\0' || !octalchar(c2) ||
308 (c3 = ibuf[3]) == '\0' || !octalchar(c3)){
309 n_err(_("*termcap*: invalid octal sequence: \"%s\"\n"), oibuf);
310 goto jerr;
312 c -= '0', c2 -= '0', c3 -= '0';
313 c <<= 3, c |= c2;
314 if((ui8_t)c > 0x1F){
315 n_err(_("*termcap*: octal number too large: \"%s\"\n"), oibuf);
316 goto jerr;
318 c <<= 3, c |= c3;
319 ibuf += 4;
320 goto jpush;
322 jebsseq:
323 n_err(_("*termcap*: invalid \"\\\" sequence: \"%s\"\n"), oibuf);
324 goto jerr;
325 }else if(c == '^'){
326 if((c = ibuf[1]) == '\0'){
327 n_err(_("*termcap*: incomplete ^CNTRL sequence: \"%s\"\n"), oibuf);
328 goto jerr;
330 c = upperconv(c) ^ 0x40;
331 if((ui8_t)c > 0x1F && c != 0x7F){ /* ASCII C0: 0..1F, 7F */
332 n_err(_("*termcap*: invalid ^CNTRL sequence: \"%s\"\n"), oibuf);
333 goto jerr;
335 ibuf += 2;
336 }else
337 ++ibuf;
339 jpush:
340 store = n_string_push_c(store, c);
343 jleave:
344 n_string_push_c(store, '\0');
345 NYD2_LEAVE;
346 return;
347 jerr:
348 store = n_string_trunc(store, olen);
349 goto jleave;
352 static void
353 a_termcap_init_altern(void){
354 /* We silently ignore user _F_DISABLED requests for those entries for which
355 * we have fallback entries, and which we need to ensure proper functioning.
356 * I.e., this allows users to explicitly disable some termcap(5) capability
357 * and enforce usage of the builtin fallback */
358 /* xxx Use table-based approach for fallback strategies */
359 #define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
360 #define a_OOK(TEP) ((TEP)->te_flags != 0)
361 #define a_SET(TEP,CMD,ALT) \
362 (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
363 ((ALT) ? a_TERMCAP_F_ALTERN : 0)
365 struct a_termcap_ent *tep;
366 NYD2_ENTER;
367 UNUSED(tep);
369 /* For simplicity in the rest of this file null flags of disabled commands,
370 * as we won't check and try to lazy query any command */
371 /* C99 */{
372 size_t i;
374 for(i = n__TERMCAP_CMD_MAX;;){
375 if(i-- == 0)
376 break;
377 if((tep = &a_termcap_g->tg_ents[i])->te_flags & a_TERMCAP_F_DISABLED)
378 tep->te_flags = 0;
382 #ifdef HAVE_TERMCAP
383 /* cl == ho+cd */
384 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cl];
385 if(!a_OOK(tep)){
386 if(a_OK(n_TERMCAP_CMD_cd) && a_OK(n_TERMCAP_CMD_ho))
387 a_SET(tep, n_TERMCAP_CMD_cl, TRU1);
389 #endif
391 #ifdef HAVE_MLE
392 /* ce == ch + [:SPC:] (start column specified by argument) */
393 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ce];
394 if(!a_OOK(tep))
395 a_SET(tep, n_TERMCAP_CMD_ce, TRU1);
397 /* ch == cr[\r] + nd[:\033C:] */
398 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ch];
399 if(!a_OOK(tep))
400 a_SET(tep, n_TERMCAP_CMD_ch, TRU1);
402 /* cr == \r */
403 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cr];
404 if(!a_OOK(tep)){
405 a_SET(tep, n_TERMCAP_CMD_cr, FAL0);
406 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
407 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\r'), '\0');
410 /* le == \b */
411 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_le];
412 if(!a_OOK(tep)){
413 a_SET(tep, n_TERMCAP_CMD_le, FAL0);
414 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
415 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\b'), '\0');
418 /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
419 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_nd];
420 if(!a_OOK(tep)){
421 a_SET(tep, n_TERMCAP_CMD_nd, FAL0);
422 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
423 n_string_push_buf(&a_termcap_g->tg_dat, "\033[C", sizeof("\033[C"));
425 #endif /* HAVE_MLE */
427 NYD2_LEAVE;
428 #undef a_OK
429 #undef a_OOK
430 #undef a_SET
433 #ifdef HAVE_TERMCAP
434 # ifdef HAVE_TERMINFO
435 static bool_t
436 a_termcap_load(char const *term){
437 bool_t rv;
438 int err;
439 NYD2_ENTER;
441 if(!(rv = (setupterm(term, STDOUT_FILENO, &err) == OK)))
442 n_err(_("Unknown ${TERM}inal \"%s\", using only *termcap*\n"), term);
443 NYD2_LEAVE;
444 return rv;
447 static bool_t
448 a_termcap_ent_query(struct a_termcap_ent *tep,
449 char const *cname, ui16_t cflags){
450 bool_t rv;
451 NYD2_ENTER;
453 if(UNLIKELY(*cname == '\0'))
454 rv = FAL0;
455 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
456 case n_TERMCAP_CAPTYPE_BOOL:
457 tep->te_off = (tigetflag(cname) > 0);
458 rv = TRU1;
459 break;
460 case n_TERMCAP_CAPTYPE_NUMERIC:{
461 int r = tigetnum(cname);
463 if((rv = (r >= 0)))
464 tep->te_off = (ui16_t)MIN(UI16_MAX, r);
465 else
466 tep->te_flags |= a_TERMCAP_F_NOENT;
467 } break;
468 default:
469 case n_TERMCAP_CAPTYPE_STRING:{
470 char *cp;
472 cp = tigetstr(cname);
473 if((rv = (cp != NULL && cp != (char*)-1))){
474 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
475 n_string_push_buf(&a_termcap_g->tg_dat, cp, strlen(cp) +1);
476 }else
477 tep->te_flags |= a_TERMCAP_F_NOENT;
478 } break;
480 NYD2_LEAVE;
481 return rv;
484 SINLINE bool_t
485 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
486 struct a_termcap_control const *tcp){
487 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off] + 2,
488 tcp->tc_flags);
491 # else /* HAVE_TERMINFO */
492 static bool_t
493 a_termcap_load(char const *term){
494 bool_t rv;
495 NYD2_ENTER;
497 /* ncurses may return -1 */
498 # ifndef HAVE_TGETENT_NULL_BUF
499 # define a_BUF &a_termcap_g->tg_lib_buf[0]
500 # else
501 # define a_BUF NULL
502 # endif
503 if(!(rv = tgetent(a_BUF, term) > 0))
504 n_err(_("Unknown ${TERM}inal \"%s\", using only *termcap*\n"), term);
505 # undef a_BUF
506 NYD2_LEAVE;
507 return rv;
510 static bool_t
511 a_termcap_ent_query(struct a_termcap_ent *tep,
512 char const *cname, ui16_t cflags){
513 bool_t rv;
514 NYD2_ENTER;
516 if(UNLIKELY(*cname == '\0'))
517 rv = FAL0;
518 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
519 case n_TERMCAP_CAPTYPE_BOOL:
520 tep->te_off = (tgetflag(cname) > 0);
521 rv = TRU1;
522 break;
523 case n_TERMCAP_CAPTYPE_NUMERIC:{
524 int r = tgetnum(cname);
526 if((rv = (r >= 0)))
527 tep->te_off = (ui16_t)MIN(UI16_MAX, r);
528 else
529 tep->te_flags |= a_TERMCAP_F_NOENT;
530 } break;
531 default:
532 case n_TERMCAP_CAPTYPE_STRING:{
533 # ifndef HAVE_TGETENT_NULL_BUF
534 char buf_base[a_TERMCAP_ENTRYSIZE_MAX], *buf = &buf_base[0];
535 # define a_BUF &buf
536 # else
537 # define a_BUF NULL
538 # endif
539 char *cp;
541 if((rv = ((cp = tgetstr(cname, a_BUF)) != NULL))){
542 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
543 n_string_push_buf(&a_termcap_g->tg_dat, cp, strlen(cp) +1);
544 # undef a_BUF
545 }else
546 tep->te_flags |= a_TERMCAP_F_NOENT;
547 } break;
549 NYD2_LEAVE;
550 return rv;
553 SINLINE bool_t
554 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
555 struct a_termcap_control const *tcp){
556 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off],
557 tcp->tc_flags);
559 # endif /* !HAVE_TERMINFO */
561 static int
562 a_termcap_putc(int c){
563 return putchar(c);
565 #endif /* HAVE_TERMCAP */
567 static si32_t
568 a_termcap_enum_for_name(char const *name, size_t nlen, si32_t min, si32_t max){
569 struct a_termcap_control const *tcp;
570 char const *cnam;
571 si32_t rv;
572 NYD2_ENTER;
574 /* Prefer terminfo(5) names */
575 for(rv = max;;){
576 if(rv-- == min){
577 rv = -1;
578 break;
581 tcp = &a_termcap_control[(ui32_t)rv];
582 cnam = &a_termcap_namedat[tcp->tc_off];
583 if(cnam[2] != '\0'){
584 char const *xcp = cnam + 2;
586 if(nlen == strlen(xcp) && !memcmp(xcp, name, nlen))
587 break;
589 if(nlen == 2 && cnam[0] == name[0] && cnam[1] == name[1])
590 break;
592 NYD2_LEAVE;
593 return rv;
596 FL void
597 n_termcap_init(void){
598 struct str termvar;
599 char const *ccp;
600 NYD_ENTER;
602 assert((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE);
604 a_termcap_g = smalloc(sizeof *a_termcap_g);
605 a_termcap_g->tg_ext_ents = NULL;
606 memset(&a_termcap_g->tg_ents[0], 0, sizeof(a_termcap_g->tg_ents));
607 if((ccp = ok_vlook(termcap)) != NULL)
608 termvar.l = strlen(termvar.s = UNCONST(ccp));
609 else
610 /*termvar.s = NULL,*/ termvar.l = 0;
611 n_string_reserve(n_string_creat(&a_termcap_g->tg_dat),
612 ((termvar.l + (256 - 64)) & ~127));
614 if(termvar.l > 0)
615 a_termcap_init_var(&termvar);
617 if(ok_blook(termcap_disable))
618 pstate |= PS_TERMCAP_DISABLE;
619 #ifdef HAVE_TERMCAP
620 else if((ccp = ok_vlook(TERM)) == NULL){
621 n_err(_("Environment variable $TERM is not set, using only *termcap*\n"));
622 pstate |= PS_TERMCAP_DISABLE;
623 }else if(!a_termcap_load(ccp))
624 pstate |= PS_TERMCAP_DISABLE;
625 else{
626 /* Query termcap(5) for each command slot that is not yet set */
627 struct a_termcap_ent *tep;
628 size_t i;
630 for(i = n__TERMCAP_CMD_MAX;;){
631 if(i-- == 0)
632 break;
633 if((tep = &a_termcap_g->tg_ents[i])->te_flags == 0)
634 a_termcap_ent_query_tcp(tep, &a_termcap_control[i]);
637 #endif
639 a_termcap_init_altern();
641 #ifdef HAVE_TERMCAP
642 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags != 0)
643 pstate |= PS_TERMCAP_CA_MODE;
644 #endif
645 n_TERMCAP_RESUME(TRU1);
646 NYD_LEAVE;
649 FL void
650 n_termcap_destroy(void){
651 NYD_ENTER;
652 assert((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE);
654 n_TERMCAP_SUSPEND(TRU1);
656 #ifdef HAVE_DEBUG
657 /* C99 */{
658 struct a_termcap_ext_ent *tmp;
660 while((tmp = a_termcap_g->tg_ext_ents) != NULL){
661 a_termcap_g->tg_ext_ents = tmp->tee_next;
662 free(tmp);
665 n_string_gut(&a_termcap_g->tg_dat);
666 free(a_termcap_g);
667 a_termcap_g = NULL;
668 #endif
669 NYD_LEAVE;
672 #ifdef HAVE_TERMCAP
673 FL void
674 n_termcap_resume(bool_t complete){
675 NYD_ENTER;
676 if(!(pstate & PS_TERMCAP_DISABLE) &&
677 (options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE){
678 if(complete && (pstate & PS_TERMCAP_CA_MODE))
679 n_termcap_cmdx(n_TERMCAP_CMD_ti);
680 n_termcap_cmdx(n_TERMCAP_CMD_ks);
681 fflush(stdout);
683 NYD_LEAVE;
686 FL void
687 n_termcap_suspend(bool_t complete){
688 NYD_ENTER;
689 if(!(pstate & PS_TERMCAP_DISABLE) &&
690 (options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) == OPT_INTERACTIVE){
691 if(complete && (pstate & PS_TERMCAP_CA_MODE))
692 n_termcap_cmdx(n_TERMCAP_CMD_ke);
693 n_termcap_cmdx(n_TERMCAP_CMD_te);
694 fflush(stdout);
696 NYD_LEAVE;
698 #endif /* HAVE_TERMCAP */
700 FL ssize_t
701 n_termcap_cmd(enum n_termcap_cmd cmd, ssize_t a1, ssize_t a2){
702 /* Commands are not lazy queried */
703 struct a_termcap_ent const *tep;
704 enum a_termcap_flags flags;
705 ssize_t rv;
706 NYD2_ENTER;
707 UNUSED(a1);
708 UNUSED(a2);
710 rv = FAL0;
711 if((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) != OPT_INTERACTIVE)
712 goto jleave;
713 assert(a_termcap_g != NULL);
715 flags = cmd & ~n__TERMCAP_CMD_MASK;
716 cmd &= n__TERMCAP_CMD_MASK;
717 tep = a_termcap_g->tg_ents;
719 if((flags & n_TERMCAP_CMD_FLAG_CA_MODE) && !(pstate & PS_TERMCAP_CA_MODE))
720 rv = TRU1;
721 else if((tep += cmd)->te_flags == 0 || (tep->te_flags & a_TERMCAP_F_NOENT))
722 rv = TRUM1;
723 else if(!(tep->te_flags & a_TERMCAP_F_ALTERN)){
724 char const *cp = a_termcap_g->tg_dat.s_dat + tep->te_off;
726 assert((tep->te_flags & a_TERMCAP_F_TYPE_MASK) ==
727 n_TERMCAP_CAPTYPE_STRING);
729 #ifdef HAVE_TERMCAP
730 if(tep->te_flags & (a_TERMCAP_F_ARG_IDX1 | a_TERMCAP_F_ARG_IDX2)){
731 if(pstate & PS_TERMCAP_DISABLE){
732 if(options & OPT_D_V){
733 char const *cnam = &a_termcap_namedat[
734 a_termcap_control[cmd].tc_off];
736 if(cnam[2] != '\0')
737 cnam += 2;
738 n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
739 "can't perform CAP \"%s\"\n"), cnam);
741 goto jleave;
744 /* Follow Thomas Dickey's advise on pre-va_arg prototypes, add 0s */
745 # ifdef HAVE_TERMINFO
746 if((cp = tparm(cp, a1, a2, 0,0,0,0,0,0,0)) == NULL)
747 goto jleave;
748 # else
749 /* curs_termcap.3:
750 * The \fBtgoto\fP function swaps the order of parameters.
751 * It does this also for calls requiring only a single parameter.
752 * In that case, the first parameter is merely a placeholder. */
753 if(!(tep->te_flags & a_TERMCAP_F_ARG_IDX2)){
754 a2 = a1;
755 a1 = (ui32_t)-1;
757 if((cp = tgoto(cp, (int)a1, (int)a2)) == NULL)
758 goto jleave;
759 # endif
761 #endif
763 for(;;){
764 #ifdef HAVE_TERMCAP
765 if(!(pstate & PS_TERMCAP_DISABLE)){
766 if(tputs(cp, 1, &a_termcap_putc) != OK)
767 break;
768 }else
769 #endif
770 if(fputs(cp, stdout) == EOF)
771 break;
772 if(!(tep->te_flags & a_TERMCAP_F_ARG_CNT) || --a1 <= 0){
773 rv = TRU1;
774 break;
777 goto jflush;
778 }else{
779 switch(cmd){
780 default:
781 rv = TRUM1;
782 break;
784 #ifdef HAVE_TERMCAP
785 case n_TERMCAP_CMD_cl: /* cl = ho + cd */
786 rv = n_termcap_cmdx(n_TERMCAP_CMD_ho);
787 if(rv > 0)
788 rv = n_termcap_cmdx(n_TERMCAP_CMD_cd | flags);
789 break;
790 #endif
792 #ifdef HAVE_MLE
793 case n_TERMCAP_CMD_ce: /* ce == ch + [:SPC:] */
794 if(a1 > 0)
795 --a1;
796 if((rv = n_termcap_cmd(n_TERMCAP_CMD_ch, a1, 0)) > 0){
797 for(a2 = scrnwidth - a1 - 1; a2 > 0; --a2)
798 if(putchar(' ') == EOF){
799 rv = FAL0;
800 break;
802 if(rv && n_termcap_cmd(n_TERMCAP_CMD_ch, a1, -1) != TRU1)
803 rv = FAL0;
805 break;
806 case n_TERMCAP_CMD_ch: /* ch == cr + nd */
807 rv = n_termcap_cmdx(n_TERMCAP_CMD_cr);
808 if(rv > 0 && a1 > 0){
809 rv = n_termcap_cmd(n_TERMCAP_CMD_nd, a1, -1);
811 break;
812 #endif /* HAVE_MLE */
815 jflush:
816 if(flags & n_TERMCAP_CMD_FLAG_FLUSH)
817 fflush(stdout);
818 if(ferror(stdout))
819 rv = FAL0;
822 jleave:
823 NYD2_LEAVE;
824 return rv;
827 FL bool_t
828 n_termcap_query(enum n_termcap_query query, struct n_termcap_value *tvp){
829 /* Queries are lazy queried upon request */
830 struct a_termcap_ent const *tep;
831 bool_t rv;
832 NYD2_ENTER;
834 assert(tvp != NULL);
835 rv = FAL0;
837 if((options & (OPT_INTERACTIVE | OPT_QUICKRUN_MASK)) != OPT_INTERACTIVE)
838 goto jleave;
839 assert(a_termcap_g != NULL);
841 /* Is it a builtin query? */
842 if(query != n__TERMCAP_QUERY_MAX){
843 tep = &a_termcap_g->tg_ents[n__TERMCAP_CMD_MAX + query];
845 if(tep->te_flags == 0
846 #ifdef HAVE_TERMCAP
847 && !a_termcap_ent_query_tcp(UNCONST(tep),
848 &a_termcap_control[n__TERMCAP_CMD_MAX + query])
849 #endif
851 goto jleave;
852 }else{
853 #ifdef HAVE_TERMCAP
854 size_t nlen;
855 #endif
856 struct a_termcap_ext_ent *teep;
857 char const *ndat = tvp->tv_data.tvd_string;
859 for(teep = a_termcap_g->tg_ext_ents; teep != NULL; teep = teep->tee_next)
860 if(!strcmp(teep->tee_name, ndat)){
861 tep = &teep->tee_super;
862 goto jextok;
865 #ifndef HAVE_TERMCAP
866 goto jleave;
867 #else
868 nlen = strlen(ndat) +1;
869 teep = smalloc(sizeof(*teep) -
870 VFIELD_SIZEOF(struct a_termcap_ext_ent, tee_name) + nlen);
871 tep = &teep->tee_super;
872 teep->tee_next = a_termcap_g->tg_ext_ents;
873 a_termcap_g->tg_ext_ents = teep;
874 memcpy(teep->tee_name, ndat, nlen);
876 if(!a_termcap_ent_query(UNCONST(tep), ndat,
877 n_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY))
878 goto jleave;
879 #endif
880 jextok:;
883 if(tep->te_flags & a_TERMCAP_F_NOENT)
884 goto jleave;
886 rv = (tep->te_flags & a_TERMCAP_F_ALTERN) ? TRUM1 : TRU1;
888 switch((tvp->tv_captype = tep->te_flags & a_TERMCAP_F_TYPE_MASK)){
889 case n_TERMCAP_CAPTYPE_BOOL:
890 tvp->tv_data.tvd_bool = (bool_t)tep->te_off;
891 break;
892 case n_TERMCAP_CAPTYPE_NUMERIC:
893 tvp->tv_data.tvd_numeric = (ui32_t)tep->te_off;
894 break;
895 default:
896 case n_TERMCAP_CAPTYPE_STRING:
897 tvp->tv_data.tvd_string = a_termcap_g->tg_dat.s_dat + tep->te_off;
898 break;
900 jleave:
901 NYD2_LEAVE;
902 return rv;
905 #ifdef HAVE_KEY_BINDINGS
906 FL si32_t
907 n_termcap_query_for_name(char const *name, enum n_termcap_captype type){
908 si32_t rv;
909 NYD2_ENTER;
911 if((rv = a_termcap_query_for_name(name, strlen(name))) >= 0){
912 struct a_termcap_control const *tcp = &a_termcap_control[(ui32_t)rv];
914 if(type != n_TERMCAP_CAPTYPE_NONE &&
915 (tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != type)
916 rv = -2;
917 else
918 rv -= n__TERMCAP_CMD_MAX;
920 NYD2_LEAVE;
921 return rv;
924 FL char const *
925 n_termcap_name_of_query(enum n_termcap_query query){
926 char const *rv;
927 NYD2_ENTER;
929 rv = &a_termcap_namedat[
930 a_termcap_control[n__TERMCAP_CMD_MAX + query].tc_off + 2];
931 NYD2_LEAVE;
932 return rv;
934 #endif /* HAVE_KEY_BINDINGS */
935 #endif /* n_HAVE_TCAP */
937 /* s-it-mode */