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