/// _ _ /// __ _____ _ __ _ __ ___ (_)_ __ __ _| | /// \ \/ / _ \ '__| '_ ` _ \| | '_ \ / _` | | /// > < __/ | | | | | | | | | | | (_| | | /// /_/\_\___|_| |_| |_| |_|_|_| |_|\__,_|_| /// /// 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 #include #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) { char marker = (toggle == true) ? '+' : '-'; 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, 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, 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, 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