605 lines
12 KiB
C
605 lines
12 KiB
C
/* $NetBSD: termcap.c,v 1.25 2023/01/31 21:11:24 andvar Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2009 The NetBSD Foundation, Inc.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Roy Marples.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__RCSID("$NetBSD: termcap.c,v 1.25 2023/01/31 21:11:24 andvar Exp $");
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <term_private.h>
|
|
#include <term.h>
|
|
#include <termcap.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
|
|
#include "termcap_map.c"
|
|
#include "termcap_hash.c"
|
|
|
|
char *UP;
|
|
char *BC;
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
tgetent(__unused char *bp, const char *name)
|
|
{
|
|
int errret;
|
|
static TERMINAL *last = NULL;
|
|
|
|
_DIAGASSERT(name != NULL);
|
|
|
|
/* Free the old term */
|
|
if (cur_term != NULL) {
|
|
if (last != NULL && cur_term != last)
|
|
del_curterm(last);
|
|
last = cur_term;
|
|
}
|
|
errret = -1;
|
|
if (setupterm(name, STDOUT_FILENO, &errret) != 0)
|
|
return errret;
|
|
|
|
if (last == NULL)
|
|
last = cur_term;
|
|
|
|
if (pad_char != NULL)
|
|
PC = pad_char[0];
|
|
UP = __UNCONST(cursor_up);
|
|
BC = __UNCONST(cursor_left);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
tgetflag(const char *id2)
|
|
{
|
|
uint32_t ind;
|
|
size_t i;
|
|
TERMUSERDEF *ud;
|
|
const char id[] = { id2[0], id2[0] ? id2[1] : '\0', '\0' };
|
|
|
|
if (cur_term == NULL)
|
|
return 0;
|
|
|
|
ind = _t_flaghash((const unsigned char *)id, strlen(id));
|
|
if (ind < __arraycount(_ti_cap_flagids)) {
|
|
if (strcmp(id, _ti_cap_flagids[ind].id) == 0)
|
|
return cur_term->flags[_ti_cap_flagids[ind].ti];
|
|
}
|
|
for (i = 0; i < cur_term->_nuserdefs; i++) {
|
|
ud = &cur_term->_userdefs[i];
|
|
if (ud->type == 'f' && strcmp(ud->id, id) == 0)
|
|
return ud->flag;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
tgetnum(const char *id2)
|
|
{
|
|
uint32_t ind;
|
|
size_t i;
|
|
TERMUSERDEF *ud;
|
|
const TENTRY *te;
|
|
const char id[] = { id2[0], id2[0] ? id2[1] : '\0', '\0' };
|
|
|
|
if (cur_term == NULL)
|
|
return -1;
|
|
|
|
ind = _t_numhash((const unsigned char *)id, strlen(id));
|
|
if (ind < __arraycount(_ti_cap_numids)) {
|
|
te = &_ti_cap_numids[ind];
|
|
if (strcmp(id, te->id) == 0) {
|
|
if (!VALID_NUMERIC(cur_term->nums[te->ti]))
|
|
return ABSENT_NUMERIC;
|
|
return cur_term->nums[te->ti];
|
|
}
|
|
}
|
|
for (i = 0; i < cur_term->_nuserdefs; i++) {
|
|
ud = &cur_term->_userdefs[i];
|
|
if (ud->type == 'n' && strcmp(ud->id, id) == 0) {
|
|
if (!VALID_NUMERIC(ud->num))
|
|
return ABSENT_NUMERIC;
|
|
return ud->num;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
char *
|
|
tgetstr(const char *id2, char **area)
|
|
{
|
|
uint32_t ind;
|
|
size_t i;
|
|
TERMUSERDEF *ud;
|
|
const char *str;
|
|
const char id[] = { id2[0], id2[0] ? id2[1] : '\0', '\0' };
|
|
|
|
if (cur_term == NULL)
|
|
return NULL;
|
|
|
|
str = NULL;
|
|
ind = _t_strhash((const unsigned char *)id, strlen(id));
|
|
if (ind < __arraycount(_ti_cap_strids)) {
|
|
if (strcmp(id, _ti_cap_strids[ind].id) == 0) {
|
|
str = cur_term->strs[_ti_cap_strids[ind].ti];
|
|
if (str == NULL)
|
|
return NULL;
|
|
}
|
|
}
|
|
if (str != NULL)
|
|
for (i = 0; i < cur_term->_nuserdefs; i++) {
|
|
ud = &cur_term->_userdefs[i];
|
|
if (ud->type == 's' && strcmp(ud->id, id) == 0)
|
|
str = ud->str;
|
|
}
|
|
|
|
/* XXX: FIXME
|
|
* We should fix sgr0(me) as it has a slightly different meaning
|
|
* for termcap. */
|
|
|
|
if (str != NULL && area != NULL && *area != NULL) {
|
|
char *s;
|
|
s = *area;
|
|
strcpy(*area, str);
|
|
*area += strlen(*area) + 1;
|
|
return s;
|
|
}
|
|
|
|
return __UNCONST(str);
|
|
}
|
|
|
|
char *
|
|
tgoto(const char *cm, int destcol, int destline)
|
|
{
|
|
_DIAGASSERT(cm != NULL);
|
|
return tiparm(cm, destline, destcol);
|
|
}
|
|
|
|
#ifdef TERMINFO_COMPILE
|
|
static const char *
|
|
flagname(const char *key)
|
|
{
|
|
uint32_t idx;
|
|
|
|
idx = _t_flaghash((const unsigned char *)key, strlen(key));
|
|
if (idx < __arraycount(_ti_cap_flagids) &&
|
|
strcmp(key, _ti_cap_flagids[idx].id) == 0)
|
|
return _ti_flagid(_ti_cap_flagids[idx].ti);
|
|
return key;
|
|
}
|
|
|
|
static const char *
|
|
numname(const char *key)
|
|
{
|
|
uint32_t idx;
|
|
|
|
idx = _t_numhash((const unsigned char *)key, strlen(key));
|
|
if (idx < __arraycount(_ti_cap_numids) &&
|
|
strcmp(key, _ti_cap_numids[idx].id) == 0)
|
|
return _ti_numid(_ti_cap_numids[idx].ti);
|
|
return key;
|
|
}
|
|
|
|
static const char *
|
|
strname(const char *key)
|
|
{
|
|
uint32_t idx;
|
|
|
|
idx = _t_strhash((const unsigned char *)key, strlen(key));
|
|
if (idx < __arraycount(_ti_cap_strids) &&
|
|
strcmp(key, _ti_cap_strids[idx].id) == 0)
|
|
return _ti_strid(_ti_cap_strids[idx].ti);
|
|
|
|
if (strcmp(key, "tc") == 0)
|
|
return "use";
|
|
|
|
return key;
|
|
}
|
|
|
|
/* Print a parameter if needed */
|
|
static size_t
|
|
printparam(char **dst, char p, bool *nop)
|
|
{
|
|
if (*nop) {
|
|
*nop = false;
|
|
return 0;
|
|
}
|
|
|
|
*(*dst)++ = '%';
|
|
*(*dst)++ = 'p';
|
|
*(*dst)++ = '0' + p;
|
|
return 3;
|
|
}
|
|
|
|
/* Convert a termcap character into terminfo equivalents */
|
|
static size_t
|
|
printchar(char **dst, const char **src)
|
|
{
|
|
char v;
|
|
size_t l;
|
|
|
|
l = 4;
|
|
v = *++(*src);
|
|
if (v == '\\') {
|
|
v = *++(*src);
|
|
switch (v) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
v = 0;
|
|
while (isdigit((unsigned char) **src))
|
|
v = 8 * v + (*(*src)++ - '0');
|
|
(*src)--;
|
|
break;
|
|
case '\0':
|
|
v = '\\';
|
|
break;
|
|
}
|
|
} else if (v == '^')
|
|
v = *++(*src) & 0x1f;
|
|
*(*dst)++ = '%';
|
|
if (isgraph((unsigned char )v) &&
|
|
v != ',' && v != '\'' && v != '\\' && v != ':')
|
|
{
|
|
*(*dst)++ = '\'';
|
|
*(*dst)++ = v;
|
|
*(*dst)++ = '\'';
|
|
} else {
|
|
*(*dst)++ = '{';
|
|
if (v > 99) {
|
|
*(*dst)++ = '0'+ v / 100;
|
|
l++;
|
|
}
|
|
if (v > 9) {
|
|
*(*dst)++ = '0' + ((int) (v / 10)) % 10;
|
|
l++;
|
|
}
|
|
*(*dst)++ = '0' + v % 10;
|
|
*(*dst)++ = '}';
|
|
}
|
|
return l;
|
|
}
|
|
|
|
/* Convert termcap commands into terminfo commands */
|
|
static const char fmtB[] = "%p0%{10}%/%{16}%*%p0%{10}%m%+";
|
|
static const char fmtD[] = "%p0%p0%{2}%*%-";
|
|
static const char fmtIf[] = "%p0%p0%?";
|
|
static const char fmtThen[] = "%>%t";
|
|
static const char fmtElse[] = "%+%;";
|
|
|
|
static char *
|
|
strval(const char *val)
|
|
{
|
|
char *info, *ip, c, p;
|
|
const char *ps, *pe;
|
|
bool nop;
|
|
size_t len, l;
|
|
|
|
len = 1024; /* no single string should be bigger */
|
|
info = ip = malloc(len);
|
|
if (info == NULL)
|
|
return 0;
|
|
|
|
/* Move the = */
|
|
*ip++ = *val++;
|
|
|
|
/* Set ps and pe to point to the start and end of the padding */
|
|
if (isdigit((unsigned char)*val)) {
|
|
for (ps = pe = val;
|
|
isdigit((unsigned char)*val) || *val == '.';
|
|
val++)
|
|
pe++;
|
|
if (*val == '*') {
|
|
val++;
|
|
pe++;
|
|
}
|
|
} else
|
|
ps = pe = NULL;
|
|
|
|
nop = false;
|
|
l = 0;
|
|
p = 1;
|
|
for (; *val != '\0'; val++) {
|
|
if (l + 2 > len)
|
|
goto elen;
|
|
if (*val != '%') {
|
|
if (*val == ',') {
|
|
if (l + 3 > len)
|
|
goto elen;
|
|
*ip++ = '\\';
|
|
l++;
|
|
}
|
|
*ip++ = *val;
|
|
l++;
|
|
continue;
|
|
}
|
|
switch (c = *++(val)) {
|
|
case 'B':
|
|
if (l + sizeof(fmtB) > len)
|
|
goto elen;
|
|
memcpy(ip, fmtB, sizeof(fmtB) - 1);
|
|
/* Replace the embedded parameters with real ones */
|
|
ip[2] += p;
|
|
ip[19] += p;
|
|
ip += sizeof(fmtB) - 1;
|
|
l += sizeof(fmtB) - 1;
|
|
nop = true;
|
|
continue;
|
|
case 'D':
|
|
if (l + sizeof(fmtD) > len)
|
|
goto elen;
|
|
memcpy(ip, fmtD, sizeof(fmtD) - 1);
|
|
/* Replace the embedded parameters with real ones */
|
|
ip[2] += p;
|
|
ip[5] += p;
|
|
ip += sizeof(fmtD) - 1;
|
|
l += sizeof(fmtD) - 1;
|
|
nop = true;
|
|
continue;
|
|
case 'r':
|
|
/* non op as switched below */
|
|
break;
|
|
case '2': /* FALLTHROUGH */
|
|
case '3': /* FALLTHROUGH */
|
|
case 'd':
|
|
if (l + 7 > len)
|
|
goto elen;
|
|
l += printparam(&ip, p, &nop);
|
|
*ip++ = '%';
|
|
if (c != 'd') {
|
|
*ip++ = c;
|
|
l++;
|
|
}
|
|
*ip++ = 'd';
|
|
l += 2;
|
|
break;
|
|
case '+':
|
|
if (l + 13 > len)
|
|
goto elen;
|
|
l += printparam(&ip, p, &nop);
|
|
l += printchar(&ip, &val);
|
|
*ip++ = '%';
|
|
*ip++ = c;
|
|
*ip++ = '%';
|
|
*ip++ = 'c';
|
|
l += 7;
|
|
break;
|
|
case '>':
|
|
if (l + sizeof(fmtIf) + sizeof(fmtThen) +
|
|
sizeof(fmtElse) + (6 * 2) > len)
|
|
goto elen;
|
|
|
|
memcpy(ip, fmtIf, sizeof(fmtIf) - 1);
|
|
/* Replace the embedded parameters with real ones */
|
|
ip[2] += p;
|
|
ip[5] += p;
|
|
ip += sizeof(fmtIf) - 1;
|
|
l += sizeof(fmtIf) - 1;
|
|
l += printchar(&ip, &val);
|
|
memcpy(ip, fmtThen, sizeof(fmtThen) - 1);
|
|
ip += sizeof(fmtThen) - 1;
|
|
l += sizeof(fmtThen) - 1;
|
|
l += printchar(&ip, &val);
|
|
memcpy(ip, fmtElse, sizeof(fmtElse) - 1);
|
|
ip += sizeof(fmtElse) - 1;
|
|
l += sizeof(fmtElse) - 1;
|
|
l += 16;
|
|
nop = true;
|
|
continue;
|
|
case '.':
|
|
if (l + 6 > len)
|
|
goto elen;
|
|
l += printparam(&ip, p, &nop);
|
|
*ip++ = '%';
|
|
*ip++ = 'c';
|
|
l += 2;
|
|
break;
|
|
default:
|
|
/* Hope it matches a terminfo command. */
|
|
*ip++ = '%';
|
|
*ip++ = c;
|
|
l += 2;
|
|
if (c == 'i')
|
|
continue;
|
|
break;
|
|
}
|
|
/* Swap p1 and p2 */
|
|
p = 3 - p;
|
|
}
|
|
|
|
/* \E\ is valid termcap.
|
|
* We need to escape the final \ for terminfo. */
|
|
if (l > 2 && info[l - 1] == '\\' &&
|
|
(info[l - 2] != '\\' && info[l - 2] != '^'))
|
|
{
|
|
if (l + 1 > len)
|
|
goto elen;
|
|
*ip++ = '\\';
|
|
}
|
|
|
|
/* Add our padding at the end. */
|
|
if (ps != NULL) {
|
|
size_t n = (size_t)(pe - ps);
|
|
if (l + n + 4 > len)
|
|
goto elen;
|
|
*ip++ = '$';
|
|
*ip++ = '<';
|
|
strncpy(ip, ps, n);
|
|
ip += n;
|
|
*ip++ = '/';
|
|
*ip++ = '>';
|
|
}
|
|
|
|
*ip = '\0';
|
|
return info;
|
|
|
|
elen:
|
|
free(info);
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
const char *cap;
|
|
} DEF_INFO;
|
|
|
|
static DEF_INFO def_infos[] = {
|
|
{ "bel", "^G" },
|
|
{ "cr", "^M" },
|
|
{ "cud1", "^J" },
|
|
{ "ht", "^I" },
|
|
{ "ind", "^J" },
|
|
{ "kbs", "^H" },
|
|
{ "kcub1", "^H" },
|
|
{ "kcud1", "^J" },
|
|
{ "nel", "^M^J" }
|
|
};
|
|
|
|
char *
|
|
captoinfo(char *cap)
|
|
{
|
|
char *info, *ip, *token, *val, *p, tok[3];
|
|
const char *name;
|
|
size_t len, lp, nl, vl, rl;
|
|
int defs[__arraycount(def_infos)], fv;
|
|
|
|
_DIAGASSERT(cap != NULL);
|
|
|
|
len = strlen(cap) * 2;
|
|
len += __arraycount(def_infos) * (5 + 4 + 3); /* reserve for defs */
|
|
info = ip = malloc(len);
|
|
if (info == NULL)
|
|
return NULL;
|
|
|
|
memset(defs, 0, sizeof(defs));
|
|
lp = 0;
|
|
tok[2] = '\0';
|
|
for (token = _ti_get_token(&cap, ':');
|
|
token != NULL;
|
|
token = _ti_get_token(&cap, ':'))
|
|
{
|
|
if (token[0] == '\0')
|
|
continue;
|
|
name = token;
|
|
val = p = NULL;
|
|
fv = 0;
|
|
nl = 0;
|
|
if (token[1] != '\0') {
|
|
tok[0] = token[0];
|
|
tok[1] = token[1];
|
|
nl = 1;
|
|
if (token[2] == '\0') {
|
|
name = flagname(tok);
|
|
val = NULL;
|
|
} else if (token[2] == '#') {
|
|
name = numname(tok);
|
|
val = token + 2;
|
|
} else if (token[2] == '=') {
|
|
name = strname(tok);
|
|
val = strval(token + 2);
|
|
fv = 1;
|
|
} else
|
|
nl = 0;
|
|
}
|
|
/* If not matched we may need to convert padding still. */
|
|
if (nl == 0) {
|
|
p = strchr(name, '=');
|
|
if (p != NULL) {
|
|
val = strval(p);
|
|
*p = '\0';
|
|
fv = 1;
|
|
}
|
|
}
|
|
|
|
/* See if this sets a default. */
|
|
for (nl = 0; nl < __arraycount(def_infos); nl++) {
|
|
if (strcmp(name, def_infos[nl].name) == 0) {
|
|
defs[nl] = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
nl = strlen(name);
|
|
if (val == NULL)
|
|
vl = 0;
|
|
else
|
|
vl = strlen(val);
|
|
rl = nl + vl + 3; /* , \0 */
|
|
|
|
if (lp + rl > len) {
|
|
if (rl < 256)
|
|
len += 256;
|
|
else
|
|
len += rl;
|
|
p = realloc(info, len);
|
|
if (p == NULL) {
|
|
if (fv == 1)
|
|
free(val);
|
|
return NULL;
|
|
}
|
|
info = p;
|
|
}
|
|
|
|
if (ip != info) {
|
|
*ip++ = ',';
|
|
*ip++ = ' ';
|
|
}
|
|
|
|
strcpy(ip, name);
|
|
ip += nl;
|
|
if (val != NULL) {
|
|
strcpy(ip, val);
|
|
ip += vl;
|
|
if (fv == 1)
|
|
free(val);
|
|
}
|
|
}
|
|
|
|
/* Add any defaults not set above. */
|
|
for (nl = 0; nl < __arraycount(def_infos); nl++) {
|
|
if (defs[nl] == 0) {
|
|
*ip++ = ',';
|
|
*ip++ = ' ';
|
|
strcpy(ip, def_infos[nl].name);
|
|
ip += strlen(def_infos[nl].name);
|
|
*ip++ = '=';
|
|
strcpy(ip, def_infos[nl].cap);
|
|
ip += strlen(def_infos[nl].cap);
|
|
}
|
|
}
|
|
|
|
*ip = '\0';
|
|
return info;
|
|
}
|
|
#endif
|