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