From ed41bffc287c8f9f2bd442d7ab48ff394ace94a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 Apr 2021 22:05:50 +0200 Subject: [PATCH 1/7] osc: add support for URxvt extension to include alpha in OSC-11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are two variants, either using a ‘[percent]’ prefix for legacy colors, or using ‘rgba:r/g/b/a’ instead of ‘rgb:r/g/b’. Closes #436 --- CHANGELOG.md | 5 +++ osc.c | 124 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46af71bd..ff80e2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ ## Unreleased ### Added + +* Support for URxvt OSC-11 extension to set background alpha + (https://codeberg.org/dnkl/foot/issues/436). + + ### Changed ### Deprecated ### Removed diff --git a/osc.c b/osc.c index 524269ef..eade736a 100644 --- a/osc.c +++ b/osc.c @@ -261,8 +261,30 @@ osc_flash(struct terminal *term) } static bool -parse_legacy_color(const char *string, uint32_t *color) +parse_legacy_color(const char *string, uint32_t *color, bool *_have_alpha, + uint16_t *_alpha) { + bool have_alpha = false; + uint16_t alpha = 0xffff; + + if (string[0] == '[') { + /* e.g. \E]11;[0.5]#00ff00 */ + const char *start = &string[1]; + const char *end = strchr(string, ']'); + if (end == NULL) + return false; + + char *_end; + double percent = strtod(start, &_end); + if (_end != end) + return false; + + have_alpha = true; + alpha = 0xffff * percent; + + string = end + 1; + } + if (string[0] != '#') return false; @@ -299,31 +321,53 @@ parse_legacy_color(const char *string, uint32_t *color) uint8_t g = 256 * (rgb[1] / 65536.); uint8_t b = 256 * (rgb[2] / 65536.); - LOG_DBG("legacy: %02x%02x%02x", r, g, b); + LOG_DBG("legacy: %02x%02x%02x (alpha=%04x)", r, g, b, + have_alpha ? alpha : 0xffff); + *color = r << 16 | g << 8 | b; + + if (_have_alpha != NULL) + *_have_alpha = have_alpha; + if (_alpha != NULL) + *_alpha = alpha; return true; } static bool -parse_rgb(const char *string, uint32_t *color) +parse_rgb(const char *string, uint32_t *color, bool *_have_alpha, + uint16_t *_alpha) { size_t len = strlen(string); + bool have_alpha = len >= 4 && strncmp(string, "rgba", 4) == 0; - /* Verify we have the minimum required length (for "rgb:x/x/x") */ - if (len < 3 /* 'rgb' */ + 1 /* ':' */ + 2 /* '/' */ + 3 * 1 /* 3 * 'x' */) - return false; + /* Verify we have the minimum required length (for "") */ + if (have_alpha) { + /* rgba:x/x/x/x */ + if (len < 4 /* 'rgba' */ + 1 /* ':' */ + 3 /* '/' */ + 4 * 1 /* 4 * 'x' */) + return false; + } else { + /* rgb:x/x/x */ + if (len < 3 /* 'rgb' */ + 1 /* ':' */ + 2 /* '/' */ + 3 * 1 /* 3 * 'x' */) + return false; + } - /* Verify prefix is "rgb:" */ - if (string[0] != 'r' || string[1] != 'g' || string[2] != 'b' || string[3] != ':') - return false; + /* Verify prefix is “rgb:” or “rgba:” */ + if (have_alpha) { + if (strncmp(string, "rgba:", 5) != 0) + return false; + string += 5; + len -= 5; + } else { + if (strncmp(string, "rgb:", 4) != 0) + return false; + string += 4; + len -= 4; + } - string += 4; - len -= 4; + int rgb[4]; + int digits[4]; - int rgb[3]; - int digits[3]; - - for (size_t i = 0; i < 3; i++) { + for (size_t i = 0; i < (have_alpha ? 4 : 3); i++) { for (rgb[i] = 0, digits[i] = 0; len > 0 && *string != '/'; len--, string++, digits[i]++) @@ -338,7 +382,7 @@ parse_rgb(const char *string, uint32_t *color) c >= 'a' && c <= 'f' ? c - 'a' + 10 : c - 'A' + 10; } - if (i >= 2) + if (i >= (have_alpha ? 3 : 2)) break; if (len == 0 || *string != '/') @@ -351,7 +395,20 @@ parse_rgb(const char *string, uint32_t *color) uint8_t g = 256 * (rgb[1] / (double)(1 << (4 * digits[1]))); uint8_t b = 256 * (rgb[2] / (double)(1 << (4 * digits[2]))); - LOG_DBG("rgb: %02x%02x%02x", r, g, b); + uint16_t alpha; + if (have_alpha) + alpha = 65536 * (rgb[3] / (double)(1 << (4 * digits[3]))); + + if (have_alpha) + LOG_DBG("rgba: %02x%02x%02x (alpha=%04x)", r, g, b, alpha); + else + LOG_DBG("rgb: %02x%02x%02x", r, g, b); + + if (_have_alpha != NULL) + *_have_alpha = have_alpha; + if (_alpha != NULL) + *_alpha = alpha; + *color = r << 16 | g << 8 | b; return true; } @@ -595,9 +652,9 @@ osc_dispatch(struct terminal *term) else { uint32_t color; - bool color_is_valid = s_color[0] == '#' - ? parse_legacy_color(s_color, &color) - : parse_rgb(s_color, &color); + bool color_is_valid = s_color[0] == '#' || s_color[0] == '[' + ? parse_legacy_color(s_color, &color, NULL, NULL) + : parse_rgb(s_color, &color, NULL, NULL); if (!color_is_valid) continue; @@ -647,15 +704,29 @@ osc_dispatch(struct terminal *term) } uint32_t color; - if (string[0] == '#' ? !parse_legacy_color(string, &color) : !parse_rgb(string, &color)) + bool have_alpha = false; + uint16_t alpha = 0xffff; + + if (string[0] == '#' || string[0] == '[' + ? !parse_legacy_color(string, &color, &have_alpha, &alpha) + : !parse_rgb(string, &color, &have_alpha, &alpha)) + { break; + } LOG_DBG("change color definition for %s to %06x", param == 10 ? "foreground" : "background", color); switch (param) { - case 10: term->colors.fg = color; break; - case 11: term->colors.bg = color; break; + case 10: + term->colors.fg = color; + break; + + case 11: + term->colors.bg = color; + if (have_alpha) + term->colors.alpha = alpha; + break; } term_damage_view(term); @@ -678,8 +749,13 @@ osc_dispatch(struct terminal *term) } uint32_t color; - if (string[0] == '#' ? !parse_legacy_color(string, &color) : !parse_rgb(string, &color)) + + if (string[0] == '#' || string[0] == '[' + ? !parse_legacy_color(string, &color, NULL, NULL) + : !parse_rgb(string, &color, NULL, NULL)) + { break; + } LOG_DBG("change cursor color to %06x", color); From 5ff655506d0e4251021c48e7923fbbed2b822810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 Apr 2021 22:31:09 +0200 Subject: [PATCH 2/7] =?UTF-8?q?osc:=20parse=5Frgb():=20ensure=20=E2=80=98a?= =?UTF-8?q?lpha=E2=80=99=20has=20been=20initialized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osc.c b/osc.c index eade736a..99803236 100644 --- a/osc.c +++ b/osc.c @@ -395,7 +395,7 @@ parse_rgb(const char *string, uint32_t *color, bool *_have_alpha, uint8_t g = 256 * (rgb[1] / (double)(1 << (4 * digits[1]))); uint8_t b = 256 * (rgb[2] / (double)(1 << (4 * digits[2]))); - uint16_t alpha; + uint16_t alpha = 0xffff; if (have_alpha) alpha = 65536 * (rgb[3] / (double)(1 << (4 * digits[3]))); From 8020c14637d345be246ff09cddd509dc763caf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 7 Apr 2021 08:00:39 +0200 Subject: [PATCH 3/7] osc: parse_legacy_color(): the alpha component is not a floating point number When using the urxvt extension of the XParseColor format, the alpha component is not a floating point number, but a decimal number in the range 0-100. --- osc.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osc.c b/osc.c index 99803236..0aa75247 100644 --- a/osc.c +++ b/osc.c @@ -268,19 +268,18 @@ parse_legacy_color(const char *string, uint32_t *color, bool *_have_alpha, uint16_t alpha = 0xffff; if (string[0] == '[') { - /* e.g. \E]11;[0.5]#00ff00 */ + /* e.g. \E]11;[50]#00ff00 */ const char *start = &string[1]; - const char *end = strchr(string, ']'); - if (end == NULL) - return false; - char *_end; - double percent = strtod(start, &_end); - if (_end != end) + errno = 0; + char *end; + unsigned long percent = strtoul(start, &end, 10); + + if (errno != 0 || *end != ']') return false; have_alpha = true; - alpha = 0xffff * percent; + alpha = (0xffff * min(percent, 100) + 50) / 100; string = end + 1; } From 339aad894b0118743a65522ca20e64270740e087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 7 Apr 2021 08:01:55 +0200 Subject: [PATCH 4/7] =?UTF-8?q?changelog:=20tweak=20wording=20for=20URxvt?= =?UTF-8?q?=E2=80=99s=20XParseColor=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff80e2f2..54e012f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ ## Unreleased ### Added -* Support for URxvt OSC-11 extension to set background alpha +* URxvt OSC-11 extension to set background alpha (https://codeberg.org/dnkl/foot/issues/436). From 747e5ba2875d006f2a59d79b9fd14ebab071efcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 7 Apr 2021 08:02:21 +0200 Subject: [PATCH 5/7] =?UTF-8?q?changelog:=20don=E2=80=99t=20use=20a=20hyph?= =?UTF-8?q?en=20between=20=E2=80=98OSC=E2=80=99=20and=20the=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e012f7..2a40e6a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ (https://codeberg.org/dnkl/foot/issues/427). * Wrong action referenced in error message for key binding collisions (https://codeberg.org/dnkl/foot/issues/432). -* OSC-4/104 out-of-bounds accesses to the color table. This was the +* OSC 4/104 out-of-bounds accesses to the color table. This was the reason pywal turned foot windows transparent (https://codeberg.org/dnkl/foot/issues/434). * PTY not being drained when the client application terminates. From 0cabeee67e566cbd9f7f428bf169c82692716636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 7 Apr 2021 08:10:56 +0200 Subject: [PATCH 6/7] doc: foot-ctlseq: mention that we support transparency in OSC 11 --- doc/foot-ctlseqs.7.scd | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 3c32442e..210e3269 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -583,7 +583,9 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_. | \\E] 11 ; _spec_ \\E\\ : xterm : Change the default background color to _spec_, a color in - *XParseColor* format. + XParseColor format. Foot implements URxvt's transparency extension; + e.g. _spec_=*[75]#ff00ff* or _spec_=*rgba:ff/00/ff/bf* (pink with + 75% alpha). | \\E] 12 ; _spec_ \\E\\ : xterm : Change cursor color to _spec_, a color in *XParseColor* format. From 28c2bea800e5de411d7c52680bf698ca7246f1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 7 Apr 2021 19:04:25 +0200 Subject: [PATCH 7/7] osc: reset background alpha when resetting the background color --- osc.c | 1 + terminal.c | 1 + 2 files changed, 2 insertions(+) diff --git a/osc.c b/osc.c index 0aa75247..157272d9 100644 --- a/osc.c +++ b/osc.c @@ -824,6 +824,7 @@ osc_dispatch(struct terminal *term) case 111: /* Reset default text background color */ LOG_DBG("resetting background color"); term->colors.bg = term->conf->colors.bg; + term->colors.alpha = term->conf->colors.alpha; term_damage_view(term); term_damage_margins(term); break; diff --git a/terminal.c b/terminal.c index c6b4705c..fd206090 100644 --- a/terminal.c +++ b/terminal.c @@ -1682,6 +1682,7 @@ term_reset(struct terminal *term, bool hard) fdm_del(term->fdm, term->blink.fd); term->blink.fd = -1; term->colors.fg = term->conf->colors.fg; term->colors.bg = term->conf->colors.bg; + term->colors.alpha = term->conf->colors.alpha; memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); term->origin = ORIGIN_ABSOLUTE;