/* 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_OFFSET ((int) sizeof ("\033[-;3-m-\033[0m") - 1) #define CURSES_REVERT ((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 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_OFFSET * (y * curses_screen_width + x)]); } static char * curses_format_character (char character, int colour, int effect) { static char curses_format [CURSES_OFFSET + 1] = "\033[-;3-m-\033[0m"; 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 = ' '; } curses_format [2] = (char) (effect % EFFECT_COUNT) + '0'; curses_format [5] = (char) (colour % COLOUR_COUNT) + '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_OFFSET * curses_screen_width * curses_screen_height + 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_OFFSET * 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_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_OFFSET); } 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_bind (int signal, void (* action) (void)) { curses_action [signal] = action; } void curses_unbind (int signal) { curses_action [signal] = curses_idle; } #endif