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