/* Copyright (c) 2023 : Ognjen 'xolatile' Milan Robovic Xurses is free software! You will redistribute it or modify it under the terms of the GNU General Public License by Free Software Foundation. And when you do redistribute it or modify it, it will use either version 3 of the License, or (at yours truly opinion) any later version. It is distributed in the hope that it will be useful or harmful, it really depends... But no warranty what so ever, seriously. See GNU/GPLv3. */ #ifndef XURSES_SOURCE #define XURSES_SOURCE #include #include #include #include #include /* Internal constant definitions. */ #define CURSES_FORMAT ((int) sizeof ("\033[-;3-m-\033[0m") - 1) #define CURSES_REVERT ((int) sizeof ("\033[H") - 1) #define CURSES_CURSOR ((int) sizeof ("\033[---;---H") - 1) /* Internal variable definitions. */ static int curses_signal = SIGNAL_NONE; static int curses_screen_width = 0; static int curses_screen_height = 0; static char * curses_screen = NULL; static char curses_format [CURSES_FORMAT + 1] = "\033[-;3-m-\033[0m"; static void (* curses_action [SIGNAL_COUNT]) (void) = { 0 }; static struct termios curses_old_terminal; static struct termios curses_new_terminal; /* Internal function definitions. */ static void curses_free (void) { curses_screen = deallocate (curses_screen); terminal_clear (); fatal_failure (tcsetattr (STDIN_FILENO, TCSAFLUSH, & curses_old_terminal) == -1, "tcsetattr: Failed to set default terminal attributes."); } /* Return offset of variable 'curses_screen' according to X and Y coordinates. */ static char * curses_screen_offset (int x, int y) { /*log_in (LOG_FAILURE, x <= -1, "curses_screen_offset: X position is below the lower bound."); log_in (LOG_FAILURE, y <= -1, "curses_screen_offset: Y position is below the lower bound."); log_in (LOG_FAILURE, x >= curses_screen_width, "curses_screen_offset: X position is above the upper bound."); log_in (LOG_FAILURE, y >= curses_screen_height, "curses_screen_offset: Y position is above the upper bound.");*/ limit (& x, 0, curses_screen_width - 1); limit (& y, 0, curses_screen_height - 1); return (& curses_screen [CURSES_REVERT + CURSES_FORMAT * (y * curses_screen_width + x)]); } static char * curses_format_character (char character, int colour, int effect) { /*log_in (LOG_WARNING, character_is_invisible (character), "curses_format_character: Can not format invisible characters."); log_in (LOG_FAILURE, colour >= COLOUR_COUNT, "curses_format_character: Colour is invalid enumeration value."); log_in (LOG_FAILURE, effect >= EFFECT_COUNT, "curses_format_character: Effect is invalid enumeration value.");*/ if (character_is_invisible (character) != 0) { character = ' '; } colour %= COLOUR_COUNT; effect %= EFFECT_COUNT; switch (effect) { case EFFECT_NORMAL: effect = 0; break; case EFFECT_BOLD: effect = 1; break; case EFFECT_ITALIC: effect = 3; break; case EFFECT_UNDERLINE: effect = 4; break; case EFFECT_BLINK: effect = 5; break; case EFFECT_REVERSE: effect = 7; break; default: effect = 0; break; } curses_format [2] = (char) effect + '0'; curses_format [5] = (char) colour + '0'; curses_format [7] = character; /*log_out ("curses.log");*/ return (curses_format); } static void curses_idle (void) { return; } /* External variable definitions. */ int curses_active = 1; /* External function definitions. */ void curses_configure (void) { struct winsize screen_dimension; char signal = 0; atexit (curses_free); fatal_failure (ioctl (STDOUT_FILENO, TIOCGWINSZ, & screen_dimension) == -1, "ioctl: Failed to get terminal dimensions."); curses_screen_width = (int) screen_dimension.ws_col; curses_screen_height = (int) screen_dimension.ws_row; fatal_failure (tcgetattr (STDIN_FILENO, & curses_old_terminal) == -1, "tcgetattr: Failed to get default terminal attributes."); curses_new_terminal = curses_old_terminal; curses_new_terminal.c_cc [VMIN] = (unsigned char) 0; curses_new_terminal.c_cc [VTIME] = (unsigned char) 1; curses_new_terminal.c_iflag &= (unsigned int) ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); curses_new_terminal.c_oflag &= (unsigned int) ~(OPOST); curses_new_terminal.c_cflag |= (unsigned int) (CS8); curses_new_terminal.c_lflag &= (unsigned int) ~(ECHO | ICANON | IEXTEN | ISIG); fatal_failure (tcsetattr (STDIN_FILENO, TCSAFLUSH, & curses_new_terminal) == -1, "tcsetattr: Failed to set reverse terminal attributes."); curses_screen = allocate (CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height + CURSES_CURSOR + 1); for (signal = SIGNAL_NONE; signal != SIGNAL_COUNT; ++signal) { curses_unbind ((char) signal); } terminal_clear (); string_copy (& curses_screen [0], "\033[H"); } void curses_synchronize (void) { curses_signal = '\0'; out (curses_screen, CURSES_REVERT + CURSES_FORMAT * curses_screen_width * curses_screen_height); in (& curses_signal, 4); switch (curses_signal) { case '\033': curses_signal = SIGNAL_ESCAPE; break; case '0': curses_signal = SIGNAL_0; break; case 'q': curses_signal = SIGNAL_Q; break; case 'Q': curses_signal = SIGNAL_Q | SIGNAL_SHIFT; break; case 'w': curses_signal = SIGNAL_W; break; case 's': curses_signal = SIGNAL_S; break; case 'a': curses_signal = SIGNAL_A; break; case 'd': curses_signal = SIGNAL_D; break; default: curses_signal = SIGNAL_NONE; break; } if (curses_signal == SIGNAL_ESCAPE) { curses_active = 0; return; } if ((curses_signal > SIGNAL_ANY) && (curses_signal < SIGNAL_COUNT)) { curses_action [curses_signal] (); } } void curses_bind (int signal, void (* action) (void)) { curses_action [signal] = action; } void curses_unbind (int signal) { curses_action [signal] = curses_idle; } void curses_render_cursor (int x, int y) { terminal_show_cursor (x + y); } void curses_render_character (char character, int colour, int effect, int x, int y) { string_copy_limit (curses_screen_offset (x, y), curses_format_character (character, colour, effect), CURSES_FORMAT); } void curses_render_background (char character, int colour, int effect) { int x, y; for (y = 0; y != curses_screen_height; ++y) { for (x = 0; x != curses_screen_width; ++x) { curses_render_character (character, colour, effect, x, y); } } } void curses_render_string_limit (char * string, int limit, int colour, int effect, int x, int y) { int offset; for (offset = 0; offset != limit; ++offset) { if (x + offset < curses_screen_width) { curses_render_character (string [offset], colour, effect, x + offset, y); } else { curses_render_character ('+', COLOUR_GREY, EFFECT_BOLD, x + offset, y); return; } } } void curses_render_number_limit (int number, int limit, int colour, int effect, int x, int y) { (void) number; (void) limit; (void) colour; (void) effect; (void) x; (void) y; return; } void curses_render_string (char * string, int colour, int effect, int x, int y) { curses_render_string_limit (string, string_length (string), colour, effect, x, y); } void curses_render_number (int number, int colour, int effect, int x, int y) { curses_render_number_limit (number, 4, colour, effect, x, y); } #endif