ANSI_COLOR_STATE_ESC,
ANSI_COLOR_STATE_CSI_SEQUENCE,
ANSI_COLOR_STATE_NEWLINE,
+ ANSI_COLOR_STATE_CARRIAGE_RETURN,
_ANSI_COLOR_STATE_MAX,
_ANSI_COLOR_STATE_INVALID = -EINVAL,
} AnsiColorState;
char *background_color;
AnsiColorState ansi_color_state;
char *csi_sequence;
+
+ char *title;
};
#define ESCAPE_USEC (1*USEC_PER_SEC)
/* STDIN/STDOUT should not be non-blocking normally, so let's reset it */
(void) fd_nonblock(f->output_fd, false);
- if (colors_enabled())
+ 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);
}
assert(f);
assert(f->background_color);
- /* This sets the background color to the desired one, and erase the rest of the line with it */
-
- return strjoin("\x1B[", f->background_color, "m", ANSI_ERASE_TO_END_OF_LINE);
+ return strjoin("\x1B[", f->background_color, "m");
}
static int insert_string(PTYForward *f, size_t offset, const char *s) {
return (int) l;
}
-static int insert_erase_newline(PTYForward *f, size_t offset) {
+static int insert_newline_color_erase(PTYForward *f, size_t offset) {
+ _cleanup_free_ char *s = NULL;
+
+ assert(f);
+ assert(f->background_color);
+
+ /* When we see a newline (ASCII 10) then this sets the background color to the desired one, and erase the rest
+ * of the line with it */
+
+ s = background_color_sequence(f);
+ if (!s)
+ return -ENOMEM;
+
+ if (!strextend(&s, ANSI_ERASE_TO_END_OF_LINE))
+ return -ENOMEM;
+
+ return insert_string(f, offset, s);
+}
+
+static int insert_carriage_return_color(PTYForward *f, size_t offset) {
_cleanup_free_ char *s = NULL;
assert(f);
assert(f->background_color);
+ /* When we see a carriage return (ASCII 13) this this sets only the background */
+
s = background_color_sequence(f);
if (!s)
return -ENOMEM;
if (!f->background_color)
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:
- if (c == '\n')
- f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE;
- if (c == 0x1B) /* ESC */
- f->ansi_color_state = ANSI_COLOR_STATE_ESC;
break;
case ANSI_COLOR_STATE_NEWLINE: {
/* Immediately after a newline insert an ANSI sequence to erase the line with a background color */
- r = insert_erase_newline(f, i);
+ r = insert_newline_color_erase(f, i);
if (r < 0)
return r;
i += r;
+ break;
+ }
- f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ case ANSI_COLOR_STATE_CARRIAGE_RETURN: {
+ /* Immediately after a carriage return insert an ANSI sequence set the background color back */
+
+ r = insert_carriage_return_color(f, i);
+ if (r < 0)
+ return r;
+
+ i += r;
break;
}
case ANSI_COLOR_STATE_ESC: {
- if (c == '[')
+ if (c == '[') {
f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE;
- else
- f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ continue;
+ }
break;
}
/* Safety check: lets not accept unbounded CSI sequences */
f->csi_sequence = mfree(f->csi_sequence);
- f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
+ break;
} else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c)))
return -ENOMEM;
} else {
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
}
- break;
+ 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 shovel(PTYForward *f) {
+static int do_shovel(PTYForward *f) {
ssize_t k;
int r;
assert(f);
- if (f->out_buffer_size == 0 && f->background_color) {
- /* Erase the first line when we start */
- f->out_buffer = background_color_sequence(f);
- if (!f->out_buffer)
- return pty_forward_done(f, log_oom());
+ 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();
- f->out_buffer_full = strlen(f->out_buffer);
- f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
+ 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 pty_forward_done(f, log_oom());
+ return log_oom();
f->out_buffer = p;
f->out_buffer_size = MALLOC_SIZEOF_SAFE(p);
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, "read(): %m");
} else if (k == 0) {
/* EOF on stdin */
f->stdin_readable = false;
/* 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;
}
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);
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
+ return log_error_errno(errno, "read(): %m");
} else {
f->read_from_master = true;
size_t scan_index = f->out_buffer_full;
r = pty_forward_ansi_process(f, scan_index);
if (r < 0)
- return pty_forward_done(f, log_error_errno(r, "Failed to scan for ANSI sequences: %m"));
+ return log_error_errno(r, "Failed to scan for ANSI sequences: %m");
}
}
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, "write(): %m");
} else {
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);
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. */
return NULL;
pty_forward_disconnect(f);
free(f->background_color);
+ free(f->title);
return mfree(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);
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ r = vasprintf(&title, format, ap);
+ REENABLE_WARNING;
+ va_end(ap);
+ if (r < 0)
+ return -ENOMEM;
+
+ return free_and_replace(f->title, title);
+}