]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
terminal-util: add helper that determines terminal default bg color
authorLennart Poettering <lennart@poettering.net>
Wed, 20 Dec 2023 11:02:01 +0000 (12:02 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 21 Dec 2023 18:15:01 +0000 (19:15 +0100)
src/basic/terminal-util.c
src/basic/terminal-util.h
src/test/test-terminal-util.c

index 63aca3684f25b916c44093633896311742133e02..b740b49775f8789311ba72f3c0d81fd62246e4d1 100644 (file)
@@ -27,6 +27,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "hexdecoct.h"
 #include "inotify-util.h"
 #include "io-util.h"
 #include "log.h"
@@ -1559,3 +1560,210 @@ void termios_disable_echo(struct termios *termios) {
         termios->c_cc[VMIN] = 1;
         termios->c_cc[VTIME] = 0;
 }
+
+typedef enum BackgroundColorState {
+        BACKGROUND_TEXT,
+        BACKGROUND_ESCAPE,
+        BACKGROUND_BRACKET,
+        BACKGROUND_FIRST_ONE,
+        BACKGROUND_SECOND_ONE,
+        BACKGROUND_SEMICOLON,
+        BACKGROUND_R,
+        BACKGROUND_G,
+        BACKGROUND_B,
+        BACKGROUND_RED,
+        BACKGROUND_GREEN,
+        BACKGROUND_BLUE,
+} BackgroundColorState;
+
+typedef struct BackgroundColorContext {
+        BackgroundColorState state;
+        uint32_t red, green, blue;
+        unsigned red_bits, green_bits, blue_bits;
+} BackgroundColorContext;
+
+static int scan_background_color_response(
+                BackgroundColorContext *context,
+                const char *buf,
+                size_t size) {
+
+        assert(context);
+        assert(buf || size == 0);
+
+        for (size_t i = 0; i < size; i++) {
+                char c = buf[i];
+
+                switch (context->state) {
+
+                case BACKGROUND_TEXT:
+                        context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_ESCAPE:
+                        context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_BRACKET:
+                        context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_FIRST_ONE:
+                        context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_SECOND_ONE:
+                        context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_SEMICOLON:
+                        context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_R:
+                        context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_G:
+                        context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_B:
+                        context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
+                        break;
+
+                case BACKGROUND_RED:
+                        if (c == '/')
+                                context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
+                        else {
+                                int d = unhexchar(c);
+                                if (d < 0 || context->red_bits >= sizeof(context->red)*8)
+                                        context->state = BACKGROUND_TEXT;
+                                else {
+                                        context->red = (context->red << 4) | d;
+                                        context->red_bits += 4;
+                                }
+                        }
+                        break;
+
+                case BACKGROUND_GREEN:
+                        if (c == '/')
+                                context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
+                        else {
+                                int d = unhexchar(c);
+                                if (d < 0 || context->green_bits >= sizeof(context->green)*8)
+                                        context->state = BACKGROUND_TEXT;
+                                else {
+                                        context->green = (context->green << 4) | d;
+                                        context->green_bits += 4;
+                                }
+                        }
+                        break;
+
+                case BACKGROUND_BLUE:
+                        if (c == '\x07') {
+                                if (context->blue_bits > 0)
+                                        return 1; /* success! */
+
+                                context->state = BACKGROUND_TEXT;
+                        } else {
+                                int d = unhexchar(c);
+                                if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
+                                        context->state = BACKGROUND_TEXT;
+                                else {
+                                        context->blue = (context->blue << 4) | d;
+                                        context->blue_bits += 4;
+                                }
+                        }
+                        break;
+                }
+
+                /* Reset any colors we might have picked up */
+                if (context->state == BACKGROUND_TEXT) {
+                        /* reset color */
+                        context->red = context->green = context->blue = 0;
+                        context->red_bits = context->green_bits = context->blue_bits = 0;
+                }
+        }
+
+        return 0; /* all good, but not enough data yet */
+}
+
+int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
+        int r;
+
+        assert(ret_red);
+        assert(ret_green);
+        assert(ret_blue);
+
+        if (!colors_enabled())
+                return -EOPNOTSUPP;
+
+        if (isatty(STDOUT_FILENO) < 1 || isatty(STDIN_FILENO) < 1)
+                return -EOPNOTSUPP;
+
+        if (streq_ptr(getenv("TERM"), "linux")) {
+                /* Linux console is black */
+                *ret_red = *ret_green = *ret_blue = 0.0;
+                return 0;
+        }
+
+        struct termios old_termios;
+        if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
+                return -errno;
+
+        struct termios new_termios = old_termios;
+        termios_disable_echo(&new_termios);
+
+        if (tcsetattr(STDOUT_FILENO, TCSADRAIN, &new_termios) < 0)
+                return -errno;
+
+        r = loop_write(STDOUT_FILENO, "\x1B]11;?\x07", SIZE_MAX);
+        if (r < 0)
+                goto finish;
+
+        usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC);
+        char buf[256];
+        size_t buf_full = 0;
+        BackgroundColorContext context = {};
+
+        for (;;) {
+                usec_t n = now(CLOCK_MONOTONIC);
+
+                if (n >= end) {
+                        r = -EOPNOTSUPP;
+                        goto finish;
+                }
+
+                r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n));
+                if (r < 0)
+                        goto finish;
+
+                ssize_t l;
+                l = read(STDIN_FILENO, buf, sizeof(buf) - buf_full);
+                if (l < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                buf_full += l;
+                assert(buf_full <= sizeof(buf));
+
+                r = scan_background_color_response(&context, buf, buf_full);
+                if (r < 0)
+                        goto finish;
+                if (r > 0) {
+                        assert(context.red_bits > 0);
+                        *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
+                        assert(context.green_bits > 0);
+                        *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
+                        assert(context.blue_bits > 0);
+                        *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
+                        r = 0;
+                        goto finish;
+                }
+        }
+
+finish:
+        (void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios);
+        return r;
+}
index d7537c23bff800278d3173f1458520b447badb3b..1ec057c2dd5be75efb345df23968b04ca22a10f7 100644 (file)
@@ -288,3 +288,5 @@ static inline const char* ansi_highlight_green_red(bool b) {
 #define TTY_MODE 0620
 
 void termios_disable_echo(struct termios *termios);
+
+int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue);
index a2b7101573404f490c0e9eea325e24d876cdbcd7..0b46cad9827a40b81adf46964c01b8d719d1d6e0 100644 (file)
@@ -165,4 +165,15 @@ TEST(get_ctty) {
                 log_notice("Not invoked with stdin == ctty, cutting get_ctty() test short");
 }
 
+TEST(get_default_background_color) {
+        double red, green, blue;
+        int r;
+
+        r = get_default_background_color(&red, &green, &blue);
+        if (r < 0)
+                log_notice_errno(r, "Can't get terminal default background color: %m");
+        else
+                log_notice("R=%g G=%g B=%g", red, green, blue);
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);