1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-18 12:55:16 +00:00
mpv/osdep/terminal-unix.c
wm4 68ff8a0484 Move compat/ and bstr/ directory contents somewhere else
bstr.c doesn't really deserve its own directory, and compat had just
a few files, most of which may as well be in osdep. There isn't really
any justification for these extra directories, so get rid of them.

The compat/libav.h was empty - just delete it. We changed our approach
to API compatibility, and will likely not need it anymore.
2014-08-29 12:31:52 +02:00

821 lines
22 KiB
C

/*
* GyS-TermIO v2.0 (for GySmail v3)
* a very small replacement of ncurses library
*
* copyright (C) 1999 A'rpi/ESP-team
*
* This file is part of MPlayer.
*
* MPlayer is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* MPlayer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <assert.h>
#if HAVE_TERMIOS
#if HAVE_TERMIOS_H
#include <termios.h>
#endif
#if HAVE_SYS_TERMIOS_H
#include <sys/termios.h>
#endif
#endif
#include <unistd.h>
#include "common/common.h"
#include "misc/bstr.h"
#include "input/input.h"
#include "input/keycodes.h"
#include "misc/ctype.h"
#include "terminal.h"
#if HAVE_TERMIOS
static volatile struct termios tio_orig;
static volatile int tio_orig_set;
#endif
#if !(HAVE_TERMINFO || HAVE_TERMCAP)
struct key_entry {
const char *seq;
int mpkey;
// If this is not NULL, then if seq is matched as unique prefix, the
// existing sequence is replaced by the following string. Matching
// continues normally, and mpkey is or-ed into the final result.
const char *replace;
};
static const struct key_entry keys[] = {
{"\011", MP_KEY_TAB},
{"\012", MP_KEY_ENTER},
{"\177", MP_KEY_BS},
{"\033[1~", MP_KEY_HOME},
{"\033[2~", MP_KEY_INS},
{"\033[3~", MP_KEY_DEL},
{"\033[4~", MP_KEY_END},
{"\033[5~", MP_KEY_PGUP},
{"\033[6~", MP_KEY_PGDWN},
{"\033[7~", MP_KEY_HOME},
{"\033[8~", MP_KEY_END},
{"\033[11~", MP_KEY_F+1},
{"\033[12~", MP_KEY_F+2},
{"\033[13~", MP_KEY_F+3},
{"\033[14~", MP_KEY_F+4},
{"\033[15~", MP_KEY_F+5},
{"\033[17~", MP_KEY_F+6},
{"\033[18~", MP_KEY_F+7},
{"\033[19~", MP_KEY_F+8},
{"\033[20~", MP_KEY_F+9},
{"\033[21~", MP_KEY_F+10},
{"\033[23~", MP_KEY_F+11},
{"\033[24~", MP_KEY_F+12},
{"\033[A", MP_KEY_UP},
{"\033[B", MP_KEY_DOWN},
{"\033[C", MP_KEY_RIGHT},
{"\033[D", MP_KEY_LEFT},
{"\033[E", MP_KEY_KP5},
{"\033[F", MP_KEY_END},
{"\033[H", MP_KEY_HOME},
{"\033[[A", MP_KEY_F+1},
{"\033[[B", MP_KEY_F+2},
{"\033[[C", MP_KEY_F+3},
{"\033[[D", MP_KEY_F+4},
{"\033[[E", MP_KEY_F+5},
{"\033OE", MP_KEY_KP5}, // mintty?
{"\033OM", MP_KEY_KPENTER},
{"\033OP", MP_KEY_F+1},
{"\033OQ", MP_KEY_F+2},
{"\033OR", MP_KEY_F+3},
{"\033OS", MP_KEY_F+4},
{"\033Oa", MP_KEY_UP | MP_KEY_MODIFIER_CTRL}, // urxvt
{"\033Ob", MP_KEY_DOWN | MP_KEY_MODIFIER_CTRL},
{"\033Oc", MP_KEY_RIGHT | MP_KEY_MODIFIER_CTRL},
{"\033Od", MP_KEY_LEFT | MP_KEY_MODIFIER_CTRL},
{"\033Oj", '*'}, // also keypad, but we don't have separate codes for them
{"\033Ok", '+'},
{"\033Om", '-'},
{"\033On", MP_KEY_KPDEC},
{"\033Oo", '/'},
{"\033Op", MP_KEY_KP0},
{"\033Oq", MP_KEY_KP1},
{"\033Or", MP_KEY_KP2},
{"\033Os", MP_KEY_KP3},
{"\033Ot", MP_KEY_KP4},
{"\033Ou", MP_KEY_KP5},
{"\033Ov", MP_KEY_KP6},
{"\033Ow", MP_KEY_KP7},
{"\033Ox", MP_KEY_KP8},
{"\033Oy", MP_KEY_KP9},
{"\033[a", MP_KEY_UP | MP_KEY_MODIFIER_SHIFT}, // urxvt
{"\033[b", MP_KEY_DOWN | MP_KEY_MODIFIER_SHIFT},
{"\033[c", MP_KEY_RIGHT | MP_KEY_MODIFIER_SHIFT},
{"\033[d", MP_KEY_LEFT | MP_KEY_MODIFIER_SHIFT},
{"\033[2^", MP_KEY_INS | MP_KEY_MODIFIER_CTRL},
{"\033[3^", MP_KEY_DEL | MP_KEY_MODIFIER_CTRL},
{"\033[5^", MP_KEY_PGUP | MP_KEY_MODIFIER_CTRL},
{"\033[6^", MP_KEY_PGDWN | MP_KEY_MODIFIER_CTRL},
{"\033[7^", MP_KEY_HOME | MP_KEY_MODIFIER_CTRL},
{"\033[8^", MP_KEY_END | MP_KEY_MODIFIER_CTRL},
{"\033[1;2", MP_KEY_MODIFIER_SHIFT, .replace = "\033["}, // xterm
{"\033[1;3", MP_KEY_MODIFIER_ALT, .replace = "\033["},
{"\033[1;5", MP_KEY_MODIFIER_CTRL, .replace = "\033["},
{"\033[1;4", MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_SHIFT, .replace = "\033["},
{"\033[1;6", MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_SHIFT, .replace = "\033["},
{"\033[1;7", MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT, .replace = "\033["},
{"\033[1;8",
MP_KEY_MODIFIER_CTRL | MP_KEY_MODIFIER_ALT | MP_KEY_MODIFIER_SHIFT,
.replace = "\033["},
{"\033[29~", MP_KEY_MENU},
{"\033[Z", MP_KEY_TAB | MP_KEY_MODIFIER_SHIFT},
{0}
};
#define BUF_LEN 256
struct termbuf {
unsigned char b[BUF_LEN];
int len;
int mods;
};
static void skip_buf(struct termbuf *b, unsigned int count)
{
assert(count <= b->len);
memmove(&b->b[0], &b->b[count], b->len - count);
b->len -= count;
b->mods = 0;
}
static struct termbuf buf;
static bool getch2(struct input_ctx *input_ctx)
{
int retval = read(0, &buf.b[buf.len], BUF_LEN - buf.len);
/* Return false on EOF to stop running select() on the FD, as it'd
* trigger all the time. Note that it's possible to get temporary
* EOF on terminal if the user presses ctrl-d, but that shouldn't
* happen if the terminal state change done in getch2_enable()
* works.
*/
if (retval == 0)
return false;
if (retval == -1)
return errno != EBADF && errno != EINVAL;
buf.len += retval;
while (buf.len) {
int utf8_len = bstr_parse_utf8_code_length(buf.b[0]);
if (utf8_len > 1) {
if (buf.len < utf8_len)
goto read_more;
mp_input_put_key_utf8(input_ctx, buf.mods, (bstr){buf.b, utf8_len});
skip_buf(&buf, utf8_len);
continue;
}
const struct key_entry *match = NULL; // may be a partial match
for (int n = 0; keys[n].seq; n++) {
const struct key_entry *e = &keys[n];
if (memcmp(e->seq, buf.b, MPMIN(buf.len, strlen(e->seq))) == 0) {
if (match)
goto read_more; /* need more bytes to disambiguate */
match = e;
}
}
if (!match) { // normal or unknown key
if (buf.b[0] == '\033') {
skip_buf(&buf, 1);
if (buf.len > 0 && mp_isalnum(buf.b[0])) { // meta+normal key
mp_input_put_key(input_ctx, buf.b[0] | MP_KEY_MODIFIER_ALT);
skip_buf(&buf, 1);
} else if (buf.len == 1 && buf.b[0] == '\033') {
mp_input_put_key(input_ctx, MP_KEY_ESC);
skip_buf(&buf, 1);
} else {
// Throw it away. Typically, this will be a complete,
// unsupported sequence, and dropping this will skip it.
skip_buf(&buf, buf.len);
}
} else {
mp_input_put_key(input_ctx, buf.b[0]);
skip_buf(&buf, 1);
}
continue;
}
int seq_len = strlen(match->seq);
if (seq_len > buf.len)
goto read_more; /* partial match */
if (match->replace) {
int rep = strlen(match->replace);
assert(rep <= seq_len);
memcpy(buf.b, match->replace, rep);
memmove(buf.b + rep, buf.b + seq_len, buf.len - seq_len);
buf.len = rep + buf.len - seq_len;
buf.mods |= match->mpkey;
continue;
}
mp_input_put_key(input_ctx, buf.mods | match->mpkey);
skip_buf(&buf, seq_len);
}
read_more: /* need more bytes */
return true;
}
static void load_termcap(void)
{
}
static void enable_kx(bool enable)
{
if (isatty(STDOUT_FILENO)) {
char *cmd = enable ? "\033=" : "\033>";
printf("%s", cmd);
fflush(stdout);
}
}
#else /* terminfo/termcap */
typedef struct {
char *cap;
int len;
int code;
char chars[8];
} keycode_st;
typedef struct {
keycode_st *map;
int len;
int cap;
} keycode_map;
static keycode_map getch2_keys;
static char *term_rmkx = NULL;
static char *term_smkx = NULL;
#if HAVE_TERMINFO
#include <curses.h>
#endif
#include <term.h>
static keycode_st *keys_push(char *p, int code) {
if (strlen(p) > 8)
return NULL;
if (getch2_keys.len == getch2_keys.cap) {
getch2_keys.cap *= 2;
if (getch2_keys.cap == 0)
getch2_keys.cap = 32;
getch2_keys.map = realloc(getch2_keys.map, sizeof(keycode_st) * getch2_keys.cap);
}
keycode_st *st = &getch2_keys.map[getch2_keys.len++];
st->cap = NULL;
st->len = strlen(p);
st->code = code;
strncpy(st->chars, p, 8);
return st;
}
static int keys_count_matches(char *buf, int buflen) {
int count = 0;
if (buflen < 0)
buflen = strlen(buf);
for (int i = 0; i < getch2_keys.len; i++) {
keycode_st *st = &getch2_keys.map[i];
int len = MPMIN(buflen, st->len);
if (memcmp(buf, st->chars, len) == 0)
count++;
}
return count;
}
static keycode_st *keys_search(char *buf, int buflen) {
if (buflen < 0)
buflen = strlen(buf);
for (int i = 0; i < getch2_keys.len; i++) {
keycode_st *st = &getch2_keys.map[i];
if (buflen >= st->len && memcmp(buf, st->chars, st->len) == 0)
return st;
}
return NULL;
}
/* pushes only if there is no duplicate.
important as we only consider keys if the matches are unique. */
static keycode_st* keys_push_once(char *p, int code) {
keycode_st *st = keys_search(p, -1);
if (!st)
return keys_push(p, code);
return st;
}
typedef struct {
char *buf;
char *pos;
int cap;
} buf_st;
static buf_st termcap_buf;
static void ensure_cap(buf_st *buf, int cap) {
if (buf->pos - buf->buf < cap) {
ptrdiff_t diff = buf->pos - buf->buf;
buf->cap += cap;
buf->buf = realloc(buf->buf, buf->cap);
buf->pos = buf->buf + diff;
}
}
static char *termcap_get(char *id) {
ensure_cap(&termcap_buf, 1024);
return tgetstr(id, &termcap_buf.pos);
}
typedef struct {
char *id;
int code;
} cap_key_pair;
#if 0
#include <stdio.h>
#include <ctype.h>
static void debug_keycode(keycode_st *st) {
if (!st)
return;
char buf[128]; /* worst case should be 70 bytes */
unsigned char *b = &buf[0];
unsigned char *p = &st->chars[0];
if (st->cap)
b += sprintf(b, "%s: ", st->cap);
for(; *p; p++) {
if (*p == 27)
b += sprintf(b, "\\e");
else if (*p < 27)
b += sprintf(b, "^%c", '@' + *p);
else if (!isgraph(*p))
b += sprintf(b, "\\x%02x", (unsigned int)*p);
else
b += sprintf(b, "%c", *p);
}
fprintf(stderr, "%s\n", buf);
}
#endif
static void termcap_add(cap_key_pair pair) {
char *p = termcap_get(pair.id);
if (p) {
keycode_st *st = keys_push_once(p, pair.code);
if (st)
st->cap = pair.id;
/* debug_keycode(st); */
}
}
static void termcap_add_extra_f_keys(void) {
char capbuf[3];
for (int i = 11; i < 0x20; i++) {
unsigned char c;
if (i < 20) { /* 1-9 */
c = '0' + (i - 10);
} else { /* A-Z */
c = 'A' + (i - 20);
}
sprintf(&capbuf[0], "F%c", c);
char *p = termcap_get(capbuf);
if (p)
keys_push_once(p, MP_KEY_F+i);
else
break; /* unlikely that the database has further keys */
}
}
static void load_termcap(void)
{
char *termtype = NULL;
#if HAVE_TERMINFO
use_env(TRUE);
int ret;
if (setupterm(termtype, 1, &ret) != OK) {
/* try again, with with "ansi" terminal if it was unset before */
if (!termtype)
termtype = getenv("TERM");
if (!termtype || *termtype == '\0')
termtype = "ansi";
if (setupterm(termtype, 1, &ret) != OK) {
if (ret < 0) {
printf("Could not access the 'terminfo' data base.\n");
return;
} else {
printf("Couldn't use terminal `%s' for input.\n", termtype);
return;
}
}
}
#else
static char term_buffer[2048];
if (!termtype) termtype = getenv("TERM");
if (!termtype) termtype = "ansi";
int success = tgetent(term_buffer, termtype);
if (success < 0) {
printf("Could not access the 'termcap' data base.\n");
return;
} else if (success == 0) {
printf("Terminal type `%s' is not defined.\n", termtype);
return;
}
#endif
ensure_cap(&termcap_buf, 2048);
static char term_buf[128];
char *buf_ptr = &term_buf[0];
// References for terminfo/termcap codes:
// http://linux.die.net/man/5/termcap
// http://unixhelp.ed.ac.uk/CGI/man-cgi?terminfo+5
term_smkx = tgetstr("ks", &buf_ptr);
term_rmkx = tgetstr("ke", &buf_ptr);
cap_key_pair keys[] = {
{"kP", MP_KEY_PGUP}, {"kN", MP_KEY_PGDWN}, {"kh", MP_KEY_HOME}, {"kH", MP_KEY_END},
{"kI", MP_KEY_INS}, {"kD", MP_KEY_DEL}, /* on PC keyboards */ {"@7", MP_KEY_END},
{"kl", MP_KEY_LEFT}, {"kd", MP_KEY_DOWN}, {"ku", MP_KEY_UP}, {"kr", MP_KEY_RIGHT},
{"do", MP_KEY_ENTER},
{"kb", MP_KEY_BS},
{"k1", MP_KEY_F+1}, {"k2", MP_KEY_F+2}, {"k3", MP_KEY_F+3},
{"k4", MP_KEY_F+4}, {"k5", MP_KEY_F+5}, {"k6", MP_KEY_F+6},
{"k7", MP_KEY_F+7}, {"k8", MP_KEY_F+8}, {"k9", MP_KEY_F+9},
{"k;", MP_KEY_F+10}, {"k0", MP_KEY_F+0},
/* K2 is the keypad center */
{"K2", MP_KEY_KP5},
/* EOL */
{NULL},
};
for (int i = 0; keys[i].id; i++) {
termcap_add(keys[i]);
}
termcap_add_extra_f_keys();
/* special cases (hardcoded, no need for HAVE_TERMCAP) */
/* it's important to use keys_push_once as we can't have duplicates */
/* many terminals, for emacs compatibility, use 0x7f instead of ^H
when typing backspace, even when the 'kb' cap says otherwise. */
keys_push_once("\177", MP_KEY_BS);
/* mintty always sends these when using the numpad arrows,
even in application mode, for telling them from regular arrows. */
keys_push_once("\033[A", MP_KEY_UP);
keys_push_once("\033[B", MP_KEY_DOWN);
keys_push_once("\033[C", MP_KEY_RIGHT);
keys_push_once("\033[D", MP_KEY_LEFT);
/* mintty uses this instead of the "K2" cap for keypad center */
keys_push_once("\033OE", MP_KEY_KP5);
/* fallback if terminfo and termcap are not available */
keys_push_once("\012", MP_KEY_ENTER);
}
static void enable_kx(bool enable)
{
char *cmd = enable ? term_smkx : term_rmkx;
if (cmd)
tputs(cmd, 1, putchar);
}
#define BUF_LEN 256
static unsigned char getch2_buf[BUF_LEN];
static int getch2_len = 0;
static int getch2_pos = 0;
static enum {
STATE_INITIAL,
STATE_UTF8,
} state = STATE_INITIAL;
static int utf8_len = 0;
static void walk_buf(unsigned int count) {
if (!(count < BUF_LEN && count <= getch2_len))
abort();
memmove(&getch2_buf[0], &getch2_buf[count], getch2_len - count);
getch2_len -= count;
getch2_pos -= count;
if (getch2_pos < 0)
getch2_pos = 0;
}
static bool getch2(struct input_ctx *input_ctx)
{
int retval = read(0, &getch2_buf[getch2_pos], BUF_LEN - getch2_len - getch2_pos);
/* Return false on EOF to stop running select() on the FD, as it'd
* trigger all the time. Note that it's possible to get temporary
* EOF on terminal if the user presses ctrl-d, but that shouldn't
* happen if the terminal state change done in getch2_enable()
* works.
*/
if (retval == 0)
return false;
if (retval == -1)
return errno != EBADF && errno != EINVAL;
getch2_len += retval;
while (getch2_pos < getch2_len) {
unsigned char c = getch2_buf[getch2_pos++];
switch (state) {
case STATE_INITIAL: {
int match_count = keys_count_matches(&getch2_buf[0], getch2_len);
if (match_count == 1) {
keycode_st *st = keys_search(&getch2_buf[0], getch2_len);
if (st) {
mp_input_put_key(input_ctx, st->code);
walk_buf(st->len);
} /* else this is still a partial (but unique) match */
continue;
} else if (match_count > 1) {
continue; /* need more bytes to disambiguate */
} else {
/* backtrack, send as UTF-8 */
getch2_pos = 1;
c = getch2_buf[0];
}
utf8_len = bstr_parse_utf8_code_length(c);
if (utf8_len > 1) {
state = STATE_UTF8;
} else if (utf8_len == 1) {
switch (c) {
case 0x1b: /* ESC that's not part of escape sequence */
/* only if ESC was typed twice, otherwise ignore it */
if (getch2_len > 1 && getch2_buf[1] == 0x1b) {
walk_buf(1); /* eat the second ESC */
mp_input_put_key(input_ctx, MP_KEY_ESC);
}
break;
default:
mp_input_put_key(input_ctx, c);
}
walk_buf(1);
} else
walk_buf(getch2_pos);
break;
}
case STATE_UTF8: {
if (getch2_pos < utf8_len) /* need more bytes */
continue;
struct bstr s = {getch2_buf, utf8_len};
int unicode = bstr_decode_utf8(s, NULL);
if (unicode > 0) {
mp_input_put_key(input_ctx, unicode);
}
walk_buf(utf8_len);
state = STATE_INITIAL;
continue;
}
}
}
return true;
}
#endif /* terminfo/termcap */
static int read_keys(void *ctx, int fd)
{
if (getch2(ctx))
return MP_INPUT_NOTHING;
return MP_INPUT_DEAD;
}
static volatile int getch2_active = 0;
static volatile int getch2_enabled = 0;
void terminal_setup_getch(struct input_ctx *ictx)
{
if (!getch2_enabled)
return;
mp_input_add_fd(ictx, 0, 1, NULL, read_keys, NULL, ictx);
}
static void do_activate_getch2(void)
{
if (getch2_active || !isatty(STDOUT_FILENO))
return;
enable_kx(true);
#if HAVE_TERMIOS
struct termios tio_new;
tcgetattr(0,&tio_new);
if (!tio_orig_set) {
tio_orig = tio_new;
tio_orig_set = 1;
}
tio_new.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tio_new.c_cc[VMIN] = 1;
tio_new.c_cc[VTIME] = 0;
tcsetattr(0,TCSANOW,&tio_new);
#endif
getch2_active = 1;
}
static void do_deactivate_getch2(void)
{
if (!getch2_active)
return;
enable_kx(false);
#if HAVE_TERMIOS
if (tio_orig_set) {
// once set, it will never be set again
// so we can cast away volatile here
tcsetattr(0, TCSANOW, (const struct termios *) &tio_orig);
}
#endif
getch2_active = 0;
}
// sigaction wrapper
static int setsigaction(int signo, void (*handler) (int),
int flags, bool do_mask)
{
struct sigaction sa;
sa.sa_handler = handler;
if(do_mask)
sigfillset(&sa.sa_mask);
else
sigemptyset(&sa.sa_mask);
sa.sa_flags = flags | SA_RESTART;
return sigaction(signo, &sa, NULL);
}
void getch2_poll(void){
if (!getch2_enabled)
return;
// check if stdin is in the foreground process group
int newstatus = (tcgetpgrp(0) == getpgrp());
// and activate getch2 if it is, deactivate otherwise
if (newstatus)
do_activate_getch2();
else
do_deactivate_getch2();
}
static void stop_sighandler(int signum)
{
do_deactivate_getch2();
// note: for this signal, we use SA_RESETHAND but do NOT mask signals
// so this will invoke the default handler
raise(SIGTSTP);
}
static void continue_sighandler(int signum)
{
// SA_RESETHAND has reset SIGTSTP, so we need to restore it here
setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false);
getch2_poll();
}
static void quit_request_sighandler(int signum)
{
do_deactivate_getch2();
async_quit_request = 1;
}
static void getch2_enable(void)
{
assert(!getch2_enabled);
// handlers to fix terminal settings
setsigaction(SIGCONT, continue_sighandler, 0, true);
setsigaction(SIGTSTP, stop_sighandler, SA_RESETHAND, false);
setsigaction(SIGINT, quit_request_sighandler, SA_RESETHAND, false);
setsigaction(SIGQUIT, quit_request_sighandler, SA_RESETHAND, false);
setsigaction(SIGTERM, quit_request_sighandler, SA_RESETHAND, false);
setsigaction(SIGTTIN, SIG_IGN, 0, true);
setsigaction(SIGTTOU, SIG_IGN, 0, true);
do_activate_getch2();
getch2_enabled = 1;
}
void terminal_uninit(void)
{
if (!getch2_enabled)
return;
// restore signals
setsigaction(SIGCONT, SIG_DFL, 0, false);
setsigaction(SIGTSTP, SIG_DFL, 0, false);
setsigaction(SIGINT, SIG_DFL, 0, false);
setsigaction(SIGQUIT, SIG_DFL, 0, false);
setsigaction(SIGTERM, SIG_DFL, 0, false);
setsigaction(SIGTTIN, SIG_DFL, 0, false);
setsigaction(SIGTTOU, SIG_DFL, 0, false);
do_deactivate_getch2();
getch2_enabled = 0;
}
bool terminal_in_background(void)
{
return isatty(STDERR_FILENO) && tcgetpgrp(STDERR_FILENO) != getpgrp();
}
void terminal_get_size(int *w, int *h)
{
struct winsize ws;
if (ioctl(0, TIOCGWINSZ, &ws) < 0 || !ws.ws_row || !ws.ws_col)
return;
*w = ws.ws_col;
*h = ws.ws_row;
}
int terminal_init(void)
{
if (isatty(STDOUT_FILENO))
load_termcap();
getch2_enable();
return 0;
}