chiark / gitweb /
dda6759709b6fba1fbed593664c27eded5ee109a
[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_printf(o, "\e[m");
486 }
487
488 static void output_draw(Output *o, bool menu, term_screen *screen) {
489         assert(o);
490
491         /*
492          * This renders the contenst of the terminal. The layout contains a
493          * header, the main body and a footer. Around all areas we draw a
494          * border. It looks something like this:
495          *
496          *     +----------------------------------------------------+
497          *     |                      Header                        |
498          *     +----------------------------------------------------+
499          *     |                                                    |
500          *     |                                                    |
501          *     |                                                    |
502          *     |                       Body                         |
503          *     |                                                    |
504          *     |                                                    |
505          *     ~                                                    ~
506          *     ~                                                    ~
507          *     +----------------------------------------------------+
508          *     |                      Footer                        |
509          *     +----------------------------------------------------+
510          *
511          * The body is the part that grows vertically.
512          *
513          * We need at least 6 vertical lines to render the screen. This would
514          * leave 0 lines for the body. Therefore, we require 7 lines so there's
515          * at least one body line. Similarly, we need 2 horizontal cells for the
516          * frame, so we require 3.
517          * If the window is too small, we print an error message instead.
518          */
519
520         if (o->in_width < 1 || o->in_height < 1) {
521                 output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
522                                  "\e[H");        /* cursor-position: home */
523                 output_printf(o, "error: screen too small, need at least 3x7 cells");
524                 output_flush(o);
525                 return;
526         }
527
528         /* hide cursor */
529         output_printf(o, "\e[?25l");
530
531         /* frame-content is contant; only resizes can change it */
532         if (o->resized || o->in_menu != menu) {
533                 output_printf(o, "\e[2J"         /* erase-in-display: whole screen */
534                                  "\e[H");        /* cursor-position: home */
535                 output_draw_frame(o);
536                 o->resized = false;
537                 o->in_menu = menu;
538         }
539
540         /* move cursor to child's position */
541         output_move_to(o, 1, 3);
542
543         if (menu)
544                 output_draw_menu(o);
545         else
546                 output_draw_screen(o, screen);
547
548         /*
549          * Hack: sd-term was not written to support TTY as output-objects, thus
550          * expects callers to use term_screen_feed_keyboard(). However, we
551          * forward TTY input directly. Hence, we're not notified about keypad
552          * changes. Update the related modes djring redraw to keep them at least
553          * in sync.
554          */
555         if (screen->flags & TERM_FLAG_CURSOR_KEYS)
556                 output_printf(o, "\e[?1h");
557         else
558                 output_printf(o, "\e[?1l");
559
560         if (screen->flags & TERM_FLAG_KEYPAD_MODE)
561                 output_printf(o, "\e=");
562         else
563                 output_printf(o, "\e>");
564
565         output_flush(o);
566 }
567
568 /*
569  * Terminal Handling
570  */
571
572 static void terminal_dirty(Terminal *t) {
573         usec_t usec;
574         int r;
575
576         assert(t);
577
578         if (t->is_scheduled) {
579                 t->is_dirty = true;
580                 return;
581         }
582
583         /* 16ms timer */
584         r = sd_event_now(t->event, CLOCK_MONOTONIC, &usec);
585         assert(r >= 0);
586
587         usec += 16 * USEC_PER_MSEC;
588         r = sd_event_source_set_time(t->frame_timer, usec);
589         if (r >= 0) {
590                 r = sd_event_source_set_enabled(t->frame_timer, SD_EVENT_ONESHOT);
591                 if (r >= 0)
592                         t->is_scheduled = true;
593         }
594
595         t->is_dirty = false;
596         output_draw(t->output, t->is_menu, t->screen);
597 }
598
599 static int terminal_frame_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) {
600         Terminal *t = userdata;
601
602         t->is_scheduled = false;
603         if (t->is_dirty)
604                 terminal_dirty(t);
605
606         return 0;
607 }
608
609 static int terminal_winch_fn(sd_event_source *source, const struct signalfd_siginfo *ssi, void *userdata) {
610         Terminal *t = userdata;
611         int r;
612
613         output_winch(t->output);
614
615         if (t->pty) {
616                 r = pty_resize(t->pty, t->output->in_width, t->output->in_height);
617                 if (r < 0)
618                         log_error("error: pty_resize() (%d): %s", r, strerror(-r));
619         }
620
621         r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
622         if (r < 0)
623                 log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
624
625         terminal_dirty(t);
626
627         return 0;
628 }
629
630 static int terminal_push_tmp(Terminal *t, uint32_t ucs4) {
631         char buf[4];
632         size_t len;
633         int r;
634
635         assert(t);
636
637         len = term_utf8_encode(buf, ucs4);
638         if (len < 1)
639                 return 0;
640
641         r = ring_push(&t->out_ring, buf, len);
642         if (r < 0)
643                 log_oom();
644
645         return r;
646 }
647
648 static int terminal_write_tmp(Terminal *t) {
649         struct iovec vec[2];
650         size_t num, i;
651         int r;
652
653         assert(t);
654
655         num = ring_peek(&t->out_ring, vec);
656         if (num < 1)
657                 return 0;
658
659         if (t->pty) {
660                 for (i = 0; i < num; ++i) {
661                         r = pty_write(t->pty, vec[i].iov_base, vec[i].iov_len);
662                         if (r < 0) {
663                                 log_error("error: cannot write to PTY (%d): %s", r, strerror(-r));
664                                 return r;
665                         }
666                 }
667         }
668
669         ring_flush(&t->out_ring);
670         return 0;
671 }
672
673 static void terminal_discard_tmp(Terminal *t) {
674         assert(t);
675
676         ring_flush(&t->out_ring);
677 }
678
679 static int terminal_menu(Terminal *t, const term_seq *seq) {
680         switch (seq->type) {
681         case TERM_SEQ_IGNORE:
682                 break;
683         case TERM_SEQ_GRAPHIC:
684                 switch (seq->terminator) {
685                 case 'q':
686                         sd_event_exit(t->event, 0);
687                         return 0;
688                 }
689
690                 break;
691         case TERM_SEQ_CONTROL:
692                 switch (seq->terminator) {
693                 case 0x03:
694                         terminal_push_tmp(t, 0x03);
695                         terminal_write_tmp(t);
696                         break;
697                 }
698
699                 break;
700         }
701
702         t->is_menu = false;
703         terminal_dirty(t);
704
705         return 0;
706 }
707
708 static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
709         Terminal *t = userdata;
710         char buf[4096];
711         ssize_t len, i;
712         int r, type;
713
714         len = read(fd, buf, sizeof(buf));
715         if (len < 0) {
716                 if (errno == EAGAIN || errno == EINTR)
717                         return 0;
718
719                 log_error("error: cannot read from TTY (%d): %m", -errno);
720                 return -errno;
721         }
722
723         for (i = 0; i < len; ++i) {
724                 const term_seq *seq;
725                 uint32_t *str;
726                 size_t n_str, j;
727
728                 n_str = term_utf8_decode(&t->utf8, &str, buf[i]);
729                 for (j = 0; j < n_str; ++j) {
730                         type = term_parser_feed(t->parser, &seq, str[j]);
731                         if (type < 0) {
732                                 log_error("error: term_parser_feed() (%d): %s", type, strerror(-type));
733                                 return type;
734                         }
735
736                         if (!t->is_menu) {
737                                 r = terminal_push_tmp(t, str[j]);
738                                 if (r < 0)
739                                         return r;
740                         }
741
742                         if (type == TERM_SEQ_NONE) {
743                                 /* We only intercept one-char sequences, so in
744                                  * case term_parser_feed() couldn't parse a
745                                  * sequence, it is waiting for more data. We
746                                  * know it can never be a one-char sequence
747                                  * then, so we can safely forward the data.
748                                  * This avoids withholding ESC or other values
749                                  * that may be one-shot depending on the
750                                  * application. */
751                                 r = terminal_write_tmp(t);
752                                 if (r < 0)
753                                         return r;
754                         } else if (t->is_menu) {
755                                 r = terminal_menu(t, seq);
756                                 if (r < 0)
757                                         return r;
758                         } else if (seq->type == TERM_SEQ_CONTROL && seq->terminator == 0x03) { /* ^C opens the menu */
759                                 terminal_discard_tmp(t);
760                                 t->is_menu = true;
761                                 terminal_dirty(t);
762                         } else {
763                                 r = terminal_write_tmp(t);
764                                 if (r < 0)
765                                         return r;
766                         }
767                 }
768         }
769
770         return 0;
771 }
772
773 static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) {
774         Terminal *t = userdata;
775         int r;
776
777         switch (event) {
778         case PTY_CHILD:
779                 sd_event_exit(t->event, 0);
780                 break;
781         case PTY_DATA:
782                 r = term_screen_feed_text(t->screen, ptr, size);
783                 if (r < 0) {
784                         log_error("error: term_screen_feed_text() (%d): %s", r, strerror(-r));
785                         return r;
786                 }
787
788                 terminal_dirty(t);
789                 break;
790         }
791
792         return 0;
793 }
794
795 static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) {
796         Terminal *t = userdata;
797         int r;
798
799         if (!t->pty)
800                 return 0;
801
802         r = ring_push(&t->out_ring, buf, size);
803         if (r < 0)
804                 log_oom();
805
806         return r;
807 }
808
809 static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) {
810         return 0;
811 }
812
813 static Terminal *terminal_free(Terminal *t) {
814         if (!t)
815                 return NULL;
816
817         ring_clear(&t->out_ring);
818         term_screen_unref(t->screen);
819         term_parser_free(t->parser);
820         output_free(t->output);
821         sd_event_source_unref(t->frame_timer);
822         sd_event_unref(t->event);
823         tcsetattr(t->in_fd, TCSANOW, &t->saved_in_attr);
824         tcsetattr(t->out_fd, TCSANOW, &t->saved_out_attr);
825         free(t);
826
827         return NULL;
828 }
829
830 static int terminal_new(Terminal **out, int in_fd, int out_fd) {
831         struct termios in_attr, out_attr;
832         Terminal *t;
833         int r;
834
835         assert_return(out, -EINVAL);
836
837         r = tcgetattr(in_fd, &in_attr);
838         if (r < 0) {
839                 log_error("error: tcgetattr() (%d): %m", -errno);
840                 return -errno;
841         }
842
843         r = tcgetattr(out_fd, &out_attr);
844         if (r < 0) {
845                 log_error("error: tcgetattr() (%d): %m", -errno);
846                 return -errno;
847         }
848
849         t = new0(Terminal, 1);
850         if (!t)
851                 return log_oom();
852
853         t->in_fd = in_fd;
854         t->out_fd = out_fd;
855         memcpy(&t->saved_in_attr, &in_attr, sizeof(in_attr));
856         memcpy(&t->saved_out_attr, &out_attr, sizeof(out_attr));
857
858         cfmakeraw(&in_attr);
859         cfmakeraw(&out_attr);
860
861         r = tcsetattr(t->in_fd, TCSANOW, &in_attr);
862         if (r < 0) {
863                 log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
864                 goto error;
865         }
866
867         r = tcsetattr(t->out_fd, TCSANOW, &out_attr);
868         if (r < 0) {
869                 log_error("error: tcsetattr() (%d): %s", r, strerror(-r));
870                 goto error;
871         }
872
873         r = sd_event_default(&t->event);
874         if (r < 0) {
875                 log_error("error: sd_event_default() (%d): %s", r, strerror(-r));
876                 goto error;
877         }
878
879         r = sigprocmask_many(SIG_BLOCK, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1);
880         if (r < 0) {
881                 log_error("error: sigprocmask_many() (%d): %s", r, strerror(-r));
882                 goto error;
883         }
884
885         r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL);
886         if (r < 0) {
887                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
888                 goto error;
889         }
890
891         r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL);
892         if (r < 0) {
893                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
894                 goto error;
895         }
896
897         r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL);
898         if (r < 0) {
899                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
900                 goto error;
901         }
902
903         r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t);
904         if (r < 0) {
905                 log_error("error: sd_event_add_signal() (%d): %s", r, strerror(-r));
906                 goto error;
907         }
908
909         /* force initial redraw on event-loop enter */
910         t->is_dirty = true;
911         r = sd_event_add_time(t->event, &t->frame_timer, CLOCK_MONOTONIC, 0, 0, terminal_frame_timer_fn, t);
912         if (r < 0) {
913                 log_error("error: sd_event_add_time() (%d): %s", r, strerror(-r));
914                 goto error;
915         }
916
917         r = output_new(&t->output, out_fd);
918         if (r < 0)
919                 goto error;
920
921         r = term_parser_new(&t->parser, true);
922         if (r < 0)
923                 goto error;
924
925         r = term_screen_new(&t->screen, terminal_write_fn, t, terminal_cmd_fn, t);
926         if (r < 0)
927                 goto error;
928
929         r = term_screen_set_answerback(t->screen, "systemd-subterm");
930         if (r < 0)
931                 goto error;
932
933         r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
934         if (r < 0) {
935                 log_error("error: term_screen_resize() (%d): %s", r, strerror(-r));
936                 goto error;
937         }
938
939         r = sd_event_add_io(t->event, NULL, in_fd, EPOLLIN, terminal_io_fn, t);
940         if (r < 0)
941                 goto error;
942
943         *out = t;
944         return 0;
945
946 error:
947         terminal_free(t);
948         return r;
949 }
950
951 static int terminal_run(Terminal *t) {
952         pid_t pid;
953
954         assert_return(t, -EINVAL);
955
956         pid = pty_fork(&t->pty, t->event, terminal_pty_fn, t, t->output->in_width, t->output->in_height);
957         if (pid < 0) {
958                 log_error("error: cannot fork PTY (%d): %s", pid, strerror(-pid));
959                 return pid;
960         } else if (pid == 0) {
961                 /* child */
962
963                 char **argv = (char*[]){
964                         (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL,
965                         NULL
966                 };
967
968                 setenv("TERM", "xterm-256color", 1);
969                 setenv("COLORTERM", "systemd-subterm", 1);
970
971                 execve(argv[0], argv, environ);
972                 log_error("error: cannot exec %s (%d): %m", argv[0], -errno);
973                 _exit(1);
974         }
975
976         /* parent */
977
978         return sd_event_loop(t->event);
979 }
980
981 /*
982  * Context Handling
983  */
984
985 int main(int argc, char *argv[]) {
986         Terminal *t = NULL;
987         int r;
988
989         r = terminal_new(&t, 0, 1);
990         if (r < 0)
991                 goto out;
992
993         r = terminal_run(t);
994         if (r < 0)
995                 goto out;
996
997 out:
998         if (r < 0)
999                 log_error("error: terminal failed (%d): %s", r, strerror(-r));
1000         terminal_free(t);
1001         return -r;
1002 }