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