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