make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / termcap.c
blobad3d3717adafdc6ceded2bdd42d5b255e8add034
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 config.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 make-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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
12 * SPDX-License-Identifier: ISC
14 * Permission to use, copy, modify, and/or distribute this software for any
15 * purpose with or without fee is hereby granted, provided that the above
16 * copyright notice and this permission notice appear in all copies.
18 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
20 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
21 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
23 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
24 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 #undef n_FILE
27 #define n_FILE termcap
29 #ifndef HAVE_AMALGAMATION
30 # include "nail.h"
31 #endif
33 EMPTY_FILE()
34 #ifdef n_HAVE_TCAP
35 /* If available, curses.h must be included before term.h! */
36 #ifdef HAVE_TERMCAP
37 # ifdef HAVE_TERMCAP_CURSES
38 # include <curses.h>
39 # endif
40 # include <term.h>
41 #endif
44 * xxx We are not really compatible with very old and strange terminals since
45 * we don't care at all for circumstances indicated by terminal flags: if we
46 * find a capability we use it and assume it works. E.g., if "Co" indicates
47 * colours we simply use ISO 6429 also for font attributes etc. That is,
48 * we don't use the ncurses/terminfo interface with all its internal logic.
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 + 128) & ~127) /* 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_MAX1 first! And don't confound with ENTRYSIZE_MAX! */
64 enum{
65 a_TERMCAP_ENT_MAX1 = n__TERMCAP_CMD_MAX1 + n__TERMCAP_QUERY_MAX1
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_MAX1 <= (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 ERR_NOENT,
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[n_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_MAX1];
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 make-tcap-map.pl output */
128 #include <gen-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_MAX1 == n_NELEM(a_termcap_control),
132 "Control array does not 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. False for parse error and empty results */
140 static bool_t 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 n_INLINE 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_MAX1)
165 #define a_termcap_query_for_name(NB,NL) \
166 a_termcap_enum_for_name(NB, NL, n__TERMCAP_CMD_MAX1, a_TERMCAP_ENT_MAX1)
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 = n_autorec_alloc(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;
219 tci = a_termcap_enum_for_name(ccp, kl, 0, a_TERMCAP_ENT_MAX1);
220 if(tci < 0){
221 /* For key binding purposes, save any given string */
222 #ifdef HAVE_KEY_BINDINGS
223 if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_STRING){
224 struct a_termcap_ext_ent *teep;
226 teep = n_alloc(n_VSTRUCT_SIZEOF(struct a_termcap_ext_ent,
227 tee_name) + kl +1);
228 teep->tee_next = a_termcap_g->tg_ext_ents;
229 a_termcap_g->tg_ext_ents = teep;
230 memcpy(teep->tee_name, ccp, kl);
231 teep->tee_name[kl] = '\0';
233 tep = &teep->tee_super;
234 tep->te_flags = n_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY;
235 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
236 if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
237 tep->te_flags |= a_TERMCAP_F_DISABLED;
238 goto jlearned;
239 }else
240 #endif /* HAVE_KEY_BINDINGS */
241 if(n_poption & n_PO_D_V)
242 n_err(_("*termcap*: unknown capability: %s\n"), ccp);
243 continue;
245 i = (size_t)tci;
247 tcp = &a_termcap_control[i];
248 if((tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != f){
249 n_err(_("*termcap*: entry type mismatch: %s\n"), ccp);
250 break;
252 tep = &a_termcap_g->tg_ents[i];
253 tep->te_flags = tcp->tc_flags;
254 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
257 if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_BOOL)
259 else if(*v == '\0')
260 tep->te_flags |= a_TERMCAP_F_DISABLED;
261 else if((f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_NUMERIC){
262 if((n_idec_ui16_cp(&tep->te_off, v, 0, NULL
263 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
264 ) != n_IDEC_STATE_CONSUMED)
265 goto jeinvent;
266 }else if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
267 tep->te_flags |= a_TERMCAP_F_DISABLED;
268 #ifdef HAVE_KEY_BINDINGS
269 jlearned:
270 #endif
271 if(n_poption & n_PO_D_V)
272 n_err(_("*termcap*: learned %.*s: %s\n"), (int)kl, ccp,
273 (tep->te_flags & a_TERMCAP_F_DISABLED ? "<disabled>"
274 : (f & a_TERMCAP_F_TYPE_MASK) == n_TERMCAP_CAPTYPE_BOOL ? "true"
275 : v));
277 DBG( if(n_poption & n_PO_D_V) n_err("*termcap* parsed: buffer used=%lu\n",
278 (ul_i)a_termcap_g->tg_dat.s_len) );
280 /* Catch some inter-dependencies the user may have triggered */
281 #ifdef HAVE_TERMCAP
282 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags & a_TERMCAP_F_DISABLED)
283 a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags = a_TERMCAP_F_DISABLED;
284 else if(a_termcap_g->tg_ents[n_TERMCAP_CMD_ti].te_flags &
285 a_TERMCAP_F_DISABLED)
286 a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags = a_TERMCAP_F_DISABLED;
287 #endif
289 j_leave:
290 NYD2_LEAVE;
293 static bool_t
294 a_termcap__strexp(struct n_string *store, char const *ibuf){ /* XXX ASCII */
295 char c;
296 char const *oibuf;
297 size_t olen;
298 NYD2_ENTER;
300 olen = store->s_len;
302 for(oibuf = ibuf; (c = *ibuf) != '\0';){
303 if(c == '\\'){
304 if((c = ibuf[1]) == '\0')
305 goto jebsseq;
307 if(c == 'E'){
308 c = '\033';
309 ibuf += 2;
310 goto jpush;
313 if(octalchar(c)){
314 char c2, c3;
316 if((c2 = ibuf[2]) == '\0' || !octalchar(c2) ||
317 (c3 = ibuf[3]) == '\0' || !octalchar(c3)){
318 n_err(_("*termcap*: invalid octal sequence: %s\n"), oibuf);
319 goto jerr;
321 c -= '0', c2 -= '0', c3 -= '0';
322 c <<= 3, c |= c2;
323 if((ui8_t)c > 0x1F){
324 n_err(_("*termcap*: octal number too large: %s\n"), oibuf);
325 goto jerr;
327 c <<= 3, c |= c3;
328 ibuf += 4;
329 goto jpush;
331 jebsseq:
332 n_err(_("*termcap*: invalid reverse solidus \\ sequence: %s\n"),oibuf);
333 goto jerr;
334 }else if(c == '^'){
335 if((c = ibuf[1]) == '\0'){
336 n_err(_("*termcap*: incomplete ^CNTRL sequence: %s\n"), oibuf);
337 goto jerr;
339 c = upperconv(c) ^ 0x40;
340 if((ui8_t)c > 0x1F && c != 0x7F){ /* ASCII C0: 0..1F, 7F */
341 n_err(_("*termcap*: invalid ^CNTRL sequence: %s\n"), oibuf);
342 goto jerr;
344 ibuf += 2;
345 }else
346 ++ibuf;
348 jpush:
349 store = n_string_push_c(store, c);
352 c = (store->s_len != olen) ? '\1' : '\0';
353 jleave:
354 n_string_push_c(store, '\0');
355 NYD2_LEAVE;
356 return (c != '\0');
357 jerr:
358 store = n_string_trunc(store, olen);
359 c = '\0';
360 goto jleave;
363 static void
364 a_termcap_init_altern(void){
365 /* We silently ignore user _F_DISABLED requests for those entries for which
366 * we have fallback entries, and which we need to ensure proper functioning.
367 * I.e., this allows users to explicitly disable some termcap(5) capability
368 * and enforce usage of the built-in fallback */
369 /* xxx Use table-based approach for fallback strategies */
370 #define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
371 #define a_OOK(TEP) \
372 ((TEP)->te_flags != 0 && !((TEP)->te_flags & a_TERMCAP_F_NOENT))
373 #define a_SET(TEP,CMD,ALT) \
374 (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
375 ((ALT) ? a_TERMCAP_F_ALTERN : 0)
377 struct a_termcap_ent *tep;
378 NYD2_ENTER;
379 n_UNUSED(tep);
381 /* For simplicity in the rest of this file null flags of disabled commands,
382 * as we won't check and try to lazy query any command */
383 /* C99 */{
384 size_t i;
386 for(i = n__TERMCAP_CMD_MAX1;;){
387 if(i-- == 0)
388 break;
389 if((tep = &a_termcap_g->tg_ents[i])->te_flags & a_TERMCAP_F_DISABLED)
390 tep->te_flags = 0;
394 #ifdef HAVE_MLE
395 /* ce == ch + [:SPACE:] (start column specified by argument) */
396 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ce];
397 if(!a_OOK(tep))
398 a_SET(tep, n_TERMCAP_CMD_ce, TRU1);
400 /* ch == cr[\r] + nd[:\033C:] */
401 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_ch];
402 if(!a_OOK(tep))
403 a_SET(tep, n_TERMCAP_CMD_ch, TRU1);
405 /* cr == \r */
406 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cr];
407 if(!a_OOK(tep)){
408 a_SET(tep, n_TERMCAP_CMD_cr, FAL0);
409 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
410 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\r'), '\0');
413 /* le == \b */
414 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_le];
415 if(!a_OOK(tep)){
416 a_SET(tep, n_TERMCAP_CMD_le, FAL0);
417 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
418 n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\b'), '\0');
421 /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
422 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_nd];
423 if(!a_OOK(tep)){
424 a_SET(tep, n_TERMCAP_CMD_nd, FAL0);
425 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
426 n_string_push_buf(&a_termcap_g->tg_dat, "\033[C", sizeof("\033[C"));
429 # ifdef HAVE_TERMCAP
430 /* cl == ho+cd */
431 tep = &a_termcap_g->tg_ents[n_TERMCAP_CMD_cl];
432 if(!a_OOK(tep)){
433 if(a_OK(n_TERMCAP_CMD_cd) && a_OK(n_TERMCAP_CMD_ho))
434 a_SET(tep, n_TERMCAP_CMD_cl, TRU1);
436 # endif
437 #endif /* HAVE_MLE */
439 NYD2_LEAVE;
440 #undef a_OK
441 #undef a_OOK
442 #undef a_SET
445 #ifdef HAVE_TERMCAP
446 # ifdef HAVE_TERMINFO
447 static bool_t
448 a_termcap_load(char const *term){
449 bool_t rv;
450 int err;
451 NYD2_ENTER;
453 if(!(rv = (setupterm(term, fileno(n_tty_fp), &err) == OK)))
454 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
455 NYD2_LEAVE;
456 return rv;
459 static bool_t
460 a_termcap_ent_query(struct a_termcap_ent *tep,
461 char const *cname, ui16_t cflags){
462 bool_t rv;
463 NYD2_ENTER;
464 assert(!(n_psonce & n_PSO_TERMCAP_DISABLE));
466 if(n_UNLIKELY(*cname == '\0'))
467 rv = FAL0;
468 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
469 case n_TERMCAP_CAPTYPE_BOOL:
470 tep->te_off = (tigetflag(cname) > 0);
471 rv = TRU1;
472 break;
473 case n_TERMCAP_CAPTYPE_NUMERIC:{
474 int r = tigetnum(cname);
476 if((rv = (r >= 0)))
477 tep->te_off = (ui16_t)n_MIN(UI16_MAX, r);
478 else
479 tep->te_flags |= a_TERMCAP_F_NOENT;
480 }break;
481 default:
482 case n_TERMCAP_CAPTYPE_STRING:{
483 char *cp;
485 cp = tigetstr(cname);
486 if((rv = (cp != NULL && cp != (char*)-1))){
487 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
488 n_string_push_buf(&a_termcap_g->tg_dat, cp, strlen(cp) +1);
489 }else
490 tep->te_flags |= a_TERMCAP_F_NOENT;
491 }break;
493 NYD2_LEAVE;
494 return rv;
497 n_INLINE bool_t
498 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
499 struct a_termcap_control const *tcp){
500 assert(!(n_psonce & n_PSO_TERMCAP_DISABLE));
501 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off] + 2,
502 tcp->tc_flags);
505 # else /* HAVE_TERMINFO */
506 static bool_t
507 a_termcap_load(char const *term){
508 bool_t rv;
509 NYD2_ENTER;
511 /* ncurses may return -1 */
512 # ifndef HAVE_TGETENT_NULL_BUF
513 # define a_BUF &a_termcap_g->tg_lib_buf[0]
514 # else
515 # define a_BUF NULL
516 # endif
517 if(!(rv = tgetent(a_BUF, term) > 0))
518 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
519 # undef a_BUF
520 NYD2_LEAVE;
521 return rv;
524 static bool_t
525 a_termcap_ent_query(struct a_termcap_ent *tep,
526 char const *cname, ui16_t cflags){
527 bool_t rv;
528 NYD2_ENTER;
529 assert(!(n_psonce & n_PSO_TERMCAP_DISABLE));
531 if(n_UNLIKELY(*cname == '\0'))
532 rv = FAL0;
533 else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
534 case n_TERMCAP_CAPTYPE_BOOL:
535 tep->te_off = (tgetflag(cname) > 0);
536 rv = TRU1;
537 break;
538 case n_TERMCAP_CAPTYPE_NUMERIC:{
539 int r = tgetnum(cname);
541 if((rv = (r >= 0)))
542 tep->te_off = (ui16_t)n_MIN(UI16_MAX, r);
543 else
544 tep->te_flags |= a_TERMCAP_F_NOENT;
545 }break;
546 default:
547 case n_TERMCAP_CAPTYPE_STRING:{
548 # ifndef HAVE_TGETENT_NULL_BUF
549 char buf_base[a_TERMCAP_ENTRYSIZE_MAX], *buf = &buf_base[0];
550 # define a_BUF &buf
551 # else
552 # define a_BUF NULL
553 # endif
554 char *cp;
556 if((rv = ((cp = tgetstr(cname, a_BUF)) != NULL))){
557 tep->te_off = (ui16_t)a_termcap_g->tg_dat.s_len;
558 n_string_push_buf(&a_termcap_g->tg_dat, cp, strlen(cp) +1);
559 # undef a_BUF
560 }else
561 tep->te_flags |= a_TERMCAP_F_NOENT;
562 }break;
564 NYD2_LEAVE;
565 return rv;
568 n_INLINE bool_t
569 a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
570 struct a_termcap_control const *tcp){
571 assert(!(n_psonce & n_PSO_TERMCAP_DISABLE));
572 return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off],
573 tcp->tc_flags);
575 # endif /* !HAVE_TERMINFO */
577 static int
578 a_termcap_putc(int c){
579 return putc(c, n_tty_fp);
581 #endif /* HAVE_TERMCAP */
583 static si32_t
584 a_termcap_enum_for_name(char const *name, size_t nlen, si32_t min, si32_t max){
585 struct a_termcap_control const *tcp;
586 char const *cnam;
587 si32_t rv;
588 NYD2_ENTER;
590 /* Prefer terminfo(5) names */
591 for(rv = max;;){
592 if(rv-- == min){
593 rv = -1;
594 break;
597 tcp = &a_termcap_control[(ui32_t)rv];
598 cnam = &a_termcap_namedat[tcp->tc_off];
599 if(cnam[2] != '\0'){
600 char const *xcp = cnam + 2;
602 if(nlen == strlen(xcp) && !memcmp(xcp, name, nlen))
603 break;
605 if(nlen == 2 && cnam[0] == name[0] && cnam[1] == name[1])
606 break;
608 NYD2_LEAVE;
609 return rv;
612 FL void
613 n_termcap_init(void){
614 struct str termvar;
615 char const *ccp;
616 NYD_ENTER;
618 assert((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_QUICKRUN_MASK));
620 a_termcap_g = n_alloc(sizeof *a_termcap_g);
621 a_termcap_g->tg_ext_ents = NULL;
622 memset(&a_termcap_g->tg_ents[0], 0, sizeof(a_termcap_g->tg_ents));
623 if((ccp = ok_vlook(termcap)) != NULL)
624 termvar.l = strlen(termvar.s = n_UNCONST(ccp));
625 else
626 /*termvar.s = NULL,*/ termvar.l = 0;
627 n_string_reserve(n_string_creat(&a_termcap_g->tg_dat),
628 ((termvar.l + (256 - 64)) & ~127));
630 if(termvar.l > 0)
631 a_termcap_init_var(&termvar);
633 if(ok_blook(termcap_disable))
634 n_psonce |= n_PSO_TERMCAP_DISABLE;
635 #ifdef HAVE_TERMCAP
636 else if((ccp = ok_vlook(TERM)) == NULL){
637 n_err(_("Environment variable $TERM is not set, using only *termcap*\n"));
638 n_psonce |= n_PSO_TERMCAP_DISABLE;
639 }else if(!a_termcap_load(ccp))
640 n_psonce |= n_PSO_TERMCAP_DISABLE;
641 else{
642 /* Query termcap(5) for each command slot that is not yet set */
643 struct a_termcap_ent *tep;
644 size_t i;
646 for(i = n__TERMCAP_CMD_MAX1;;){
647 if(i-- == 0)
648 break;
649 if((tep = &a_termcap_g->tg_ents[i])->te_flags == 0)
650 a_termcap_ent_query_tcp(tep, &a_termcap_control[i]);
653 #endif /* HAVE_TERMCAP */
655 a_termcap_init_altern();
657 #ifdef HAVE_TERMCAP
658 if(a_termcap_g->tg_ents[n_TERMCAP_CMD_te].te_flags != 0 &&
659 ok_blook(termcap_ca_mode))
660 n_psonce |= n_PSO_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((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_QUICKRUN_MASK));
670 assert(a_termcap_g != NULL);
672 n_TERMCAP_SUSPEND(TRU1);
674 #ifdef HAVE_DEBUG
675 /* C99 */{
676 struct a_termcap_ext_ent *tmp;
678 while((tmp = a_termcap_g->tg_ext_ents) != NULL){
679 a_termcap_g->tg_ext_ents = tmp->tee_next;
680 n_free(tmp);
683 n_string_gut(&a_termcap_g->tg_dat);
684 n_free(a_termcap_g);
685 a_termcap_g = NULL;
686 #endif
687 NYD_LEAVE;
690 #ifdef HAVE_TERMCAP
691 FL void
692 n_termcap_resume(bool_t complete){
693 NYD_ENTER;
694 if(a_termcap_g != NULL && !(n_psonce & n_PSO_TERMCAP_DISABLE)){
695 if(complete && (n_psonce & n_PSO_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(a_termcap_g != NULL && !(n_psonce & n_PSO_TERMCAP_DISABLE)){
707 n_termcap_cmdx(n_TERMCAP_CMD_ke);
708 if(complete && (n_psonce & n_PSO_TERMCAP_CA_MODE))
709 n_termcap_cmdx(n_TERMCAP_CMD_te);
710 fflush(n_tty_fp);
712 NYD_LEAVE;
714 #endif /* HAVE_TERMCAP */
716 FL ssize_t
717 n_termcap_cmd(enum n_termcap_cmd cmd, ssize_t a1, ssize_t a2){
718 /* Commands are not lazy queried */
719 struct a_termcap_ent const *tep;
720 enum a_termcap_flags flags;
721 ssize_t rv;
722 NYD2_ENTER;
723 n_UNUSED(a1);
724 n_UNUSED(a2);
726 rv = FAL0;
727 if(a_termcap_g == NULL)
728 goto jleave;
730 flags = cmd & ~n__TERMCAP_CMD_MASK;
731 cmd &= n__TERMCAP_CMD_MASK;
732 tep = a_termcap_g->tg_ents;
734 if((flags & n_TERMCAP_CMD_FLAG_CA_MODE) &&
735 !(n_psonce & n_PSO_TERMCAP_CA_MODE))
736 rv = TRU1;
737 else if((tep += cmd)->te_flags == 0 || (tep->te_flags & a_TERMCAP_F_NOENT))
738 rv = TRUM1;
739 else if(!(tep->te_flags & a_TERMCAP_F_ALTERN)){
740 char const *cp;
742 assert((tep->te_flags & a_TERMCAP_F_TYPE_MASK) ==
743 n_TERMCAP_CAPTYPE_STRING);
745 cp = &a_termcap_g->tg_dat.s_dat[tep->te_off];
747 #ifdef HAVE_TERMCAP
748 if(tep->te_flags & (a_TERMCAP_F_ARG_IDX1 | a_TERMCAP_F_ARG_IDX2)){
749 if(n_psonce & n_PSO_TERMCAP_DISABLE){
750 if(n_poption & n_PO_D_V){
751 char const *cnam = &a_termcap_namedat[
752 a_termcap_control[cmd].tc_off];
754 if(cnam[2] != '\0')
755 cnam += 2;
756 n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
757 "can't perform CAP: %s\n"), cnam);
759 goto jleave;
762 /* Follow Thomas Dickey's advise on pre-va_arg prototypes, add 0s */
763 # ifdef HAVE_TERMINFO
764 if((cp = tparm(cp, a1, a2, 0,0,0,0,0,0,0)) == NULL)
765 goto jleave;
766 # else
767 /* curs_termcap.3:
768 * The \fBtgoto\fP function swaps the order of parameters.
769 * It does this also for calls requiring only a single parameter.
770 * In that case, the first parameter is merely a placeholder. */
771 if(!(tep->te_flags & a_TERMCAP_F_ARG_IDX2)){
772 a2 = a1;
773 a1 = (ui32_t)-1;
775 if((cp = tgoto(cp, (int)a1, (int)a2)) == NULL)
776 goto jleave;
777 # endif
779 #endif /* HAVE_TERMCAP */
781 for(;;){
782 #ifdef HAVE_TERMCAP
783 if(!(n_psonce & n_PSO_TERMCAP_DISABLE)){
784 if(tputs(cp, 1, &a_termcap_putc) != OK)
785 break;
786 }else
787 #endif
788 if(fputs(cp, n_tty_fp) == EOF)
789 break;
790 if(!(tep->te_flags & a_TERMCAP_F_ARG_CNT) || --a1 <= 0){
791 rv = TRU1;
792 break;
795 goto jflush;
796 }else{
797 switch(cmd){
798 default:
799 rv = TRUM1;
800 break;
802 #ifdef HAVE_MLE
803 case n_TERMCAP_CMD_ce: /* ce == ch + [:SPACE:] */
804 if(a1 > 0)
805 --a1;
806 if((rv = n_termcap_cmd(n_TERMCAP_CMD_ch, a1, 0)) > 0){
807 for(a2 = n_scrnwidth - a1 - 1; a2 > 0; --a2)
808 if(putc(' ', n_tty_fp) == EOF){
809 rv = FAL0;
810 break;
812 if(rv && n_termcap_cmd(n_TERMCAP_CMD_ch, a1, -1) != TRU1)
813 rv = FAL0;
815 break;
816 case n_TERMCAP_CMD_ch: /* ch == cr + nd */
817 rv = n_termcap_cmdx(n_TERMCAP_CMD_cr);
818 if(rv > 0 && a1 > 0){
819 rv = n_termcap_cmd(n_TERMCAP_CMD_nd, a1, -1);
821 break;
822 # ifdef HAVE_TERMCAP
823 case n_TERMCAP_CMD_cl: /* cl = ho + cd */
824 rv = n_termcap_cmdx(n_TERMCAP_CMD_ho);
825 if(rv > 0)
826 rv = n_termcap_cmdx(n_TERMCAP_CMD_cd | flags);
827 break;
828 # endif
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);
853 rv = FAL0;
854 if(a_termcap_g == NULL)
855 goto jleave;
857 /* Is it a built-in query? */
858 if(query != n__TERMCAP_QUERY_MAX1){
859 tep = &a_termcap_g->tg_ents[n__TERMCAP_CMD_MAX1 + query];
861 if(tep->te_flags == 0
862 #ifdef HAVE_TERMCAP
863 && ((n_psonce & n_PSO_TERMCAP_DISABLE) ||
864 !a_termcap_ent_query_tcp(n_UNCONST(tep),
865 &a_termcap_control[n__TERMCAP_CMD_MAX1 + query]))
866 #endif
868 goto jleave;
869 }else{
870 #ifdef HAVE_TERMCAP
871 size_t nlen;
872 #endif
873 struct a_termcap_ext_ent *teep;
874 char const *ndat = tvp->tv_data.tvd_string;
876 for(teep = a_termcap_g->tg_ext_ents; teep != NULL; teep = teep->tee_next)
877 if(!strcmp(teep->tee_name, ndat)){
878 tep = &teep->tee_super;
879 goto jextok;
882 #ifdef HAVE_TERMCAP
883 if(n_psonce & n_PSO_TERMCAP_DISABLE)
884 #endif
885 goto jleave;
886 #ifdef HAVE_TERMCAP
887 nlen = strlen(ndat) +1;
888 teep = n_alloc(n_VSTRUCT_SIZEOF(struct a_termcap_ext_ent, tee_name) +
889 nlen);
890 tep = &teep->tee_super;
891 teep->tee_next = a_termcap_g->tg_ext_ents;
892 a_termcap_g->tg_ext_ents = teep;
893 memcpy(teep->tee_name, ndat, nlen);
895 if(!a_termcap_ent_query(n_UNCONST(tep), ndat,
896 n_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY))
897 goto jleave;
898 #endif
899 jextok:;
902 if(tep->te_flags & a_TERMCAP_F_NOENT)
903 goto jleave;
905 rv = (tep->te_flags & a_TERMCAP_F_ALTERN) ? TRUM1 : TRU1;
907 switch((tvp->tv_captype = tep->te_flags & a_TERMCAP_F_TYPE_MASK)){
908 case n_TERMCAP_CAPTYPE_BOOL:
909 tvp->tv_data.tvd_bool = (bool_t)tep->te_off;
910 break;
911 case n_TERMCAP_CAPTYPE_NUMERIC:
912 tvp->tv_data.tvd_numeric = (ui32_t)tep->te_off;
913 break;
914 default:
915 case n_TERMCAP_CAPTYPE_STRING:
916 tvp->tv_data.tvd_string = a_termcap_g->tg_dat.s_dat + tep->te_off;
917 break;
919 jleave:
920 NYD2_LEAVE;
921 return rv;
924 #ifdef HAVE_KEY_BINDINGS
925 FL si32_t
926 n_termcap_query_for_name(char const *name, enum n_termcap_captype type){
927 si32_t rv;
928 NYD2_ENTER;
930 if((rv = a_termcap_query_for_name(name, strlen(name))) >= 0){
931 struct a_termcap_control const *tcp = &a_termcap_control[(ui32_t)rv];
933 if(type != n_TERMCAP_CAPTYPE_NONE &&
934 (tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != type)
935 rv = -2;
936 else
937 rv -= n__TERMCAP_CMD_MAX1;
939 NYD2_LEAVE;
940 return rv;
943 FL char const *
944 n_termcap_name_of_query(enum n_termcap_query query){
945 char const *rv;
946 NYD2_ENTER;
948 rv = &a_termcap_namedat[
949 a_termcap_control[n__TERMCAP_CMD_MAX1 + query].tc_off + 2];
950 NYD2_LEAVE;
951 return rv;
953 #endif /* HAVE_KEY_BINDINGS */
954 #endif /* n_HAVE_TCAP */
956 /* s-it-mode */