390 lines
15 KiB
C
Executable File
390 lines
15 KiB
C
Executable File
/// _ _
|
|
/// __ _____ _ __ _ __ ___ (_)_ __ __ _| |
|
|
/// \ \/ / _ \ '__| '_ ` _ \| | '_ \ / _` | |
|
|
/// > < __/ | | | | | | | | | | | (_| | |
|
|
/// /_/\_\___|_| |_| |_| |_|_|_| |_|\__,_|_|
|
|
///
|
|
/// 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 {
|
|
character * screen;
|
|
natural screen_width;
|
|
natural screen_height;
|
|
|
|
character format [terminal_format_length + 1];
|
|
character cursor [terminal_cursor_length + 1];
|
|
|
|
boolean active;
|
|
boolean signal [signal_count];
|
|
|
|
natural literal;
|
|
|
|
struct termios * old_terminal;
|
|
struct termios * new_terminal;
|
|
} terminal_structure;
|
|
|
|
static character * terminal_screen_offset (terminal_structure * terminal, natural x, natural y) {
|
|
return (& terminal->screen [terminal_revert_length + terminal_format_length * (y * terminal->screen_width + x) + 2 * y]);
|
|
}
|
|
|
|
static natural terminal_screen_length (terminal_structure * terminal) {
|
|
natural constant = terminal_revert_length + terminal_cursor_length + 1;
|
|
natural variable = terminal_format_length * terminal->screen_height * terminal->screen_width;
|
|
natural new_line = 2 * (terminal->screen_height - 1);
|
|
|
|
return (constant + variable + new_line);
|
|
}
|
|
|
|
static procedure terminal_screen_dimensions (terminal_structure * terminal) {
|
|
struct winsize screen_dimension = { 0 };
|
|
|
|
natural old_width = terminal->screen_width;
|
|
natural old_height = terminal->screen_height;
|
|
|
|
integer 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 (natural 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 character * terminal_format_character (terminal_structure * terminal, character data, integer colour, integer effect) {
|
|
if (character_is_visible (data) == false) {
|
|
data = ' ';
|
|
}
|
|
|
|
colour %= colour_count;
|
|
effect %= effect_count;
|
|
|
|
terminal->format [2] = (character) effect + '0';
|
|
terminal->format [5] = (character) colour + '0';
|
|
terminal->format [7] = data;
|
|
|
|
return (terminal->format);
|
|
}
|
|
|
|
static terminal_structure * terminal_initialize (none) {
|
|
terminal_structure * terminal = allocate (sizeof (* terminal));
|
|
|
|
integer 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] = (natural_8) 0;
|
|
terminal->new_terminal->c_cc [VTIME] = (natural_8) 1;
|
|
|
|
terminal->new_terminal->c_iflag &= (natural) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
|
terminal->new_terminal->c_oflag &= (natural) ~(OPOST);
|
|
terminal->new_terminal->c_cflag |= (natural) (CS8);
|
|
terminal->new_terminal->c_lflag &= (natural) ~(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) {
|
|
integer 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 procedure terminal_synchronize (terminal_structure * terminal) {
|
|
natural signal = 0;
|
|
|
|
output (terminal->screen, terminal_screen_length (terminal));
|
|
|
|
terminal_screen_dimensions (terminal);
|
|
|
|
for (natural index = 0; index < signal_count; ++index) {
|
|
terminal->signal [index] = false;
|
|
}
|
|
|
|
input (& signal, sizeof (signal));
|
|
|
|
terminal->literal = (natural) signal;
|
|
|
|
if (signal == 0x0000001b) {
|
|
terminal->signal [signal_escape] = true;
|
|
} else if (signal == 0x00415b1b) {
|
|
terminal->signal [signal_arrow_up] = true;
|
|
} else if (signal == 0x00425b1b) {
|
|
terminal->signal [signal_arrow_down] = true;
|
|
} else if (signal == 0x00435b1b) {
|
|
terminal->signal [signal_arrow_right] = true;
|
|
} else if (signal == 0x00445b1b) {
|
|
terminal->signal [signal_arrow_left] = true;
|
|
} else if (signal == 0x00000020) {
|
|
terminal->signal [signal_space] = true;
|
|
} else if (signal == 0x0000007f) {
|
|
terminal->signal [signal_backspace] = true;
|
|
} else if (signal == 0x0000000d) {
|
|
terminal->signal [signal_return] = true;
|
|
} else if (character_is_digit ((character) signal) == true) {
|
|
terminal->signal [signal_0 + signal - '0'] = true;
|
|
} else if (character_is_lowercase ((character) signal) == true) {
|
|
terminal->signal [signal_a + signal - 'a'] = true;
|
|
} else if (character_is_uppercase ((character) signal) == true) {
|
|
terminal->signal [signal_a + signal - 'A'] = true;
|
|
terminal->signal [signal_left_shift] = true;
|
|
terminal->signal [signal_right_shift] = true;
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_cursor (terminal_structure * terminal, natural x, natural 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 procedure terminal_render_character (terminal_structure * terminal, character data, natural colour, natural effect, natural x, natural y) {
|
|
if ((x >= terminal->screen_width) || (y >= terminal->screen_height)) {
|
|
return;
|
|
}
|
|
|
|
string_copy_limit (terminal_screen_offset (terminal, x, y), terminal_format_character (terminal, data, colour, effect), terminal_format_length);
|
|
}
|
|
|
|
static procedure terminal_render_toggle (terminal_structure * terminal, boolean toggle, natural x, natural y) {
|
|
character marker = (toggle == true) ? '+' : '-';
|
|
character 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 procedure terminal_render_fill_bar (terminal_structure * terminal, natural value, natural limit, character data, natural colour, natural effect, natural x, natural y) {
|
|
terminal_render_character (terminal, '[', colour_grey, effect_bold, x, y);
|
|
terminal_render_character (terminal, ']', colour_grey, effect_bold, x + limit + 1, y);
|
|
|
|
for (natural index = 0; index < limit; ++index) {
|
|
terminal_render_character (terminal, (index < value) ? data : ' ', colour, effect, x + index + 1, y);
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_string (terminal_structure * terminal, character * string, natural colour, natural effect, natural x, natural y) {
|
|
for (natural index = 0; string [index] != '\0'; ++index) {
|
|
terminal_render_character (terminal, string [index], colour, effect, x + index, y);
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_number (terminal_structure * terminal, integer number, natural colour, natural effect, natural x, natural y) {
|
|
terminal_render_string (terminal, number_to_string (number), colour, effect, x, y);
|
|
}
|
|
|
|
static procedure terminal_render_string_crop (terminal_structure * terminal, character * string, natural colour, natural effect, natural x, natural y, natural crop) {
|
|
for (natural index = 0; (string [index] != '\0') && (index < crop); ++index) {
|
|
terminal_render_character (terminal, string [index], colour, effect, x + index, y);
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_number_crop (terminal_structure * terminal, integer number, natural colour, natural effect, natural x, natural y, natural crop) {
|
|
terminal_render_string_crop (terminal, number_to_string (number), colour, effect, x, y, crop);
|
|
}
|
|
|
|
static procedure terminal_render_vertical_line (terminal_structure * terminal, character data, natural colour, natural effect, natural x, natural y, natural height) {
|
|
for (natural offset = 0; offset != height; ++offset) {
|
|
terminal_render_character (terminal, data, colour, effect, x, y + offset);
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_horizontal_line (terminal_structure * terminal, character data, natural colour, natural effect, natural x, natural y, natural width) {
|
|
for (natural offset = 0; offset != width; ++offset) {
|
|
terminal_render_character (terminal, data, colour, effect, x + offset, y);
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_rectangle_line (terminal_structure * terminal, character data, natural colour, natural effect, natural x, natural y, natural width, natural height) {
|
|
terminal_render_vertical_line (terminal, data, colour, effect, x + 0, y + 0, height + 0);
|
|
terminal_render_vertical_line (terminal, data, colour, effect, x + width - 1, y + 0, height + 0);
|
|
terminal_render_horizontal_line (terminal, data, colour, effect, x + 1, y + 0, width - 1);
|
|
terminal_render_horizontal_line (terminal, data, colour, effect, x + 1, y + height - 1, width - 1);
|
|
}
|
|
|
|
static procedure terminal_render_rectangle_fill (terminal_structure * terminal, character data, natural colour, natural effect, natural x, natural y, natural width, natural height) {
|
|
for (natural offset_y = 0; offset_y != height; ++offset_y) {
|
|
for (natural offset_x = 0; offset_x != width; ++offset_x) {
|
|
terminal_render_character (terminal, data, colour, effect, x + offset_x, y + offset_y);
|
|
}
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_background (terminal_structure * terminal, character data, natural colour, natural effect) {
|
|
for (natural y = 0; y != terminal->screen_height; ++y) {
|
|
for (natural x = 0; x != terminal->screen_width; ++x) {
|
|
terminal_render_character (terminal, data, colour, effect, x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
static procedure terminal_render_format (terminal_structure * terminal, character * format, natural x, natural y, ...) {
|
|
va_list list;
|
|
|
|
natural offset_x = 0;
|
|
natural 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'): {
|
|
character * data = number_to_string (va_arg (list, int));
|
|
terminal_render_string (terminal, data, colour, effect, x + offset_x, y + offset_y);
|
|
offset_x += string_length (data);
|
|
} break;
|
|
case ('t'): {
|
|
boolean data = (boolean) va_arg (list, int);
|
|
terminal_render_toggle (terminal, data, x + offset_x, y + offset_y);
|
|
offset_x += 3;
|
|
} break;
|
|
case ('b'): {
|
|
boolean data = (boolean) va_arg (list, int);
|
|
terminal_render_string (terminal, (data == true) ? "true" : "false", colour, effect, x + offset_x, y + offset_y);
|
|
offset_x += (data == true) ? 4 : 5;
|
|
} break;
|
|
case ('c'): {
|
|
character data = (character) va_arg (list, int);
|
|
terminal_render_character (terminal, data, colour, effect, x + offset_x, y + offset_y);
|
|
++offset_x;
|
|
} break;
|
|
case ('s'): {
|
|
character * data = va_arg (list, character *);
|
|
terminal_render_string (terminal, data, colour, effect, x + offset_x, y + offset_y);
|
|
offset_x += string_length (data);
|
|
} 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_darken; break;
|
|
case ('D'): effect = effect_italic; 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
|