]> git.proxmox.com Git - systemd.git/blame - src/libsystemd-terminal/subterm.c
Imported Upstream version 219
[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
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"
e735f4d4 44#include "utf8.h"
5eef597e
MP
45
46typedef struct Output Output;
47typedef struct Terminal Terminal;
48
49struct 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
65struct 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
99static 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);
f47781d8
MP
106 if (r < 0)
107 return log_error_errno(errno, "error: cannot read window-size: %m");
5eef597e
MP
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
120static int output_flush(Output *o) {
f47781d8 121 int r;
5eef597e
MP
122
123 if (o->n_obuf < 1)
124 return 0;
125
f47781d8
MP
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");
5eef597e
MP
129
130 o->n_obuf = 0;
131
132 return 0;
133}
134
135static 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);
f47781d8
MP
156 if (len < 0)
157 return log_error_errno(len, "error: cannot write to TTY (%zd): %m", len);
5eef597e
MP
158
159 return 0;
160}
161
162_printf_(3,0)
163static int output_vnprintf(Output *o, size_t max, const char *format, va_list args) {
e735f4d4 164 char buf[max];
5eef597e
MP
165 int r;
166
167 assert_return(o, -EINVAL);
168 assert_return(format, -EINVAL);
e735f4d4 169 assert_return(max <= 4096, -EINVAL);
5eef597e 170
e735f4d4 171 r = MIN(vsnprintf(buf, max, format, args), (int) max);
5eef597e
MP
172
173 return output_write(o, buf, r);
174}
175
176_printf_(3,4)
177static 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)
189static 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)
204static 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
215static 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
231static 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)
257static 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
278static 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
294static 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
327error:
328 output_free(o);
329 return r;
330}
331
332static 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
386static 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
395static 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) {
e735f4d4 461 ulen = utf8_encode_unichar(utf8, ch[k]);
5eef597e
MP
462 output_write(o, utf8, ulen);
463 }
464 }
465
466 return 0;
467}
468
469static 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
478static 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
562static 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
589static 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
599static 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)
f47781d8 608 log_error_errno(r, "error: pty_resize() (%d): %m", r);
5eef597e
MP
609 }
610
611 r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height);
612 if (r < 0)
f47781d8 613 log_error_errno(r, "error: term_screen_resize() (%d): %m", r);
5eef597e
MP
614
615 terminal_dirty(t);
616
617 return 0;
618}
619
620static 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
e735f4d4 627 len = utf8_encode_unichar(buf, ucs4);
5eef597e
MP
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
638static 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);
f47781d8
MP
652 if (r < 0)
653 return log_error_errno(r, "error: cannot write to PTY (%d): %m", r);
5eef597e
MP
654 }
655 }
656
657 ring_flush(&t->out_ring);
658 return 0;
659}
660
661static void terminal_discard_tmp(Terminal *t) {
662 assert(t);
663
664 ring_flush(&t->out_ring);
665}
666
667static 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
696static 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
f47781d8 707 log_error_errno(errno, "error: cannot read from TTY (%d): %m", -errno);
5eef597e
MP
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]);
f47781d8
MP
719 if (type < 0)
720 return log_error_errno(type, "error: term_parser_feed() (%d): %m", type);
5eef597e
MP
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
759static 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);
f47781d8
MP
769 if (r < 0)
770 return log_error_errno(r, "error: term_screen_feed_text() (%d): %m", r);
5eef597e
MP
771
772 terminal_dirty(t);
773 break;
774 }
775
776 return 0;
777}
778
779static 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
793static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) {
794 return 0;
795}
796
797static 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
814static 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);
f47781d8
MP
822 if (r < 0)
823 return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno);
5eef597e
MP
824
825 r = tcgetattr(out_fd, &out_attr);
f47781d8
MP
826 if (r < 0)
827 return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno);
5eef597e
MP
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) {
f47781d8 843 log_error_errno(r, "error: tcsetattr() (%d): %m", r);
5eef597e
MP
844 goto error;
845 }
846
847 r = tcsetattr(t->out_fd, TCSANOW, &out_attr);
848 if (r < 0) {
f47781d8 849 log_error_errno(r, "error: tcsetattr() (%d): %m", r);
5eef597e
MP
850 goto error;
851 }
852
853 r = sd_event_default(&t->event);
854 if (r < 0) {
f47781d8 855 log_error_errno(r, "error: sd_event_default() (%d): %m", r);
5eef597e
MP
856 goto error;
857 }
858
859 r = sigprocmask_many(SIG_BLOCK, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1);
860 if (r < 0) {
f47781d8 861 log_error_errno(r, "error: sigprocmask_many() (%d): %m", r);
5eef597e
MP
862 goto error;
863 }
864
865 r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL);
866 if (r < 0) {
f47781d8 867 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
868 goto error;
869 }
870
871 r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL);
872 if (r < 0) {
f47781d8 873 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
874 goto error;
875 }
876
877 r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL);
878 if (r < 0) {
f47781d8 879 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
880 goto error;
881 }
882
883 r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t);
884 if (r < 0) {
f47781d8 885 log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r);
5eef597e
MP
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) {
f47781d8 893 log_error_errno(r, "error: sd_event_add_time() (%d): %m", r);
5eef597e
MP
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) {
f47781d8 915 log_error_errno(r, "error: term_screen_resize() (%d): %m", r);
5eef597e
MP
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
926error:
927 terminal_free(t);
928 return r;
929}
930
931static 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);
f47781d8
MP
937 if (pid < 0)
938 return log_error_errno(pid, "error: cannot fork PTY (%d): %m", pid);
939 else if (pid == 0) {
5eef597e
MP
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);
f47781d8 951 log_error_errno(errno, "error: cannot exec %s (%d): %m", argv[0], -errno);
5eef597e
MP
952 _exit(1);
953 }
954
955 /* parent */
956
957 return sd_event_loop(t->event);
958}
959
960/*
961 * Context Handling
962 */
963
964int 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
976out:
977 if (r < 0)
f47781d8 978 log_error_errno(r, "error: terminal failed (%d): %m", r);
5eef597e
MP
979 terminal_free(t);
980 return -r;
981}