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