summaryrefslogtreecommitdiffstats
path: root/src/shared/ptyfwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/ptyfwd.c')
-rw-r--r--src/shared/ptyfwd.c516
1 files changed, 472 insertions, 44 deletions
diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c
index 195e603..998ce96 100644
--- a/src/shared/ptyfwd.c
+++ b/src/shared/ptyfwd.c
@@ -18,13 +18,28 @@
#include "alloc-util.h"
#include "errno-util.h"
+#include "extract-word.h"
#include "fd-util.h"
+#include "io-util.h"
#include "log.h"
#include "macro.h"
#include "ptyfwd.h"
+#include "stat-util.h"
+#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
+typedef enum AnsiColorState {
+ ANSI_COLOR_STATE_TEXT,
+ ANSI_COLOR_STATE_ESC,
+ ANSI_COLOR_STATE_CSI_SEQUENCE,
+ ANSI_COLOR_STATE_OSC_SEQUENCE,
+ ANSI_COLOR_STATE_NEWLINE,
+ ANSI_COLOR_STATE_CARRIAGE_RETURN,
+ _ANSI_COLOR_STATE_MAX,
+ _ANSI_COLOR_STATE_INVALID = -EINVAL,
+} AnsiColorState;
+
struct PTYForward {
sd_event *event;
@@ -65,7 +80,8 @@ struct PTYForward {
bool last_char_set:1;
char last_char;
- char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
+ char in_buffer[LINE_MAX], *out_buffer;
+ size_t out_buffer_size;
size_t in_buffer_full, out_buffer_full;
usec_t escape_timestamp;
@@ -73,6 +89,14 @@ struct PTYForward {
PTYForwardHandler handler;
void *userdata;
+
+ char *background_color;
+ AnsiColorState ansi_color_state;
+ char *csi_sequence;
+ char *osc_sequence;
+
+ char *title; /* Window title to show by default */
+ char *title_prefix; /* If terminal client overrides window title, prefix this string */
};
#define ESCAPE_USEC (1*USEC_PER_SEC)
@@ -95,6 +119,14 @@ static void pty_forward_disconnect(PTYForward *f) {
/* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
(void) fd_nonblock(f->output_fd, false);
+
+ if (colors_enabled()) {
+ (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX);
+
+ if (f->title)
+ (void) loop_write(f->output_fd, ANSI_WINDOW_TITLE_POP, SIZE_MAX);
+ }
+
if (f->close_output_fd)
f->output_fd = safe_close(f->output_fd);
}
@@ -109,6 +141,15 @@ static void pty_forward_disconnect(PTYForward *f) {
}
f->saved_stdout = f->saved_stdin = false;
+
+ f->out_buffer = mfree(f->out_buffer);
+ f->out_buffer_size = 0;
+ f->out_buffer_full = 0;
+ f->in_buffer_full = 0;
+
+ f->csi_sequence = mfree(f->csi_sequence);
+ f->osc_sequence = mfree(f->osc_sequence);
+ f->ansi_color_state = _ANSI_COLOR_STATE_INVALID;
}
static int pty_forward_done(PTYForward *f, int rcode) {
@@ -142,7 +183,7 @@ static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
if (*p == 0x1D) {
usec_t nw = now(CLOCK_MONOTONIC);
- if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
+ if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) {
f->escape_timestamp = nw;
f->escape_counter = 1;
} else {
@@ -196,11 +237,321 @@ static bool drained(PTYForward *f) {
return true;
}
-static int shovel(PTYForward *f) {
+static char *background_color_sequence(PTYForward *f) {
+ assert(f);
+ assert(f->background_color);
+
+ return strjoin("\x1B[", f->background_color, "m");
+}
+
+static int insert_string(PTYForward *f, size_t offset, const char *s) {
+ assert(f);
+ assert(offset <= f->out_buffer_full);
+ assert(s);
+
+ size_t l = strlen(s);
+ assert(l <= INT_MAX); /* Make sure we can still return this */
+
+ void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX));
+ if (!p)
+ return -ENOMEM;
+
+ f->out_buffer = p;
+ f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
+
+ memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset);
+ memcpy(f->out_buffer + offset, s, l);
+ f->out_buffer_full += l;
+
+ return (int) l;
+}
+
+static int insert_background_color(PTYForward *f, size_t offset) {
+ _cleanup_free_ char *s = NULL;
+
+ assert(f);
+
+ if (!f->background_color)
+ return 0;
+
+ s = background_color_sequence(f);
+ if (!s)
+ return -ENOMEM;
+
+ return insert_string(f, offset, s);
+}
+
+static int is_csi_background_reset_sequence(const char *seq) {
+ enum {
+ COLOR_TOKEN_NO,
+ COLOR_TOKEN_START,
+ COLOR_TOKEN_8BIT,
+ COLOR_TOKEN_24BIT_R,
+ COLOR_TOKEN_24BIT_G,
+ COLOR_TOKEN_24BIT_B,
+ } token_state = COLOR_TOKEN_NO;
+
+ bool b = false;
+ int r;
+
+ assert(seq);
+
+ /* This parses CSI "m" sequences, and determines if they reset the background color. If so returns
+ * 1. This can then be used to insert another sequence that sets the color to the desired
+ * replacement. */
+
+ for (;;) {
+ _cleanup_free_ char *token = NULL;
+
+ r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ switch (token_state) {
+
+ case COLOR_TOKEN_NO:
+
+ if (STR_IN_SET(token, "", "0", "00", "49"))
+ b = true; /* These tokens set the background back to normal */
+ else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48"))
+ b = false; /* And these tokens set them to something other than normal */
+
+ if (STR_IN_SET(token, "38", "48", "58"))
+ token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */
+ break;
+
+ case COLOR_TOKEN_START:
+
+ if (STR_IN_SET(token, "5", "05"))
+ token_state = COLOR_TOKEN_8BIT; /* 8bit color */
+ else if (STR_IN_SET(token, "2", "02"))
+ token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */
+ else
+ token_state = COLOR_TOKEN_NO; /* something weird? */
+ break;
+
+ case COLOR_TOKEN_24BIT_R:
+ token_state = COLOR_TOKEN_24BIT_G;
+ break;
+
+ case COLOR_TOKEN_24BIT_G:
+ token_state = COLOR_TOKEN_24BIT_B;
+ break;
+
+ case COLOR_TOKEN_8BIT:
+ case COLOR_TOKEN_24BIT_B:
+ token_state = COLOR_TOKEN_NO;
+ break;
+ }
+ }
+
+ return b;
+}
+
+static int insert_background_fix(PTYForward *f, size_t offset) {
+ assert(f);
+
+ if (!f->background_color)
+ return 0;
+
+ if (!is_csi_background_reset_sequence(strempty(f->csi_sequence)))
+ return 0;
+
+ _cleanup_free_ char *s = NULL;
+ s = strjoin(";", f->background_color);
+ if (!s)
+ return -ENOMEM;
+
+ return insert_string(f, offset, s);
+}
+
+static int insert_window_title_fix(PTYForward *f, size_t offset) {
+ assert(f);
+
+ if (!f->title_prefix)
+ return 0;
+
+ if (!f->osc_sequence)
+ return 0;
+
+ const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence*/
+ if (!t)
+ return 0;
+
+ _cleanup_free_ char *joined = strjoin("\x1b]0;", f->title_prefix, t, "\a");
+ if (!joined)
+ return -ENOMEM;
+
+ return insert_string(f, offset, joined);
+}
+
+static int pty_forward_ansi_process(PTYForward *f, size_t offset) {
+ int r;
+
+ assert(f);
+ assert(offset <= f->out_buffer_full);
+
+ if (!f->background_color && !f->title_prefix)
+ return 0;
+
+ if (FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL))
+ return 0;
+
+ for (size_t i = offset; i < f->out_buffer_full; i++) {
+ char c = f->out_buffer[i];
+
+ switch (f->ansi_color_state) {
+
+ case ANSI_COLOR_STATE_TEXT:
+ break;
+
+ case ANSI_COLOR_STATE_NEWLINE:
+ case ANSI_COLOR_STATE_CARRIAGE_RETURN:
+ /* Immediately after a newline (ASCII 10) or carriage return (ASCII 13) insert an
+ * ANSI sequence set the background color back. */
+
+ r = insert_background_color(f, i);
+ if (r < 0)
+ return r;
+
+ i += r;
+ break;
+
+ case ANSI_COLOR_STATE_ESC:
+
+ if (c == '[') {
+ f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE;
+ continue;
+ } else if (c == ']') {
+ f->ansi_color_state = ANSI_COLOR_STATE_OSC_SEQUENCE;
+ continue;
+ }
+ break;
+
+ case ANSI_COLOR_STATE_CSI_SEQUENCE:
+
+ if (c >= 0x20 && c <= 0x3F) {
+ /* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and
+ * 0x30…0x3F) then we are still in the CSI sequence */
+
+ if (strlen_ptr(f->csi_sequence) >= 64) {
+ /* Safety check: lets not accept unbounded CSI sequences */
+
+ f->csi_sequence = mfree(f->csi_sequence);
+ break;
+ } else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c)))
+ return -ENOMEM;
+ } else {
+ /* Otherwise, the CSI sequence is over */
+
+ if (c == 'm') {
+ /* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */
+ r = insert_background_fix(f, i);
+ if (r < 0)
+ return r;
+
+ i += r;
+ }
+
+ f->csi_sequence = mfree(f->csi_sequence);
+ f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ }
+ continue;
+
+ case ANSI_COLOR_STATE_OSC_SEQUENCE:
+
+ if ((uint8_t) c >= ' ') {
+ if (strlen_ptr(f->osc_sequence) >= 64) {
+ /* Safety check: lets not accept unbounded OSC sequences */
+ f->osc_sequence = mfree(f->osc_sequence);
+ break;
+ } else if (!strextend(&f->osc_sequence, CHAR_TO_STR(c)))
+ return -ENOMEM;
+ } else {
+ /* Otherwise, the OSC sequence is over
+ *
+ * There are two allowed ways to end an OSC sequence:
+ * BEL '\x07'
+ * String Terminator (ST): <Esc>\ - "\x1b\x5c"
+ * since we cannot lookahead to see if the Esc is followed by a \
+ * we cut a corner here and assume it will be \. */
+
+ if (IN_SET(c, '\x07', '\x1b')) {
+ r = insert_window_title_fix(f, i+1);
+ if (r < 0)
+ return r;
+
+ i += r;
+ }
+
+ f->osc_sequence = mfree(f->osc_sequence);
+ f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ }
+ continue;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (c == '\n')
+ f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE;
+ else if (c == '\r')
+ f->ansi_color_state = ANSI_COLOR_STATE_CARRIAGE_RETURN;
+ else if (c == 0x1B) /* ESC */
+ f->ansi_color_state = ANSI_COLOR_STATE_ESC;
+ else
+ f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ }
+
+ return 0;
+}
+
+static int do_shovel(PTYForward *f) {
ssize_t k;
+ int r;
assert(f);
+ if (f->out_buffer_size == 0 && !FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) {
+ /* If the output hasn't been allocated yet, we are at the beginning of the first
+ * shovelling. Hence, possibly send some initial ANSI sequences. But do so only if we are
+ * talking to an actual TTY. */
+
+ if (f->background_color) {
+ /* Erase the first line when we start */
+ f->out_buffer = background_color_sequence(f);
+ if (!f->out_buffer)
+ return log_oom();
+
+ if (!strextend(&f->out_buffer, ANSI_ERASE_TO_END_OF_LINE))
+ return log_oom();
+ }
+
+ if (f->title) {
+ if (!strextend(&f->out_buffer,
+ ANSI_WINDOW_TITLE_PUSH
+ "\x1b]2;", f->title, "\a"))
+ return log_oom();
+ }
+
+ if (f->out_buffer) {
+ f->out_buffer_full = strlen(f->out_buffer);
+ f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
+ }
+ }
+
+ if (f->out_buffer_size < LINE_MAX) {
+ /* Make sure we always have room for at least one "line" */
+ void *p = realloc(f->out_buffer, LINE_MAX);
+ if (!p)
+ return log_oom();
+
+ f->out_buffer = p;
+ f->out_buffer_size = MALLOC_SIZEOF_SAFE(p);
+ }
+
while ((f->stdin_readable && f->in_buffer_full <= 0) ||
(f->master_writable && f->in_buffer_full > 0) ||
(f->master_readable && f->out_buffer_full <= 0) ||
@@ -218,21 +569,19 @@ static int shovel(PTYForward *f) {
f->stdin_hangup = true;
f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
- } else {
- log_error_errno(errno, "read(): %m");
- return pty_forward_done(f, -errno);
- }
+ } else
+ return log_error_errno(errno, "Failed to read from pty input fd: %m");
} else if (k == 0) {
/* EOF on stdin */
f->stdin_readable = false;
f->stdin_hangup = true;
f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
- } else {
+ } else {
/* Check if ^] has been pressed three times within one second. If we get this we quite
* immediately. */
if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
- return pty_forward_done(f, -ECANCELED);
+ return -ECANCELED;
f->in_buffer_full += (size_t) k;
}
@@ -250,10 +599,8 @@ static int shovel(PTYForward *f) {
f->master_hangup = true;
f->master_event_source = sd_event_source_unref(f->master_event_source);
- } else {
- log_error_errno(errno, "write(): %m");
- return pty_forward_done(f, -errno);
- }
+ } else
+ return log_error_errno(errno, "write(): %m");
} else {
assert(f->in_buffer_full >= (size_t) k);
memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k);
@@ -261,17 +608,14 @@ static int shovel(PTYForward *f) {
}
}
- if (f->master_readable && f->out_buffer_full < LINE_MAX) {
+ if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) {
- k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
+ k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full);
if (k < 0) {
- /* Note that EIO on the master device
- * might be caused by vhangup() or
- * temporary closing of everything on
- * the other side, we treat it like
- * EAGAIN here and try again, unless
- * ignore_vhangup is off. */
+ /* Note that EIO on the master device might be caused by vhangup() or
+ * temporary closing of everything on the other side, we treat it like EAGAIN
+ * here and try again, unless ignore_vhangup is off. */
if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
f->master_readable = false;
@@ -280,13 +624,16 @@ static int shovel(PTYForward *f) {
f->master_hangup = true;
f->master_event_source = sd_event_source_unref(f->master_event_source);
- } else {
- log_error_errno(errno, "read(): %m");
- return pty_forward_done(f, -errno);
- }
- } else {
+ } else
+ return log_error_errno(errno, "Failed to read from pty master fd: %m");
+ } else {
f->read_from_master = true;
+ size_t scan_index = f->out_buffer_full;
f->out_buffer_full += (size_t) k;
+
+ r = pty_forward_ansi_process(f, scan_index);
+ if (r < 0)
+ return log_error_errno(r, "Failed to scan for ANSI sequences: %m");
}
}
@@ -301,10 +648,8 @@ static int shovel(PTYForward *f) {
f->stdout_writable = false;
f->stdout_hangup = true;
f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
- } else {
- log_error_errno(errno, "write(): %m");
- return pty_forward_done(f, -errno);
- }
+ } else
+ return log_error_errno(errno, "Failed to write to pty output fd: %m");
} else {
@@ -337,6 +682,18 @@ static int shovel(PTYForward *f) {
return 0;
}
+static int shovel(PTYForward *f) {
+ int r;
+
+ assert(f);
+
+ r = do_shovel(f);
+ if (r < 0)
+ return pty_forward_done(f, r);
+
+ return r;
+}
+
static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
PTYForward *f = ASSERT_PTR(userdata);
@@ -406,11 +763,14 @@ int pty_forward_new(
struct winsize ws;
int r;
+ assert(master >= 0);
+ assert(ret);
+
f = new(PTYForward, 1);
if (!f)
return -ENOMEM;
- *f = (struct PTYForward) {
+ *f = (PTYForward) {
.flags = flags,
.master = -EBADF,
.input_fd = -EBADF,
@@ -430,13 +790,17 @@ int pty_forward_new(
else {
/* If we shall be invoked in interactive mode, let's switch on non-blocking mode, so that we
* never end up staving one direction while we block on the other. However, let's be careful
- * here and not turn on O_NONBLOCK for stdin/stdout directly, but of re-opened copies of
+ * here and not turn on O_NONBLOCK for stdin/stdout directly, but of reopened copies of
* them. This has two advantages: when we are killed abruptly the stdin/stdout fds won't be
* left in O_NONBLOCK state for the next process using them. In addition, if some process
* running in the background wants to continue writing to our stdout it can do so without
- * being confused by O_NONBLOCK. */
+ * being confused by O_NONBLOCK.
+ * We keep O_APPEND (if present) on the output FD and (try to) keep current file position on
+ * both input and output FD (principle of least surprise).
+ */
- f->input_fd = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ f->input_fd = fd_reopen_propagate_append_and_position(
+ STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (f->input_fd < 0) {
/* Handle failures gracefully, after all certain fd types cannot be reopened
* (sockets, …) */
@@ -450,7 +814,8 @@ int pty_forward_new(
} else
f->close_input_fd = true;
- f->output_fd = fd_reopen(STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ f->output_fd = fd_reopen_propagate_append_and_position(
+ STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (f->output_fd < 0) {
log_debug_errno(f->output_fd, "Failed to reopen stdout, using original fd: %m");
@@ -469,6 +834,10 @@ int pty_forward_new(
f->master = master;
+ /* Disable color/window title setting unless we talk to a good TTY */
+ if (!isatty_safe(f->output_fd) || get_color_mode() == COLOR_OFF)
+ f->flags |= PTY_FORWARD_DUMB_TERMINAL;
+
if (ioctl(f->output_fd, TIOCGWINSZ, &ws) < 0)
/* If we can't get the resolution from the output fd, then use our internal, regular width/height,
* i.e. something derived from $COLUMNS and $LINES if set. */
@@ -479,9 +848,16 @@ int pty_forward_new(
(void) ioctl(master, TIOCSWINSZ, &ws);
- if (!(flags & PTY_FORWARD_READ_ONLY)) {
+ if (!FLAGS_SET(flags, PTY_FORWARD_READ_ONLY)) {
+ bool same;
+
assert(f->input_fd >= 0);
+ r = fd_inode_same(f->input_fd, f->output_fd);
+ if (r < 0)
+ return r;
+ same = r > 0;
+
if (tcgetattr(f->input_fd, &f->saved_stdin_attr) >= 0) {
struct termios raw_stdin_attr;
@@ -489,11 +865,14 @@ int pty_forward_new(
raw_stdin_attr = f->saved_stdin_attr;
cfmakeraw(&raw_stdin_attr);
- raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
- tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr);
+
+ if (!same)
+ raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
+
+ (void) tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr);
}
- if (tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) {
+ if (!same && tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) {
struct termios raw_stdout_attr;
f->saved_stdout = true;
@@ -502,7 +881,7 @@ int pty_forward_new(
cfmakeraw(&raw_stdout_attr);
raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
- tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr);
+ (void) tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr);
}
r = sd_event_add_io(f->event, &f->stdin_event_source, f->input_fd, EPOLLIN|EPOLLET, on_stdin_event, f);
@@ -540,7 +919,14 @@ int pty_forward_new(
}
PTYForward *pty_forward_free(PTYForward *f) {
+ if (!f)
+ return NULL;
+
pty_forward_disconnect(f);
+ free(f->background_color);
+ free(f->title);
+ free(f->title_prefix);
+
return mfree(f);
}
@@ -560,15 +946,14 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
assert(f);
- if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
+ if (FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP) == b)
return 0;
SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
if (!ignore_vhangup(f)) {
- /* We shall now react to vhangup()s? Let's check
- * immediately if we might be in one */
+ /* We shall now react to vhangup()s? Let's check immediately if we might be in one. */
f->master_readable = true;
r = shovel(f);
@@ -582,7 +967,7 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
bool pty_forward_get_ignore_vhangup(PTYForward *f) {
assert(f);
- return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
+ return FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP);
}
bool pty_forward_is_done(PTYForward *f) {
@@ -614,6 +999,7 @@ bool pty_forward_drain(PTYForward *f) {
int pty_forward_set_priority(PTYForward *f, int64_t priority) {
int r;
+
assert(f);
if (f->stdin_event_source) {
@@ -675,3 +1061,45 @@ int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height)
return 0;
}
+
+int pty_forward_set_background_color(PTYForward *f, const char *color) {
+ assert(f);
+
+ return free_and_strdup(&f->background_color, color);
+}
+
+int pty_forward_set_title(PTYForward *f, const char *title) {
+ assert(f);
+
+ /* Refuse accepting a title when we already started shoveling */
+ if (f->out_buffer_size > 0)
+ return -EBUSY;
+
+ return free_and_strdup(&f->title, title);
+}
+
+int pty_forward_set_titlef(PTYForward *f, const char *format, ...) {
+ _cleanup_free_ char *title = NULL;
+ va_list ap;
+ int r;
+
+ assert(f);
+ assert(format);
+
+ if (f->out_buffer_size > 0)
+ return -EBUSY;
+
+ va_start(ap, format);
+ r = vasprintf(&title, format, ap);
+ va_end(ap);
+ if (r < 0)
+ return -ENOMEM;
+
+ return free_and_replace(f->title, title);
+}
+
+int pty_forward_set_title_prefix(PTYForward *f, const char *title_prefix) {
+ assert(f);
+
+ return free_and_strdup(&f->title_prefix, title_prefix);
+}