236cf88a3e
Make strncpy a little less error-prone. Just use memcpy where that suffices: https://stackoverflow.com/questions/56782248/gcc-specified-bound-depends-on-the-length-of-the-source-argument/56782476#56782476
321 lines
10 KiB
C++
321 lines
10 KiB
C++
enum {
|
|
T_ENTER_CA,
|
|
T_EXIT_CA,
|
|
T_SHOW_CURSOR,
|
|
T_HIDE_CURSOR,
|
|
T_CLEAR_SCREEN,
|
|
T_SGR0,
|
|
T_UNDERLINE,
|
|
T_BOLD,
|
|
T_BLINK,
|
|
T_REVERSE,
|
|
T_ENTER_KEYPAD,
|
|
T_EXIT_KEYPAD,
|
|
T_ENTER_MOUSE,
|
|
T_EXIT_MOUSE,
|
|
T_ENTER_BRACKETED_PASTE,
|
|
T_EXIT_BRACKETED_PASTE,
|
|
T_FUNCS_NUM,
|
|
};
|
|
|
|
#define EUNSUPPORTED_TERM -1
|
|
|
|
// rxvt-256color
|
|
static const char *rxvt_256color_keys[] = {
|
|
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
|
};
|
|
static const char *rxvt_256color_funcs[] = {
|
|
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
|
};
|
|
|
|
// Eterm
|
|
static const char *eterm_keys[] = {
|
|
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
|
};
|
|
static const char *eterm_funcs[] = {
|
|
"\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", "", "",
|
|
};
|
|
|
|
// screen
|
|
static const char *screen_keys[] = {
|
|
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0
|
|
};
|
|
static const char *screen_funcs[] = {
|
|
"\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
|
};
|
|
|
|
// rxvt-unicode
|
|
static const char *rxvt_unicode_keys[] = {
|
|
"\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
|
};
|
|
static const char *rxvt_unicode_funcs[] = {
|
|
"\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
|
};
|
|
|
|
// linux
|
|
static const char *linux_keys[] = {
|
|
"\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", "\033[A", "\033[B", "\033[D", "\033[C", 0
|
|
};
|
|
static const char *linux_funcs[] = {
|
|
"", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", "", "",
|
|
};
|
|
|
|
// xterm
|
|
static const char *xterm_keys[] = {
|
|
"\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", "\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB", "\033OD", "\033OC", 0
|
|
};
|
|
static const char *xterm_funcs[] = {
|
|
"\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l", "\033[?2004h", "\033[?2004l",
|
|
};
|
|
|
|
static struct term {
|
|
const char *name;
|
|
const char **keys;
|
|
const char **funcs;
|
|
} terms[] = {
|
|
{"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
|
|
{"Eterm", eterm_keys, eterm_funcs},
|
|
{"screen", screen_keys, screen_funcs},
|
|
{"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
|
|
{"linux", linux_keys, linux_funcs},
|
|
{"xterm", xterm_keys, xterm_funcs},
|
|
{0, 0, 0},
|
|
};
|
|
|
|
static bool init_from_terminfo = false;
|
|
static const char **keys;
|
|
static const char **funcs;
|
|
|
|
static int try_compatible(const char *term, const char *name,
|
|
const char **tkeys, const char **tfuncs)
|
|
{
|
|
if (strstr(term, name)) {
|
|
keys = tkeys;
|
|
funcs = tfuncs;
|
|
return 0;
|
|
}
|
|
|
|
return EUNSUPPORTED_TERM;
|
|
}
|
|
|
|
static int init_term_builtin(void)
|
|
{
|
|
int i;
|
|
const char *term = getenv("TERM");
|
|
|
|
if (term) {
|
|
for (i = 0; terms[i].name; i++) {
|
|
if (!strcmp(terms[i].name, term)) {
|
|
keys = terms[i].keys;
|
|
funcs = terms[i].funcs;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* let's do some heuristic, maybe it's a compatible terminal */
|
|
if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
|
|
return 0;
|
|
if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0)
|
|
return 0;
|
|
if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
|
|
return 0;
|
|
if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
|
|
return 0;
|
|
if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0)
|
|
return 0;
|
|
/* let's assume that 'cygwin' is xterm compatible */
|
|
if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0)
|
|
return 0;
|
|
}
|
|
|
|
return EUNSUPPORTED_TERM;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// terminfo
|
|
//----------------------------------------------------------------------
|
|
|
|
static char *read_file(const char *file) {
|
|
FILE *f = fopen(file, "rb");
|
|
if (!f)
|
|
return 0;
|
|
|
|
struct stat st;
|
|
if (fstat(fileno(f), &st) != 0) {
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
char *data = malloc(st.st_size);
|
|
if (!data) {
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) {
|
|
fclose(f);
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
fclose(f);
|
|
return data;
|
|
}
|
|
|
|
static char *terminfo_try_path(const char *path, const char *term) {
|
|
char tmp[4096];
|
|
// snprintf guarantee for older compilers
|
|
assert(sizeof(tmp) > sizeof(path)+sizeof("/x/")+sizeof(term)+1);
|
|
snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
|
|
char *data = read_file(tmp);
|
|
if (data) {
|
|
return data;
|
|
}
|
|
|
|
// fallback to darwin specific dirs structure
|
|
// snprintf guarantee above still applies
|
|
snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
|
|
return read_file(tmp);
|
|
}
|
|
|
|
void string_copy(char* dest, const char* src, int dest_capacity) {
|
|
strncpy(dest, src, dest_capacity-1);
|
|
dest[dest_capacity-1] = '\0';
|
|
}
|
|
|
|
void string_append(char* dest, const char* src, int dest_capacity) {
|
|
strncat(dest, src, dest_capacity);
|
|
dest[dest_capacity-1] = '\0';
|
|
}
|
|
|
|
static char *load_terminfo(void) {
|
|
char tmp[4096];
|
|
const char *term = getenv("TERM");
|
|
if (!term) {
|
|
return 0;
|
|
}
|
|
|
|
// if TERMINFO is set, no other directory should be searched
|
|
const char *terminfo = getenv("TERMINFO");
|
|
if (terminfo) {
|
|
return terminfo_try_path(terminfo, term);
|
|
}
|
|
|
|
// next, consider ~/.terminfo
|
|
const char *home = getenv("HOME");
|
|
if (home) {
|
|
// snprintf guarantee for older compilers
|
|
assert(sizeof(tmp) > sizeof(home)+sizeof("/.terminfo")+1);
|
|
string_copy(tmp, home, sizeof(tmp));
|
|
string_append(tmp, "/.terminfo", sizeof(tmp));
|
|
char *data = terminfo_try_path(tmp, term);
|
|
if (data)
|
|
return data;
|
|
}
|
|
|
|
// next, TERMINFO_DIRS
|
|
const char *dirs = getenv("TERMINFO_DIRS");
|
|
if (dirs) {
|
|
// snprintf guarantee for older compilers
|
|
assert(sizeof(tmp) > sizeof(dirs));
|
|
string_copy(tmp, dirs, sizeof(tmp));
|
|
char *dir = strtok(tmp, ":");
|
|
while (dir) {
|
|
const char *cdir = dir;
|
|
if (strcmp(cdir, "") == 0) {
|
|
cdir = "/usr/share/terminfo";
|
|
}
|
|
char *data = terminfo_try_path(cdir, term);
|
|
if (data)
|
|
return data;
|
|
dir = strtok(0, ":");
|
|
}
|
|
}
|
|
|
|
// fallback to /usr/share/terminfo
|
|
return terminfo_try_path("/usr/share/terminfo", term);
|
|
}
|
|
|
|
#define TI_MAGIC 0432
|
|
#define TI_HEADER_LENGTH 12
|
|
#define TB_KEYS_NUM 22
|
|
|
|
static const char *terminfo_copy_string(char *data, int str, int table) {
|
|
const int16_t off = *(int16_t*)(data + str);
|
|
const char *src = data + table + off;
|
|
int len = strlen(src);
|
|
char *dst = malloc(len+1);
|
|
memcpy(dst, src, len+1);
|
|
return dst;
|
|
}
|
|
|
|
static const int16_t ti_funcs[] = {
|
|
28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
|
|
};
|
|
|
|
static const int16_t ti_keys[] = {
|
|
66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69,
|
|
70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61,
|
|
79, 83,
|
|
};
|
|
|
|
static int init_term(void) {
|
|
int i;
|
|
char *data = load_terminfo();
|
|
if (!data) {
|
|
init_from_terminfo = false;
|
|
return init_term_builtin();
|
|
}
|
|
|
|
int16_t *header = (int16_t*)data;
|
|
if ((header[1] + header[2]) % 2) {
|
|
// old quirk to align everything on word boundaries
|
|
header[2] += 1;
|
|
}
|
|
|
|
const int str_offset = TI_HEADER_LENGTH +
|
|
header[1] + header[2] + 2 * header[3];
|
|
const int table_offset = str_offset + 2 * header[4];
|
|
|
|
keys = malloc(sizeof(const char*) * (TB_KEYS_NUM+1));
|
|
for (i = 0; i < TB_KEYS_NUM; i++) {
|
|
keys[i] = terminfo_copy_string(data,
|
|
str_offset + 2 * ti_keys[i], table_offset);
|
|
}
|
|
keys[TB_KEYS_NUM] = 0;
|
|
|
|
funcs = malloc(sizeof(const char*) * T_FUNCS_NUM);
|
|
// the last four entries are reserved for mouse, bracketed paste. because the table offset is
|
|
// not there, the two entries have to fill in manually
|
|
for (i = 0; i < T_FUNCS_NUM-4; i++) {
|
|
funcs[i] = terminfo_copy_string(data,
|
|
str_offset + 2 * ti_funcs[i], table_offset);
|
|
}
|
|
|
|
funcs[T_FUNCS_NUM-4] = "\033[?1000h";
|
|
funcs[T_FUNCS_NUM-3] = "\033[?1000l";
|
|
funcs[T_FUNCS_NUM-2] = "\033[?2004h";
|
|
funcs[T_FUNCS_NUM-1] = "\033[?2004l";
|
|
|
|
init_from_terminfo = true;
|
|
free(data);
|
|
return 0;
|
|
}
|
|
|
|
static void shutdown_term(void) {
|
|
if (init_from_terminfo) {
|
|
int i;
|
|
for (i = 0; i < TB_KEYS_NUM; i++) {
|
|
free((void*)keys[i]);
|
|
}
|
|
// the last four entries are reserved for mouse, bracketed paste. because the table offset
|
|
// is not there, the two entries have to fill in manually and do not
|
|
// need to be freed.
|
|
for (i = 0; i < T_FUNCS_NUM-4; i++) {
|
|
free((void*)funcs[i]);
|
|
}
|
|
free(keys);
|
|
free(funcs);
|
|
}
|
|
}
|