chiark / gitweb /
terminal/screen: add color converter
[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         /* re-enable cursor */
290         output_printf(o, "\e[?25h");
291         /* disable alternate screen buffer */
292         output_printf(o, "\e[?1049l");
293         output_flush(o);
294
295         /* o->fd is owned by the caller */
296         free(o);
297
298         return NULL;
299 }
300
301 static int output_new(Output **out, int fd) {
302         Output *o;
303         int r;
304
305         assert_return(out, -EINVAL);
306
307         o = new0(Output, 1);
308         if (!o)
309                 return log_oom();
310
311         o->fd = fd;
312
313         r = output_winch(o);
314         if (r < 0)
315                 goto error;
316
317         /* enable alternate screen buffer */
318         r = output_printf(o, "\e[?1049h");
319         if (r < 0)
320                 goto error;
321
322         /* always hide cursor */
323         r = output_printf(o, "\e[?25l");
324         if (r < 0)
325                 goto error;
326
327         r = output_flush(o);
328         if (r < 0)
329                 goto error;
330
331         *out = o;
332         return 0;
333
334 error:
335         output_free(o);
336         return r;
337 }
338
339 static void output_draw_frame(Output *o) {
340         unsigned int i;
341
342         assert(o);
343
344         /* print header-frame */
345
346         output_printf(o, BORDER_DOWN_RIGHT);
347         output_print_line(o, o->width - 2);
348         output_printf(o, BORDER_DOWN_LEFT
349                          "\r\n"
350                          BORDER_VERT
351                          "\e[2;%uH"    /* cursor-position: 2/x */
352                          BORDER_VERT
353                          "\r\n"
354                          BORDER_VERT_RIGHT,
355                       o->width);
356         output_print_line(o, o->width - 2);
357         output_printf(o, BORDER_VERT_LEFT
358                          "\r\n");
359
360         /* print body-frame */
361
362         for (i = 0; i < o->in_height; ++i) {
363                 output_printf(o, BORDER_VERT
364                                  "\e[%u;%uH"    /* cursor-position: 2/x */
365                                  BORDER_VERT
366                                  "\r\n",
367                               i + 4, o->width);
368         }
369
370         /* print footer-frame */
371
372         output_printf(o, BORDER_VERT_RIGHT);
373         output_print_line(o, o->width - 2);
374         output_printf(o, BORDER_VERT_LEFT
375                          "\r\n"
376                          BORDER_VERT
377                          "\e[%u;%uH"    /* cursor-position: 2/x */
378                          BORDER_VERT
379                          "\r\n"
380                          BORDER_UP_RIGHT,
381                       o->height - 1, o->width);
382         output_print_line(o, o->width - 2);
383         output_printf(o, BORDER_UP_LEFT);
384
385         /* print header/footer text */
386
387         output_printf(o, "\e[2;3H");
388         output_nprintf(o, o->width - 4, "systemd - embedded terminal emulator");
389         output_printf(o, "\e[%u;3H", o->height - 1);
390         output_nprintf(o, o->width - 4, "press ^C to enter menu");
391 }
392
393 static void output_draw_menu(Output *o) {
394         assert(o);
395
396         output_frame_printl(o, "%s", "");
397         output_frame_printl(o, "    Menu: (the following keys are recognized)");
398         output_frame_printl(o, "      q: quit");
399         output_frame_printl(o, "     ^C: send ^C to the PTY");
400 }
401
402 static int output_draw_cell_fn(term_screen *screen,
403                                void *userdata,
404                                unsigned int x,
405                                unsigned int y,
406                                const term_attr *attr,
407                                const uint32_t *ch,
408                                size_t n_ch,
409                                unsigned int ch_width) {
410         Output *o = userdata;
411         size_t k, ulen;
412         char utf8[4];
413
414         if (x >= o->in_width || y >= o->in_height)
415                 return 0;
416
417         if (x == 0 && y != 0)
418                 output_printf(o, "\e[m\r\n" BORDER_VERT);
419
420         switch (attr->fg.ccode) {
421         case TERM_CCODE_DEFAULT:
422                 output_printf(o, "\e[39m");
423                 break;
424         case TERM_CCODE_256:
425                 output_printf(o, "\e[38;5;%um", attr->fg.c256);
426                 break;
427         case TERM_CCODE_RGB:
428                 output_printf(o, "\e[38;2;%u;%u;%um", attr->fg.red, attr->fg.green, attr->fg.blue);
429                 break;
430         case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
431                 if (attr->bold)
432                         output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 90);
433                 else
434                         output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 30);
435                 break;
436         case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
437                 output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_LIGHT_BLACK + 90);
438                 break;
439         }
440
441         switch (attr->bg.ccode) {
442         case TERM_CCODE_DEFAULT:
443                 output_printf(o, "\e[49m");
444                 break;
445         case TERM_CCODE_256:
446                 output_printf(o, "\e[48;5;%um", attr->bg.c256);
447                 break;
448         case TERM_CCODE_RGB:
449                 output_printf(o, "\e[48;2;%u;%u;%um", attr->bg.red, attr->bg.green, attr->bg.blue);
450                 break;
451         case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
452                 output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_BLACK + 40);
453                 break;
454         case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
455                 output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_LIGHT_BLACK + 100);
456                 break;
457         }
458
459         output_printf(o, "\e[%u;%u;%u;%u;%u;%um",
460                       attr->bold ? 1 : 22,
461                       attr->italic ? 3 : 23,
462                       attr->underline ? 4 : 24,
463                       attr->inverse ? 7 : 27,
464                       attr->blink ? 5 : 25,
465                       attr->hidden ? 8 : 28);
466
467         if (n_ch < 1) {
468                 output_printf(o, " ");
469         } else {
470                 for (k = 0; k < n_ch; ++k) {
471                         ulen = term_utf8_encode(utf8, ch[k]);
472                         output_write(o, utf8, ulen);
473                 }
474         }
475
476         return 0;
477 }
478
479 static void output_draw_screen(Output *o, term_screen *s) {
480         assert(o);
481         assert(s);
482
483         term_screen_draw(s, output_draw_cell_fn, o, NULL);
484
485         output_move_to(o, s->cursor_x + 1, s->cursor_y + 3);
486         output_printf(o, "\e[m");
487 }
488
489 static void output_draw(Output *o, bool menu, term_screen *screen) {
490         assert(o);
491
492         /*
493          * This renders the contenst of the terminal. The layout contains a
494          * header, the main body and a footer. Around all areas we draw a
495          * border. It looks something like this:
496          *
497          *     +----------------------------------------------------+
498          *     |                      Header                        |
499          *     +----------------------------------------------------+
500          *     |                                                    |
501          *     |                                                    |
502          *     |                                                    |
503          *     |                       Body                         |
504          *     |                                                    |
505          *     |                                                    |
506          *     ~                                                    ~
507          *     ~                                                    ~
508          *     +----------------------------------------------------+
509          *     |                      Footer                        |
510          *     +----------------------------------------------------+
511          *
512          * The body is the part that grows vertically.
513          *
514          * We need at least 6 vertical lines to render the screen. This would
515          * leave 0 lines for the body. Therefore, we require 7 lines so there's
516          * at least one body line. Similarly, we need 2 horizontal cells for the
517          * frame, so we require 3.
518          * If the window is too small, we print an error message instead.
519          */
520
521         if (o->in_width < 1 || o->in_height < 1) {
522                 output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
523                                  "\e[H");        /* cursor-position: home */
524                 output_printf(o, "error: screen too small, need at least 3x7 cells");
525                 output_flush(o);
526                 return;
527         }
528
529         /* hide cursor */
530         output_printf(o, "\e[?25l");
531
532         /* frame-content is contant; only resizes can change it */
533         if (o->resized || o->in_menu != menu) {
534                 output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
535                                  "\e[H");        /* cursor-position: home */
536                 output_draw_frame(o);
537                 o->resized = false;
538                 o->in_menu = menu;
539         }
540
541         /* move cursor to child's position */
542         output_move_to(o, 1, 3);
543
544         if (menu)
545                 output_draw_menu(o);
546         else
547                 output_draw_screen(o, screen);
548
549         /*
550          * Hack: sd-term was not written to support TTY as output-objects, thus
551          * expects callers to use term_screen_feed_keyboard(). However, we
552          * forward TTY input directly. Hence, we're not notified about keypad
553          * changes. Update the related modes djring redraw to keep them at least
554          * in sync.
555          */
556         if (screen->flags & TERM_FLAG_CURSOR_KEYS)
557                 output_printf(o, "\e[?1h");
558         else
559                 output_printf(o, "\e[?1l");
560
561         if (screen->flags & TERM_FLAG_KEYPAD_MODE)
562                 output_printf(o, "\e=");
563         else
564                 output_printf(o, "\e>");
565
566         output_flush(o);
567 }
568
569 /*
570  * Terminal Handling
571  */
572
573 static void terminal_dirty(Terminal *t) {
574         usec_t usec;
575         int r;
576
577         assert(t);
578
579         if (t->is_scheduled) {
580                 t->is_dirty = true;
581                 return;
582         }
583
584         /* 16ms timer */
585         r = sd_event_now(t->event, CLOCK_MONOTONIC, &usec);
586         assert(r >= 0);
587
588         usec += 16 * USEC_PER_MSEC;
589         r = sd_event_source_set_time(t->frame_timer, usec);
590         if (r >= 0) {
591                 r = sd_event_source_set_enabled(t->frame_timer, SD_EVENT_ONESHOT);
592                 if (r >= 0)
593                         t->is_scheduled = true;
594         }
595
596         t->is_dirty = false;
597         output_draw(t->output, t->is_menu, t->screen);
598 }
599
600 static int terminal_frame_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) {
601         Terminal *t = userdata;
602
603         t->is_scheduled = false;
604         if (t->is_dirty)
605                 terminal_dirty(t);
606
607         return 0;
608 }
609
610 static int terminal_winch_fn(sd_event_source *source, const struct signalfd_siginfo *ssi, void *userdata) {
611         Terminal *t = userdata;
612         int r;
613
614         output_winch(t->output);
615
616         if (t->pty) {
617                 r = pty_resize(t->pty, t->output->in_width, t->output->in_height);
618                 if (r < 0)
619                         log_error("error: pty_resize() (%d): %s", r, strerror(-r));
620         }
621
622         r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
623         if (r < 0)
624                 log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
625
626         terminal_dirty(t);
627
628         return 0;
629 }
630
631 static int terminal_push_tmp(Terminal *t, uint32_t ucs4) {
632         char buf[4];
633         size_t len;
634         int r;
635
636         assert(t);
637
638         len = term_utf8_encode(buf, ucs4);
639         if (len < 1)
640                 return 0;
641
642         r = ring_push(&t->out_ring, buf, len);
643         if (r < 0)
644                 log_oom();
645
646         return r;
647 }
648
649 static int terminal_write_tmp(Terminal *t) {
650         struct iovec vec[2];
651         size_t num, i;
652         int r;
653
654         assert(t);
655
656         num = ring_peek(&t->out_ring, vec);
657         if (num < 1)
658                 return 0;
659
660         if (t->pty) {
661                 for (i = 0; i < num; ++i) {
662                         r = pty_write(t->pty, vec[i].iov_base, vec[i].iov_len);
663                         if (r < 0) {
664                                 log_error("error: cannot write to PTY (%d): %s", r, strerror(-r));
665                                 return r;
666                         }
667                 }
668         }
669
670         ring_flush(&t->out_ring);
671         return 0;
672 }
673
674 static void terminal_discard_tmp(Terminal *t) {
675         assert(t);
676
677         ring_flush(&t->out_ring);
678 }
679
680 static int terminal_menu(Terminal *t, const term_seq *seq) {
681         switch (seq->type) {
682         case TERM_SEQ_IGNORE:
683                 break;
684         case TERM_SEQ_GRAPHIC:
685                 switch (seq->terminator) {
686                 case 'q':
687                         sd_event_exit(t->event, 0);
688                         return 0;
689                 }
690
691                 break;
692         case TERM_SEQ_CONTROL:
693                 switch (seq->terminator) {
694                 case 0x03:
695                         terminal_push_tmp(t, 0x03);
696                         terminal_write_tmp(t);
697                         break;
698                 }
699
700                 break;
701         }
702
703         t->is_menu = false;
704         terminal_dirty(t);
705
706         return 0;
707 }
708
709 static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
710         Terminal *t = userdata;
711         char buf[4096];
712         ssize_t len, i;
713         int r, type;
714
715         len = read(fd, buf, sizeof(buf));
716         if (len < 0) {
717                 if (errno == EAGAIN || errno == EINTR)
718                         return 0;
719
720                 log_error("error: cannot read from TTY (%d): %m", -errno);
721                 return -errno;
722         }
723
724         for (i = 0; i < len; ++i) {
725                 const term_seq *seq;
726                 uint32_t *str;
727                 size_t n_str, j;
728
729                 n_str = term_utf8_decode(&t->utf8, &str, buf[i]);
730                 for (j = 0; j < n_str; ++j) {
731                         type = term_parser_feed(t->parser, &seq, str[j]);
732                         if (type < 0) {
733                                 log_error("error: term_parser_feed() (%d): %s", type, strerror(-type));
734                                 return type;
735                         }
736
737                         if (!t->is_menu) {
738                                 r = terminal_push_tmp(t, str[j]);
739                                 if (r < 0)
740                                         return r;
741                         }
742
743                         if (type == TERM_SEQ_NONE) {
744                                 /* We only intercept one-char sequences, so in
745                                  * case term_parser_feed() couldn't parse a
746                                  * sequence, it is waiting for more data. We
747                                  * know it can never be a one-char sequence
748                                  * then, so we can safely forward the data.
749                                  * This avoids withholding ESC or other values
750                                  * that may be one-shot depending on the
751                                  * application. */
752                                 r = terminal_write_tmp(t);
753                                 if (r < 0)
754                                         return r;
755                         } else if (t->is_menu) {
756                                 r = terminal_menu(t, seq);
757                                 if (r < 0)
758                                         return r;
759                         } else if (seq->type == TERM_SEQ_CONTROL && seq->terminator == 0x03) { /* ^C opens the menu */
760                                 terminal_discard_tmp(t);
761                                 t->is_menu = true;
762                                 terminal_dirty(t);
763                         } else {
764                                 r = terminal_write_tmp(t);
765                                 if (r < 0)
766                                         return r;
767                         }
768                 }
769         }
770
771         return 0;
772 }
773
774 static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) {
775         Terminal *t = userdata;
776         int r;
777
778         switch (event) {
779         case PTY_CHILD:
780                 sd_event_exit(t->event, 0);
781                 break;
782         case PTY_DATA:
783                 r = term_screen_feed_text(t->screen, ptr, size);
784                 if (r < 0) {
785                         log_error("error: term_screen_feed_text() (%d): %s", r, strerror(-r));
786                         return r;
787                 }
788
789                 terminal_dirty(t);
790                 break;
791         }
792
793         return 0;
794 }
795
796 static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) {
797         Terminal *t = userdata;
798         int r;
799
800         if (!t->pty)
801                 return 0;
802
803         r = ring_push(&t->out_ring, buf, size);
804         if (r < 0)
805                 log_oom();
806
807         return r;
808 }
809
810 static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) {
811         return 0;
812 }
813
814 static Terminal *terminal_free(Terminal *t) {
815         if (!t)
816                 return NULL;
817
818         ring_clear(&t->out_ring);
819         term_screen_unref(t->screen);
820         term_parser_free(t->parser);
821         output_free(t->output);
822         sd_event_source_unref(t->frame_timer);
823         sd_event_unref(t->event);
824         tcsetattr(t->in_fd, TCSANOW, &t->saved_in_attr);
825         tcsetattr(t->out_fd, TCSANOW, &t->saved_out_attr);
826         free(t);
827
828         return NULL;
829 }
830
831 static int terminal_new(Terminal **out, int in_fd, int out_fd) {
832         struct termios in_attr, out_attr;
833         Terminal *t;
834         int r;
835
836         assert_return(out, -EINVAL);
837
838         r = tcgetattr(in_fd, &in_attr);
839         if (r < 0) {
840                 log_error("error: tcgetattr() (%d): %m", -errno);
841                 return -errno;
842         }
843
844         r = tcgetattr(out_fd, &out_attr);
845         if (r < 0) {
846                 log_error("error: tcgetattr() (%d): %m", -errno);
847                 return -errno;
848         }
849
850         t = new0(Terminal, 1);
851         if (!t)
852                 return log_oom();
853
854         t->in_fd = in_fd;
855         t->out_fd = out_fd;
856         memcpy(&t->saved_in_attr, &in_attr, sizeof(in_attr));
857         memcpy(&t->saved_out_attr, &out_attr, sizeof(out_attr));
858
859         cfmakeraw(&in_attr);
860         cfmakeraw(&out_attr);
861
862         r = tcsetattr(t->in_fd, TCSANOW, &in_attr);
863         if (r < 0) {
864                 log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
865                 goto error;
866         }
867
868         r = tcsetattr(t->out_fd, TCSANOW, &out_attr);
869         if (r < 0) {
870                 log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
871                 goto error;
872         }
873
874         r = sd_event_default(&t->event);
875         if (r < 0) {
876                 log_error("error: sd_event_default() (%d): %s", r, strerror(-r));
877                 goto error;
878         }
879
880         r = sigprocmask_many(SIG_BLOCK, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1);
881         if (r < 0) {
882                 log_error("error: sigprocmask_many() (%d): %s", r, strerror(-r));
883                 goto error;
884         }
885
886         r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL);
887         if (r < 0) {
888                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
889                 goto error;
890         }
891
892         r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL);
893         if (r < 0) {
894                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
895                 goto error;
896         }
897
898         r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL);
899         if (r < 0) {
900                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
901                 goto error;
902         }
903
904         r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t);
905         if (r < 0) {
906                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
907                 goto error;
908         }
909
910         /* force initial redraw on event-loop enter */
911         t->is_dirty = true;
912         r = sd_event_add_time(t->event, &t->frame_timer, CLOCK_MONOTONIC, 0, 0, terminal_frame_timer_fn, t);
913         if (r < 0) {
914                 log_error("error: sd_event_add_time() (%d): %s", r, strerror(-r));
915                 goto error;
916         }
917
918         r = output_new(&t->output, out_fd);
919         if (r < 0)
920                 goto error;
921
922         r = term_parser_new(&t->parser, true);
923         if (r < 0)
924                 goto error;
925
926         r = term_screen_new(&t->screen, terminal_write_fn, t, terminal_cmd_fn, t);
927         if (r < 0)
928                 goto error;
929
930         r = term_screen_set_answerback(t->screen, "systemd-subterm");
931         if (r < 0)
932                 goto error;
933
934         r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
935         if (r < 0) {
936                 log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
937                 goto error;
938         }
939
940         r = sd_event_add_io(t->event, NULL, in_fd, EPOLLIN, terminal_io_fn, t);
941         if (r < 0)
942                 goto error;
943
944         *out = t;
945         return 0;
946
947 error:
948         terminal_free(t);
949         return r;
950 }
951
952 static int terminal_run(Terminal *t) {
953         pid_t pid;
954
955         assert_return(t, -EINVAL);
956
957         pid = pty_fork(&t->pty, t->event, terminal_pty_fn, t, t->output->in_width, t->output->in_height);
958         if (pid < 0) {
959                 log_error("error: cannot fork PTY (%d): %s", pid, strerror(-pid));
960                 return pid;
961         } else if (pid == 0) {
962                 /* child */
963
964                 char **argv = (char*[]){
965                         (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL,
966                         NULL
967                 };
968
969                 setenv("TERM", "xterm-256color", 1);
970                 setenv("COLORTERM", "systemd-subterm", 1);
971
972                 execve(argv[0], argv, environ);
973                 log_error("error: cannot exec %s (%d): %m", argv[0], -errno);
974                 _exit(1);
975         }
976
977         /* parent */
978
979         return sd_event_loop(t->event);
980 }
981
982 /*
983  * Context Handling
984  */
985
986 int main(int argc, char *argv[]) {
987         Terminal *t = NULL;
988         int r;
989
990         r = terminal_new(&t, 0, 1);
991         if (r < 0)
992                 goto out;
993
994         r = terminal_run(t);
995         if (r < 0)
996                 goto out;
997
998 out:
999         if (r < 0)
1000                 log_error("error: terminal failed (%d): %s", r, strerror(-r));
1001         terminal_free(t);
1002         return -r;
1003 }