You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
3.4 KiB
C
165 lines
3.4 KiB
C
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#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;
|
|
}
|