forked from dnkl/foot
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
922 lines
25 KiB
922 lines
25 KiB
#include "osc.h" |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <errno.h> |
|
|
|
#define LOG_MODULE "osc" |
|
#define LOG_ENABLE_DBG 0 |
|
#include "log.h" |
|
#include "base64.h" |
|
#include "config.h" |
|
#include "grid.h" |
|
#include "notify.h" |
|
#include "render.h" |
|
#include "selection.h" |
|
#include "terminal.h" |
|
#include "uri.h" |
|
#include "util.h" |
|
#include "vt.h" |
|
#include "xmalloc.h" |
|
#include "xsnprintf.h" |
|
|
|
#define UNHANDLED() LOG_DBG("unhandled: OSC: %.*s", (int)term->vt.osc.idx, term->vt.osc.data) |
|
|
|
static void |
|
osc_to_clipboard(struct terminal *term, const char *target, |
|
const char *base64_data) |
|
{ |
|
bool to_clipboard = false; |
|
bool to_primary = false; |
|
|
|
if (target[0] == '\0') |
|
to_clipboard = true; |
|
|
|
for (const char *t = target; *t != '\0'; t++) { |
|
switch (*t) { |
|
case 'c': |
|
to_clipboard = true; |
|
break; |
|
|
|
case 's': |
|
case 'p': |
|
to_primary = true; |
|
break; |
|
|
|
default: |
|
LOG_WARN("unimplemented: clipboard target '%c'", *t); |
|
break; |
|
} |
|
} |
|
|
|
/* Find a seat in which the terminal has focus */ |
|
struct seat *seat = NULL; |
|
tll_foreach(term->wl->seats, it) { |
|
if (it->item.kbd_focus == term) { |
|
seat = &it->item; |
|
break; |
|
} |
|
} |
|
|
|
if (seat == NULL) { |
|
LOG_WARN("OSC52: client tried to write to clipboard data while window was unfocused"); |
|
return; |
|
} |
|
|
|
char *decoded = base64_decode(base64_data); |
|
if (decoded == NULL) { |
|
if (errno == EINVAL) |
|
LOG_WARN("OSC: invalid clipboard data: %s", base64_data); |
|
else |
|
LOG_ERRNO("base64_decode() failed"); |
|
|
|
if (to_clipboard) |
|
selection_clipboard_unset(seat); |
|
if (to_primary) |
|
selection_primary_unset(seat); |
|
return; |
|
} |
|
|
|
LOG_DBG("decoded: %s", decoded); |
|
|
|
if (to_clipboard) { |
|
char *copy = xstrdup(decoded); |
|
if (!text_to_clipboard(seat, term, copy, seat->kbd.serial)) |
|
free(copy); |
|
} |
|
|
|
if (to_primary) { |
|
char *copy = xstrdup(decoded); |
|
if (!text_to_primary(seat, term, copy, seat->kbd.serial)) |
|
free(copy); |
|
} |
|
|
|
free(decoded); |
|
} |
|
|
|
struct clip_context { |
|
struct seat *seat; |
|
struct terminal *term; |
|
uint8_t buf[3]; |
|
int idx; |
|
}; |
|
|
|
static void |
|
from_clipboard_cb(char *text, size_t size, void *user) |
|
{ |
|
struct clip_context *ctx = user; |
|
struct terminal *term = ctx->term; |
|
|
|
xassert(ctx->idx >= 0 && ctx->idx <= 2); |
|
|
|
const char *t = text; |
|
size_t left = size; |
|
|
|
if (ctx->idx > 0) { |
|
for (size_t i = ctx->idx; i < 3 && left > 0; i++, t++, left--) |
|
ctx->buf[ctx->idx++] = *t; |
|
|
|
xassert(ctx->idx <= 3); |
|
if (ctx->idx == 3) { |
|
char *chunk = base64_encode(ctx->buf, 3); |
|
xassert(chunk != NULL); |
|
xassert(strlen(chunk) == 4); |
|
|
|
term_to_slave(term, chunk, 4); |
|
free(chunk); |
|
|
|
ctx->idx = 0; |
|
} |
|
} |
|
|
|
if (left == 0) |
|
return; |
|
|
|
xassert(ctx->idx == 0); |
|
|
|
int remaining = left % 3; |
|
for (int i = remaining; i > 0; i--) |
|
ctx->buf[ctx->idx++] = text[size - i]; |
|
xassert(ctx->idx == remaining); |
|
|
|
char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3); |
|
xassert(chunk != NULL); |
|
xassert(strlen(chunk) % 4 == 0); |
|
term_to_slave(term, chunk, strlen(chunk)); |
|
free(chunk); |
|
} |
|
|
|
static void |
|
from_clipboard_done(void *user) |
|
{ |
|
struct clip_context *ctx = user; |
|
struct terminal *term = ctx->term; |
|
|
|
if (ctx->idx > 0) { |
|
char res[4]; |
|
base64_encode_final(ctx->buf, ctx->idx, res); |
|
term_to_slave(term, res, 4); |
|
} |
|
|
|
term_to_slave(term, "\033\\", 2); |
|
free(ctx); |
|
} |
|
|
|
static void |
|
osc_from_clipboard(struct terminal *term, const char *source) |
|
{ |
|
/* Find a seat in which the terminal has focus */ |
|
struct seat *seat = NULL; |
|
tll_foreach(term->wl->seats, it) { |
|
if (it->item.kbd_focus == term) { |
|
seat = &it->item; |
|
break; |
|
} |
|
} |
|
|
|
if (seat == NULL) { |
|
LOG_WARN("OSC52: client tried to read clipboard data while window was unfocused"); |
|
return; |
|
} |
|
|
|
/* Use clipboard if no source has been specified */ |
|
char src = source[0] == '\0' ? 'c' : 0; |
|
bool from_clipboard = src == 'c'; |
|
bool from_primary = false; |
|
|
|
for (const char *s = source; |
|
*s != '\0' && !from_clipboard && !from_primary; |
|
s++) |
|
{ |
|
if (*s == 'c' || *s == 'p' || *s == 's') { |
|
src = *s; |
|
|
|
switch (src) { |
|
case 'c': |
|
from_clipboard = selection_clipboard_has_data(seat); |
|
break; |
|
|
|
case 's': |
|
case 'p': |
|
from_primary = selection_primary_has_data(seat); |
|
break; |
|
} |
|
} else |
|
LOG_WARN("unimplemented: clipboard source '%c'", *s); |
|
} |
|
|
|
if (!from_clipboard && !from_primary) |
|
return; |
|
|
|
term_to_slave(term, "\033]52;", 5); |
|
term_to_slave(term, &src, 1); |
|
term_to_slave(term, ";", 1); |
|
|
|
struct clip_context *ctx = xmalloc(sizeof(*ctx)); |
|
*ctx = (struct clip_context) {.seat = seat, .term = term}; |
|
|
|
if (from_clipboard) { |
|
text_from_clipboard( |
|
seat, term, &from_clipboard_cb, &from_clipboard_done, ctx); |
|
} |
|
|
|
if (from_primary) { |
|
text_from_primary( |
|
seat, term, &from_clipboard_cb, &from_clipboard_done, ctx); |
|
} |
|
} |
|
|
|
static void |
|
osc_selection(struct terminal *term, char *string) |
|
{ |
|
char *p = string; |
|
bool clipboard_done = false; |
|
|
|
/* The first parameter is a string of clipbard sources/targets */ |
|
while (*p != '\0' && !clipboard_done) { |
|
switch (*p) { |
|
case ';': |
|
clipboard_done = true; |
|
*p = '\0'; |
|
break; |
|
} |
|
|
|
p++; |
|
} |
|
|
|
LOG_DBG("clipboard: target = %s data = %s", string, p); |
|
|
|
if (strlen(p) == 1 && p[0] == '?') |
|
osc_from_clipboard(term, string); |
|
else |
|
osc_to_clipboard(term, string, p); |
|
} |
|
|
|
static void |
|
osc_flash(struct terminal *term) |
|
{ |
|
/* Our own private - flash */ |
|
term_flash(term, 50); |
|
} |
|
|
|
static bool |
|
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;[50]#00ff00 */ |
|
const char *start = &string[1]; |
|
|
|
errno = 0; |
|
char *end; |
|
unsigned long percent = strtoul(start, &end, 10); |
|
|
|
if (errno != 0 || *end != ']') |
|
return false; |
|
|
|
have_alpha = true; |
|
alpha = (0xffff * min(percent, 100) + 50) / 100; |
|
|
|
string = end + 1; |
|
} |
|
|
|
if (string[0] != '#') |
|
return false; |
|
|
|
string++; |
|
const size_t len = strlen(string); |
|
|
|
if (len % 3 != 0) |
|
return false; |
|
|
|
const int digits = len / 3; |
|
|
|
int rgb[3]; |
|
for (size_t i = 0; i < 3; i++) { |
|
rgb[i] = 0; |
|
for (size_t j = 0; j < digits; j++) { |
|
size_t idx = i * digits + j; |
|
char c = string[idx]; |
|
rgb[i] <<= 4; |
|
|
|
if (!isxdigit(c)) |
|
rgb[i] |= 0; |
|
else |
|
rgb[i] |= c >= '0' && c <= '9' ? c - '0' : |
|
c >= 'a' && c <= 'f' ? c - 'a' + 10 : c - 'A' + 10; |
|
} |
|
|
|
/* Values with less than 16 bits represent the *most |
|
* significant bits*. I.e. the values are *not* scaled */ |
|
rgb[i] <<= 16 - (4 * digits); |
|
} |
|
|
|
/* Re-scale to 8-bit */ |
|
uint8_t r = 256 * (rgb[0] / 65536.); |
|
uint8_t g = 256 * (rgb[1] / 65536.); |
|
uint8_t b = 256 * (rgb[2] / 65536.); |
|
|
|
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, 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 "") */ |
|
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:” 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; |
|
} |
|
|
|
int rgb[4]; |
|
int digits[4]; |
|
|
|
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]++) |
|
{ |
|
char c = *string; |
|
rgb[i] <<= 4; |
|
|
|
if (!isxdigit(c)) |
|
rgb[i] |= 0; |
|
else |
|
rgb[i] |= c >= '0' && c <= '9' ? c - '0' : |
|
c >= 'a' && c <= 'f' ? c - 'a' + 10 : c - 'A' + 10; |
|
} |
|
|
|
if (i >= (have_alpha ? 3 : 2)) |
|
break; |
|
|
|
if (len == 0 || *string != '/') |
|
return false; |
|
string++; len--; |
|
} |
|
|
|
/* Re-scale to 8-bit */ |
|
uint8_t r = 256 * (rgb[0] / (double)(1 << (4 * digits[0]))); |
|
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 = 0xffff; |
|
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; |
|
} |
|
|
|
static void |
|
osc_set_pwd(struct terminal *term, char *string) |
|
{ |
|
LOG_DBG("PWD: URI: %s", string); |
|
|
|
char *scheme, *host, *path; |
|
if (!uri_parse(string, strlen(string), &scheme, NULL, NULL, &host, NULL, &path, NULL, NULL)) { |
|
LOG_ERR("OSC7: invalid URI: %s", string); |
|
return; |
|
} |
|
|
|
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) { |
|
LOG_DBG("OSC7: pwd: %s", path); |
|
free(term->cwd); |
|
term->cwd = path; |
|
} else |
|
free(path); |
|
|
|
free(scheme); |
|
free(host); |
|
} |
|
|
|
static void |
|
osc_uri(struct terminal *term, char *string) |
|
{ |
|
/* |
|
* \E]8;<params>;URI\e\\ |
|
* |
|
* Params are key=value pairs, separated by ‘:’. |
|
* |
|
* The only defined key (as of 2020-05-31) is ‘id’, which is used |
|
* to group split-up URIs: |
|
* |
|
* ╔═ file1 ════╗ |
|
* ║ ╔═ file2 ═══╗ |
|
* ║http://exa║Lorem ipsum║ |
|
* ║le.com ║ dolor sit ║ |
|
* ║ ║amet, conse║ |
|
* ╚══════════║ctetur adip║ |
|
* ╚═══════════╝ |
|
* |
|
* This lets a terminal emulator highlight both parts at the same |
|
* time (e.g. when hovering over one of the parts with the mouse). |
|
*/ |
|
|
|
char *params = string; |
|
char *params_end = strchr(params, ';'); |
|
if (params_end == NULL) |
|
return; |
|
|
|
*params_end = '\0'; |
|
const char *uri = params_end + 1; |
|
uint64_t id = (uint64_t)rand() << 32 | rand(); |
|
|
|
char *ctx = NULL; |
|
for (const char *key_value = strtok_r(params, ":", &ctx); |
|
key_value != NULL; |
|
key_value = strtok_r(NULL, ":", &ctx)) |
|
{ |
|
const char *key = key_value; |
|
char *operator = strchr(key_value, '='); |
|
|
|
if (operator == NULL) |
|
continue; |
|
*operator = '\0'; |
|
|
|
const char *value = operator + 1; |
|
|
|
if (strcmp(key, "id") == 0) |
|
id = sdbm_hash(value); |
|
} |
|
|
|
LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id); |
|
|
|
if (uri[0] == '\0') |
|
term_osc8_close(term); |
|
else |
|
term_osc8_open(term, id, uri); |
|
} |
|
|
|
static void |
|
osc_notify(struct terminal *term, char *string) |
|
{ |
|
/* |
|
* The 'notify' perl extension |
|
* (https://pub.phyks.me/scripts/urxvt/notify) is very simple: |
|
* |
|
* #!/usr/bin/perl |
|
* |
|
* sub on_osc_seq_perl { |
|
* my ($term, $osc, $resp) = @_; |
|
* if ($osc =~ /^notify;(\S+);(.*)$/) { |
|
* system("notify-send '$1' '$2'"); |
|
* } |
|
* } |
|
* |
|
* As can be seen, the notification text is not encoded in any |
|
* way. The regex does a greedy match of the ';' separator. Thus, |
|
* any extra ';' will end up being part of the title. There's no |
|
* way to have a ';' in the message body. |
|
* |
|
* I've changed that behavior slightly in; we split the title from |
|
* body on the *first* ';', allowing us to have semicolons in the |
|
* message body, but *not* in the title. |
|
*/ |
|
char *ctx = NULL; |
|
const char *title = strtok_r(string, ";", &ctx); |
|
const char *msg = strtok_r(NULL, "\x00", &ctx); |
|
|
|
notify_notify(term, title, msg != NULL ? msg : ""); |
|
} |
|
|
|
static void |
|
update_color_in_grids(struct terminal *term, uint32_t old_color, |
|
uint32_t new_color) |
|
{ |
|
/* |
|
* Update color of already rendered cells. |
|
* |
|
* Note that we do *not* store the original palette |
|
* index. Therefore, the best we can do is compare colors - if |
|
* they match, assume "our" palette index was the one used to |
|
* render the cell. |
|
* |
|
* There are a couple of cases where this isn't necessarily true: |
|
* - user has configured the 16 base colors with non-unique |
|
* colors. - the client has used 24-bit escapes for colors |
|
* |
|
* In general though, if the client configures the palette, it is |
|
* very likely only using index:ed coloring (i.e. not 24-bit |
|
* direct colors), and I hope that it is unusual with palettes |
|
* where all the colors aren't unique. |
|
* |
|
* TODO(?): for performance reasons, we only update the current |
|
* screen rows (of both grids). I.e. scrollback is *not* updated. |
|
*/ |
|
for (size_t i = 0; i < 2; i++) { |
|
struct grid *grid = i == 0 ? &term->normal : &term->alt; |
|
|
|
for (size_t r = 0; r < term->rows; r++) { |
|
struct row *row = grid_row(grid, r); |
|
xassert(row != NULL); |
|
|
|
for (size_t c = 0; c < term->grid->num_cols; c++) { |
|
struct cell *cell = &row->cells[c]; |
|
if (cell->attrs.have_fg && |
|
cell->attrs.fg == old_color) |
|
{ |
|
cell->attrs.fg = new_color; |
|
cell->attrs.clean = 0; |
|
row->dirty = true; |
|
} |
|
|
|
if ( cell->attrs.have_bg && |
|
cell->attrs.bg == old_color) |
|
{ |
|
cell->attrs.bg = new_color; |
|
cell->attrs.clean = 0; |
|
row->dirty = true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void |
|
osc_dispatch(struct terminal *term) |
|
{ |
|
unsigned param = 0; |
|
int data_ofs = 0; |
|
|
|
for (size_t i = 0; i < term->vt.osc.idx; i++, data_ofs++) { |
|
char c = term->vt.osc.data[i]; |
|
|
|
if (c == ';') { |
|
data_ofs++; |
|
break; |
|
} |
|
|
|
if (!isdigit(c)) { |
|
UNHANDLED(); |
|
return; |
|
} |
|
|
|
param *= 10; |
|
param += c - '0'; |
|
} |
|
|
|
LOG_DBG("OSC: %.*s (param = %d)", |
|
(int)term->vt.osc.idx, term->vt.osc.data, param); |
|
|
|
char *string = (char *)&term->vt.osc.data[data_ofs]; |
|
|
|
switch (param) { |
|
case 0: term_set_window_title(term, string); break; /* icon + title */ |
|
case 1: break; /* icon */ |
|
case 2: term_set_window_title(term, string); break; /* title */ |
|
|
|
case 4: { |
|
/* Set color<idx> */ |
|
|
|
string--; |
|
if (*string != ';') |
|
break; |
|
|
|
xassert(*string == ';'); |
|
|
|
for (const char *s_idx = strtok(string, ";"), *s_color = strtok(NULL, ";"); |
|
s_idx != NULL && s_color != NULL; |
|
s_idx = strtok(NULL, ";"), s_color = strtok(NULL, ";")) |
|
{ |
|
/* Parse <idx> parameter */ |
|
unsigned idx = 0; |
|
for (; *s_idx != '\0'; s_idx++) { |
|
char c = *s_idx; |
|
idx *= 10; |
|
idx += c - '0'; |
|
} |
|
|
|
if (idx >= ALEN(term->colors.table)) { |
|
LOG_WARN("invalid OSC 4 color index: %u", idx); |
|
break; |
|
} |
|
|
|
/* Client queried for current value */ |
|
if (strlen(s_color) == 1 && s_color[0] == '?') { |
|
uint32_t color = term->colors.table[idx]; |
|
uint8_t r = (color >> 16) & 0xff; |
|
uint8_t g = (color >> 8) & 0xff; |
|
uint8_t b = (color >> 0) & 0xff; |
|
|
|
char reply[32]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033]4;%u;rgb:%02x/%02x/%02x\033\\", |
|
idx, r, g, b); |
|
term_to_slave(term, reply, n); |
|
} |
|
|
|
else { |
|
uint32_t 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; |
|
|
|
LOG_DBG("change color definition for #%u from %06x to %06x", |
|
idx, term->colors.table[idx], color); |
|
|
|
update_color_in_grids(term, term->colors.table[idx], color); |
|
term->colors.table[idx] = color; |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 7: |
|
/* Update terminal's understanding of PWD */ |
|
osc_set_pwd(term, string); |
|
break; |
|
|
|
case 8: |
|
osc_uri(term, string); |
|
break; |
|
|
|
case 9: |
|
/* iTerm2 Growl notifications */ |
|
osc_notify(term, string); |
|
break; |
|
|
|
case 10: |
|
case 11: |
|
case 17: |
|
case 19: { |
|
/* Set default foreground/background/highlight-bg/highlight-fg color */ |
|
|
|
/* Client queried for current value */ |
|
if (strlen(string) == 1 && string[0] == '?') { |
|
uint32_t color = param == 10 ? term->colors.fg : term->colors.bg; |
|
uint8_t r = (color >> 16) & 0xff; |
|
uint8_t g = (color >> 8) & 0xff; |
|
uint8_t b = (color >> 0) & 0xff; |
|
|
|
/* |
|
* Reply in XParseColor format |
|
* E.g. for color 0xdcdccc we reply "\033]10;rgb:dc/dc/cc\033\\" |
|
*/ |
|
char reply[32]; |
|
size_t n = xsnprintf( |
|
reply, sizeof(reply), "\033]%u;rgb:%02x/%02x/%02x\033\\", |
|
param, r, g, b); |
|
|
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
uint32_t 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" : |
|
param == 11 ? "background" : |
|
param == 17 ? "selection background" : |
|
"selection foreground", |
|
color); |
|
|
|
switch (param) { |
|
case 10: |
|
term->colors.fg = color; |
|
break; |
|
|
|
case 11: |
|
term->colors.bg = color; |
|
if (have_alpha) |
|
term->colors.alpha = alpha; |
|
break; |
|
|
|
case 17: |
|
term->colors.selection_bg = color; |
|
term->colors.use_custom_selection = true; |
|
break; |
|
|
|
case 19: |
|
term->colors.selection_fg = color; |
|
term->colors.use_custom_selection = true; |
|
break; |
|
} |
|
|
|
term_damage_view(term); |
|
term_damage_margins(term); |
|
break; |
|
} |
|
|
|
case 12: /* Set cursor color */ |
|
|
|
/* Client queried for current value */ |
|
if (strlen(string) == 1 && string[0] == '?') { |
|
uint8_t r = (term->cursor_color.cursor >> 16) & 0xff; |
|
uint8_t g = (term->cursor_color.cursor >> 8) & 0xff; |
|
uint8_t b = (term->cursor_color.cursor >> 0) & 0xff; |
|
|
|
char reply[32]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033]12;rgb:%02x/%02x/%02x\033\\", r, g, b); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
uint32_t 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); |
|
|
|
if (color == 0) |
|
term->cursor_color.cursor = 0; /* Invert fg/bg */ |
|
else |
|
term->cursor_color.cursor = 1u << 31 | color; |
|
|
|
term_damage_cursor(term); |
|
break; |
|
|
|
case 30: /* Set tab title */ |
|
break; |
|
|
|
case 52: /* Copy to/from clipboard/primary */ |
|
osc_selection(term, string); |
|
break; |
|
|
|
case 104: { |
|
/* Reset Color Number 'c' (whole table if no parameter) */ |
|
|
|
if (strlen(string) == 0) { |
|
LOG_DBG("resetting all colors"); |
|
for (size_t i = 0; i < ALEN(term->colors.table); i++) { |
|
update_color_in_grids( |
|
term, term->colors.table[i], term->conf->colors.table[i]); |
|
term->colors.table[i] = term->conf->colors.table[i]; |
|
} |
|
} |
|
|
|
else { |
|
for (const char *s_idx = strtok(string, ";"); |
|
s_idx != NULL; |
|
s_idx = strtok(NULL, ";")) |
|
{ |
|
unsigned idx = 0; |
|
for (; *s_idx != '\0'; s_idx++) { |
|
char c = *s_idx; |
|
idx *= 10; |
|
idx += c - '0'; |
|
} |
|
|
|
if (idx >= ALEN(term->colors.table)) { |
|
LOG_WARN("invalid OSC 104 color index: %u", idx); |
|
continue; |
|
} |
|
|
|
LOG_DBG("resetting color #%u", idx); |
|
update_color_in_grids( |
|
term, term->colors.table[idx], term->conf->colors.table[idx]); |
|
term->colors.table[idx] = term->conf->colors.table[idx]; |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 105: /* Reset Special Color Number 'c' */ |
|
break; |
|
|
|
case 110: /* Reset default text foreground color */ |
|
LOG_DBG("resetting foreground color"); |
|
term->colors.fg = term->conf->colors.fg; |
|
term_damage_view(term); |
|
break; |
|
|
|
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; |
|
|
|
case 112: |
|
LOG_DBG("resetting cursor color"); |
|
term->cursor_color.text = term->conf->cursor.color.text; |
|
term->cursor_color.cursor = term->conf->cursor.color.cursor; |
|
term_damage_cursor(term); |
|
break; |
|
|
|
case 117: |
|
LOG_DBG("resetting selection background color"); |
|
term->colors.selection_bg = term->conf->colors.selection_bg; |
|
term->colors.use_custom_selection = term->conf->colors.use_custom.selection; |
|
break; |
|
|
|
case 119: |
|
LOG_DBG("resetting selection foreground color"); |
|
term->colors.selection_fg = term->conf->colors.selection_fg; |
|
term->colors.use_custom_selection = term->conf->colors.use_custom.selection; |
|
break; |
|
|
|
case 555: |
|
osc_flash(term); |
|
break; |
|
|
|
case 777: { |
|
/* |
|
* OSC 777 is an URxvt generic escape used to send commands to |
|
* perl extensions. The generic syntax is: \E]777;<command>;<string>ST |
|
* |
|
* We only recognize the 'notify' command, which is, if not |
|
* well established, at least fairly well known. |
|
*/ |
|
|
|
char *param_brk = strchr(string, ';'); |
|
if (param_brk == NULL) { |
|
UNHANDLED(); |
|
return; |
|
} |
|
|
|
if (strncmp(string, "notify", param_brk - string) == 0) |
|
osc_notify(term, param_brk + 1); |
|
else |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
} |
|
|
|
bool |
|
osc_ensure_size(struct terminal *term, size_t required_size) |
|
{ |
|
if (required_size <= term->vt.osc.size) |
|
return true; |
|
|
|
size_t new_size = (required_size + 127) / 128 * 128; |
|
xassert(new_size > 0); |
|
|
|
uint8_t *new_data = realloc(term->vt.osc.data, new_size); |
|
if (new_data == NULL) { |
|
LOG_ERRNO("failed to increase size of OSC buffer"); |
|
return false; |
|
} |
|
|
|
term->vt.osc.data = new_data; |
|
term->vt.osc.size = new_size; |
|
return true; |
|
}
|
|
|