From 93bb30c682bdc984b9acced58e9a1268eefade15 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Fri, 11 Jun 2021 01:37:27 +0200 Subject: Add helper for handling colored terminal output --- include/Makefile.am | 3 +- include/libimobiledevice-glue/termcolors.h | 86 ++++++++ src/Makefile.am | 10 +- src/glue.c | 72 +++++++ src/termcolors.c | 319 +++++++++++++++++++++++++++++ 5 files changed, 485 insertions(+), 5 deletions(-) create mode 100644 include/libimobiledevice-glue/termcolors.h create mode 100644 src/glue.c create mode 100644 src/termcolors.c diff --git a/include/Makefile.am b/include/Makefile.am index 5d54892..a661eeb 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,4 +2,5 @@ nobase_include_HEADERS = \ libimobiledevice-glue/socket.h \ libimobiledevice-glue/thread.h \ libimobiledevice-glue/utils.h \ - libimobiledevice-glue/collection.h + libimobiledevice-glue/collection.h \ + libimobiledevice-glue/termcolors.h diff --git a/include/libimobiledevice-glue/termcolors.h b/include/libimobiledevice-glue/termcolors.h new file mode 100644 index 0000000..2bac741 --- /dev/null +++ b/include/libimobiledevice-glue/termcolors.h @@ -0,0 +1,86 @@ +/* + * termcolors.h + * + * Copyright (c) 2020-2021 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef TERMCOLORS_H +#define TERMCOLORS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#define COLOR_RESET "\e[m" +#define STYLE_NORMAL "\e[0m" +#define STYLE_BRIGHT "\e[1m" +#define STYLE_DARK "\e[2m" +#define COLOR_BLACK "\e[0;30m" +#define COLOR_DARK_GRAY "\e[1;30m" +#define COLOR_RED "\e[0;31m" +#define COLOR_BRIGHT_RED "\e[1;31m" +#define COLOR_DARK_RED "\e[2;31m" +#define COLOR_GREEN "\e[0;32m" +#define COLOR_BRIGHT_GREEN "\e[1;32m" +#define COLOR_DARK_GREEN "\e[2;32m" +#define COLOR_YELLOW "\e[0;33m" +#define COLOR_BRIGHT_YELLOW "\e[1;33m" +#define COLOR_DARK_YELLOW "\e[2;33m" +#define COLOR_BLUE "\e[0;34m" +#define COLOR_BRIGHT_BLUE "\e[1;34m" +#define COLOR_DARK_BLUE "\e[2;34m" +#define COLOR_MAGENTA "\e[0;35m" +#define COLOR_BRIGHT_MAGENTA "\e[1;35m" +#define COLOR_DARK_MAGENTA "\e[2;35m" +#define COLOR_CYAN "\e[0;36m" +#define COLOR_BRIGHT_CYAN "\e[1;36m" +#define COLOR_DARK_CYAN "\e[2;36m" +#define COLOR_LIGHT_GRAY "\e[0;37m" +#define COLOR_WHITE "\e[1;37m" +#define COLOR_GRAY "\e[2;37m" +#define COLOR_DEFAULT "\e[39m" +#define BG_BLACK "\e[40m" +#define BG_GRAY "\e[100m" +#define BG_RED "\e[41m" +#define BG_BRIGHT_RED "\e[101m" +#define BG_GREEN "\e[42m" +#define BG_BRIGHT_GREEN "\e[102m" +#define BG_YELLOW "\e[43m" +#define BG_BRIGHT_YELLOW "\e[103m" +#define BG_BLUE "\e[44m" +#define BG_BRIGHT_BLUE "\e[104m" +#define BG_MAGENTA "\e[45m" +#define BG_BRIGHT_MAGENTA "\e[105m" +#define BG_CYAN "\e[46m" +#define BG_BRIGHT_CYAN "\e[106m" +#define BG_LIGHT_GRAY "\e[47m" +#define BG_WHITE "\e[107m" +#define BG_DEFAULT "\e[49m" + +/* automatically called by library constructor */ +void term_colors_init(); + +/* enable / disable terminal colors */ +void term_colors_set_enabled(int en); + +/* color-aware *printf variants */ +int cprintf(const char* fmt, ...); +int cfprintf(FILE* stream, const char* fmt, ...); +int cvfprintf(FILE* stream, const char* fmt, va_list vargs); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 2856eab..67986cb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,10 +8,12 @@ lib_LTLIBRARIES = libimobiledevice-glue-1.0.la libimobiledevice_glue_1_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBIMOBILEDEVICE_GLUE_SO_VERSION) -no-undefined libimobiledevice_glue_1_0_la_LIBADD = libimobiledevice_glue_1_0_la_SOURCES = \ - socket.c \ - thread.c \ - utils.c \ - collection.c \ + glue.c \ + socket.c \ + thread.c \ + utils.c \ + collection.c \ + termcolors.c \ common.h if WIN32 diff --git a/src/glue.c b/src/glue.c new file mode 100644 index 0000000..e65ef56 --- /dev/null +++ b/src/glue.c @@ -0,0 +1,72 @@ +/* + * glue.c + * + * Copyright (c) 2021 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef WIN32 +#include +#endif + +#include "common.h" +#include "libimobiledevice-glue/thread.h" + +extern void term_colors_init(); + +static void internal_glue_init(void) +{ + term_colors_init(); +} + +static void internal_glue_deinit(void) +{ + +} + +static thread_once_t init_once = THREAD_ONCE_INIT; +static thread_once_t deinit_once = THREAD_ONCE_INIT; + +#ifdef WIN32 +BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) { + case DLL_PROCESS_ATTACH: + thread_once(&init_once, internal_glue_init); + break; + case DLL_PROCESS_DETACH: + thread_once(&deinit_once, internal_glue_deinit); + break; + default: + break; + } + return 1; +} +#else +static void __attribute__((constructor)) limd_glue_initialize(void) +{ + thread_once(&init_once, internal_glue_init); +} + +static void __attribute__((destructor)) limd_glue_deinitialize(void) +{ + thread_once(&deinit_once, internal_glue_deinit); +} +#endif diff --git a/src/termcolors.c b/src/termcolors.c new file mode 100644 index 0000000..703df30 --- /dev/null +++ b/src/termcolors.c @@ -0,0 +1,319 @@ +/* + * termcolors.c + * + * Copyright (c) 2020-2021 Nikias Bassen, All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef WIN32 +#include +#endif + +#include +#include +#include +#include +#include + +#include "common.h" +#include "libimobiledevice-glue/termcolors.h" + +static int use_colors = 0; + +#ifdef WIN32 +static int WIN32_LEGACY_MODE = 1; +static WORD COLOR_RESET_ATTR = 0; +static WORD DEFAULT_FG_ATTR = 0; +static WORD DEFAULT_BG_ATTR = 0; +static HANDLE h_stdout = INVALID_HANDLE_VALUE; +static HANDLE h_stderr = INVALID_HANDLE_VALUE; + +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif + +#define STYLE_DIM (1 << 16) + +#define FG_COLOR_MASK (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) +#define BG_COLOR_MASK (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) +#define FG_COLOR_ATTR_MASK (FG_COLOR_MASK | FOREGROUND_INTENSITY) +#define BG_COLOR_ATTR_MASK (BG_COLOR_MASK | BACKGROUND_INTENSITY) + +static int style_map[9] = { + /* RESET ALL */ 0, + /* BRIGHT */ FOREGROUND_INTENSITY, + /* DIM */ STYLE_DIM, + /* (n/a) */ 0, + /* UNDERLINED */ 0, // COMMON_LVB_UNDERSCORE ? + /* BLINK */ 0, // NOT SUPPORTED + /* (n/a) */ 0, + /* REVERSE CLR */ 0, // COMMON_LVB_REVERSE_VIDEO ? + /* HIDDEN */ 0 // NOT SUPPORTED +}; + +static int fgcolor_map[8] = { + /* BLACK */ 0, + /* RED */ FOREGROUND_RED, + /* GREEN */ FOREGROUND_GREEN, + /* YELLOW */ FOREGROUND_GREEN | FOREGROUND_RED, + /* BLUE */ FOREGROUND_BLUE, + /* MAGENTA */ FOREGROUND_BLUE | FOREGROUND_RED, + /* CYAN */ FOREGROUND_BLUE | FOREGROUND_GREEN, + /* WHITE */ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE +}; +static int bgcolor_map[8] = { + /* BLACK */ 0, + /* RED */ BACKGROUND_RED, + /* GREEN */ BACKGROUND_GREEN, + /* YELLOW */ BACKGROUND_GREEN | BACKGROUND_RED, + /* BLUE */ BACKGROUND_BLUE, + /* MAGENTA */ BACKGROUND_BLUE | BACKGROUND_RED, + /* CYAN */ BACKGROUND_BLUE | BACKGROUND_GREEN, + /* WHITE */ BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE +}; +#else +static int WIN32_LEGACY_MODE = 0; +#endif + +LIBIMOBILEDEVICE_GLUE_API void term_colors_init() +{ +#ifdef WIN32 + DWORD conmode = 0; + h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleMode(h_stdout, &conmode); + if (conmode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) { + WIN32_LEGACY_MODE = 0; + } else { + conmode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (SetConsoleMode(h_stdout, conmode)) { + WIN32_LEGACY_MODE = 0; + } else { + WIN32_LEGACY_MODE = 1; + } + } + if (WIN32_LEGACY_MODE) { + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo(h_stdout, &csbi)) { + COLOR_RESET_ATTR = csbi.wAttributes; + DEFAULT_FG_ATTR = csbi.wAttributes & FG_COLOR_ATTR_MASK; + DEFAULT_BG_ATTR = csbi.wAttributes & BG_COLOR_ATTR_MASK; + } + h_stderr = GetStdHandle(STD_ERROR_HANDLE); + } +#endif + use_colors = isatty(1); + const char* color_env = getenv("COLOR"); + if (color_env) { + long val = strtol(color_env, NULL, 10); + use_colors = (val != 0); + } +} + +LIBIMOBILEDEVICE_GLUE_API void term_colors_set_enabled(int en) +{ + use_colors = en; +} + +LIBIMOBILEDEVICE_GLUE_API int cvfprintf(FILE* stream, const char* fmt, va_list vargs) +{ + int res = 0; + int colorize = use_colors; +#ifdef WIN32 + struct esc_item { + int pos; + WORD attr; + }; + HANDLE h_stream = h_stdout; + + if (WIN32_LEGACY_MODE) { + if (stream == stdout) { + h_stream = h_stdout; + } else if (stream == stderr) { + h_stream = h_stderr; + } else { + colorize = 0; + } + } +#endif + if (!colorize || WIN32_LEGACY_MODE) { + // first, we need to print the string WITH escape sequences + va_list vargs_copy; + va_copy(vargs_copy, vargs); + int len = vsnprintf(NULL, 0, fmt, vargs); + char* newbuf = (char*)malloc(len+1); + res = vsnprintf(newbuf, len+1, fmt, vargs_copy); + va_end(vargs_copy); + + // then, we need to remove the escape sequences, if any +#ifdef WIN32 + // if colorize is on, we need to keep their positions for later + struct esc_item* esc_items = NULL; + int attr = 0; + if (colorize) { + esc_items = (struct esc_item*)malloc(sizeof(struct esc_item) * ((len/3)+1)); + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (GetConsoleScreenBufferInfo(h_stream, &csbi)) { + attr = csbi.wAttributes; + } + } +#endif + int num_esc = 0; + char* start = &newbuf[0]; + char* p = start; + char* end = start + len + 1; + while (p < end-1) { + char* cur = p; + if (*p == '\e' && end-p > 2 && *(p+1) == '[') { + p+=2; + if (*p == 'm') { +#ifdef WIN32 + attr = COLOR_RESET_ATTR; +#endif + int move_by = (p+1)-cur; + int move_amount = end-(p+1); + memmove(cur, p+1, move_amount); + end -= move_by; + p = cur; + } else { + char* endp = NULL; + do { + long lval = strtol(p, &endp, 10); + if (!endp || (*endp != ';' && *endp != 'm')) { + fprintf(stderr, "\n*** %s: Invalid escape sequence in format string, expected ';' or 'm' ***\n", __func__); +#ifdef WIN32 + free(esc_items); +#endif + free(newbuf); + return -1; + } +#ifdef WIN32 + if (colorize) { + if (lval >= 0 && lval <= 8) { + /* style attributes */ + attr &= ~FOREGROUND_INTENSITY; + attr |= style_map[lval]; + } else if (lval >= 30 && lval <= 37) { + /* foreground color */ + attr &= ~FG_COLOR_MASK; + attr |= fgcolor_map[lval-30]; + } else if (lval == 39) { + /* default foreground color */ + attr &= ~FG_COLOR_ATTR_MASK; + attr |= DEFAULT_FG_ATTR; + } else if (lval >= 40 && lval <= 47) { + /* background color */ + attr &= ~BG_COLOR_MASK; + attr |= bgcolor_map[lval-40]; + } else if (lval == 49) { + /* default background color */ + attr &= ~BG_COLOR_ATTR_MASK; + attr |= DEFAULT_BG_ATTR; + } else if (lval >= 90 && lval <= 97) { + /* foreground color bright */ + attr &= ~FG_COLOR_ATTR_MASK; + attr |= fgcolor_map[lval-90]; + attr |= FOREGROUND_INTENSITY; + } else if (lval >= 100 && lval <= 107) { + /* background color bright */ + attr &= ~BG_COLOR_MASK; + attr |= bgcolor_map[lval-100]; + attr |= BACKGROUND_INTENSITY; + } + } +#else + (void)lval; // suppress compiler warning about unused variable +#endif + p = endp+1; + } while (p < end && *endp == ';'); + + int move_by = (endp+1)-cur; + int move_amount = end-(endp+1); + memmove(cur, endp+1, move_amount); + end -= move_by; + p = cur; + } +#ifdef WIN32 + if (colorize) { + esc_items[num_esc].pos = p-start; + if (attr & STYLE_DIM) { + attr &= ~STYLE_DIM; + attr &= ~FOREGROUND_INTENSITY; + } + esc_items[num_esc].attr = (WORD)attr; + num_esc++; + } +#endif + } else { + p++; + } + } + + if (num_esc == 0) { + res = fputs(newbuf, stream); + free(newbuf); +#ifdef WIN32 + free(esc_items); +#endif + return res; + } +#ifdef WIN32 + else { + p = &newbuf[0]; + char* lastp = &newbuf[0]; + int i; + for (i = 0; i < num_esc; i++) { + p = &newbuf[esc_items[i].pos]; + if (lastp < p) { + fprintf(stream, "%.*s", p-lastp, lastp); + lastp = p; + } + SetConsoleTextAttribute(h_stream, esc_items[i].attr); + } + if (lastp < end) { + fprintf(stream, "%.*s", end-lastp, lastp); + } + return res; + } +#endif + } else { + res = vfprintf(stream, fmt, vargs); + } + return res; +} + +LIBIMOBILEDEVICE_GLUE_API int cfprintf(FILE* stream, const char* fmt, ...) +{ + int res = 0; + va_list va; + va_start(va, fmt); + res = cvfprintf(stream, fmt, va); + va_end(va); + return res; +} + +LIBIMOBILEDEVICE_GLUE_API int cprintf(const char* fmt, ...) +{ + int res = 0; + va_list va; + va_start(va, fmt); + res = cvfprintf(stdout, fmt, va); + va_end(va); + return res; +} -- cgit v1.1-32-gdbae