so://phie

so-term

An easy-to-use terminal library
git clone git://soophie.de/so-term

blob: c2455bc0725eddbdd872fc0aaaff20aa08f97a2a (8113 bytes)[doc][blame][raw]

1#ifndef _LIB_TERM_H

2#define _LIB_TERM_H

3

4#include <stddef.h>

5#include <stdbool.h>

6#include <termios.h>

7

8typedef enum {

9 TERM_CODE_RAWMODE_ENABLE,

10 TERM_CODE_RAWMODE_DISABLE,

11 TERM_CODE_CURSOR_READ,

12 TERM_CODE_WINDOW_READ,

13 TERM_CODE_KEY_POLL,

14 TERM_CODE_KEY_READ,

15 TERM_CODE_WRITE,

16 TERM_CODE_FLUSH,

17} term_code_t;

18

19typedef enum {

20 KEY_ENTER = 13,

21 KEY_ESC = 27,

22 KEY_BACKSPACE = 127,

23 KEY_ARROW_LEFT = 1000,

24 KEY_ARROW_RIGHT,

25 KEY_ARROW_UP,

26 KEY_ARROW_DOWN,

27 KEY_DEL,

28 KEY_HOME,

29 KEY_END,

30 KEY_PAGE_UP,

31 KEY_PAGE_DOWN,

32} term_keycode_t;

33

34typedef void (*term_dispatch_fn_t)(int fd, term_code_t code, void *data, size_t len);

35typedef void *(*term_fetch_fn_t)(int fd, term_code_t code, void *data, size_t len, size_t ret_len);

36

37typedef struct {

38 struct termios termios;

39 char *buffer;

40 size_t len;

41 bool teleterm_mode;

42 term_dispatch_fn_t dispatch;

43 term_fetch_fn_t fetch;

44 int fd;

45} term_t;

46

47term_t term_init(void);

48void term_cleanup(term_t *self);

49void term_fd_set(term_t *self, int fd);

50void term_teleterm_enable(term_t *self, term_dispatch_fn_t dispatch, term_fetch_fn_t fetch);

51void term_rawmode_enable(term_t *self);

52void term_rawmode_disable(term_t *self);

53void term_write(term_t *self, char *str);

54void term_writef(term_t *self, const char *format, ...);

55void term_flush(term_t *self);

56int term_read_cursor(term_t *self, size_t *rows, size_t *cols);

57int term_read_window(term_t *self, size_t *rows, size_t *cols);

58int term_read_key(term_t *self);

59int term_poll_key(term_t *self, int timeout_ms);

60

61#ifdef LIB_TERM_IMPL

62

63#include <stdio.h>

64#include <stdlib.h>

65#include <string.h>

66#include <unistd.h>

67#include <sys/ioctl.h>

68#include <errno.h>

69#include <stdarg.h>

70#include <poll.h>

71

72void _term_panic(term_t *self, const char *str) {

73 write(self->fd, "\x1b[2J", 4);

74 write(self->fd, "\x1b[H", 3);

75 write(self->fd, "\x1b[?25h", 6);

76 perror(str);

77 exit(EXIT_FAILURE);

78}

79

80term_t term_init(void) {

81 return (term_t) {

82 .buffer = NULL,

83 .len = 0,

84 .teleterm_mode = false,

85 .dispatch = NULL,

86 .fetch = NULL,

87 .fd = STDOUT_FILENO,

88 };

89}

90

91void term_cleanup(term_t *self) {

92 if (self) {

93 if (self->buffer) {

94 free(self->buffer);

95 self->buffer = NULL;

96 self->len = 0;

97 }

98 }

99}

100

101void term_fd_set(term_t *self, int fd) {

102 self->fd = fd;

103}

104

105void term_teleterm_enable(term_t *self, term_dispatch_fn_t dispatch, term_fetch_fn_t fetch) {

106 self->teleterm_mode = true;

107 self->dispatch = dispatch;

108 self->fetch = fetch;

109}

110

111void term_rawmode_enable(term_t *self) {

112 if (self->teleterm_mode) {

113 self->dispatch(self->fd, TERM_CODE_RAWMODE_ENABLE, NULL, 0);

114 return;

115 }

116 if (tcgetattr(STDIN_FILENO, &self->termios) == -1) {

117 _term_panic(self, "tcgetattr");

118 }

119 struct termios raw = self->termios;

120 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

121 raw.c_oflag &= ~(OPOST);

122 raw.c_cflag |= (CS8);

123 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

124 raw.c_cc[VMIN] = 0;

125 raw.c_cc[VTIME] = 1;

126 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {

127 _term_panic(self, "tcsetattr");

128 }

129}

130

131void term_rawmode_disable(term_t *self) {

132 if (self->teleterm_mode) {

133 self->dispatch(self->fd, TERM_CODE_RAWMODE_DISABLE, NULL, 0);

134 return;

135 }

136 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &self->termios) == -1) {

137 _term_panic(self, "tcsetattr");

138 }

139}

140

141void term_write(term_t *self, char *str) {

142 if (self->teleterm_mode) {

143 self->dispatch(self->fd, TERM_CODE_WRITE, NULL, 0);

144 }

145 size_t str_len = strlen(str);

146 char *new_buffer = (char *) realloc(self->buffer, sizeof(char) * (self->len + str_len + 1));

147 if (new_buffer == NULL) {

148 fprintf(stderr, "Cannot realloc memory!\n");

149 return;

150 }

151 self->buffer = new_buffer;

152 memcpy(self->buffer + self->len, str, str_len);

153 self->len += str_len;

154 self->buffer[self->len] = '\0';

155}

156

157void term_writef(term_t *self, const char *format, ...) {

158 va_list args;

159 va_start(args, format);

160 char str[1000];

161 vsnprintf(str, sizeof(str), format, args);

162 term_write(self, str);

163 va_end(args);

164}

165

166void term_flush(term_t *self) {

167 if (self->len > 0) {

168 if (self->teleterm_mode) {

169 self->dispatch(self->fd, TERM_CODE_FLUSH, self->buffer, self->len);

170 }

171 else {

172 write(self->fd, self->buffer, self->len);

173 }

174 free(self->buffer);

175 self->buffer = NULL;

176 self->len = 0;

177 }

178}

179

180int term_read_cursor(term_t *self, size_t *rows, size_t *cols) {

181 if (self->teleterm_mode) {

182 void *data = self->fetch(self->fd, TERM_CODE_CURSOR_READ, NULL, 0, 2 * sizeof(size_t));

183 if (data) {

184 size_t *tuple = (size_t *) data;

185 *rows = tuple[0];

186 *cols = tuple[1];

187 free(data);

188 return 0;

189 }

190 return -1;

191 }

192 char buf[32];

193 unsigned int i = 0;

194 if (write(self->fd, "\x1b[6n", 4) != 4) {

195 return -1;

196 }

197 while (i < sizeof(buf) - 1) {

198 if (read(STDIN_FILENO, &buf[i], 1) != 1) {

199 break;

200 }

201 if (buf[i] == 'R') {

202 break;

203 }

204 i++;

205 }

206 buf[i] = '\0';

207 if (buf[0] != '\x1b' || buf[1] != '[') {

208 return -1;

209 }

210 if (sscanf(&buf[2], "%zu;%zu", rows, cols) != 2) {

211 return -1;

212 }

213 return 0;

214}

215

216int term_read_window(term_t *self, size_t *rows, size_t *cols) {

217 if (self->teleterm_mode) {

218 void *data = self->fetch(self->fd, TERM_CODE_WINDOW_READ, NULL, 0, 2 * sizeof(size_t));

219 if (data) {

220 size_t *tuple = (size_t *) data;

221 *rows = tuple[0];

222 *cols = tuple[1];

223 free(data);

224 return 0;

225 }

226 return -1;

227 }

228 struct winsize ws;

229 if (ioctl(self->fd, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {

230 if (write(self->fd, "\x1b[999C\x1b[999B", 12) != 12) {

231 return -1;

232 }

233 return term_read_cursor(self, rows, cols);

234 }

235 else {

236 *cols = ws.ws_col;

237 *rows = ws.ws_row;

238 return 0;

239 }

240}

241

242int term_read_key(term_t *self) {

243 if (self->teleterm_mode) {

244 void *data = self->fetch(self->fd, TERM_CODE_KEY_READ, NULL, 0, sizeof(int));

245 if (data) {

246 int *single = (int *) data;

247 int key = *single;

248 free(data);

249 return key;

250 }

251 return -1;

252 }

253 int len;

254 char c;

255 while ((len = read(STDIN_FILENO, &c, 1)) != 1) {

256 if (len == -1 && errno != EAGAIN) {

257 _term_panic(self, "read");

258 }

259 }

260 if (c == '\x1b') {

261 char seq[3];

262 if (read(STDIN_FILENO, &seq[0], 1) != 1) {

263 return '\x1b';

264 }

265 if (read(STDIN_FILENO, &seq[1], 1) != 1) {

266 return '\x1b';

267 }

268 if (seq[0] == '[') {

269 if (seq[1] >= '0' && seq[1] <= '9') {

270 if (read(STDIN_FILENO, &seq[2], 1) != 1) {

271 return '\x1b';

272 }

273 if (seq[2] == '~') {

274 switch (seq[1]) {

275 case '1':

276 return KEY_HOME;

277 case '3':

278 return KEY_DEL;

279 case '4':

280 return KEY_END;

281 case '5':

282 return KEY_PAGE_UP;

283 case '6':

284 return KEY_PAGE_DOWN;

285 case '7':

286 return KEY_HOME;

287 case '8':

288 return KEY_END;

289 }

290 }

291 }

292 else {

293 switch (seq[1]) {

294 case 'A':

295 return KEY_ARROW_UP;

296 case 'B':

297 return KEY_ARROW_DOWN;

298 case 'C':

299 return KEY_ARROW_RIGHT;

300 case 'D':

301 return KEY_ARROW_LEFT;

302 case 'H':

303 return KEY_HOME;

304 case 'F':

305 return KEY_END;

306 }

307 return '\x1b';

308 }

309 }

310 else if (seq[0] == 'O') {

311 switch (seq[1]) {

312 case 'H':

313 return KEY_HOME;

314 case 'F':

315 return KEY_END;

316 }

317 }

318 }

319 return c;

320}

321

322int term_poll_key(term_t *self, int timeout_ms) {

323 if (self->teleterm_mode) {

324 void *data = self->fetch(self->fd, TERM_CODE_KEY_POLL, &timeout_ms, sizeof(timeout_ms), sizeof(int));

325 if (data) {

326 int *single = (int *) data;

327 int key = *single;

328 free(data);

329 return key;

330 }

331 return -1;

332 }

333 struct pollfd fds[1];

334 fds[0].fd = STDIN_FILENO;

335 fds[0].events = POLLIN | POLLPRI;

336 if (poll(fds, 1, timeout_ms)) {

337 return term_read_key(self);

338 }

339 return 0;

340}

341

342#endif // LIB_TERM_IMPL

343#endif // _LIB_TERM_H