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