#include #include #include #include #include #include #include #include #define SHOW_ALTERNATIVE_SCREEN "\033[?1049h" #define SHOW_NORMAL_SCREEN "\033[?1049l" #define HIDE_CURSOR "\033[?25l" #define SHOW_CURSOR "\033[?25h" #define CLEAR_SCREEN "\033[2J" #define MOVE_CURSOR_HOME "\033[H" #define MOVE_CURSOR_UP "\x1b[1A" #define MOVE_CURSOR_DOWN "\x1b[1B" #define MOVE_CURSOR_RIGHT "\x1b[1C" #define MOVE_CURSOR_LEFT "\x1b[1D" #define MOVE_CURSOR_UP_X "\x1b[%dA" #define MOVE_CURSOR_DOWN_X "\x1b[%dB" #define MOVE_CURSOR_RIGHT_X "\x1b[%dC" #define MOVE_CURSOR_LEFT_X "\x1b[%dD" // man console_codes // Directions // A - Up // B - Down // C - Right // D - Left typedef struct { int line; int column; int number_of_display_lines; int number_of_display_columns; bool resized; bool update_display; bool should_exit; struct termios original_termios; } State; static State state = {0}; void sigwinch_handler(int signal) { fprintf(stderr, "INFO: received signal \"%s\"\n", strsignal(signal)); state.resized = true; } bool get_terminal_size() { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) { return false; } state.number_of_display_columns = ws.ws_col; state.number_of_display_lines = ws.ws_row; return true; } void enable_raw_mode(void) { struct termios t = state.original_termios; if (tcgetattr(STDIN_FILENO, &state.original_termios) == -1) { perror("tcgetattr"); exit(1); } t = state.original_termios; t.c_lflag &= ~(ECHO | ICANON | ISIG); // turn off echo, canonical mode, signals t.c_iflag &= ~(IXON | ICRNL); // disable Ctrl-S/Q and CR->NL t.c_oflag &= ~(OPOST); // disable all output processing t.c_cc[VMIN] = 1; // read() returns after 1 byte t.c_cc[VTIME] = 0; // no timeout if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t) == -1) { perror("tcsetattr"); exit(1); } } int draw_ui(void) { get_terminal_size(); printf("%s%s", CLEAR_SCREEN, MOVE_CURSOR_HOME); for (int i = 0; i < state.number_of_display_lines; i++) { printf(" ~ \n"); printf(MOVE_CURSOR_LEFT_X, state.number_of_display_columns); } } int main(void) { // TODO: switch to sigaction signal(SIGWINCH, sigwinch_handler); signal(SIGINT, SIG_IGN); enable_raw_mode(); printf(SHOW_ALTERNATIVE_SCREEN); printf(HIDE_CURSOR); printf(MOVE_CURSOR_HOME); fflush(stdout); get_terminal_size(); state.update_display = true; char c; while (true) { if (state.update_display) { draw_ui(); state.update_display = false; } ssize_t n = read(STDIN_FILENO, &c, 1); if (n == -1) continue; if (c == 'q') break; switch (c) { case 'h': printf("%s%s", MOVE_CURSOR_LEFT, MOVE_CURSOR_LEFT); state.update_display = true; break; case 'j': printf("%s%s", MOVE_CURSOR_DOWN, MOVE_CURSOR_LEFT); state.update_display = true; break; case 'k': printf("%s%s", MOVE_CURSOR_UP, MOVE_CURSOR_LEFT); state.update_display = true; break; case 'l': state.update_display = true; break; } printf(CLEAR_SCREEN); printf("\033[43m \033[0m"); fflush(stdout); } tcsetattr(STDIN_FILENO, TCSAFLUSH, &state.original_termios); printf(SHOW_NORMAL_SCREEN); printf(SHOW_CURSOR); fflush(stdout); return 0; }