304 lines
7.9 KiB
C
304 lines
7.9 KiB
C
|
#ifndef RTERM_H
|
||
|
#define RTERM_H
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <termios.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <string.h>
|
||
|
#include "rio.h"
|
||
|
#include "rtime.h"
|
||
|
typedef struct winsize winsize_t;
|
||
|
|
||
|
typedef struct rshell_keypress_t {
|
||
|
bool pressed;
|
||
|
bool ctrl;
|
||
|
bool shift;
|
||
|
bool escape;
|
||
|
char c;
|
||
|
int ms;
|
||
|
int fd;
|
||
|
} rshell_keypress_t;
|
||
|
|
||
|
typedef struct rterm_t {
|
||
|
bool show_cursor;
|
||
|
bool show_footer;
|
||
|
int ms_tick;
|
||
|
rshell_keypress_t key;
|
||
|
void (*before_cursor_move)(struct rterm_t *);
|
||
|
void (*after_cursor_move)(struct rterm_t *);
|
||
|
void (*after_key_press)(struct rterm_t *);
|
||
|
void (*before_key_press)(struct rterm_t *);
|
||
|
void (*before_draw)(struct rterm_t *);
|
||
|
void (*after_draw)(struct rterm_t *);
|
||
|
void *session;
|
||
|
unsigned long iterations;
|
||
|
void (*tick)(struct rterm_t *);
|
||
|
char *status_text;
|
||
|
char *_status_text_previous;
|
||
|
winsize_t size;
|
||
|
struct {
|
||
|
int x;
|
||
|
int y;
|
||
|
int pos;
|
||
|
int available;
|
||
|
} cursor;
|
||
|
} rterm_t;
|
||
|
|
||
|
typedef void (*rterm_event)(rterm_t *);
|
||
|
|
||
|
void rterm_init(rterm_t *rterm) {
|
||
|
memset(rterm, 0, sizeof(rterm_t));
|
||
|
rterm->show_cursor = true;
|
||
|
rterm->cursor.x = 0;
|
||
|
rterm->cursor.y = 0;
|
||
|
rterm->ms_tick = 100;
|
||
|
rterm->_status_text_previous = NULL;
|
||
|
}
|
||
|
|
||
|
void rterm_getwinsize(winsize_t *w) {
|
||
|
// Get the terminal size
|
||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, w) == -1) {
|
||
|
perror("ioctl");
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void rrawfd(int fd) {
|
||
|
struct termios orig_termios;
|
||
|
tcgetattr(fd, &orig_termios); // Get current terminal attributes
|
||
|
|
||
|
struct termios raw = orig_termios;
|
||
|
raw.c_lflag &= ~(ICANON | ISIG | ECHO); // ECHO // Disable canonical mode and echoing
|
||
|
raw.c_cc[VMIN] = 1;
|
||
|
raw.c_cc[VTIME] = 240; // Set timeout for read input
|
||
|
|
||
|
tcsetattr(fd, TCSAFLUSH, &raw);
|
||
|
}
|
||
|
|
||
|
// Terminal setup functions
|
||
|
void enableRawMode(struct termios *orig_termios) {
|
||
|
|
||
|
struct termios raw = *orig_termios;
|
||
|
raw.c_lflag &= ~(ICANON | ECHO); // Disable canonical mode and echoing
|
||
|
raw.c_cc[VMIN] = 1;
|
||
|
raw.c_cc[VTIME] = 240; // Set timeout for read input
|
||
|
|
||
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||
|
}
|
||
|
|
||
|
void disableRawMode(struct termios *orig_termios) {
|
||
|
tcsetattr(STDIN_FILENO, TCSAFLUSH,
|
||
|
orig_termios); // Restore original terminal settings
|
||
|
}
|
||
|
|
||
|
void rterm_clear_screen() {
|
||
|
printf("\x1b[2J"); // Clear the entire screen
|
||
|
printf("\x1b[H"); // Move cursor to the home position (0,0)
|
||
|
}
|
||
|
|
||
|
void setBackgroundColor() {
|
||
|
printf("\x1b[34m"); // Set background color to blue
|
||
|
}
|
||
|
|
||
|
void rterm_move_cursor(int x, int y) {
|
||
|
|
||
|
printf("\x1b[%d;%dH", y + 1, x + 1); // Move cursor to (x, y)
|
||
|
}
|
||
|
|
||
|
void cursor_set(rterm_t *rt, int x, int y) {
|
||
|
rt->cursor.x = x;
|
||
|
rt->cursor.y = y;
|
||
|
rt->cursor.pos = y * rt->size.ws_col + x;
|
||
|
rterm_move_cursor(rt->cursor.x, rt->cursor.y);
|
||
|
}
|
||
|
void cursor_restore(rterm_t *rt) { rterm_move_cursor(rt->cursor.x, rt->cursor.y); }
|
||
|
|
||
|
void rterm_print_status_bar(rterm_t *rt, char c, unsigned long i) {
|
||
|
if (rt->_status_text_previous && !strcmp(rt->_status_text_previous, rt->status_text)) {
|
||
|
return;
|
||
|
}
|
||
|
if (rt->_status_text_previous) {
|
||
|
free(rt->_status_text_previous);
|
||
|
}
|
||
|
rt->_status_text_previous = strdup(rt->status_text);
|
||
|
winsize_t ws = rt->size;
|
||
|
cursor_set(rt, rt->cursor.x, rt->cursor.y);
|
||
|
rterm_move_cursor(0, ws.ws_row - 1);
|
||
|
|
||
|
char output_str[1024];
|
||
|
output_str[0] = 0;
|
||
|
|
||
|
// strcat(output_str, "\x1b[48;5;240m");
|
||
|
|
||
|
for (int i = 0; i < ws.ws_col; i++) {
|
||
|
strcat(output_str, " ");
|
||
|
}
|
||
|
char content[500];
|
||
|
content[0] = 0;
|
||
|
if (!rt->status_text) {
|
||
|
sprintf(content, "\rp:%d:%d | k:%c:%d | i:%ld ", rt->cursor.x + 1, rt->cursor.y + 1, c == 0 ? '0' : c, c, i);
|
||
|
} else {
|
||
|
sprintf(content, "\r%s", rt->status_text);
|
||
|
}
|
||
|
strcat(output_str, content);
|
||
|
// strcat(output_str, "\x1b[0m");
|
||
|
printf("%s", output_str);
|
||
|
cursor_restore(rt);
|
||
|
}
|
||
|
|
||
|
void rterm_show_cursor() {
|
||
|
printf("\x1b[?25h"); // Show the cursor
|
||
|
}
|
||
|
|
||
|
void rterm_hide_cursor() {
|
||
|
printf("\x1b[?25l"); // Hide the cursor
|
||
|
}
|
||
|
|
||
|
rshell_keypress_t rshell_getkey(rterm_t *rt) {
|
||
|
static rshell_keypress_t press;
|
||
|
press.c = 0;
|
||
|
press.ctrl = false;
|
||
|
press.shift = false;
|
||
|
press.escape = false;
|
||
|
press.pressed = rfd_wait(0, rt->ms_tick);
|
||
|
if (!press.pressed) {
|
||
|
return press;
|
||
|
}
|
||
|
press.c = getchar();
|
||
|
char ch = press.c;
|
||
|
if (ch == '\x1b') {
|
||
|
// Get detail
|
||
|
ch = getchar();
|
||
|
|
||
|
if (ch == '[') {
|
||
|
// non char key:
|
||
|
press.escape = true;
|
||
|
|
||
|
ch = getchar(); // is a number. 1 if shift + arrow
|
||
|
press.c = ch;
|
||
|
if (ch >= '0' && ch <= '9')
|
||
|
ch = getchar();
|
||
|
press.c = ch;
|
||
|
if (ch == ';') {
|
||
|
ch = getchar();
|
||
|
press.c = ch;
|
||
|
if (ch == '5') {
|
||
|
press.ctrl = true;
|
||
|
press.c = getchar(); // De arrow
|
||
|
}
|
||
|
}
|
||
|
} else if (ch == 27) {
|
||
|
press.escape = true;
|
||
|
press.c = ch;
|
||
|
} else {
|
||
|
press.c = ch;
|
||
|
}
|
||
|
}
|
||
|
return press;
|
||
|
}
|
||
|
|
||
|
// Main function
|
||
|
void rterm_loop(rterm_t *rt) {
|
||
|
struct termios orig_termios;
|
||
|
tcgetattr(STDIN_FILENO, &orig_termios); // Get current terminal attributes
|
||
|
enableRawMode(&orig_termios);
|
||
|
|
||
|
int x = 0, y = 0; // Initial cursor position
|
||
|
char ch = 0;
|
||
|
|
||
|
;
|
||
|
while (1) {
|
||
|
rterm_getwinsize(&rt->size);
|
||
|
rt->cursor.available = rt->size.ws_col * rt->size.ws_row;
|
||
|
if (rt->tick) {
|
||
|
rt->tick(rt);
|
||
|
}
|
||
|
|
||
|
rterm_hide_cursor();
|
||
|
setBackgroundColor();
|
||
|
rterm_clear_screen();
|
||
|
if (rt->before_draw) {
|
||
|
rt->before_draw(rt);
|
||
|
}
|
||
|
rterm_print_status_bar(rt, ch, rt->iterations);
|
||
|
if (rt->after_draw) {
|
||
|
rt->after_draw(rt);
|
||
|
}
|
||
|
if (!rt->iterations || (x != rt->cursor.x || y != rt->cursor.y)) {
|
||
|
if (rt->cursor.y == rt->size.ws_row) {
|
||
|
rt->cursor.y--;
|
||
|
}
|
||
|
if (rt->cursor.y < 0) {
|
||
|
rt->cursor.y = 0;
|
||
|
}
|
||
|
x = rt->cursor.x;
|
||
|
y = rt->cursor.y;
|
||
|
if (rt->before_cursor_move)
|
||
|
rt->before_cursor_move(rt);
|
||
|
cursor_set(rt, rt->cursor.x, rt->cursor.y);
|
||
|
if (rt->after_cursor_move)
|
||
|
rt->after_cursor_move(rt);
|
||
|
// x = rt->cursor.x;
|
||
|
// y = rt->cursor.y;
|
||
|
}
|
||
|
if (rt->show_cursor)
|
||
|
rterm_show_cursor();
|
||
|
|
||
|
fflush(stdout);
|
||
|
|
||
|
rt->key = rshell_getkey(rt);
|
||
|
if (rt->key.pressed && rt->before_key_press) {
|
||
|
rt->before_key_press(rt);
|
||
|
}
|
||
|
rshell_keypress_t key = rt->key;
|
||
|
ch = key.c;
|
||
|
if (ch == 'q')
|
||
|
break; // Press 'q' to quit
|
||
|
if (key.c == -1) {
|
||
|
nsleep(1000 * 1000);
|
||
|
}
|
||
|
// Escape
|
||
|
if (key.escape) {
|
||
|
switch (key.c) {
|
||
|
case 65: // Move up
|
||
|
if (rt->cursor.y > -1)
|
||
|
rt->cursor.y--;
|
||
|
break;
|
||
|
case 66: // Move down
|
||
|
if (rt->cursor.y < rt->size.ws_row)
|
||
|
rt->cursor.y++;
|
||
|
break;
|
||
|
case 68: // Move left
|
||
|
if (rt->cursor.x > 0)
|
||
|
rt->cursor.x--;
|
||
|
if (key.ctrl)
|
||
|
rt->cursor.x -= 4;
|
||
|
break;
|
||
|
case 67: // Move right
|
||
|
if (rt->cursor.x < rt->size.ws_col) {
|
||
|
rt->cursor.x++;
|
||
|
}
|
||
|
if (key.ctrl) {
|
||
|
rt->cursor.x += 4;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (rt->key.pressed && rt->after_key_press) {
|
||
|
rt->after_key_press(rt);
|
||
|
}
|
||
|
rt->iterations++;
|
||
|
|
||
|
// usleep (1000);
|
||
|
}
|
||
|
|
||
|
// Cleanup
|
||
|
printf("\x1b[0m"); // Reset colors
|
||
|
rterm_clear_screen();
|
||
|
disableRawMode(&orig_termios);
|
||
|
}
|
||
|
#endif
|