]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31162 from poettering/tint-tweaks
authorLuca Boccassi <bluca@debian.org>
Mon, 12 Feb 2024 16:47:09 +0000 (16:47 +0000)
committerGitHub <noreply@github.com>
Mon, 12 Feb 2024 16:47:09 +0000 (16:47 +0000)
ptyfwd: some tweaks to terminal handling

src/basic/glyph-util.c
src/basic/glyph-util.h
src/basic/terminal-util.h
src/nspawn/nspawn.c
src/run/run.c
src/shared/ptyfwd.c
src/shared/ptyfwd.h
src/test/test-locale-util.c

index 803bdd90e24e3250b7c4fc607b1ca1c36ace1720..2cec3d82cf0b140d06e164d043e7d58c3b54cd7d 100644 (file)
@@ -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"🔵",
                 },
         };
 
index a7709976e1d422b4e01d8a902a0ed2d4aa804615..e476fefe943c5bbf1c575da25d6fa0b46785a209 100644 (file)
@@ -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;
index 4822917f28fe38155abaea1e06f024e1f1a6a65b..2aed260526a66fed4ec6b4e00e9ee2245cf348e1 100644 (file)
 /* 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);
index e7dd2a370c19460ae528d9173ceaeff02d080213..664637685bb6e7ef11dd21686a1a2baa23ebc25a 100644 (file)
@@ -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:
index a695fea0f07b1405d1e4103f34e0a9e6b2cf3c68..b42ed44231053c34826c52fba2ff16a6b04c3da6 100644 (file)
@@ -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);
index c05ce7948926e9748bdcebfb3e60911587db1faa..654e719180a25ae8dbe7f2c0161e120307b951f2 100644 (file)
@@ -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. */
+
+                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);
@@ -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);
+}
index bae8d3591e44834b59259b568178291f90418cc4..f87becd0308b4f1afdf6c7121753d6cacaf1be39 100644 (file)
 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);
index 39f71c67d369113b9bfea505c3014629d52ec248..dd9a8134bf2e5ca0730c14e6659acac0be83946c 100644 (file)
@@ -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);