diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c index 803bdd90e2..2cec3d82cf 100644 --- a/src/basic/glyph-util.c +++ b/src/basic/glyph-util.c @@ -74,6 +74,9 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_SPARKLES] = "*", [SPECIAL_GLYPH_LOW_BATTERY] = "!", [SPECIAL_GLYPH_WARNING_SIGN] = "!", + [SPECIAL_GLYPH_RED_CIRCLE] = "o", + [SPECIAL_GLYPH_YELLOW_CIRCLE] = "o", + [SPECIAL_GLYPH_BLUE_CIRCLE] = "o", }, /* UTF-8 */ @@ -136,6 +139,10 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", [SPECIAL_GLYPH_COMPUTER_DISK] = u8"💽", [SPECIAL_GLYPH_WORLD] = u8"🌍", + + [SPECIAL_GLYPH_RED_CIRCLE] = u8"🔴", + [SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡", + [SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵", }, }; diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h index a7709976e1..e476fefe94 100644 --- a/src/basic/glyph-util.h +++ b/src/basic/glyph-util.h @@ -49,6 +49,9 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_WARNING_SIGN, SPECIAL_GLYPH_COMPUTER_DISK, SPECIAL_GLYPH_WORLD, + SPECIAL_GLYPH_RED_CIRCLE, + SPECIAL_GLYPH_YELLOW_CIRCLE, + SPECIAL_GLYPH_BLUE_CIRCLE, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 4822917f28..2aed260526 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -89,6 +89,10 @@ /* Set cursor to top left corner and clear screen */ #define ANSI_HOME_CLEAR "\x1B[H\x1B[2J" +/* Push/pop a window title off the stack of window titles */ +#define ANSI_WINDOW_TITLE_PUSH "\x1b[22;2t" +#define ANSI_WINDOW_TITLE_POP "\x1b[23;2t" + bool isatty_safe(int fd); int reset_terminal_fd(int fd, bool switch_to_text); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index e7dd2a370c..664637685b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -4429,6 +4429,22 @@ static int setup_notify_parent(sd_event *event, int fd, pid_t *inner_child_pid, return 0; } +static void set_window_title(PTYForward *f) { + _cleanup_free_ char *hn = NULL, *dot = NULL; + + assert(f); + + (void) gethostname_strict(&hn); + + if (emoji_enabled()) + dot = strjoin(special_glyph(SPECIAL_GLYPH_BLUE_CIRCLE), " "); + + if (hn) + (void) pty_forward_set_titlef(f, "%sContainer %s on %s", strempty(dot), arg_machine, hn); + else + (void) pty_forward_set_titlef(f, "%sContainer %s", strempty(dot), arg_machine); +} + static int merge_settings(Settings *settings, const char *path) { int rl; @@ -5361,6 +5377,7 @@ static int run_container( } else if (!isempty(arg_background)) (void) pty_forward_set_background_color(forward, arg_background); + set_window_title(forward); break; default: diff --git a/src/run/run.c b/src/run/run.c index a695fea0f0..b42ed44231 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -22,6 +22,7 @@ #include "exit-status.h" #include "fd-util.h" #include "format-util.h" +#include "hostname-util.h" #include "main-func.h" #include "parse-argument.h" #include "parse-util.h" @@ -188,6 +189,13 @@ static int help_sudo_mode(void) { return 0; } +static bool privileged_execution(void) { + if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) + return false; + + return !arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"); +} + static int add_timer_property(const char *name, const char *val) { char *p; @@ -941,7 +949,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { if (!arg_background && arg_stdio == ARG_STDIO_PTY) { double hue; - if (!arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0")) + if (privileged_execution()) hue = 0; /* red */ else hue = 60 /* yellow */; @@ -1584,6 +1592,26 @@ static int acquire_invocation_id(sd_bus *bus, const char *unit, sd_id128_t *ret) return !sd_id128_is_null(*ret); } +static void set_window_title(PTYForward *f) { + _cleanup_free_ char *hn = NULL, *cl = NULL, *dot = NULL; + assert(f); + + if (!arg_host) + (void) gethostname_strict(&hn); + + cl = strv_join(arg_cmdline, " "); + if (!cl) + return (void) log_oom(); + + if (emoji_enabled()) + dot = strjoin(special_glyph(privileged_execution() ? SPECIAL_GLYPH_RED_CIRCLE : SPECIAL_GLYPH_YELLOW_CIRCLE), " "); + + if (arg_host || hn) + (void) pty_forward_set_titlef(f, "%s%s on %s", strempty(dot), cl, arg_host ?: hn); + else + (void) pty_forward_set_titlef(f, "%s%s", strempty(dot), cl); +} + static int start_transient_service(sd_bus *bus) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1739,6 +1767,8 @@ static int start_transient_service(sd_bus *bus) { if (!isempty(arg_background)) (void) pty_forward_set_background_color(c.forward, arg_background); + + set_window_title(c.forward); } path = unit_dbus_path_from_name(service); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index c05ce79489..654e719180 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -34,6 +34,7 @@ typedef enum AnsiColorState { 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; @@ -91,6 +92,8 @@ struct PTYForward { char *background_color; AnsiColorState ansi_color_state; char *csi_sequence; + + char *title; }; #define ESCAPE_USEC (1*USEC_PER_SEC) @@ -114,9 +117,13 @@ 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()) + 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); } @@ -230,9 +237,7 @@ static char *background_color_sequence(PTYForward *f) { 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) { @@ -257,12 +262,33 @@ 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; @@ -363,37 +389,45 @@ static int pty_forward_ansi_process(PTYForward *f, size_t offset) { 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; } @@ -408,7 +442,7 @@ static int pty_forward_ansi_process(PTYForward *f, size_t offset) { /* 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 { @@ -427,38 +461,65 @@ static int pty_forward_ansi_process(PTYForward *f, size_t offset) { 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. */ - f->out_buffer_full = strlen(f->out_buffer); - f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer); + 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 pty_forward_done(f, log_oom()); + return log_oom(); f->out_buffer = p; f->out_buffer_size = MALLOC_SIZEOF_SAFE(p); @@ -481,10 +542,8 @@ 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, "read(): %m"); } else if (k == 0) { /* EOF on stdin */ f->stdin_readable = false; @@ -495,7 +554,7 @@ static int shovel(PTYForward *f) { /* 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; } @@ -513,10 +572,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); @@ -540,10 +597,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, "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; @@ -551,7 +606,7 @@ static int shovel(PTYForward *f) { 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"); } } @@ -566,10 +621,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, "write(): %m"); } else { @@ -602,6 +655,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); @@ -734,6 +799,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. */ @@ -818,6 +887,7 @@ PTYForward *pty_forward_free(PTYForward *f) { return NULL; pty_forward_disconnect(f); free(f->background_color); + free(f->title); return mfree(f); } @@ -958,3 +1028,35 @@ int pty_forward_set_background_color(PTYForward *f, const char *color) { 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); +} diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index bae8d3591e..f87becd030 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -10,13 +10,17 @@ typedef struct PTYForward PTYForward; typedef enum PTYForwardFlags { - PTY_FORWARD_READ_ONLY = 1, + /* Only output to STDOUT, never try to read from STDIN */ + PTY_FORWARD_READ_ONLY = 1 << 0, /* Continue reading after hangup? */ - PTY_FORWARD_IGNORE_VHANGUP = 2, + PTY_FORWARD_IGNORE_VHANGUP = 1 << 1, /* Continue reading after hangup but only if we never read anything else? */ - PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, + PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 1 << 2, + + /* Don't tint the background, or set window title */ + PTY_FORWARD_DUMB_TERMINAL = 1 << 3, } PTYForwardFlags; typedef int (*PTYForwardHandler)(PTYForward *f, int rcode, void *userdata); @@ -40,5 +44,7 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority); int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height); int pty_forward_set_background_color(PTYForward *f, const char *color); +int pty_forward_set_title(PTYForward *f, const char *title); +int pty_forward_set_titlef(PTYForward *f, const char *format, ...) _printf_(2,3); DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 39f71c67d3..dd9a8134bf 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -82,7 +82,7 @@ TEST(keymaps) { #define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x)) TEST(dump_special_glyphs) { - assert_cc(SPECIAL_GLYPH_WORLD + 1 == _SPECIAL_GLYPH_MAX); + assert_cc(SPECIAL_GLYPH_BLUE_CIRCLE + 1 == _SPECIAL_GLYPH_MAX); log_info("is_locale_utf8: %s", yes_no(is_locale_utf8())); @@ -127,6 +127,9 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_WARNING_SIGN); dump_glyph(SPECIAL_GLYPH_COMPUTER_DISK); dump_glyph(SPECIAL_GLYPH_WORLD); + dump_glyph(SPECIAL_GLYPH_RED_CIRCLE); + dump_glyph(SPECIAL_GLYPH_YELLOW_CIRCLE); + dump_glyph(SPECIAL_GLYPH_BLUE_CIRCLE); } DEFINE_TEST_MAIN(LOG_INFO);