diff --git a/Makefile b/Makefile index fc15a5d..b02bbb7 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ sh: check: shellcheck sh/* -mkc: c/scream c/timer c/boid c/anaconda c/colors c/xgetnewwindow +mkc: c/scream c/timer c/boid c/anaconda c/colors c/xgetnewwindow c/tmessage c/boid: cc c/boid.c -o c/boid -lm -lX11 @@ -62,6 +62,7 @@ c: cp -f c/anaconda $(DESTDIR)$(PREFIX)/bin cp -f c/colors $(DESTDIR)$(PREFIX)/bin cp -f c/xgetnewwindow $(DESTDIR)$(PREFIX)/bin + cp -f c/tmessage $(DESTDIR)$(PREFIX)/bin clean: rm -f c/scream @@ -70,3 +71,4 @@ clean: rm -f c/anaconda rm -f c/simplestatus rm -f c/xgetnewwindow + rm -f c/tmessage diff --git a/c/tmessage.c b/c/tmessage.c new file mode 100644 index 0000000..e696d4b --- /dev/null +++ b/c/tmessage.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ESC_CLEAR "\033[2J" +#define ESC_CUSHOME "\033[H" +#define ESC_NEWLINE "\033[E\033[1C" + +const char MSG_OK[] = "(O)k"; +const char MSG_CANCEL[] = "(C)ancel"; + +typedef struct winsize ws; + +int lines, cols; +char *message; +size_t message_len; + +struct termios original_termios; + +void render(char *message, int message_len, int lines, int cols); + +void rawmode_start() { + tcgetattr(STDIN_FILENO, &original_termios); + struct termios raw = original_termios; + raw.c_lflag &= ~(ECHO | ICANON); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} + +void rawmode_stop() { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios); + printf("\n\n"); + fflush(stdout); +} + +ws *getWinSize(void) { + /* we're only going to use these values once per invocation, + * so it's fine that it's static. */ + static ws w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + return &w; +} + +void handler(int signal, siginfo_t *info, void *context) { + ws *w = getWinSize(); + lines = w->ws_row; + cols = w->ws_col; + + render(message, message_len, lines, cols); + return; +} + +void termhandler(int signal, siginfo_t *info, void *context) { + rawmode_stop(); + + return; +} + +int calculate_number_of_lines(int length, int cols) { + /* cols - 1 accounts for right padding */ + return length / (cols - 1) + 1; +} + +void go_to_initial_message_printing_pos(void) { + printf(ESC_CUSHOME "\033[1B\033[1C"); +} + +void print_message(char *message, int messagelen, int cols) { + int linecount = calculate_number_of_lines(messagelen, cols); + int adjcols = cols - 2; + int offset = 1; + for(int character = 0; character < messagelen; character++) { + if(character == adjcols * offset) { + printf(ESC_NEWLINE); + offset++; + } + putchar(message[character]); + } +} + +void render(char *message, int messagelen, int lines, int cols) { + int cancel_length = sizeof(MSG_CANCEL) / sizeof(MSG_CANCEL[0]); + + /* print the stuff */ + printf(ESC_CLEAR "" ESC_CUSHOME); + go_to_initial_message_printing_pos(); + print_message(message, message_len, cols); + + printf(ESC_NEWLINE); + printf(ESC_NEWLINE); + printf("%s", MSG_OK); + printf("\033[1D\033[%iC\033[%iD%s", cols, cancel_length, MSG_CANCEL); + fflush(stdout); +} + +int main(int argc, char **argv) { + int cancel_length = strlen(MSG_CANCEL); + char c; + struct sigaction resizeaction = {}; + struct sigaction termaction = {}; + ws *w; + + /* check if we have a message */ + if(argc > 1) { + message = argv[1]; /* second argument's a message */ + message_len = strlen(message); + } else { + return 1; + } + + /* setup sigaction handlers */ + resizeaction.sa_sigaction = &handler; + if(sigaction(SIGWINCH, &resizeaction, NULL) == -1) { + return 1; + } + + termaction.sa_sigaction = &termhandler; + if(sigaction(SIGTERM, &termaction, NULL) == -1) { + return 1; + } + + rawmode_start(); + + /* get window properties */ + w = getWinSize(); + lines = w->ws_row; + cols = w->ws_col; + + render(message, message_len, lines, cols); + + for(;;) { + read(STDIN_FILENO, &c, 1); + if(c == 'o') { + return 2; + rawmode_stop(); + } else if(c == 'c') { + return 3; + rawmode_stop(); + } + } + + return 0; +}