1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Implementation of termcap.h.
3 *@ For encapsulation purposes provide a basic foundation even without
4 *@ HOWTO add a new non-dynamic command or query:
5 *@ - add an entry to enum mx_termcap_{cmd,query}
6 *@ - run make-tcap-map.pl
7 *@ - update the *termcap* member documentation on changes!
8 *@ Bug: in case of clashes of two-letter names terminfo(5) wins.
10 * Copyright (c) 2016 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
11 * SPDX-License-Identifier: ISC
13 * Permission to use, copy, modify, and/or distribute this software for any
14 * purpose with or without fee is hereby granted, provided that the above
15 * copyright notice and this permission notice appear in all copies.
17 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
18 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
20 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
21 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
22 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
23 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 #define su_FILE termcap
29 #ifndef mx_HAVE_AMALGAMATION
34 #include "mx/termcap.h"
37 /* If available, curses.h must be included before term.h! */
38 #ifdef mx_HAVE_TERMCAP
39 # ifdef mx_HAVE_TERMCAP_CURSES
46 #include <su/icodec.h>
49 #include "mx/termios.h"
52 /* Already: #include "mx/termcap.h"*/
54 #include "su/code-in.h"
57 * xxx We are not really compatible with very old and strange terminals since
58 * we don't care at all for circumstances indicated by terminal flags: if we
59 * find a capability we use it and assume it works. E.g., if "Co" indicates
60 * colours we simply use ISO 6429 also for font attributes etc. That is,
61 * we don't use the ncurses/terminfo interface with all its internal logic.
64 /* Unless mx_HAVE_TERMINFO or mx_HAVE_TGETENT_NULL_BUF are defined we use this
65 * value to space the buffer we pass through to tgetent(3).
66 * Since for (such) elder non-emulated terminals really weird things will
67 * happen if an entry would require more than 1024 bytes, don't really mind.
68 * Use a u16 for storage */
69 #define a_TERMCAP_ENTRYSIZE_MAX ((2668 + 128) & ~127) /* As of ncurses 6.0 */
71 CTA(a_TERMCAP_ENTRYSIZE_MAX
< U16_MAX
,
72 "Chosen buffer size exceeds datatype capability");
74 /* For simplicity we store commands and queries in single continuous control
75 * and entry structure arrays: to index queries one has to add
76 * mx__TERMCAP_CMD_MAX1 first! And don't confound with ENTRYSIZE_MAX! */
78 a_TERMCAP_ENT_MAX1
= mx__TERMCAP_CMD_MAX1
+ mx__TERMCAP_QUERY_MAX1
83 /* enum mx_termcap_captype values stored here.
84 * Note presence of a type in an a_termcap_ent signals initialization */
85 a_TERMCAP_F_TYPE_MASK
= (1u<<4) - 1,
87 a_TERMCAP_F_QUERY
= 1u<<4, /* A query rather than a command */
88 a_TERMCAP_F_DISABLED
= 1u<<5, /* User explicitly disabled command/query */
89 a_TERMCAP_F_ALTERN
= 1u<<6, /* Not available, but has alternative */
90 a_TERMCAP_F_NOENT
= 1u<<7, /* Not available */
92 /* _cmd() argument interpretation (_T_STR) */
93 a_TERMCAP_F_ARG_IDX1
= 1u<<11, /* Argument 1 used, and is an index */
94 a_TERMCAP_F_ARG_IDX2
= 1u<<12,
95 a_TERMCAP_F_ARG_CNT
= 1u<<13, /* .., and is a count */
97 a_TERMCAP_F__LAST
= a_TERMCAP_F_ARG_CNT
99 CTA(S(u32
,mx__TERMCAP_CAPTYPE_MAX1
) <= S(u32
,a_TERMCAP_F_TYPE_MASK
),
100 "enum mx_termcap_captype exceeds bit range of a_termcap_flags");
102 struct a_termcap_control
{
104 /* Offset base into a_termcap_namedat[], which stores the two-letter
105 * termcap(5) name directly followed by a NUL terminated terminfo(5) name.
106 * A termcap(5) name may consist of two NULs meaning ERR_NOENT,
107 * a terminfo(5) name may be empty for the same purpose */
110 CTA(a_TERMCAP_F__LAST
<= U16_MAX
,
111 "a_termcap_flags exceed storage datatype in a_termcap_control");
113 struct a_termcap_ent
{
115 u16 te_off
; /* in a_termcap_g->tg_dat / value for T_BOOL and T_NUM */
117 CTA(a_TERMCAP_F__LAST
<= U16_MAX
,
118 "a_termcap_flags exceed storage datatype in a_termcap_ent");
120 /* Structure for extended queries, which don't have an entry constant in
121 * mx_termcap_query (to allow free query/binding of keycodes) */
122 struct a_termcap_ext_ent
{
123 struct a_termcap_ent tee_super
;
125 struct a_termcap_ext_ent
*tee_next
;
126 /* Resolvable termcap(5)/terminfo(5) name as given by user; the actual data
127 * is stored just like for normal queries */
128 char tee_name
[VFIELD_SIZE(0)];
132 struct a_termcap_ext_ent
*tg_ext_ents
; /* List of extended queries */
133 struct a_termcap_ent tg_ents
[a_TERMCAP_ENT_MAX1
];
134 struct n_string tg_dat
; /* Storage for resolved caps */
135 # if !defined mx_HAVE_TGETENT_NULL_BUF && !defined mx_HAVE_TERMINFO
136 char tg_lib_buf
[a_TERMCAP_ENTRYSIZE_MAX
];
140 /* Include the constant make-tcap-map.pl output */
141 #include "mx/gen-tcaps.h" /* $(MX_SRCDIR) */
142 CTA(sizeof a_termcap_namedat
<= U16_MAX
,
143 "Termcap command and query name data exceed storage datatype");
144 CTA(a_TERMCAP_ENT_MAX1
== NELEM(a_termcap_control
),
145 "Control array does not match command/query array to be controlled");
147 static struct a_termcap_g
*a_termcap_g
;
149 /* Query *termcap*, parse it and incorporate into a_termcap_g */
150 static void a_termcap_init_var(struct str
const *termvar
);
152 /* Expand ^CNTRL, \[Ee] and \OCT. False for parse error and empty results */
153 static boole
a_termcap__strexp(struct n_string
*store
, char const *ibuf
);
155 /* Initialize any _ent for which we have _F_ALTERN and which isn't yet set */
156 static void a_termcap_init_altern(void);
158 #ifdef mx_HAVE_TERMCAP
159 /* Setup the library we use to work with term */
160 static boole
a_termcap_load(char const *term
);
162 /* Query the capability tcp and fill in tep (upon success) */
163 static boole
a_termcap_ent_query(struct a_termcap_ent
*tep
,
164 char const *cname
, u16 cflags
);
165 su_SINLINE boole
a_termcap_ent_query_tcp(struct a_termcap_ent
*tep
,
166 struct a_termcap_control
const *tcp
);
168 /* Output PTF for both, termcap(5) and terminfo(5) */
169 static int a_termcap_putc(int c
);
172 /* Get mx_termcap_cmd or mx_termcap_query constant belonging to (nlen bytes of)
173 * name, -1 if not found. min and max have to be used to cramp the result */
174 static s32
a_termcap_enum_for_name(char const *name
, uz nlen
,
176 #define a_termcap_cmd_for_name(NB,NL) \
177 a_termcap_enum_for_name(NB, NL, 0, mx__TERMCAP_CMD_MAX1)
178 #define a_termcap_query_for_name(NB,NL) \
179 a_termcap_enum_for_name(NB, NL, mx__TERMCAP_CMD_MAX1, a_TERMCAP_ENT_MAX1)
182 a_termcap_init_var(struct str
const *termvar
){
183 char *cbp_base
, *cbp
;
188 if(termvar
->l
>= U16_MAX
){
189 n_err(_("*termcap*: length excesses internal limit, skipping\n"));
193 ASSERT(termvar
->s
[termvar
->l
] == '\0');
195 cbp_base
= n_autorec_alloc(i
);
196 su_mem_copy(cbp
= cbp_base
, termvar
->s
, i
);
198 for(; (ccp
= su_cs_sep_c(&cbp
, ',', TRU1
)) != NULL
;){
199 struct a_termcap_ent
*tep
;
204 /* Separate key/value, if any */
205 if(/* no empties ccp[0] == '\0' ||*/ ccp
[1] == '\0'){
207 n_err(_("*termcap*: invalid entry: %s\n"), ccp
);
210 for(kl
= 2, v
= &ccp
[2];; ++kl
, ++v
){
214 f
= mx_TERMCAP_CAPTYPE_BOOL
;
217 f
= mx_TERMCAP_CAPTYPE_NUMERIC
;
221 f
= mx_TERMCAP_CAPTYPE_STRING
;
227 /* Do we know about this one? */
229 struct a_termcap_control
const *tcp
;
232 tci
= a_termcap_enum_for_name(ccp
, kl
, 0, a_TERMCAP_ENT_MAX1
);
234 /* For key binding purposes, save any given string */
235 #ifdef mx_HAVE_KEY_BINDINGS
236 if((f
& a_TERMCAP_F_TYPE_MASK
) == mx_TERMCAP_CAPTYPE_STRING
){
237 struct a_termcap_ext_ent
*teep
;
239 teep
= n_alloc(VSTRUCT_SIZEOF(struct a_termcap_ext_ent
,
241 teep
->tee_next
= a_termcap_g
->tg_ext_ents
;
242 a_termcap_g
->tg_ext_ents
= teep
;
243 su_mem_copy(teep
->tee_name
, ccp
, kl
);
244 teep
->tee_name
[kl
] = '\0';
246 tep
= &teep
->tee_super
;
247 tep
->te_flags
= mx_TERMCAP_CAPTYPE_STRING
| a_TERMCAP_F_QUERY
;
248 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
249 if(!a_termcap__strexp(&a_termcap_g
->tg_dat
, v
))
250 tep
->te_flags
|= a_TERMCAP_F_DISABLED
;
253 #endif /* mx_HAVE_KEY_BINDINGS */
254 if(n_poption
& n_PO_D_V
)
255 n_err(_("*termcap*: unknown capability: %s\n"), ccp
);
260 tcp
= &a_termcap_control
[i
];
261 if((tcp
->tc_flags
& a_TERMCAP_F_TYPE_MASK
) != f
){
262 n_err(_("*termcap*: entry type mismatch: %s\n"), ccp
);
265 tep
= &a_termcap_g
->tg_ents
[i
];
266 tep
->te_flags
= tcp
->tc_flags
;
267 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
270 if((f
& a_TERMCAP_F_TYPE_MASK
) == mx_TERMCAP_CAPTYPE_BOOL
)
273 tep
->te_flags
|= a_TERMCAP_F_DISABLED
;
274 else if((f
& a_TERMCAP_F_TYPE_MASK
) == mx_TERMCAP_CAPTYPE_NUMERIC
){
275 if((su_idec_u16_cp(&tep
->te_off
, v
, 0, NULL
276 ) & (su_IDEC_STATE_EMASK
| su_IDEC_STATE_CONSUMED
)
277 ) != su_IDEC_STATE_CONSUMED
)
279 }else if(!a_termcap__strexp(&a_termcap_g
->tg_dat
, v
))
280 tep
->te_flags
|= a_TERMCAP_F_DISABLED
;
281 #ifdef mx_HAVE_KEY_BINDINGS
284 if(n_poption
& n_PO_D_VV
)
285 n_err(_("*termcap*: learned %.*s: %s\n"), (int)kl
, ccp
,
286 (tep
->te_flags
& a_TERMCAP_F_DISABLED
? "<disabled>"
287 : (f
& a_TERMCAP_F_TYPE_MASK
) == mx_TERMCAP_CAPTYPE_BOOL
? "true"
290 su_DBG( if(n_poption
& n_PO_D_VV
)
291 n_err("*termcap* parsed: buffer used=%lu\n",
292 (ul
)a_termcap_g
->tg_dat
.s_len
) );
294 /* Catch some inter-dependencies the user may have triggered */
295 #ifdef mx_HAVE_TERMCAP
296 if(a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_te
].te_flags
& a_TERMCAP_F_DISABLED
)
297 a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_ti
].te_flags
= a_TERMCAP_F_DISABLED
;
298 else if(a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_ti
].te_flags
&
299 a_TERMCAP_F_DISABLED
)
300 a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_te
].te_flags
= a_TERMCAP_F_DISABLED
;
308 a_termcap__strexp(struct n_string
*store
, char const *ibuf
){ /* XXX ASCII */
316 for(oibuf
= ibuf
; (c
= *ibuf
) != '\0';){
318 if((c
= ibuf
[1]) == '\0')
327 if(su_cs_is_digit(c
) && c
<= '7'){
330 if((c2
= ibuf
[2]) == '\0' || !su_cs_is_digit(c2
) || c2
> '7' ||
331 (c3
= ibuf
[3]) == '\0' || !su_cs_is_digit(c3
) || c3
> '7'){
332 n_err(_("*termcap*: invalid octal sequence: %s\n"), oibuf
);
335 c
-= '0', c2
-= '0', c3
-= '0';
338 n_err(_("*termcap*: octal number too large: %s\n"), oibuf
);
346 n_err(_("*termcap*: invalid reverse solidus \\ sequence: %s\n"),
350 if((c
= ibuf
[1]) == '\0'){
351 n_err(_("*termcap*: incomplete ^CNTRL sequence: %s\n"), oibuf
);
354 c
= su_cs_to_upper(c
) ^ 0x40;
355 if((u8
)c
> 0x1F && c
!= 0x7F){ /* ASCII C0: 0..1F, 7F */
356 n_err(_("*termcap*: invalid ^CNTRL sequence: %s\n"), oibuf
);
364 store
= n_string_push_c(store
, c
);
367 c
= (store
->s_len
!= olen
) ? '\1' : '\0';
369 n_string_push_c(store
, '\0');
373 store
= n_string_trunc(store
, olen
);
379 a_termcap_init_altern(void){
380 /* We silently ignore user _F_DISABLED requests for those entries for which
381 * we have fallback entries, and which we need to ensure proper functioning.
382 * I.e., this allows users to explicitly disable some termcap(5) capability
383 * and enforce usage of the built-in fallback */
384 /* xxx Use table-based approach for fallback strategies */
385 #define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
387 ((TEP)->te_flags != 0 && !((TEP)->te_flags & a_TERMCAP_F_NOENT))
388 #define a_SET(TEP,CMD,ALT) \
389 (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
390 ((ALT) ? a_TERMCAP_F_ALTERN : 0)
392 struct a_termcap_ent
*tep
;
396 /* For simplicity in the rest of this file null flags of disabled commands,
397 * as we won't check and try to lazy query any command */
401 for(i
= mx__TERMCAP_CMD_MAX1
;;){
404 if((tep
= &a_termcap_g
->tg_ents
[i
])->te_flags
& a_TERMCAP_F_DISABLED
)
410 /* ce == ch + [:SPACE:] (start column specified by argument) */
411 tep
= &a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_ce
];
413 a_SET(tep
, mx_TERMCAP_CMD_ce
, TRU1
);
415 /* ch == cr[\r] + nd[:\033C:] */
416 tep
= &a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_ch
];
418 a_SET(tep
, mx_TERMCAP_CMD_ch
, TRU1
);
421 tep
= &a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_cr
];
423 a_SET(tep
, mx_TERMCAP_CMD_cr
, FAL0
);
424 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
425 n_string_push_c(n_string_push_c(&a_termcap_g
->tg_dat
, '\r'), '\0');
429 tep
= &a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_le
];
431 a_SET(tep
, mx_TERMCAP_CMD_le
, FAL0
);
432 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
433 n_string_push_c(n_string_push_c(&a_termcap_g
->tg_dat
, '\b'), '\0');
436 /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
437 tep
= &a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_nd
];
439 a_SET(tep
, mx_TERMCAP_CMD_nd
, FAL0
);
440 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
441 n_string_push_buf(&a_termcap_g
->tg_dat
, "\033[C", sizeof("\033[C"));
444 # ifdef mx_HAVE_TERMCAP
446 tep
= &a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_cl
];
448 if(a_OK(mx_TERMCAP_CMD_cd
) && a_OK(mx_TERMCAP_CMD_ho
))
449 a_SET(tep
, mx_TERMCAP_CMD_cl
, TRU1
);
452 #endif /* mx_HAVE_MLE */
460 #ifdef mx_HAVE_TERMCAP
461 # ifdef mx_HAVE_TERMINFO
463 a_termcap_load(char const *term
){
468 if(!(rv
= (setupterm(term
, fileno(mx_tty_fp
), &err
) == OK
)))
469 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term
);
475 a_termcap_ent_query(struct a_termcap_ent
*tep
,
476 char const *cname
, u16 cflags
){
479 ASSERT(!(n_psonce
& n_PSO_TERMCAP_DISABLE
));
481 if(UNLIKELY(*cname
== '\0'))
483 else switch((tep
->te_flags
= cflags
) & a_TERMCAP_F_TYPE_MASK
){
484 case mx_TERMCAP_CAPTYPE_BOOL
:
485 if(!(rv
= (tigetflag(cname
) > 0)))
486 tep
->te_flags
|= a_TERMCAP_F_NOENT
;
489 case mx_TERMCAP_CAPTYPE_NUMERIC
:{
490 int r
= tigetnum(cname
);
493 tep
->te_off
= (u16
)MIN(U16_MAX
, r
);
495 tep
->te_flags
|= a_TERMCAP_F_NOENT
;
498 case mx_TERMCAP_CAPTYPE_STRING
:{
501 cp
= tigetstr(cname
);
502 if((rv
= (cp
!= NULL
&& cp
!= (char*)-1))){
503 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
504 n_string_push_buf(&a_termcap_g
->tg_dat
, cp
, su_cs_len(cp
) +1);
506 tep
->te_flags
|= a_TERMCAP_F_NOENT
;
514 a_termcap_ent_query_tcp(struct a_termcap_ent
*tep
,
515 struct a_termcap_control
const *tcp
){
516 ASSERT(!(n_psonce
& n_PSO_TERMCAP_DISABLE
));
517 return a_termcap_ent_query(tep
, &a_termcap_namedat
[tcp
->tc_off
] + 2,
521 # else /* mx_HAVE_TERMINFO */
523 a_termcap_load(char const *term
){
527 /* ncurses may return -1 */
528 # ifndef mx_HAVE_TGETENT_NULL_BUF
529 # define a_BUF &a_termcap_g->tg_lib_buf[0]
533 if(!(rv
= tgetent(a_BUF
, term
) > 0))
534 n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term
);
541 a_termcap_ent_query(struct a_termcap_ent
*tep
,
542 char const *cname
, u16 cflags
){
545 ASSERT(!(n_psonce
& n_PSO_TERMCAP_DISABLE
));
547 if(UNLIKELY(*cname
== '\0'))
549 else switch((tep
->te_flags
= cflags
) & a_TERMCAP_F_TYPE_MASK
){
550 case mx_TERMCAP_CAPTYPE_BOOL
:
551 if(!(rv
= (tgetflag(cname
) > 0)))
552 tep
->te_flags
|= a_TERMCAP_F_NOENT
;
555 case mx_TERMCAP_CAPTYPE_NUMERIC
:{
556 int r
= tgetnum(cname
);
559 tep
->te_off
= (u16
)MIN(U16_MAX
, r
);
561 tep
->te_flags
|= a_TERMCAP_F_NOENT
;
564 case mx_TERMCAP_CAPTYPE_STRING
:{
565 # ifndef mx_HAVE_TGETENT_NULL_BUF
566 char buf_base
[a_TERMCAP_ENTRYSIZE_MAX
], *buf
= &buf_base
[0];
573 if((rv
= ((cp
= tgetstr(cname
, a_BUF
)) != NULL
))){
574 tep
->te_off
= (u16
)a_termcap_g
->tg_dat
.s_len
;
575 n_string_push_buf(&a_termcap_g
->tg_dat
, cp
, su_cs_len(cp
) +1);
578 tep
->te_flags
|= a_TERMCAP_F_NOENT
;
586 a_termcap_ent_query_tcp(struct a_termcap_ent
*tep
,
587 struct a_termcap_control
const *tcp
){
588 ASSERT(!(n_psonce
& n_PSO_TERMCAP_DISABLE
));
589 return a_termcap_ent_query(tep
, &a_termcap_namedat
[tcp
->tc_off
],
592 # endif /* !mx_HAVE_TERMINFO */
595 a_termcap_putc(int c
){
596 return putc(c
, mx_tty_fp
);
598 #endif /* mx_HAVE_TERMCAP */
601 a_termcap_enum_for_name(char const *name
, uz nlen
, s32 min
, s32 max
){
602 struct a_termcap_control
const *tcp
;
607 /* Prefer terminfo(5) names */
614 tcp
= &a_termcap_control
[(u32
)rv
];
615 cnam
= &a_termcap_namedat
[tcp
->tc_off
];
617 char const *xcp
= cnam
+ 2;
619 if(nlen
== su_cs_len(xcp
) && !su_mem_cmp(xcp
, name
, nlen
))
622 if(nlen
== 2 && cnam
[0] == name
[0] && cnam
[1] == name
[1])
630 mx_termcap_init(void){
631 struct mx_termcap_value tv
;
636 ASSERT(n_psonce
& n_PSO_TTYANY
);
638 a_termcap_g
= n_alloc(sizeof *a_termcap_g
);
639 a_termcap_g
->tg_ext_ents
= NULL
;
640 su_mem_set(&a_termcap_g
->tg_ents
[0], 0, sizeof(a_termcap_g
->tg_ents
));
641 if((ccp
= ok_vlook(termcap
)) != NULL
)
642 termvar
.l
= su_cs_len(termvar
.s
= n_UNCONST(ccp
));
644 /*termvar.s = NULL,*/ termvar
.l
= 0;
645 n_string_reserve(n_string_creat(&a_termcap_g
->tg_dat
),
646 ((termvar
.l
+ (256 - 64)) & ~127));
649 a_termcap_init_var(&termvar
);
651 if(ok_blook(termcap_disable
))
652 n_psonce
|= n_PSO_TERMCAP_DISABLE
;
653 #ifdef mx_HAVE_TERMCAP
654 else if((ccp
= ok_vlook(TERM
)) == NULL
){
655 n_err(_("Environment variable $TERM not set, using only *termcap*\n"));
656 n_psonce
|= n_PSO_TERMCAP_DISABLE
;
657 }else if(!a_termcap_load(ccp
))
658 n_psonce
|= n_PSO_TERMCAP_DISABLE
;
660 /* Query termcap(5) for each command slot that is not yet set */
661 struct a_termcap_ent
*tep
;
664 for(i
= mx__TERMCAP_CMD_MAX1
;;){
667 if((tep
= &a_termcap_g
->tg_ents
[i
])->te_flags
== 0)
668 a_termcap_ent_query_tcp(tep
, &a_termcap_control
[i
]);
671 #endif /* mx_HAVE_TERMCAP */
673 a_termcap_init_altern();
675 #ifdef mx_HAVE_TERMCAP
676 if(a_termcap_g
->tg_ents
[mx_TERMCAP_CMD_te
].te_flags
!= 0 &&
677 ok_blook(termcap_ca_mode
))
678 n_psonce
|= n_PSO_TERMCAP_CA_MODE
;
681 /* TODO We do not handle !mx_TERMCAP_QUERY_sam in this software! */
683 #ifdef mx_HAVE_TERMCAP
684 !mx_termcap_query(mx_TERMCAP_QUERY_am
, &tv
) ||
686 mx_termcap_query(mx_TERMCAP_QUERY_xenl
, &tv
)){
687 n_psonce
|= n_PSO_TERMCAP_FULLWIDTH
;
689 /* Since termcap was not initialized when we did TERMIOS_SETUP_TERMSIZE
690 * we need/should adjust the found setting to reality (without causing
691 * a synthesized SIGWINCH or something even more expensive that is) */
692 if(mx_termios_dimen
.tiosd_width
> 0)
693 ++mx_termios_dimen
.tiosd_width
;
696 mx_TERMCAP_RESUME(TRU1
);
701 mx_termcap_destroy(void){
703 ASSERT(a_termcap_g
!= NULL
);
705 mx_TERMCAP_SUSPEND(TRU1
);
709 struct a_termcap_ext_ent
*tmp
;
711 while((tmp
= a_termcap_g
->tg_ext_ents
) != NULL
){
712 a_termcap_g
->tg_ext_ents
= tmp
->tee_next
;
716 n_string_gut(&a_termcap_g
->tg_dat
);
723 #ifdef mx_HAVE_TERMCAP
725 mx_termcap_resume(boole complete
){
727 if(a_termcap_g
!= NULL
&& !(n_psonce
& n_PSO_TERMCAP_DISABLE
)){
728 if(complete
&& (n_psonce
& n_PSO_TERMCAP_CA_MODE
))
729 mx_termcap_cmdx(mx_TERMCAP_CMD_ti
);
730 mx_termcap_cmdx(mx_TERMCAP_CMD_ks
);
737 mx_termcap_suspend(boole complete
){
739 if(a_termcap_g
!= NULL
&& !(n_psonce
& n_PSO_TERMCAP_DISABLE
)){
740 mx_termcap_cmdx(mx_TERMCAP_CMD_ke
);
741 if(complete
&& (n_psonce
& n_PSO_TERMCAP_CA_MODE
))
742 mx_termcap_cmdx(mx_TERMCAP_CMD_te
);
747 #endif /* mx_HAVE_TERMCAP */
750 mx_termcap_cmd(enum mx_termcap_cmd cmd
, sz a1
, sz a2
){
751 /* Commands are not lazy queried */
752 struct a_termcap_ent
const *tep
;
753 enum a_termcap_flags flags
;
760 if(a_termcap_g
== NULL
)
763 flags
= cmd
& ~mx__TERMCAP_CMD_MASK
;
764 cmd
&= mx__TERMCAP_CMD_MASK
;
765 tep
= a_termcap_g
->tg_ents
;
767 if((flags
& mx_TERMCAP_CMD_FLAG_CA_MODE
) &&
768 !(n_psonce
& n_PSO_TERMCAP_CA_MODE
))
770 else if((tep
+= cmd
)->te_flags
== 0 || (tep
->te_flags
& a_TERMCAP_F_NOENT
))
772 else if(!(tep
->te_flags
& a_TERMCAP_F_ALTERN
)){
775 ASSERT((tep
->te_flags
& a_TERMCAP_F_TYPE_MASK
) ==
776 mx_TERMCAP_CAPTYPE_STRING
);
778 cp
= &a_termcap_g
->tg_dat
.s_dat
[tep
->te_off
];
780 #ifdef mx_HAVE_TERMCAP
781 if(tep
->te_flags
& (a_TERMCAP_F_ARG_IDX1
| a_TERMCAP_F_ARG_IDX2
)){
782 if(n_psonce
& n_PSO_TERMCAP_DISABLE
){
783 if(n_poption
& n_PO_D_V
){
784 char const *cnam
= &a_termcap_namedat
[
785 a_termcap_control
[cmd
].tc_off
];
789 n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
790 "can't perform CAP: %s\n"), cnam
);
795 /* Follow Thomas Dickey's advise on pre-va_arg prototypes, add 0s */
796 # ifdef mx_HAVE_TERMINFO
797 if((cp
= tparm(cp
, a1
, a2
, 0,0,0,0,0,0,0)) == NULL
)
801 * The \fBtgoto\fP function swaps the order of parameters.
802 * It does this also for calls requiring only a single parameter.
803 * In that case, the first parameter is merely a placeholder. */
804 if(!(tep
->te_flags
& a_TERMCAP_F_ARG_IDX2
)){
808 if((cp
= tgoto(cp
, (int)a1
, (int)a2
)) == NULL
)
812 #endif /* mx_HAVE_TERMCAP */
815 #ifdef mx_HAVE_TERMCAP
816 if(!(n_psonce
& n_PSO_TERMCAP_DISABLE
)){
817 if(tputs(cp
, 1, &a_termcap_putc
) != OK
)
821 if(fputs(cp
, mx_tty_fp
) == EOF
)
823 if(!(tep
->te_flags
& a_TERMCAP_F_ARG_CNT
) || --a1
<= 0){
836 case mx_TERMCAP_CMD_ce
: /* ce == ch + [:SPACE:] */
839 if((rv
= mx_termcap_cmd(mx_TERMCAP_CMD_ch
, a1
, 0)) > 0){
840 for(a2
= mx_termios_dimen
.tiosd_width
- a1
; a2
> 0; --a2
)
841 if(putc(' ', mx_tty_fp
) == EOF
){
845 if(rv
&& mx_termcap_cmd(mx_TERMCAP_CMD_ch
, a1
, -1) != TRU1
)
849 case mx_TERMCAP_CMD_ch
: /* ch == cr + nd */
850 rv
= mx_termcap_cmdx(mx_TERMCAP_CMD_cr
);
851 if(rv
> 0 && a1
> 0){
852 rv
= mx_termcap_cmd(mx_TERMCAP_CMD_nd
, a1
, -1);
855 # ifdef mx_HAVE_TERMCAP
856 case mx_TERMCAP_CMD_cl
: /* cl = ho + cd */
857 rv
= mx_termcap_cmdx(mx_TERMCAP_CMD_ho
);
859 rv
= mx_termcap_cmdx(mx_TERMCAP_CMD_cd
| flags
);
862 #endif /* mx_HAVE_MLE */
866 if(flags
& mx_TERMCAP_CMD_FLAG_FLUSH
)
868 if(ferror(mx_tty_fp
))
878 mx_termcap_query(enum mx_termcap_query query
, struct mx_termcap_value
*tvp
){
879 /* Queries are lazy queried upon request */
880 /* XXX mx_termcap_query(): boole handling suboptimal, tvp used on success */
881 struct a_termcap_ent
const *tep
;
888 if(a_termcap_g
== NULL
)
891 /* Is it a built-in query? */
892 if(query
!= mx__TERMCAP_QUERY_MAX1
){
893 tep
= &a_termcap_g
->tg_ents
[mx__TERMCAP_CMD_MAX1
+ query
];
895 if(tep
->te_flags
== 0
896 #ifdef mx_HAVE_TERMCAP
897 && ((n_psonce
& n_PSO_TERMCAP_DISABLE
) ||
898 !a_termcap_ent_query_tcp(n_UNCONST(tep
),
899 &a_termcap_control
[mx__TERMCAP_CMD_MAX1
+ query
]))
904 #ifdef mx_HAVE_TERMCAP
907 struct a_termcap_ext_ent
*teep
;
908 char const *ndat
= tvp
->tv_data
.tvd_string
;
910 for(teep
= a_termcap_g
->tg_ext_ents
; teep
!= NULL
; teep
= teep
->tee_next
)
911 if(!su_cs_cmp(teep
->tee_name
, ndat
)){
912 tep
= &teep
->tee_super
;
916 #ifdef mx_HAVE_TERMCAP
917 if(n_psonce
& n_PSO_TERMCAP_DISABLE
)
920 #ifdef mx_HAVE_TERMCAP
921 nlen
= su_cs_len(ndat
) +1;
922 teep
= n_alloc(VSTRUCT_SIZEOF(struct a_termcap_ext_ent
, tee_name
) +
924 tep
= &teep
->tee_super
;
925 teep
->tee_next
= a_termcap_g
->tg_ext_ents
;
926 a_termcap_g
->tg_ext_ents
= teep
;
927 su_mem_copy(teep
->tee_name
, ndat
, nlen
);
929 if(!a_termcap_ent_query(n_UNCONST(tep
), ndat
,
930 mx_TERMCAP_CAPTYPE_STRING
| a_TERMCAP_F_QUERY
))
936 if(tep
->te_flags
& a_TERMCAP_F_NOENT
)
939 rv
= (tep
->te_flags
& a_TERMCAP_F_ALTERN
) ? TRUM1
: TRU1
;
941 switch((tvp
->tv_captype
= tep
->te_flags
& a_TERMCAP_F_TYPE_MASK
)){
942 case mx_TERMCAP_CAPTYPE_BOOL
:
943 tvp
->tv_data
.tvd_bool
= (boole
)tep
->te_off
;
945 case mx_TERMCAP_CAPTYPE_NUMERIC
:
946 tvp
->tv_data
.tvd_numeric
= (u32
)tep
->te_off
;
949 case mx_TERMCAP_CAPTYPE_STRING
:
950 tvp
->tv_data
.tvd_string
= a_termcap_g
->tg_dat
.s_dat
+ tep
->te_off
;
958 #ifdef mx_HAVE_KEY_BINDINGS
960 mx_termcap_query_for_name(char const *name
, enum mx_termcap_captype type
){
964 if((rv
= a_termcap_query_for_name(name
, su_cs_len(name
))) >= 0){
965 struct a_termcap_control
const *tcp
= &a_termcap_control
[(u32
)rv
];
967 if(type
!= mx_TERMCAP_CAPTYPE_NONE
&&
968 (tcp
->tc_flags
& a_TERMCAP_F_TYPE_MASK
) != type
)
971 rv
-= mx__TERMCAP_CMD_MAX1
;
978 mx_termcap_name_of_query(enum mx_termcap_query query
){
982 rv
= &a_termcap_namedat
[
983 a_termcap_control
[mx__TERMCAP_CMD_MAX1
+ query
].tc_off
+ 2];
987 #endif /* mx_HAVE_KEY_BINDINGS */
989 #include "su/code-ou.h"
990 #endif /* mx_HAVE_TCAP */