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