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