mirror of https://github.com/merbanan/rtl_433.git
582 lines
15 KiB
C
582 lines
15 KiB
C
/** @file
|
|
Terminal control utility functions.
|
|
|
|
Copyright (C) 2018 Christian Zuckschwerdt
|
|
|
|
This program 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.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
|
|
#include "term_ctl.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <limits.h>
|
|
#include <windows.h>
|
|
#include <lm.h>
|
|
|
|
#ifndef STDOUT_FILENO
|
|
#define STDOUT_FILENO 1
|
|
#endif
|
|
|
|
#ifndef STDERR_FILENO
|
|
#define STDERR_FILENO 2
|
|
#endif
|
|
|
|
#ifndef UNUSED
|
|
#define UNUSED(x) (void)(x)
|
|
#endif
|
|
|
|
typedef struct console {
|
|
CONSOLE_SCREEN_BUFFER_INFO info;
|
|
BOOL redirected;
|
|
BOOL ansi;
|
|
HANDLE hnd;
|
|
FILE *file;
|
|
WORD fg, bg;
|
|
} console_t;
|
|
|
|
static WORD _term_get_win_color(BOOL fore, term_color_t color)
|
|
{
|
|
// applies intensity only on foreground,
|
|
// i.e. background will fallback to dim
|
|
switch (color) {
|
|
case TERM_COLOR_RESET: /* Cannot occur; just to suppress a warning */
|
|
break;
|
|
case TERM_COLOR_BLACK:
|
|
return (0);
|
|
case TERM_COLOR_BLUE:
|
|
return (1);
|
|
case TERM_COLOR_GREEN:
|
|
return (2);
|
|
case TERM_COLOR_CYAN:
|
|
return (3);
|
|
case TERM_COLOR_RED:
|
|
return (4);
|
|
case TERM_COLOR_MAGENTA:
|
|
return (5);
|
|
case TERM_COLOR_YELLOW:
|
|
return (6);
|
|
case TERM_COLOR_WHITE:
|
|
return (7);
|
|
case TERM_COLOR_BRIGHT_BLACK:
|
|
return (fore ? 0 + FOREGROUND_INTENSITY : 0);
|
|
case TERM_COLOR_BRIGHT_BLUE:
|
|
return (fore ? 1 + FOREGROUND_INTENSITY : 1);
|
|
case TERM_COLOR_BRIGHT_GREEN:
|
|
return (fore ? 2 + FOREGROUND_INTENSITY : 2);
|
|
case TERM_COLOR_BRIGHT_CYAN:
|
|
return (fore ? 3 + FOREGROUND_INTENSITY : 3);
|
|
case TERM_COLOR_BRIGHT_RED:
|
|
return (fore ? 4 + FOREGROUND_INTENSITY : 4);
|
|
case TERM_COLOR_BRIGHT_MAGENTA:
|
|
return (fore ? 5 + FOREGROUND_INTENSITY : 5);
|
|
case TERM_COLOR_BRIGHT_YELLOW:
|
|
return (fore ? 6 + FOREGROUND_INTENSITY : 6);
|
|
case TERM_COLOR_BRIGHT_WHITE:
|
|
return (fore ? 7 + FOREGROUND_INTENSITY : 7);
|
|
}
|
|
fprintf(stderr,"FATAL: No mapping for TERM_COLOR_x=%d (fore: %d)\n", color, fore);
|
|
return (0);
|
|
}
|
|
|
|
static void _term_set_color(console_t *console, BOOL fore, term_color_t color)
|
|
{
|
|
WORD win_color;
|
|
|
|
if (!console->file)
|
|
return;
|
|
|
|
if (color == TERM_COLOR_RESET) {
|
|
console->fg = (console->info.wAttributes & 7);
|
|
console->bg = (console->info.wAttributes & ~7);
|
|
}
|
|
else if (fore) {
|
|
console->fg = _term_get_win_color(TRUE, color);
|
|
}
|
|
else if (color <= TERM_COLOR_WHITE ||
|
|
(color >= TERM_COLOR_BRIGHT_BLACK && color <= TERM_COLOR_BRIGHT_WHITE)) {
|
|
console->bg = 16 * _term_get_win_color(FALSE, color);
|
|
}
|
|
else
|
|
return;
|
|
|
|
win_color = console->bg + console->fg;
|
|
|
|
/* Hack: as WinCon does not have color-themes (as Linux have) and no 'TERM_COLOR_BRIGHT_x'
|
|
* codes are used, always use a high-intensity foreground color. This look best in
|
|
* CMD with the default black background color.
|
|
*
|
|
* Note: do not do this for "BLACK" as that would turn it into "GREY".
|
|
*/
|
|
if (fore && color != TERM_COLOR_RESET && color != TERM_COLOR_BLACK)
|
|
win_color |= FOREGROUND_INTENSITY;
|
|
|
|
fflush(console->file);
|
|
SetConsoleTextAttribute(console->hnd, win_color);
|
|
}
|
|
|
|
/*
|
|
* Cleanup in case we got a SIGINT signal in the middle of a
|
|
* non-default colour output.
|
|
*/
|
|
static void _term_free(console_t *console)
|
|
{
|
|
if (console->hnd && console->hnd != INVALID_HANDLE_VALUE) {
|
|
fflush(console->file);
|
|
SetConsoleTextAttribute(console->hnd, console->info.wAttributes);
|
|
}
|
|
free(console);
|
|
}
|
|
|
|
static BOOL _term_has_color(console_t *console)
|
|
{
|
|
return console->hnd && !console->redirected;
|
|
}
|
|
|
|
static void *_term_init(FILE *fp)
|
|
{
|
|
console_t *console = calloc(1, sizeof(*console));
|
|
if (!console) {
|
|
fprintf(stderr, "term_init failed\n");
|
|
return NULL; // NOTE: return NULL on alloc failure.
|
|
}
|
|
|
|
int fd = fileno(fp);
|
|
if (fd == STDOUT_FILENO) {
|
|
console->hnd = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
console->file = fp;
|
|
}
|
|
else if (fd == STDERR_FILENO) {
|
|
console->hnd = GetStdHandle(STD_ERROR_HANDLE);
|
|
console->file = fp;
|
|
}
|
|
console->redirected = (console->hnd == INVALID_HANDLE_VALUE) ||
|
|
(!GetConsoleScreenBufferInfo(console->hnd, &console->info)) ||
|
|
(GetFileType(console->hnd) != FILE_TYPE_CHAR);
|
|
|
|
// Test for Windows 10 to enable ANSI output, needs netapi32.dll
|
|
LPWKSTA_INFO_100 pBuf = NULL;
|
|
NET_API_STATUS nStatus;
|
|
nStatus = NetWkstaGetInfo(NULL, 100, (LPBYTE *)&pBuf);
|
|
if (nStatus == NERR_Success) {
|
|
console->ansi = (pBuf->wki100_platform_id == 500) && (pBuf->wki100_ver_major >= 10);
|
|
}
|
|
if (pBuf != NULL) {
|
|
NetApiBufferFree(pBuf);
|
|
}
|
|
|
|
// Windows 10 version 1511 added ANSI filters to cmd and terminal.
|
|
// To use ANSI colors in Windows versions 1511 to 1903 requires setting VirtualTerminalLevel
|
|
// by calling the SetConsoleMode API with the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.
|
|
// ANSI colors are available by default in Windows version 1909 or newer
|
|
if (console->ansi) {
|
|
DWORD dwMode = 0;
|
|
GetConsoleMode(console->hnd, &dwMode);
|
|
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
SetConsoleMode(console->hnd, dwMode);
|
|
// Check if it worked, it will fail for Legacy console-mode
|
|
GetConsoleMode (console->hnd, &dwMode);
|
|
if (!(dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
|
|
console->ansi = 0;
|
|
}
|
|
}
|
|
|
|
_term_set_color(console, FALSE, TERM_COLOR_RESET); /* Set 'console->fg' and 'console->bg' */
|
|
|
|
return console;
|
|
}
|
|
#endif /* _WIN32 */
|
|
|
|
int term_get_columns(void *ctx)
|
|
{
|
|
#ifdef _WIN32
|
|
console_t *console = (console_t *)ctx;
|
|
/*
|
|
* Call this again as the screen dimensions could have changes since
|
|
* we called '_term_init()'.
|
|
*/
|
|
CONSOLE_SCREEN_BUFFER_INFO c_info;
|
|
|
|
if (!console->hnd || console->hnd == INVALID_HANDLE_VALUE)
|
|
return (80);
|
|
|
|
if (!GetConsoleScreenBufferInfo(console->hnd, &c_info))
|
|
return (80);
|
|
return (c_info.srWindow.Right - c_info.srWindow.Left + 1);
|
|
#else
|
|
FILE *fp = (FILE *)ctx;
|
|
struct winsize w;
|
|
ioctl(fileno(fp), TIOCGWINSZ, &w);
|
|
return w.ws_col;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
Returns wether the environment suggests a dark or light
|
|
terminal background.
|
|
|
|
Our default color theme is for dark backgrounds.
|
|
|
|
Check the COLORFGBG environment variable, which should
|
|
be 15;0 for a dark theme and 0;15 for a light theme.
|
|
|
|
If the last value is 7 or 9 to 15 then assume a light theme.
|
|
|
|
@return 1 if a light background was detected, 0 for a dark background otherwise
|
|
*/
|
|
static int term_get_bg(void)
|
|
{
|
|
char const *colorfgbg = getenv("COLORFGBG");
|
|
if (!colorfgbg) {
|
|
return 0; // default dark theme
|
|
}
|
|
char *p = strrchr(colorfgbg, ';');
|
|
if (!p) {
|
|
return 0; // default dark theme
|
|
}
|
|
|
|
// Check if the last value is 7 or 9 to 15.
|
|
if (p[1] == '7' || p[1] == '9'
|
|
|| (p[1] == '1' && p[2] != '\0')) {
|
|
return 1; // light theme
|
|
}
|
|
return 0; // dark theme
|
|
}
|
|
|
|
int term_has_color(void *ctx)
|
|
{
|
|
#ifdef _WIN32
|
|
return _term_has_color(ctx);
|
|
#else
|
|
char const *color = getenv("RTL433_COLOR");
|
|
if (color && strcmp(color, "always") == 0) {
|
|
return 1;
|
|
}
|
|
if (color && strcmp(color, "never") == 0) {
|
|
return 0;
|
|
}
|
|
|
|
char const *no_color = getenv("NO_COLOR");
|
|
if (no_color && no_color[0] != '\0') {
|
|
return 0;
|
|
}
|
|
|
|
FILE *fp = (FILE *)ctx;
|
|
return isatty(fileno(fp));
|
|
#endif
|
|
}
|
|
|
|
void *term_init(FILE *fp)
|
|
{
|
|
#ifdef _WIN32
|
|
return _term_init(fp);
|
|
#else
|
|
return fp;
|
|
#endif
|
|
}
|
|
|
|
void term_free(void *ctx)
|
|
{
|
|
if (!ctx)
|
|
return;
|
|
#ifdef _WIN32
|
|
_term_free(ctx);
|
|
#else
|
|
FILE *fp = (FILE *)ctx;
|
|
if (term_has_color(ctx))
|
|
fprintf(fp, "\033[0m");
|
|
#endif
|
|
}
|
|
|
|
void term_ring_bell(void *ctx)
|
|
{
|
|
#ifdef _WIN32
|
|
Beep(800, 20);
|
|
UNUSED(ctx);
|
|
#else
|
|
FILE *fp = (FILE *)ctx;
|
|
fprintf(fp, "\a");
|
|
#endif
|
|
}
|
|
|
|
void term_set_fg(void *ctx, term_color_t color)
|
|
{
|
|
// Cache the detected terminal background color
|
|
static int light_bg = -1;
|
|
if (light_bg == -1) {
|
|
light_bg = term_get_bg();
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
console_t *console = (console_t *)ctx;
|
|
if (!console->ansi) {
|
|
_term_set_color(ctx, TRUE, color);
|
|
return;
|
|
}
|
|
FILE *fp = console->file;
|
|
#else
|
|
FILE *fp = (FILE *)ctx;
|
|
#endif
|
|
if (color == TERM_COLOR_RESET) {
|
|
fprintf(fp, "\033[0m");
|
|
}
|
|
else if (light_bg) {
|
|
fprintf(fp, "\033[%dm", color); // normal colors on light backgrounds
|
|
}
|
|
else {
|
|
fprintf(fp, "\033[%d;1m", color); // bright/bold colors on dark backgrounds
|
|
}
|
|
}
|
|
|
|
void term_set_bg(void *ctx, term_color_t bg, term_color_t fg)
|
|
{
|
|
// Cache the detected terminal background color
|
|
static int light_bg = -1;
|
|
if (light_bg == -1) {
|
|
light_bg = term_get_bg();
|
|
}
|
|
if (light_bg && fg >= TERM_COLOR_BRIGHT_BLACK && fg <= TERM_COLOR_BRIGHT_WHITE) {
|
|
fg -= 60; // remove bright/bold foreground on light backgrounds
|
|
}
|
|
|
|
if (bg < TERM_COLOR_BLACK
|
|
|| (bg > TERM_COLOR_WHITE && bg < TERM_COLOR_BRIGHT_BLACK)
|
|
|| bg > TERM_COLOR_BRIGHT_WHITE) {
|
|
bg = 0;
|
|
}
|
|
if (fg < TERM_COLOR_BLACK
|
|
|| (fg > TERM_COLOR_WHITE && fg < TERM_COLOR_BRIGHT_BLACK)
|
|
|| fg > TERM_COLOR_BRIGHT_WHITE) {
|
|
fg = 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
console_t *console = (console_t *)ctx;
|
|
if (!console->ansi) {
|
|
if (bg)
|
|
_term_set_color(ctx, FALSE, bg);
|
|
if (fg)
|
|
_term_set_color(ctx, TRUE, fg);
|
|
return;
|
|
}
|
|
FILE *fp = console->file;
|
|
#else
|
|
FILE *fp = (FILE *)ctx;
|
|
#endif
|
|
if (bg && fg)
|
|
fprintf(fp, "\033[%d;%dm", bg + 10, fg);
|
|
else if (bg)
|
|
fprintf(fp, "\033[%dm", bg + 10);
|
|
else if (fg)
|
|
fprintf(fp, "\033[%dm", fg);
|
|
}
|
|
|
|
#define DIM(array) (int) (sizeof(array) / sizeof(array[0]))
|
|
|
|
static term_color_t color_map[] = {
|
|
TERM_COLOR_RESET, /* "~0" */
|
|
TERM_COLOR_GREEN,
|
|
TERM_COLOR_WHITE, /* "~2" */
|
|
TERM_COLOR_BLUE,
|
|
TERM_COLOR_CYAN, /* "~4" */
|
|
TERM_COLOR_MAGENTA,
|
|
TERM_COLOR_YELLOW, /* "~6" */
|
|
TERM_COLOR_BLACK,
|
|
TERM_COLOR_RED, /* "~8" */
|
|
};
|
|
|
|
int term_set_color_map(int ascii_idx, term_color_t color)
|
|
{
|
|
ascii_idx -= '0';
|
|
if (ascii_idx < 0 || ascii_idx >= DIM(color_map))
|
|
return -1;
|
|
color_map[ascii_idx] = color;
|
|
return ascii_idx;
|
|
}
|
|
|
|
int term_get_color_map(int ascii_idx)
|
|
{
|
|
int i;
|
|
|
|
ascii_idx -= '0';
|
|
for (i = 0; ascii_idx >= 0 && i < DIM(color_map); i++)
|
|
if (i == ascii_idx)
|
|
return (int)color_map[i];
|
|
return -1;
|
|
}
|
|
|
|
int term_puts(void *ctx, char const *buf)
|
|
{
|
|
char const *p = buf;
|
|
int i, len, buf_len, color;
|
|
FILE *fp;
|
|
|
|
if (!ctx)
|
|
return fprintf(stderr, "%s", buf);
|
|
|
|
#ifdef _WIN32
|
|
console_t *console = (console_t *)ctx;
|
|
fp = console->file;
|
|
#else
|
|
fp = (FILE *)ctx;
|
|
#endif
|
|
|
|
if (!fp)
|
|
fp = stderr;
|
|
|
|
buf_len = (int)strlen(buf);
|
|
for (i = len = 0; *p && i < buf_len; i++, p++) {
|
|
if (*p == '~') {
|
|
p++;
|
|
color = ctx ? term_get_color_map(*p) : -1;
|
|
if (color >= 0)
|
|
term_set_fg(ctx, (term_color_t)color);
|
|
}
|
|
else {
|
|
fputc(*p, fp);
|
|
len++;
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int term_printf(void *ctx, _Printf_format_string_ char const *format, ...)
|
|
{
|
|
int len;
|
|
va_list args;
|
|
char buf[4000];
|
|
|
|
va_start(args, format);
|
|
|
|
// Terminate first in case a buggy '_MSC_VER < 1900' is used.
|
|
buf[sizeof(buf)-1] = '\0';
|
|
vsnprintf(buf, sizeof(buf)-1, format, args);
|
|
len = term_puts(ctx, buf);
|
|
va_end (args);
|
|
return len;
|
|
}
|
|
|
|
int term_help_fputs(void *ctx, char const *buf, FILE *fp)
|
|
{
|
|
char const *p = buf;
|
|
int i, len, buf_len, color, state = 0, set_color = -1, next_color = -1;
|
|
if (!fp) {
|
|
fp = stderr;
|
|
}
|
|
|
|
if (!ctx) {
|
|
return fprintf(fp, "%s", buf);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
console_t *console = (console_t *)ctx;
|
|
fp = console->file;
|
|
#else
|
|
fp = (FILE *)ctx;
|
|
#endif
|
|
|
|
buf_len = (int)strlen(buf);
|
|
for (i = len = 0; *p && i < buf_len; i++, p++) {
|
|
if (*p == '~') {
|
|
p++;
|
|
color = ctx ? term_get_color_map(*p) : -1;
|
|
if (color >= 0)
|
|
term_set_fg(ctx, (term_color_t)color);
|
|
continue;
|
|
}
|
|
|
|
if (state == 0 && *p == '[') {
|
|
state = 1;
|
|
next_color = 5;
|
|
}
|
|
else if ((state == 1 || state == 2) && *p == ']' && (p[1] == ',' || (p[1] == ' ' && p[2] != '|') || p[1] == '\n' || p[1] == '\0')) {
|
|
state = 0;
|
|
set_color = 0;
|
|
}
|
|
else if (state == 1 && *p == ' ') {
|
|
state = 2;
|
|
next_color = 6;
|
|
}
|
|
else if (state == 2 && *p == '|') {
|
|
set_color = 0;
|
|
next_color = 6;
|
|
}
|
|
|
|
else if (state == 0 && *p == '=' && p[1] == ' ') {
|
|
state = 3;
|
|
set_color = 1;
|
|
}
|
|
else if (state == 3 && *p == '=') {
|
|
state = 0;
|
|
next_color = 0;
|
|
}
|
|
|
|
else if (state == 0 && *p == '\'') {
|
|
state = 4;
|
|
next_color = 4;
|
|
}
|
|
else if (state == 4 && *p == '\'') {
|
|
state = 0;
|
|
set_color = 0;
|
|
}
|
|
|
|
else if (state == 0 && *p == '\"') {
|
|
state = 5;
|
|
set_color = 4;
|
|
}
|
|
else if (state == 5 && *p == '\"') {
|
|
state = 0;
|
|
next_color = 0;
|
|
}
|
|
|
|
if (set_color >= 0) {
|
|
color = ctx ? (int)color_map[set_color] : -1;
|
|
if (color >= 0)
|
|
term_set_fg(ctx, (term_color_t)color);
|
|
}
|
|
set_color = next_color;
|
|
next_color = -1;
|
|
|
|
fputc(*p, fp);
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int term_help_fprintf(FILE *fp, _Printf_format_string_ char const *format, ...)
|
|
{
|
|
int len;
|
|
va_list args;
|
|
char buf[4000];
|
|
|
|
va_start(args, format);
|
|
|
|
void *term = term_init(fp);
|
|
if (!term_has_color(term)) {
|
|
term_free(term);
|
|
term = NULL;
|
|
}
|
|
|
|
// Terminate first in case a buggy '_MSC_VER < 1900' is used.
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
vsnprintf(buf, sizeof(buf) - 1, format, args);
|
|
len = term_help_fputs(term, buf, fp);
|
|
|
|
term_free(term);
|
|
|
|
va_end(args);
|
|
return len;
|
|
}
|