chiark / gitweb /
terminal/subterm: use screen renderer
[elogind.git] / src / libsystemd-terminal / subterm.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2 /***
3   This file is part of systemd.
4
5   Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
6
7   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 /*
22  * Stacked Terminal-Emulator
23  * This is an interactive test of the term_screen implementation. It runs a
24  * fully-fletched terminal-emulator inside of a parent TTY. That is, instead of
25  * rendering the terminal as X11-window, it renders it as sub-window in the
26  * parent TTY. Think of this like what "GNU-screen" does.
27  */
28
29 #include <assert.h>
30 #include <errno.h>
31 #include <stdarg.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/ioctl.h>
37 #include <termios.h>
38 #include "macro.h"
39 #include "pty.h"
40 #include "ring.h"
41 #include "sd-event.h"
42 #include "term-internal.h"
43 #include "util.h"
44
45 typedef struct Output Output;
46 typedef struct Terminal Terminal;
47
48 struct Output {
49         int fd;
50         unsigned int width;
51         unsigned int height;
52         unsigned int in_width;
53         unsigned int in_height;
54         unsigned int cursor_x;
55         unsigned int cursor_y;
56
57         char obuf[4096];
58         size_t n_obuf;
59
60         bool resized : 1;
61         bool in_menu : 1;
62 };
63
64 struct Terminal {
65         sd_event *event;
66         sd_event_source *frame_timer;
67         Output *output;
68         term_utf8 utf8;
69         term_parser *parser;
70         term_screen *screen;
71
72         int in_fd;
73         int out_fd;
74         struct termios saved_in_attr;
75         struct termios saved_out_attr;
76
77         Pty *pty;
78         Ring out_ring;
79
80         bool is_scheduled : 1;
81         bool is_dirty : 1;
82         bool is_menu : 1;
83 };
84
85 /*
86  * Output Handling
87  */
88
89 #define BORDER_HORIZ            "\xe2\x94\x81"
90 #define BORDER_VERT             "\xe2\x94\x83"
91 #define BORDER_VERT_RIGHT       "\xe2\x94\xa3"
92 #define BORDER_VERT_LEFT        "\xe2\x94\xab"
93 #define BORDER_DOWN_RIGHT       "\xe2\x94\x8f"
94 #define BORDER_DOWN_LEFT        "\xe2\x94\x93"
95 #define BORDER_UP_RIGHT         "\xe2\x94\x97"
96 #define BORDER_UP_LEFT          "\xe2\x94\x9b"
97
98 static int output_winch(Output *o) {
99         struct winsize wsz = { };
100         int r;
101
102         assert_return(o, -EINVAL);
103
104         r = ioctl(o->fd, TIOCGWINSZ, &wsz);
105         if (r < 0) {
106                 log_error("error: cannot read window-size: %m");
107                 return -errno;
108         }
109
110         if (wsz.ws_col != o->width || wsz.ws_row != o->height) {
111                 o->width = wsz.ws_col;
112                 o->height = wsz.ws_row;
113                 o->in_width = MAX(o->width, 2U) - 2;
114                 o->in_height = MAX(o->height, 6U) - 6;
115                 o->resized = true;
116         }
117
118         return 0;
119 }
120
121 static int output_flush(Output *o) {
122         ssize_t len;
123
124         if (o->n_obuf < 1)
125                 return 0;
126
127         len = loop_write(o->fd, o->obuf, o->n_obuf, false);
128         if (len < 0) {
129                 log_error("error: cannot write to TTY (%zd): %s", len, strerror(-len));
130                 return len;
131         }
132
133         o->n_obuf = 0;
134
135         return 0;
136 }
137
138 static int output_write(Output *o, const void *buf, size_t size) {
139         ssize_t len;
140         int r;
141
142         assert_return(o, -EINVAL);
143         assert_return(buf || size < 1, -EINVAL);
144
145         if (size < 1)
146                 return 0;
147
148         if (o->n_obuf + size > o->n_obuf && o->n_obuf + size <= sizeof(o->obuf)) {
149                 memcpy(o->obuf + o->n_obuf, buf, size);
150                 o->n_obuf += size;
151                 return 0;
152         }
153
154         r = output_flush(o);
155         if (r < 0)
156                 return r;
157
158         len = loop_write(o->fd, buf, size, false);
159         if (len < 0) {
160                 log_error("error: cannot write to TTY (%zd): %s", len, strerror(-len));
161                 return len;
162         }
163
164         return 0;
165 }
166
167 _printf_(3,0)
168 static int output_vnprintf(Output *o, size_t max, const char *format, va_list args) {
169         char buf[4096];
170         int r;
171
172         assert_return(o, -EINVAL);
173         assert_return(format, -EINVAL);
174         assert_return(max <= sizeof(buf), -EINVAL);
175
176         r = vsnprintf(buf, max, format, args);
177         if (r > (ssize_t)max)
178                 r = max;
179
180         return output_write(o, buf, r);
181 }
182
183 _printf_(3,4)
184 static int output_nprintf(Output *o, size_t max, const char *format, ...) {
185         va_list args;
186         int r;
187
188         va_start(args, format);
189         r = output_vnprintf(o, max, format, args);
190         va_end(args);
191
192         return r;
193 }
194
195 _printf_(2,0)
196 static int output_vprintf(Output *o, const char *format, va_list args) {
197         char buf[4096];
198         int r;
199
200         assert_return(o, -EINVAL);
201         assert_return(format, -EINVAL);
202
203         r = vsnprintf(buf, sizeof(buf), format, args);
204
205         assert_return(r < (ssize_t)sizeof(buf), -ENOBUFS);
206
207         return output_write(o, buf, r);
208 }
209
210 _printf_(2,3)
211 static int output_printf(Output *o, const char *format, ...) {
212         va_list args;
213         int r;
214
215         va_start(args, format);
216         r = output_vprintf(o, format, args);
217         va_end(args);
218
219         return r;
220 }
221
222 static int output_move_to(Output *o, unsigned int x, unsigned int y) {
223         int r;
224
225         assert_return(o, -EINVAL);
226
227         /* force the \e[H code as o->cursor_x/y might be out-of-date */
228
229         r = output_printf(o, "\e[%u;%uH", y + 1, x + 1);
230         if (r < 0)
231                 return r;
232
233         o->cursor_x = x;
234         o->cursor_y = y;
235         return 0;
236 }
237
238 static int output_print_line(Output *o, size_t len) {
239         const char line[] =
240                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
241                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
242                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
243                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
244                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
245                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ
246                 BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ;
247         const size_t max = (sizeof(line) - 1) / (sizeof(BORDER_HORIZ) - 1);
248         size_t i;
249         int r = 0;
250
251         assert_return(o, -EINVAL);
252
253         for ( ; len > 0; len -= i) {
254                 i = (len > max) ? max : len;
255                 r = output_write(o, line, i * (sizeof(BORDER_HORIZ) - 1));
256                 if (r < 0)
257                         break;
258         }
259
260         return r;
261 }
262
263 _printf_(2,3)
264 static int output_frame_printl(Output *o, const char *format, ...) {
265         va_list args;
266         int r;
267
268         assert(o);
269         assert(format);
270
271         /* out of frame? */
272         if (o->cursor_y < 3 || o->cursor_y >= o->height - 3)
273                 return 0;
274
275         va_start(args, format);
276         r = output_vnprintf(o, o->width - 2, format, args);
277         va_end(args);
278
279         if (r < 0)
280                 return r;
281
282         return output_move_to(o, 1, o->cursor_y + 1);
283 }
284
285 static Output *output_free(Output *o) {
286         if (!o)
287                 return NULL;
288
289         /* disable alternate screen buffer */
290         output_printf(o, "\e[?1049l");
291         output_flush(o);
292
293         /* o->fd is owned by the caller */
294         free(o);
295
296         return NULL;
297 }
298
299 static int output_new(Output **out, int fd) {
300         Output *o;
301         int r;
302
303         assert_return(out, -EINVAL);
304
305         o = new0(Output, 1);
306         if (!o)
307                 return log_oom();
308
309         o->fd = fd;
310
311         r = output_winch(o);
312         if (r < 0)
313                 goto error;
314
315         /* enable alternate screen buffer */
316         r = output_printf(o, "\e[?1049h");
317         if (r < 0)
318                 goto error;
319
320         r = output_flush(o);
321         if (r < 0)
322                 goto error;
323
324         *out = o;
325         return 0;
326
327 error:
328         output_free(o);
329         return r;
330 }
331
332 static void output_draw_frame(Output *o) {
333         unsigned int i;
334
335         assert(o);
336
337         /* print header-frame */
338
339         output_printf(o, BORDER_DOWN_RIGHT);
340         output_print_line(o, o->width - 2);
341         output_printf(o, BORDER_DOWN_LEFT
342                          "\r\n"
343                          BORDER_VERT
344                          "\e[2;%uH"    /* cursor-position: 2/x */
345                          BORDER_VERT
346                          "\r\n"
347                          BORDER_VERT_RIGHT,
348                       o->width);
349         output_print_line(o, o->width - 2);
350         output_printf(o, BORDER_VERT_LEFT
351                          "\r\n");
352
353         /* print body-frame */
354
355         for (i = 0; i < o->in_height; ++i) {
356                 output_printf(o, BORDER_VERT
357                                  "\e[%u;%uH"    /* cursor-position: 2/x */
358                                  BORDER_VERT
359                                  "\r\n",
360                               i + 4, o->width);
361         }
362
363         /* print footer-frame */
364
365         output_printf(o, BORDER_VERT_RIGHT);
366         output_print_line(o, o->width - 2);
367         output_printf(o, BORDER_VERT_LEFT
368                          "\r\n"
369                          BORDER_VERT
370                          "\e[%u;%uH"    /* cursor-position: 2/x */
371                          BORDER_VERT
372                          "\r\n"
373                          BORDER_UP_RIGHT,
374                       o->height - 1, o->width);
375         output_print_line(o, o->width - 2);
376         output_printf(o, BORDER_UP_LEFT);
377
378         /* print header/footer text */
379
380         output_printf(o, "\e[2;3H");
381         output_nprintf(o, o->width - 4, "systemd - embedded terminal emulator");
382         output_printf(o, "\e[%u;3H", o->height - 1);
383         output_nprintf(o, o->width - 4, "press ^C to enter menu");
384 }
385
386 static void output_draw_menu(Output *o) {
387         assert(o);
388
389         output_frame_printl(o, "%s", "");
390         output_frame_printl(o, "    Menu: (the following keys are recognized)");
391         output_frame_printl(o, "      q: quit");
392         output_frame_printl(o, "     ^C: send ^C to the PTY");
393 }
394
395 static int output_draw_cell_fn(term_screen *screen,
396                                void *userdata,
397                                unsigned int x,
398                                unsigned int y,
399                                const term_attr *attr,
400                                const uint32_t *ch,
401                                size_t n_ch,
402                                unsigned int ch_width) {
403         Output *o = userdata;
404         size_t k, ulen;
405         char utf8[4];
406
407         if (x >= o->in_width || y >= o->in_height)
408                 return 0;
409
410         if (x == 0 && y != 0)
411                 output_printf(o, "\e[m\r\n" BORDER_VERT);
412
413         switch (attr->fg.ccode) {
414         case TERM_CCODE_DEFAULT:
415                 output_printf(o, "\e[39m");
416                 break;
417         case TERM_CCODE_256:
418                 output_printf(o, "\e[38;5;%um", attr->fg.c256);
419                 break;
420         case TERM_CCODE_RGB:
421                 output_printf(o, "\e[38;2;%u;%u;%um", attr->fg.red, attr->fg.green, attr->fg.blue);
422                 break;
423         case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
424                 if (attr->bold)
425                         output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 90);
426                 else
427                         output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 30);
428                 break;
429         case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
430                 output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_LIGHT_BLACK + 90);
431                 break;
432         }
433
434         switch (attr->bg.ccode) {
435         case TERM_CCODE_DEFAULT:
436                 output_printf(o, "\e[49m");
437                 break;
438         case TERM_CCODE_256:
439                 output_printf(o, "\e[48;5;%um", attr->bg.c256);
440                 break;
441         case TERM_CCODE_RGB:
442                 output_printf(o, "\e[48;2;%u;%u;%um", attr->bg.red, attr->bg.green, attr->bg.blue);
443                 break;
444         case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
445                 output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_BLACK + 40);
446                 break;
447         case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
448                 output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_LIGHT_BLACK + 100);
449                 break;
450         }
451
452         output_printf(o, "\e[%u;%u;%u;%u;%u;%um",
453                       attr->bold ? 1 : 22,
454                       attr->italic ? 3 : 23,
455                       attr->underline ? 4 : 24,
456                       attr->inverse ? 7 : 27,
457                       attr->blink ? 5 : 25,
458                       attr->hidden ? 8 : 28);
459
460         if (n_ch < 1) {
461                 output_printf(o, " ");
462         } else {
463                 for (k = 0; k < n_ch; ++k) {
464                         ulen = term_utf8_encode(utf8, ch[k]);
465                         output_write(o, utf8, ulen);
466                 }
467         }
468
469         return 0;
470 }
471
472 static void output_draw_screen(Output *o, term_screen *s) {
473         assert(o);
474         assert(s);
475
476         term_screen_draw(s, output_draw_cell_fn, o, NULL);
477
478         output_move_to(o, s->cursor_x + 1, s->cursor_y + 3);
479         output_printf(o, "\e[m");
480 }
481
482 static void output_draw(Output *o, bool menu, term_screen *screen) {
483         assert(o);
484
485         /*
486          * This renders the contenst of the terminal. The layout contains a
487          * header, the main body and a footer. Around all areas we draw a
488          * border. It looks something like this:
489          *
490          *     +----------------------------------------------------+
491          *     |                      Header                        |
492          *     +----------------------------------------------------+
493          *     |                                                    |
494          *     |                                                    |
495          *     |                                                    |
496          *     |                       Body                         |
497          *     |                                                    |
498          *     |                                                    |
499          *     ~                                                    ~
500          *     ~                                                    ~
501          *     +----------------------------------------------------+
502          *     |                      Footer                        |
503          *     +----------------------------------------------------+
504          *
505          * The body is the part that grows vertically.
506          *
507          * We need at least 6 vertical lines to render the screen. This would
508          * leave 0 lines for the body. Therefore, we require 7 lines so there's
509          * at least one body line. Similarly, we need 2 horizontal cells for the
510          * frame, so we require 3.
511          * If the window is too small, we print an error message instead.
512          */
513
514         if (o->in_width < 1 || o->in_height < 1) {
515                 output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
516                                  "\e[H");        /* cursor-position: home */
517                 output_printf(o, "error: screen too small, need at least 3x7 cells");
518                 output_flush(o);
519                 return;
520         }
521
522         /* hide cursor */
523         output_printf(o, "\e[?25l");
524
525         /* frame-content is contant; only resizes can change it */
526         if (o->resized || o->in_menu != menu) {
527                 output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
528                                  "\e[H");        /* cursor-position: home */
529                 output_draw_frame(o);
530                 o->resized = false;
531                 o->in_menu = menu;
532         }
533
534         /* move cursor to child's position */
535         output_move_to(o, 1, 3);
536
537         if (menu)
538                 output_draw_menu(o);
539         else
540                 output_draw_screen(o, screen);
541
542         /* show cursor */
543         if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
544                 output_printf(o, "\e[?25h");
545
546         /*
547          * Hack: sd-term was not written to support TTY as output-objects, thus
548          * expects callers to use term_screen_feed_keyboard(). However, we
549          * forward TTY input directly. Hence, we're not notified about keypad
550          * changes. Update the related modes djring redraw to keep them at least
551          * in sync.
552          */
553         if (screen->flags & TERM_FLAG_CURSOR_KEYS)
554                 output_printf(o, "\e[?1h");
555         else
556                 output_printf(o, "\e[?1l");
557
558         if (screen->flags & TERM_FLAG_KEYPAD_MODE)
559                 output_printf(o, "\e=");
560         else
561                 output_printf(o, "\e>");
562
563         output_flush(o);
564 }
565
566 /*
567  * Terminal Handling
568  */
569
570 static void terminal_dirty(Terminal *t) {
571         usec_t usec;
572         int r;
573
574         assert(t);
575
576         if (t->is_scheduled) {
577                 t->is_dirty = true;
578                 return;
579         }
580
581         /* 16ms timer */
582         r = sd_event_now(t->event, CLOCK_MONOTONIC, &usec);
583         assert(r >= 0);
584
585         usec += 16 * USEC_PER_MSEC;
586         r = sd_event_source_set_time(t->frame_timer, usec);
587         if (r >= 0) {
588                 r = sd_event_source_set_enabled(t->frame_timer, SD_EVENT_ONESHOT);
589                 if (r >= 0)
590                         t->is_scheduled = true;
591         }
592
593         t->is_dirty = false;
594         output_draw(t->output, t->is_menu, t->screen);
595 }
596
597 static int terminal_frame_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) {
598         Terminal *t = userdata;
599
600         t->is_scheduled = false;
601         if (t->is_dirty)
602                 terminal_dirty(t);
603
604         return 0;
605 }
606
607 static int terminal_winch_fn(sd_event_source *source, const struct signalfd_siginfo *ssi, void *userdata) {
608         Terminal *t = userdata;
609         int r;
610
611         output_winch(t->output);
612
613         if (t->pty) {
614                 r = pty_resize(t->pty, t->output->in_width, t->output->in_height);
615                 if (r < 0)
616                         log_error("error: pty_resize() (%d): %s", r, strerror(-r));
617         }
618
619         r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
620         if (r < 0)
621                 log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
622
623         terminal_dirty(t);
624
625         return 0;
626 }
627
628 static int terminal_push_tmp(Terminal *t, uint32_t ucs4) {
629         char buf[4];
630         size_t len;
631         int r;
632
633         assert(t);
634
635         len = term_utf8_encode(buf, ucs4);
636         if (len < 1)
637                 return 0;
638
639         r = ring_push(&t->out_ring, buf, len);
640         if (r < 0)
641                 log_oom();
642
643         return r;
644 }
645
646 static int terminal_write_tmp(Terminal *t) {
647         struct iovec vec[2];
648         size_t num, i;
649         int r;
650
651         assert(t);
652
653         num = ring_peek(&t->out_ring, vec);
654         if (num < 1)
655                 return 0;
656
657         if (t->pty) {
658                 for (i = 0; i < num; ++i) {
659                         r = pty_write(t->pty, vec[i].iov_base, vec[i].iov_len);
660                         if (r < 0) {
661                                 log_error("error: cannot write to PTY (%d): %s", r, strerror(-r));
662                                 return r;
663                         }
664                 }
665         }
666
667         ring_flush(&t->out_ring);
668         return 0;
669 }
670
671 static void terminal_discard_tmp(Terminal *t) {
672         assert(t);
673
674         ring_flush(&t->out_ring);
675 }
676
677 static int terminal_menu(Terminal *t, const term_seq *seq) {
678         switch (seq->type) {
679         case TERM_SEQ_IGNORE:
680                 break;
681         case TERM_SEQ_GRAPHIC:
682                 switch (seq->terminator) {
683                 case 'q':
684                         sd_event_exit(t->event, 0);
685                         return 0;
686                 }
687
688                 break;
689         case TERM_SEQ_CONTROL:
690                 switch (seq->terminator) {
691                 case 0x03:
692                         terminal_push_tmp(t, 0x03);
693                         terminal_write_tmp(t);
694                         break;
695                 }
696
697                 break;
698         }
699
700         t->is_menu = false;
701         terminal_dirty(t);
702
703         return 0;
704 }
705
706 static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
707         Terminal *t = userdata;
708         char buf[4096];
709         ssize_t len, i;
710         int r, type;
711
712         len = read(fd, buf, sizeof(buf));
713         if (len < 0) {
714                 if (errno == EAGAIN || errno == EINTR)
715                         return 0;
716
717                 log_error("error: cannot read from TTY (%d): %m", -errno);
718                 return -errno;
719         }
720
721         for (i = 0; i < len; ++i) {
722                 const term_seq *seq;
723                 uint32_t *str;
724                 size_t n_str, j;
725
726                 n_str = term_utf8_decode(&t->utf8, &str, buf[i]);
727                 for (j = 0; j < n_str; ++j) {
728                         type = term_parser_feed(t->parser, &seq, str[j]);
729                         if (type < 0) {
730                                 log_error("error: term_parser_feed() (%d): %s", type, strerror(-type));
731                                 return type;
732                         }
733
734                         if (!t->is_menu) {
735                                 r = terminal_push_tmp(t, str[j]);
736                                 if (r < 0)
737                                         return r;
738                         }
739
740                         if (type == TERM_SEQ_NONE) {
741                                 /* We only intercept one-char sequences, so in
742                                  * case term_parser_feed() couldn't parse a
743                                  * sequence, it is waiting for more data. We
744                                  * know it can never be a one-char sequence
745                                  * then, so we can safely forward the data.
746                                  * This avoids withholding ESC or other values
747                                  * that may be one-shot depending on the
748                                  * application. */
749                                 r = terminal_write_tmp(t);
750                                 if (r < 0)
751                                         return r;
752                         } else if (t->is_menu) {
753                                 r = terminal_menu(t, seq);
754                                 if (r < 0)
755                                         return r;
756                         } else if (seq->type == TERM_SEQ_CONTROL && seq->terminator == 0x03) { /* ^C opens the menu */
757                                 terminal_discard_tmp(t);
758                                 t->is_menu = true;
759                                 terminal_dirty(t);
760                         } else {
761                                 r = terminal_write_tmp(t);
762                                 if (r < 0)
763                                         return r;
764                         }
765                 }
766         }
767
768         return 0;
769 }
770
771 static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) {
772         Terminal *t = userdata;
773         int r;
774
775         switch (event) {
776         case PTY_CHILD:
777                 sd_event_exit(t->event, 0);
778                 break;
779         case PTY_DATA:
780                 r = term_screen_feed_text(t->screen, ptr, size);
781                 if (r < 0) {
782                         log_error("error: term_screen_feed_text() (%d): %s", r, strerror(-r));
783                         return r;
784                 }
785
786                 terminal_dirty(t);
787                 break;
788         }
789
790         return 0;
791 }
792
793 static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) {
794         Terminal *t = userdata;
795         int r;
796
797         if (!t->pty)
798                 return 0;
799
800         r = ring_push(&t->out_ring, buf, size);
801         if (r < 0)
802                 log_oom();
803
804         return r;
805 }
806
807 static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) {
808         return 0;
809 }
810
811 static Terminal *terminal_free(Terminal *t) {
812         if (!t)
813                 return NULL;
814
815         ring_clear(&t->out_ring);
816         term_screen_unref(t->screen);
817         term_parser_free(t->parser);
818         output_free(t->output);
819         sd_event_source_unref(t->frame_timer);
820         sd_event_unref(t->event);
821         tcsetattr(t->in_fd, TCSANOW, &t->saved_in_attr);
822         tcsetattr(t->out_fd, TCSANOW, &t->saved_out_attr);
823         free(t);
824
825         return NULL;
826 }
827
828 static int terminal_new(Terminal **out, int in_fd, int out_fd) {
829         struct termios in_attr, out_attr;
830         Terminal *t;
831         int r;
832
833         assert_return(out, -EINVAL);
834
835         r = tcgetattr(in_fd, &in_attr);
836         if (r < 0) {
837                 log_error("error: tcgetattr() (%d): %m", -errno);
838                 return -errno;
839         }
840
841         r = tcgetattr(out_fd, &out_attr);
842         if (r < 0) {
843                 log_error("error: tcgetattr() (%d): %m", -errno);
844                 return -errno;
845         }
846
847         t = new0(Terminal, 1);
848         if (!t)
849                 return log_oom();
850
851         t->in_fd = in_fd;
852         t->out_fd = out_fd;
853         memcpy(&t->saved_in_attr, &in_attr, sizeof(in_attr));
854         memcpy(&t->saved_out_attr, &out_attr, sizeof(out_attr));
855
856         cfmakeraw(&in_attr);
857         cfmakeraw(&out_attr);
858
859         r = tcsetattr(t->in_fd, TCSANOW, &in_attr);
860         if (r < 0) {
861                 log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
862                 goto error;
863         }
864
865         r = tcsetattr(t->out_fd, TCSANOW, &out_attr);
866         if (r < 0) {
867                 log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
868                 goto error;
869         }
870
871         r = sd_event_default(&t->event);
872         if (r < 0) {
873                 log_error("error: sd_event_default() (%d): %s", r, strerror(-r));
874                 goto error;
875         }
876
877         r = sigprocmask_many(SIG_BLOCK, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1);
878         if (r < 0) {
879                 log_error("error: sigprocmask_many() (%d): %s", r, strerror(-r));
880                 goto error;
881         }
882
883         r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL);
884         if (r < 0) {
885                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
886                 goto error;
887         }
888
889         r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL);
890         if (r < 0) {
891                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
892                 goto error;
893         }
894
895         r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL);
896         if (r < 0) {
897                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
898                 goto error;
899         }
900
901         r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t);
902         if (r < 0) {
903                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
904                 goto error;
905         }
906
907         /* force initial redraw on event-loop enter */
908         t->is_dirty = true;
909         r = sd_event_add_time(t->event, &t->frame_timer, CLOCK_MONOTONIC, 0, 0, terminal_frame_timer_fn, t);
910         if (r < 0) {
911                 log_error("error: sd_event_add_time() (%d): %s", r, strerror(-r));
912                 goto error;
913         }
914
915         r = output_new(&t->output, out_fd);
916         if (r < 0)
917                 goto error;
918
919         r = term_parser_new(&t->parser, true);
920         if (r < 0)
921                 goto error;
922
923         r = term_screen_new(&t->screen, terminal_write_fn, t, terminal_cmd_fn, t);
924         if (r < 0)
925                 goto error;
926
927         r = term_screen_set_answerback(t->screen, "systemd-subterm");
928         if (r < 0)
929                 goto error;
930
931         r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
932         if (r < 0) {
933                 log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
934                 goto error;
935         }
936
937         r = sd_event_add_io(t->event, NULL, in_fd, EPOLLIN, terminal_io_fn, t);
938         if (r < 0)
939                 goto error;
940
941         *out = t;
942         return 0;
943
944 error:
945         terminal_free(t);
946         return r;
947 }
948
949 static int terminal_run(Terminal *t) {
950         pid_t pid;
951
952         assert_return(t, -EINVAL);
953
954         pid = pty_fork(&t->pty, t->event, terminal_pty_fn, t, t->output->in_width, t->output->in_height);
955         if (pid < 0) {
956                 log_error("error: cannot fork PTY (%d): %s", pid, strerror(-pid));
957                 return pid;
958         } else if (pid == 0) {
959                 /* child */
960
961                 char **argv = (char*[]){
962                         (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL,
963                         NULL
964                 };
965
966                 setenv("TERM", "xterm-256color", 1);
967                 setenv("COLORTERM", "systemd-subterm", 1);
968
969                 execve(argv[0], argv, environ);
970                 log_error("error: cannot exec %s (%d): %m", argv[0], -errno);
971                 _exit(1);
972         }
973
974         /* parent */
975
976         return sd_event_loop(t->event);
977 }
978
979 /*
980  * Context Handling
981  */
982
983 int main(int argc, char *argv[]) {
984         Terminal *t = NULL;
985         int r;
986
987         r = terminal_new(&t, 0, 1);
988         if (r < 0)
989                 goto out;
990
991         r = terminal_run(t);
992         if (r < 0)
993                 goto out;
994
995 out:
996         if (r < 0)
997                 log_error("error: terminal failed (%d): %s", r, strerror(-r));
998         terminal_free(t);
999         return -r;
1000 }