--- /dev/null
+/// _ _
+/// __ _____ _ __ _ __ ___ (_)_ __ __ _| |
+/// \ \/ / _ \ '__| '_ ` _ \| | '_ \ / _` | |
+/// > < __/ | | | | | | | | | | | (_| | |
+/// /_/\_\___|_| |_| |_| |_|_|_| |_|\__,_|_|
+///
+/// Copyright (c) 1997 - Ognjen 'xolatile' Milan Robovic
+///
+/// xolatile@chud.cyou - xerminal - Library containing the full power of VT100 escape sequences or something for TUI programs.
+///
+/// This program is free software, free as in freedom and as in free beer, 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 3 of the License, or any later version if you wish...
+///
+/// This program is distributed in the hope that it will be useful, but it is probably not, and without any warranty, without even the implied
+/// warranty of merchantability or fitness for a particular purpose, because it is pointless. Please see the GNU (Geenoo) General Public License
+/// for more details, if you dare, it is a lot of text that nobody wants to read...
+
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#define terminal_format_length (sizeof ("\033[-;3-m-\033[0m") - 1)
+#define terminal_revert_length (sizeof ("\033[H") - 1)
+#define terminal_cursor_length (sizeof ("\033[---;---H") - 1)
+
+typedef struct {
+ char * screen;
+ uint screen_width;
+ uint screen_height;
+
+ char format [terminal_format_length + 1];
+ char cursor [terminal_cursor_length + 1];
+
+ bool active;
+ bool signal [signal_count];
+
+ uint character;
+
+ struct termios * old_terminal;
+ struct termios * new_terminal;
+} terminal_structure;
+
+static char * terminal_screen_offset (terminal_structure * terminal, uint x, uint y) {
+ return (& terminal->screen [terminal_revert_length + terminal_format_length * (y * terminal->screen_width + x) + 2 * y]);
+}
+
+static uint terminal_screen_length (terminal_structure * terminal) {
+ uint constant = terminal_revert_length + terminal_cursor_length + 1;
+ uint variable = terminal_format_length * terminal->screen_height * terminal->screen_width;
+ uint new_line = 2 * (terminal->screen_height - 1);
+
+ return (constant + variable + new_line);
+}
+
+static void terminal_screen_dimensions (terminal_structure * terminal) {
+ struct winsize screen_dimension = { 0 };
+
+ uint old_width = terminal->screen_width;
+ uint old_height = terminal->screen_height;
+
+ int status = ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension);
+
+ fatal_failure (status == -1, "ioctl: Failed to get dimensions.");
+
+ terminal->screen_width = screen_dimension.ws_col;
+ terminal->screen_height = screen_dimension.ws_row;
+
+ if ((old_width != terminal->screen_width) || (old_height != terminal->screen_height)) {
+ if (terminal->screen != null) {
+ terminal->screen = deallocate (terminal->screen);
+ }
+
+ terminal->screen = allocate (terminal_screen_length (terminal));
+ }
+
+ string_copy (& terminal->screen [0], "\033[H");
+
+ for (uint index = 0; index < terminal->screen_height - 1; ++index) {
+ string_copy (& terminal->screen [terminal_revert_length + index * terminal_format_length * terminal->screen_width], "\r\n");
+ }
+}
+
+static char * terminal_format_character (terminal_structure * terminal, char character, int colour, int effect) {
+ if (character_is_visible (character) == false) {
+ character = ' ';
+ }
+
+ colour %= colour_count;
+ effect %= effect_count;
+
+ terminal->format [2] = (char) effect + '0';
+ terminal->format [5] = (char) colour + '0';
+ terminal->format [7] = character;
+
+ return (terminal->format);
+}
+
+static terminal_structure * terminal_initialize (void) {
+ terminal_structure * terminal = allocate (sizeof (* terminal));
+
+ int status = -1;
+
+ string_copy_limit (terminal->format, "\033[-;3-m-\033[0m", terminal_format_length + 1);
+ string_copy_limit (terminal->cursor, "\033[---;---H", terminal_cursor_length + 1);
+
+ terminal->old_terminal = allocate (sizeof (* terminal->old_terminal));
+ terminal->new_terminal = allocate (sizeof (* terminal->new_terminal));
+
+ terminal_screen_dimensions (terminal);
+
+ status = tcgetattr (STDIN_FILENO, terminal->old_terminal);
+
+ fatal_failure (status == -1, "tcgetattr: Failed to get default attributes.");
+
+ memory_copy (terminal->new_terminal, terminal->old_terminal, sizeof (* terminal->old_terminal));
+
+ terminal->new_terminal->c_cc [VMIN] = (uchar) 0;
+ terminal->new_terminal->c_cc [VTIME] = (uchar) 1;
+
+ terminal->new_terminal->c_iflag &= (uint) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ terminal->new_terminal->c_oflag &= (uint) ~(OPOST);
+ terminal->new_terminal->c_cflag |= (uint) (CS8);
+ terminal->new_terminal->c_lflag &= (uint) ~(ECHO | ICANON | IEXTEN | ISIG);
+
+ status = tcsetattr (STDIN_FILENO, TCSAFLUSH, terminal->new_terminal);
+
+ fatal_failure (status == -1, "tcsetattr: Failed to set reverse attributes.");
+
+ terminal->active = true;
+
+ show_cursor (false);
+
+ echo_clear ();
+
+ return (terminal);
+}
+
+static terminal_structure * terminal_deinitialize (terminal_structure * terminal) {
+ int status = tcsetattr (STDIN_FILENO, TCSAFLUSH, terminal->old_terminal);
+
+ fatal_failure (status == -1, "tcsetattr: Failed to set default attributes.");
+
+ terminal->screen = deallocate (terminal->screen);
+ terminal->old_terminal = deallocate (terminal->old_terminal);
+ terminal->new_terminal = deallocate (terminal->new_terminal);
+
+ echo_clear ();
+
+ show_cursor (true);
+
+ return (deallocate (terminal));
+}
+
+static void terminal_synchronize (terminal_structure * terminal) {
+ uint character = 0;
+
+ output (terminal->screen, terminal_screen_length (terminal));
+
+ terminal_screen_dimensions (terminal);
+
+ for (uint index = 0; index < signal_count; ++index) {
+ terminal->signal [index] = false;
+ }
+
+ input (& character, sizeof (character));
+
+ terminal->character = (uint) character;
+
+ if (character == 0x0000001b) {
+ terminal->signal [signal_escape] = true;
+ } else if (character == 0x00415b1b) {
+ terminal->signal [signal_arrow_up] = true;
+ } else if (character == 0x00425b1b) {
+ terminal->signal [signal_arrow_down] = true;
+ } else if (character == 0x00435b1b) {
+ terminal->signal [signal_arrow_right] = true;
+ } else if (character == 0x00445b1b) {
+ terminal->signal [signal_arrow_left] = true;
+ } else if (character == 0x00000020) {
+ terminal->signal [signal_space] = true;
+ } else if (character == 0x0000007f) {
+ terminal->signal [signal_backspace] = true;
+ } else if (character == 0x0000000d) {
+ terminal->signal [signal_return] = true;
+ } else if (character_is_digit ((char) character) == true) {
+ terminal->signal [signal_0 + character - '0'] = true;
+ } else if (character_is_lowercase ((char) character) == true) {
+ terminal->signal [signal_a + character - 'a'] = true;
+ } else if (character_is_uppercase ((char) character) == true) {
+ terminal->signal [signal_a + character - 'A'] = true;
+ terminal->signal [signal_left_shift] = true;
+ terminal->signal [signal_right_shift] = true;
+ }
+}
+
+static void terminal_render_cursor (terminal_structure * terminal, uint x, uint y) { /* BROKE IT INTENTIONALLY */
+ string_copy_limit (terminal->cursor + 2, string_align_left (number_to_string (y % 1000 + 1), 3, '0'), 3);
+ string_copy_limit (terminal->cursor + 6, string_align_left (number_to_string (x % 1000 + 1), 3, '0'), 3);
+
+ string_copy_limit (& terminal->screen [terminal_screen_length (terminal) - terminal_cursor_length - 1], terminal->cursor, terminal_cursor_length);
+}
+
+static void terminal_render_character (terminal_structure * terminal, char character, uint colour, uint effect, uint x, uint y) {
+ if ((x >= terminal->screen_width) || (y >= terminal->screen_height)) {
+ return;
+ }
+
+ string_copy_limit (terminal_screen_offset (terminal, x, y), terminal_format_character (terminal, character, colour, effect), terminal_format_length);
+}
+
+static void terminal_render_toggle (terminal_structure * terminal, bool toggle, uint x, uint y) {
+ const char marker = (toggle == true) ? '+' : '-';
+ const char colour = (toggle == true) ? colour_green : colour_red;
+
+ terminal_render_character (terminal, '[', colour_grey, effect_bold, x + 0, y);
+ terminal_render_character (terminal, marker, colour, effect_bold, x + 1, y);
+ terminal_render_character (terminal, ']', colour_grey, effect_bold, x + 2, y);
+}
+
+static void terminal_render_fill_bar (terminal_structure * terminal, uint value, uint limit, char character, uint colour, uint effect, uint x, uint y) {
+ terminal_render_character (terminal, '[', colour_grey, effect_bold, x, y);
+ terminal_render_character (terminal, ']', colour_grey, effect_bold, x + limit + 1, y);
+
+ for (uint index = 0; index < limit; ++index) {
+ terminal_render_character (terminal, (index < value) ? character : ' ', colour, effect, x + index + 1, y);
+ }
+}
+
+static void terminal_render_string (terminal_structure * terminal, const char * string, uint colour, uint effect, uint x, uint y) {
+ for (uint index = 0; string [index] != '\0'; ++index) {
+ terminal_render_character (terminal, string [index], colour, effect, x + index, y);
+ }
+}
+
+static void terminal_render_number (terminal_structure * terminal, int number, uint colour, uint effect, uint x, uint y) {
+ terminal_render_string (terminal, number_to_string (number), colour, effect, x, y);
+}
+
+static void terminal_render_string_crop (terminal_structure * terminal, const char * string, uint colour, uint effect, uint x, uint y, uint crop) {
+ for (uint index = 0; (string [index] != '\0') && (index < crop); ++index) {
+ terminal_render_character (terminal, string [index], colour, effect, x + index, y);
+ }
+}
+
+static void terminal_render_number_crop (terminal_structure * terminal, int number, uint colour, uint effect, uint x, uint y, uint crop) {
+ terminal_render_string_crop (terminal, number_to_string (number), colour, effect, x, y, crop);
+}
+
+static void terminal_render_vertical_line (terminal_structure * terminal, char character, uint colour, uint effect, uint x, uint y, uint height) {
+ for (uint offset = 0; offset != height; ++offset) {
+ terminal_render_character (terminal, character, colour, effect, x, y + offset);
+ }
+}
+
+static void terminal_render_horizontal_line (terminal_structure * terminal, char character, uint colour, uint effect, uint x, uint y, uint width) {
+ for (uint offset = 0; offset != width; ++offset) {
+ terminal_render_character (terminal, character, colour, effect, x + offset, y);
+ }
+}
+
+static void terminal_render_rectangle_line (terminal_structure * terminal, char character, uint colour, uint effect, uint x, uint y, uint width, uint height) {
+ terminal_render_vertical_line (terminal, character, colour, effect, x + 0, y + 0, height + 0);
+ terminal_render_vertical_line (terminal, character, colour, effect, x + width - 1, y + 0, height + 0);
+ terminal_render_horizontal_line (terminal, character, colour, effect, x + 1, y + 0, width - 1);
+ terminal_render_horizontal_line (terminal, character, colour, effect, x + 1, y + height - 1, width - 1);
+}
+
+static void terminal_render_rectangle_fill (terminal_structure * terminal, char character, uint colour, uint effect, uint x, uint y, uint width, uint height) {
+ for (uint offset_y = 0; offset_y != height; ++offset_y) {
+ for (uint offset_x = 0; offset_x != width; ++offset_x) {
+ terminal_render_character (terminal, character, colour, effect, x + offset_x, y + offset_y);
+ }
+ }
+}
+
+static void terminal_render_background (terminal_structure * terminal, char character, uint colour, uint effect) {
+ for (uint y = 0; y != terminal->screen_height; ++y) {
+ for (uint x = 0; x != terminal->screen_width; ++x) {
+ terminal_render_character (terminal, character, colour, effect, x, y);
+ }
+ }
+}
+
+static void terminal_render_format (terminal_structure * terminal, const char * format, uint x, uint y, ...) {
+ va_list list;
+
+ uint offset_x = 0;
+ uint offset_y = 0;
+
+ colour_enumeration colour = colour_white;
+ effect_enumeration effect = effect_normal;
+
+ va_start (list, format);
+
+ for (; * format != character_null; ++format) {
+ switch (* format) {
+ case '\t': {
+ offset_x += 8;
+ } break;
+ case '\n': {
+ offset_x *= 0;
+ offset_y += 1;
+ } break;
+ case '\r': {
+ offset_x *= 0;
+ } break;
+ case '%': {
+ ++format;
+ switch (* format) {
+ case '%': {
+ terminal_render_character (terminal, '%', colour, effect, x + offset_x, y + offset_y);
+ ++offset_x;
+ } break;
+ case 'i': {
+ char * number = number_to_string (va_arg (list, int));
+ terminal_render_string (terminal, number, colour, effect, x + offset_x, y + offset_y);
+ offset_x += string_length (number);
+ } break;
+ case 't': {
+ bool toggle = (bool) va_arg (list, int);
+ terminal_render_toggle (terminal, toggle, x + offset_x, y + offset_y);
+ offset_x += 3;
+ } break;
+ case 'b': {
+ bool boolean = (bool) va_arg (list, int);
+ terminal_render_string (terminal, (boolean == true) ? "true" : "false", colour, effect, x + offset_x, y + offset_y);
+ offset_x += (boolean == true) ? 4 : 5;
+ } break;
+ case 'c': {
+ char character = (char) va_arg (list, int);
+ terminal_render_character (terminal, character, colour, effect, x + offset_x, y + offset_y);
+ ++offset_x;
+ } break;
+ case 's': {
+ char * string = va_arg (list, char *);
+ terminal_render_string (terminal, string, colour, effect, x + offset_x, y + offset_y);
+ offset_x += string_length (string);
+ } break;
+ default: {
+ terminal_render_character (terminal, '?', colour, effect, x + offset_x, y + offset_y);
+ ++offset_x;
+ } break;
+ }
+ } break;
+ case '/': {
+ ++format;
+ switch (* format) {
+ case '/': {
+ terminal_render_character (terminal, '/', colour, effect, x + offset_x, y + offset_y);
+ ++offset_x;
+ } break;
+ case 'A': effect = effect_normal; break;
+ case 'B': effect = effect_bold; break;
+ case 'C': effect = effect_italic; break;
+ case 'D': effect = effect_undefined_code; break;
+ case 'E': effect = effect_underline; break;
+ case 'F': effect = effect_blink; break;
+ case 'G': effect = effect_reverse; break;
+ case 'H': effect = effect_invisible_text; break;
+ case '0': colour = colour_grey; break;
+ case '1': colour = colour_red; break;
+ case '2': colour = colour_green; break;
+ case '3': colour = colour_yellow; break;
+ case '4': colour = colour_blue; break;
+ case '5': colour = colour_pink; break;
+ case '6': colour = colour_cyan; break;
+ case '7': colour = colour_white; break;
+ case '-': {
+ colour = colour_white;
+ effect = effect_normal;
+ } break;
+ default: {
+ terminal_render_character (terminal, '?', colour, effect, x + offset_x, y + offset_y);
+ ++offset_x;
+ } break;
+ }
+ } break;
+ default: {
+ terminal_render_character (terminal, * format, colour, effect, x + offset_x, y + offset_y);
+ ++offset_x;
+ } break;
+ }
+ }
+
+ va_end (list);
+}
+
+#undef terminal_format_length
+#undef terminal_revert_length
+#undef terminal_cursor_length