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