1#include "maus_input.h"
2#include <errno.h>
3#include <fcntl.h>
4#include <locale.h>
5#include <poll.h>
6#include <stdbool.h>
7#include <stdint.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/ioctl.h>
11#include <sys/wait.h>
12#include <time.h>
13#ifdef __linux__
14#include <pty.h>
15#else
16#include <util.h>
17#endif
18#include <unistd.h>
19
20#include <maus.h>
21
22#include "../config.h"
23#include "draw.h"
24#include "font.h"
25#include "term.h"
26#include "utils.h"
27
28static void cleanup(void);
29static bool handle_key(const MausEvent* ev);
30static void init(void);
31static bool ptyread(void);
32static void ptywrite(const char* s, size_t n);
33static bool reload_font(void);
34static void resize_pty(void);
35static int resize_window(int w, int h);
36static void resize_terminal(int w, int h);
37static void run(void);
38static bool shell_exited(void);
39static void wait_events(void);
40
41static Fontface font;
42static Caret car;
43static Maus* win = NULL;
44static int winw;
45static int winh;
46
47static Term term = {
48 .runes = NULL,
49 .cols = 0,
50 .rows = 0,
51 .ptyfd = -1,
52 .ptypid = -1,
53 .fg = default_fg,
54 .bg = default_bg,
55 .cursor_visible = true,
56};
57
58/* clean up resources */
59static void cleanup(void)
60{
61 free(term.runes);
62 free(term.alt);
63 font_free(&font);
64
65 if (win) {
66 maus_close(win);
67 free(win);
68 }
69}
70
71/* dispatch correct keycode handling non ascii characters */
72static bool handle_key(const MausEvent* ev)
73{
74 char text;
75
76 if (ev->type != MAUS_EV_KEY || !ev->key.pressed)
77 return false;
78
79 if (BIND_RELOAD_FONT)
80 return reload_font();
81
82 if (BIND_PASTE) {
83 char* text = maus_clipboard_get_text(win);
84
85 if (text && text[0] != '\0') {
86 /* bracketed paste mode on */
87 if (term.bracketed_paste)
88 ptywrite("\033[200~", 6);
89
90 for (char* p = text; *p; p++) {
91 /* replace newlines with carriage returns */
92 char ch = (*p == '\n') ? '\r' : *p;
93 ptywrite(&ch, 1);
94 }
95
96 /* bracketed paste mode off */
97 if (term.bracketed_paste)
98 ptywrite("\033[201~", 6);
99 }
100
101 return true;
102 }
103
104 switch(ev->key.key) {
105 case MAUS_KEY_ENTER:
106 case MAUS_KEY_KP_ENTER: ptywrite("\r", 1); break;
107 case MAUS_KEY_BACKSPACE: ptywrite("\x7F", 1); break;
108 case MAUS_KEY_TAB: ptywrite("\t", 1); break;
109 case MAUS_KEY_ESCAPE: ptywrite("\x1B", 1); break;
110 case MAUS_KEY_UP: ptywrite("\x1B[A", 3); break;
111 case MAUS_KEY_DOWN: ptywrite("\x1B[B", 3); break;
112 case MAUS_KEY_RIGHT: ptywrite("\x1B[C", 3); break;
113 case MAUS_KEY_LEFT: ptywrite("\x1B[D", 3); break;
114 case MAUS_KEY_HOME: ptywrite("\x1B[H", 3); break;
115 case MAUS_KEY_END: ptywrite("\x1B[F", 3); break;
116 case MAUS_KEY_INSERT: ptywrite("\x1B[2~", 4); break;
117 case MAUS_KEY_DELETE: ptywrite("\x1B[3~", 4); break;
118 case MAUS_KEY_PAGE_UP: ptywrite("\x1B[5~", 4); break;
119 case MAUS_KEY_PAGE_DOWN: ptywrite("\x1B[6~", 4); break;
120 default:
121 text = ev->key.text;
122 if (text != '\0')
123 ptywrite(&text, 1);
124 break;
125 }
126
127 return false;
128}
129
130/* initialise font, window, pty */
131static void init(void)
132{
133 setlocale(LC_CTYPE, "");
134
135 /* font */
136 if (font_load(&font, font_path) < 0)
137 die(1, "failed to load font");
138
139 winw = win_width*font.cellw;
140 winh = win_height*font.cellh;
141
142 if (term_resize(&term, &car, win_width, win_height) < 0)
143 die(1, "failed to resize terminal");
144
145 /* window */
146 win = maus_init(win_title, 0, 0, winw, winh);
147 if (!win)
148 die(1, "failed to init window");
149 if (!maus_create_window(win))
150 die(1, "failed to create window");
151
152 /* pty */
153 struct winsize ws = {
154 .ws_col = term.cols,
155 .ws_row = term.rows,
156 .ws_xpixel = winw,
157 .ws_ypixel = winh
158 };
159 term.ptypid = forkpty(&term.ptyfd, NULL, NULL, &ws);
160 if (term.ptypid < 0)
161 die(EXIT_FAILURE, "failed to fork pty");
162 if (term.ptypid == 0) {
163 setenv("TERM", term_name, 1);
164
165 /* shell */
166 const char* sh = shell;
167 if (sh == NULL)
168 sh = getenv("SHELL");
169
170 execl(sh, sh, "-i", (char *)NULL);
171
172 _exit(127);
173 }
174
175 int flags = fcntl(term.ptyfd, F_GETFL, 0);
176 if (flags >= 0) /* append to existing flags */
177 fcntl(term.ptyfd, F_SETFL, flags|O_NONBLOCK);
178
179 term_clear(&term, &car);
180}
181
182/* read from pty master */
183static bool ptyread(void)
184{
185 char buf[4096];
186 bool damaged = false;
187
188 for (;;) {
189 ssize_t n = read(term.ptyfd, buf, sizeof(buf));
190 if (n < 0 && errno == EINTR)
191 continue;
192 if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
193 break;
194 if (n > 0) {
195 if (term_write(&term, &car, buf, n))
196 damaged = true;
197 continue;
198 }
199
200 /* EOF or other error */
201 break;
202 }
203
204 return damaged;
205}
206
207/* write to pty master */
208static void ptywrite(const char* s, size_t n)
209{
210 while (n > 0) {
211 ssize_t ret = write(term.ptyfd, s, n);
212 if (ret < 0 && (errno == EINTR))
213 continue;
214 if (ret > 0) {
215 s+= ret;
216 n -= ret;
217 continue;
218 }
219
220 break;
221 }
222}
223
224/* reload the font. true on success, false on failure.
225 in event of failure, current font will still be in use */
226static bool reload_font(void)
227{
228 Fontface next;
229 if (font_load(&next, font_path) < 0)
230 return false;
231
232 font_free(&font);
233 font = next;
234 resize_terminal(winw, winh);
235 term_damage_all(&term);
236
237 return true;
238}
239
240/* resize pty to reflect terminal values */
241static void resize_pty(void)
242{
243 struct winsize ws = {
244 .ws_col = term.cols,
245 .ws_row = term.rows,
246 .ws_xpixel = winw,
247 .ws_ypixel = winh,
248 };
249
250 if (term.ptyfd >= 0)
251 ioctl(term.ptyfd, TIOCSWINSZ, &ws);
252}
253
254/* resize window framebuffer to reflect resized size */
255static int resize_window(int w, int h)
256{
257 if (w < 1)
258 w = 1;
259 if (h < 1)
260 h = 1;
261
262 if (w == winw && h == winh)
263 return 0;
264
265 if (!maus_resize(win, w, h))
266 return -1;
267 winw = w;
268 winh = h;
269
270 return 0;
271}
272
273/* TODO */
274static void resize_terminal(int w, int h)
275{
276 int cols = w / font.cellw;
277 int rows = h / font.cellh;
278
279 if (cols < 1)
280 cols = 1;
281 if (rows < 1)
282 rows = 1;
283
284 if (cols == term.cols && rows == term.rows)
285 return;
286
287 if (term_resize(&term, &car, cols, rows) < 0)
288 die(1, "failed to resize terminal");
289
290 resize_pty();
291}
292
293/* event loop (TODO) */
294static void run(void)
295{
296 MausEvent ev;
297 Caret ocar = { -1, -1 };
298 bool running = true;
299 bool dirty = true;
300 bool redraw_all = true;
301
302 while (running) {
303 while (maus_event_poll(win, &ev)) {
304 if (ev.type == MAUS_EV_CLOSE || shell_exited()) {
305 running = false;
306 break;
307 }
308
309 if (ev.type == MAUS_EV_RESIZE) {
310 if (resize_window(ev.resize.width, ev.resize.height) < 0)
311 die(EXIT_FAILURE, "failed to resize window");
312 resize_terminal(ev.resize.width, ev.resize.height);
313 term_damage_all(&term);
314 dirty = true;
315 redraw_all = true;
316 continue;
317 }
318
319 if (ev.type == MAUS_EV_REDRAW) {
320 dirty = true;
321 continue;
322 }
323
324 if (handle_key(&ev)) {
325 dirty = true;
326 redraw_all = true;
327 }
328 }
329
330 if (!running)
331 break;
332
333 if (ptyread())
334 dirty = true;
335
336 if (ocar.x != car.x || ocar.y != car.y) {
337 term_damage_rune(&term, ocar.x, ocar.y);
338 term_damage_rune(&term, car.x, car.y);
339 dirty = true;
340 }
341
342 if (!dirty) {
343 wait_events();
344 continue;
345 }
346
347 if (redraw_all) {
348 draw_clear(win->bfb, win->stride, winh, rgba(0, 0, 0, 255));
349 term_damage_all(&term);
350 }
351
352 for (int y = 0; y < term.rows; y++) {
353 for (int x = 0; x < term.cols; x++) {
354 Rune* r = &RUNE(&term, x, y);
355 bool cursor = term.cursor_visible &&
356 x == car.x && y == car.y;
357 bool reverse = (r->attr & TERM_ATTR_REVERSE) != 0;
358 uint32_t fg = reverse ? r->bg : r->fg;
359 uint32_t bg = reverse ? r->fg : r->bg;
360
361 if (cursor) {
362 fg = cursor_fg;
363 bg = cursor_bg;
364 }
365
366 if (!r->dmg)
367 continue;
368
369 draw_rune(
370 win->bfb, win->stride, winh, x, y,
371 font.cellw, font.cellh, bg
372 );
373
374 if (r->cp != ' ') {
375 draw_codepoint(
376 &font, win->bfb, win->stride, winh,
377 x * font.cellw,
378 y * font.cellh + (int)font.asc,
379 r->cp,
380 fg
381 );
382 }
383
384 r->dmg = false;
385 }
386 }
387
388 maus_target_fps(win, win_fps);
389 maus_present(win);
390
391 ocar = car;
392 dirty = false;
393 redraw_all = false;
394 }
395}
396
397/* check if the shell has exited */
398static bool shell_exited(void)
399{
400 int status;
401
402 if (term.ptypid <= 0)
403 return true;
404 return waitpid(term.ptypid, &status, WNOHANG) == term.ptypid;
405}
406
407/* wait for activity while allowing window events */
408static void wait_events(void)
409{
410 struct pollfd pfd = {
411 .fd = term.ptyfd,
412 .events = POLLIN,
413 };
414
415 if (term.ptyfd < 0) {
416 struct timespec ts = {
417 .tv_sec = 0,
418 .tv_nsec = 16 * 1000 * 1000, /* TODO? libmaus */
419 };
420 nanosleep(&ts, NULL);
421 return;
422 }
423
424 for (;;) {
425 int ret = poll(&pfd, 1, 16);
426 if (ret < 0 && errno == EINTR)
427 continue;
428 break;
429 }
430}
431
432int main(int argc, char* argv[])
433{
434 /* TODO: pledges */
435 init();
436 run();
437 cleanup();
438
439 return EXIT_SUCCESS;
440 (void) argc, (void) argv;
441}
442