main.c (3818B)
1 #include <ctype.h> 2 #include <err.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <sys/ioctl.h> 8 #include <termios.h> 9 #include <unistd.h> 10 11 struct screen_size { 12 unsigned short width; 13 unsigned short height; 14 }; 15 16 struct data { 17 char **data; 18 size_t lines; 19 size_t max_width; 20 }; 21 22 // global variable for terminal state restoring 23 struct termios orig_term; 24 25 void 26 restoreTerminal() { 27 // show cursor 28 puts("\e[?25h"); 29 if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig_term) < 0) 30 err(1, "can't restore terminal state"); 31 } 32 33 void 34 prepareTerminal() { 35 if (tcgetattr(STDOUT_FILENO, &orig_term) < 0) 36 err(1, "can't get terminal attributes"); 37 38 atexit(restoreTerminal); 39 40 // havely inspired by https://github.com/antirez/kilo/blob/master/kilo.c 41 struct termios raw = orig_term; 42 raw.c_iflag &= 43 (unsigned) ~(BRKINT | INPCK | ISTRIP | ICRNL | IXON); 44 raw.c_oflag &= (unsigned) ~(OPOST); 45 raw.c_cflag |= (CS8); 46 raw.c_lflag &= 47 (unsigned) ~(ECHO | ICANON); 48 raw.c_cc[VMIN] = 0; 49 raw.c_cc[VTIME] = 1; 50 51 if (tcsetattr(STDOUT_FILENO, TCSAFLUSH, &raw) < 0) 52 err(1, "can't set terminal attributes"); 53 54 // hide cursor 55 puts("\e[?25l"); 56 // clear screen 57 puts("\x1b[2J"); 58 } 59 60 void 61 processInput() { 62 // read from /dev/tty, because standart input 63 // will be redirected 64 int input = open("/dev/tty", O_RDONLY); 65 char c = '\0'; 66 while (1) { 67 ssize_t read_s = read(input, &c, 1); 68 if (read_s < 0) 69 err(1, "can't read from tty"); 70 if (read_s == 0) 71 continue; 72 break; 73 } 74 } 75 76 struct screen_size 77 getScreenSize() { 78 struct winsize size; 79 ioctl(STDOUT_FILENO, TIOCGWINSZ, &size); 80 return (struct screen_size) { 81 .width = size.ws_col, 82 .height = size.ws_row, 83 }; 84 } 85 86 struct data 87 getInput(struct screen_size s) { 88 struct data text; 89 text.data = calloc(sizeof(char*), s.height); 90 text.lines = 0; 91 text.max_width = 0; 92 93 while (1) { 94 char *line = NULL; 95 ssize_t len = 0; 96 size_t buf_len = 0; 97 98 if ((len = getline(&line, &buf_len, stdin)) < 0) 99 break; 100 101 // we can reach EOF here, if there is no delimiter on the last line 102 // if there is delimiter - we should remove it 103 if (!feof(stdin)) { 104 len -= 1; 105 line[len] = '\0'; 106 } 107 108 if ((size_t) len > text.max_width) { 109 text.max_width = len; 110 if (len > s.width) 111 errx(1, "too long line for this terminal, should be shorter than %d", s.width); 112 } 113 text.data[text.lines] = line; 114 text.lines += 1; 115 if (text.lines > s.height) 116 errx(1, "too much lines for this terminal, should be less than %d", s.height); 117 } 118 119 if (ferror(stdin)) 120 err(1, "can't read line from stdin"); 121 122 if (text.lines == 0) 123 errx(1, "empty input"); 124 125 return text; 126 } 127 128 int 129 main(void) { 130 // TODO: -h for help 131 132 struct screen_size s = getScreenSize(); 133 struct data text = getInput(s); 134 135 size_t start_position = (s.height - text.lines) / 2; 136 int margin = (int) ((s.width - text.max_width) / 2); 137 138 prepareTerminal(); 139 140 for (size_t i = 0; i < start_position; i++) 141 printf("\r\n"); 142 143 for (size_t i = 0; i < text.lines; i++) { 144 if (margin) 145 printf("%*s", margin, " "); 146 printf("%s", text.data[i]); 147 if (i != text.lines - 1) 148 printf("\r\n"); 149 } 150 151 for (size_t i = start_position + text.lines; i < s.height; i++) 152 printf("\r\n"); 153 154 fflush(stdout); 155 156 processInput(); 157 158 for (size_t i = 0; i < text.lines; i++) 159 free(text.data[i]); 160 free(text.data); 161 162 // clear screen 163 puts("\x1b[2J"); 164 165 return 0; 166 }