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