]> git.proxmox.com Git - systemd.git/blame - src/libsystemd-terminal/subterm.c
Imported Upstream version 220
[systemd.git] / src / libsystemd-terminal / subterm.c
CommitLineData
5eef597e
MP
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
5eef597e
MP
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"
e735f4d4 43#include "utf8.h"
5eef597e
MP
44
45typedef struct Output Output;
46typedef struct Terminal Terminal;
47
48struct 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
64struct 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
98static 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);
f47781d8
MP
105 if (r < 0)
106 return log_error_errno(errno, "error: cannot read window-size: %m");
5eef597e
MP
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
119static int output_flush(Output *o) {
f47781d8 120 int r;
5eef597e
MP
121
122 if (o->n_obuf < 1)
123 return 0;
124
f47781d8
MP
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");
5eef597e
MP
128
129 o->n_obuf = 0;
130
131 return 0;
132}
133
134static 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);
f47781d8
MP
155 if (len < 0)
156 return log_error_errno(len, "error: cannot write to TTY (%zd): %m", len);
5eef597e
MP
157
158 return 0;
159}
160
161_printf_(3,0)
162static int output_vnprintf(Output *o, size_t max, const char *format, va_list args) {
e735f4d4 163 char buf[max];
5eef597e
MP
164 int r;
165
166 assert_return(o, -EINVAL);
167 assert_return(format, -EINVAL);
e735f4d4 168 assert_return(max <= 4096, -EINVAL);
5eef597e 169
e735f4d4 170 r = MIN(vsnprintf(buf, max, format, args), (int) max);
5eef597e
MP
171
172 return output_write(o, buf, r);
173}
174
175_printf_(3,4)
176static 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)
188static 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)
203static 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
214static 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
230static 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)
256static 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
277static 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
293static 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
326error:
327 output_free(o);
328 return r;
329}
330
331static 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
385static 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
394static 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) {
e735f4d4 460 ulen = utf8_encode_unichar(utf8, ch[k]);
5eef597e
MP
461 output_write(o, utf8, ulen);
462 }
463 }
464
465 return 0;
466}
467
468static 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
477static 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
561static 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
588static 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
598static 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)
f47781d8 607 log_error_errno(r, "error: pty_resize() (%d): %m", r);
5eef597e
MP
608 }
609
610 r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
611 if (r < 0)
f47781d8 612 log_error_errno(r, "error: term_screen_resize() (%d): %m", r);
5eef597e
MP
613
614 terminal_dirty(t);
615
616 return 0;
617}
618
619static 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
e735f4d4 626 len = utf8_encode_unichar(buf, ucs4);
5eef597e
MP
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
637static 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);
f47781d8
MP
651 if (r < 0)
652 return log_error_errno(r, "error: cannot write to PTY (%d): %m", r);
5eef597e
MP
653 }
654 }
655
656 ring_flush(&t->out_ring);
657 return 0;
658}
659
660static void terminal_discard_tmp(Terminal *t) {
661 assert(t);
662
663 ring_flush(&t->out_ring);
664}
665
666static 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
695static 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
f47781d8 706 log_error_errno(errno, "error: cannot read from TTY (%d): %m", -errno);
5eef597e
MP
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]);
f47781d8
MP
718 if (type < 0)
719 return log_error_errno(type, "error: term_parser_feed() (%d): %m", type);
5eef597e
MP
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
758static 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);
f47781d8
MP
768 if (r < 0)
769 return log_error_errno(r, "error: term_screen_feed_text() (%d): %m", r);
5eef597e
MP
770
771 terminal_dirty(t);
772 break;
773 }
774
775 return 0;
776}
777
778static 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
792static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) {
793 return 0;
794}
795
796static 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
813static 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);
f47781d8
MP
821 if (r < 0)
822 return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno);
5eef597e
MP
823
824 r = tcgetattr(out_fd, &out_attr);
f47781d8
MP
825 if (r < 0)
826 return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno);
5eef597e
MP
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) {
f47781d8 842 log_error_errno(r, "error: tcsetattr() (%d): %m", r);
5eef597e
MP
843 goto error;
844 }
845
846 r = tcsetattr(t->out_fd, TCSANOW, &out_attr);
847 if (r < 0) {
f47781d8 848 log_error_errno(r, "error: tcsetattr() (%d): %m", r);
5eef597e
MP
849 goto error;
850 }
851
852 r = sd_event_default(&t->event);
853 if (r < 0) {
f47781d8 854 log_error_errno(r, "error: sd_event_default() (%d): %m", r);
5eef597e
MP
855 goto error;
856 }
857
858 r = sigprocmask_many(SIG_BLOCK, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1);
859 if (r < 0) {
f47781d8 860 log_error_errno(r, "error: sigprocmask_many() (%d): %m", r);
5eef597e
MP
861 goto error;
862 }
863
864 r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL);
865 if (r < 0) {
f47781d8 866 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
867 goto error;
868 }
869
870 r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL);
871 if (r < 0) {
f47781d8 872 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
873 goto error;
874 }
875
876 r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL);
877 if (r < 0) {
f47781d8 878 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
879 goto error;
880 }
881
882 r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t);
883 if (r < 0) {
f47781d8 884 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
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) {
f47781d8 892 log_error_errno(r, "error: sd_event_add_time() (%d): %m", r);
5eef597e
MP
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) {
f47781d8 914 log_error_errno(r, "error: term_screen_resize() (%d): %m", r);
5eef597e
MP
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
925error:
926 terminal_free(t);
927 return r;
928}
929
930static 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);
f47781d8
MP
936 if (pid < 0)
937 return log_error_errno(pid, "error: cannot fork PTY (%d): %m", pid);
938 else if (pid == 0) {
5eef597e
MP
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);
f47781d8 950 log_error_errno(errno, "error: cannot exec %s (%d): %m", argv[0], -errno);
5eef597e
MP
951 _exit(1);
952 }
953
954 /* parent */
955
956 return sd_event_loop(t->event);
957}
958
959/*
960 * Context Handling
961 */
962
963int 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
975out:
976 if (r < 0)
f47781d8 977 log_error_errno(r, "error: terminal failed (%d): %m", r);
5eef597e
MP
978 terminal_free(t);
979 return -r;
980}